Servlet_JSP
(一)什么是Servlet?
是sun公司制订的一种用来扩展web服务器功能的组件规范。
也是用来扩展web服务器功能的一种程序。
组件:遵守特定规范(组件规范)开发的并且可以单独部署的软件模块。
容器:遵守特定规范(组件规范)开发的一种程序,提供组件的运行环境并且管理组件的生命周期。
(二)有什么特点?
只能用JA VA语言来开发
Servlet程序的运行不依赖于容器(可以在任何符合规范的容器里运行)
(三)Servlet如何运行?
http://ip:port/appname/url-pattern
1)、浏览器通过ip及port与服务器建立连接(socket连接)
2)、浏览器及请求信息打成http请求数据包向服务器发送请求。
3)、servlet引擎(服务器当中负责通讯的一个模块)将请求中的数据封装成Request对象
(是HttpServletRequest接口实现类的实例),该对象包含了请求中的数据
(封装的目的为了方便servlet获取请求数据)。同时,还会创建一个Response对象
(HttpServletResponse接口实现类的实例,该对象用于存放servlet处理之后的结果)。
4)、servlet引擎通过分析web.xml,找到servlet类名,并查看该类名对应的实例是否存在,
若不存在,则创建实例,否则,不创建(多个请求,有可能访问同一个servlet实例)。
5)、调用servlet实例的service方法(会同时将Request,Response对象作为参数传过来)。
6)、servlet引擎从response对象获取处理结果,生成http响应数据包,发送给浏览器,浏览器解析,生成相应的界面。
(四)开发一个Servlet的步骤:
1)、写一个类,实现Servlet或继承HttpServlet(后者更方便)
2)、重写service(HttpServletRequest req,HttpServletResponse resp)方法或重写
doGet(HttpServletRequest req,HttpServletResponse resp)
doPost(HttpServletRequest req,HttpServletResponse resp)这两个方法.
在方法中书写我们的处理代码。
3)、编译(依赖servlet-api.jar可以Tomcat下common\lib目录中找到)
javac-d.-cp servlet-api.jar HelloServlet.java
4)、依据如下目录打包,部署到应用服务器(如Tomcat是部署到webapps目录下):
appname(应用名,任意的一个名字)
WEB-INF(按规范这样写)
classes(必须这样写,存放我们刚刚编译后的字节码文件)
lib(这个目录用来存放开发中用到的外部.jar文件)
web.xml(必须这样写,servlet的部署描述信息)
5)、在web.xml文件中配置好servlet信息,示例如下:
6)、关于url-pattern的匹配原则:
A、精确匹配:/hello,/abc/hello以"/"开头
B、使用通配符"*":/*,/abc/*也是以"/"开头
C、使用后缀匹配:*.do,*.action不能以"/"开头(不能跟前面两种一起使用:/*.do)
这三种匹配优先级是:a>b>c且只匹配一次
如果在web.xml没有找到匹配的,就会去查找相应的资源,找到返回,找不到报404
Tomcat:
是一个开源的servlet容器,并且,提供了web服务器的大部分功能,
所以,有时候,也认为tomcat是一个web服务器。可以在MyEclipse直接关联配置Linux Tomcat的安装:
step1下载如:apache-tomcat-5.5.28.zip
step2解压如:/home/tarena/apache-tomcat-5.5.28
step3配置环境变量
cd/home/tarena
vi.bash_profile
CATALINA_HOME=/home/tarena/apache-tomcat-5.5.28
PA TH=/home/tarena/apache-tomcat-5.5.28/bin
保存退出,加载配置文件
..bash_profile
step4将bin目录下的所有文件属性添加可执行权限
cd/home/tarena/apache-tomcat-5.5.28/bin
chmod755*.*
step-启动Tomcat服务器:sh startup.sh
step-关闭Tomcat服务器:sh shutdown.sh
(五)http协议(只做了解)
1)、http协议是一个应用层协议,定义了浏览器与web服务器之间通讯的过程以及数据的格式。
2)、通讯的过程:
a,浏览器尝试与服务器建立连接。
b,浏览器向服务器发送请求。
c,服务器处理请求,生成响应
d,关闭连接。
3)、数据包的结构
A、请求数据包的结构
<1>、请求行
请求方式资源的路径http协议的版本号(1.0/1.1)
<2>、消息头
由w3c定义的一组通讯规则,比如浏览器可以通过cookie消息头携带部分数据给服务器;
服务器可以通过content-type消息头告诉浏览器返回数据的类型及编码。
<3>、实体内容
当请求方式是post时,数据会存放在这儿,否则,数据会存放到资源路径之后。
B、响应数据包
<1>、状态行
协议的版本号状态码描述
200:正确
404:找不到资源
500:系统内部出错
<2>、消息头
<3>、实体内容
servlet处理之后的结果
(六)请求方式
1)、Get方式
A、通过链接
B、表单默认提交方式
C、通过浏览器地址栏输入地址
特点:请求数据放到请求行之后,提交的数据量有限,并且不安全。
2)、Post方式
通过设置表单的:method="post"
特点:请求数据放到实体内容里,提交的数据量比较大。并且相对安全。
向服务器获取数据,用get;提交数据,用post。
(七)表单的处理
1)、获取表单提交的数据
String data=request.getParameter(String paramName);
依据参数名返回参数值
String[]datas=request.getParameterValues(String paramName);
依据参数名返回参数值(当有多个参数名相同的情况下)
要注意,对于多选框,有可能返回的结果是null。
(八)重定向
response.sendRedirect("地址");
服务器向浏览器发送一个302的状态码和一个消息头(location),
浏览器立即向location所指向的地址发送新的请求。(两次请求)
重定向之前,如果response缓存有数据,则缓存的数据会自动清空。
a,重定向的地址是任意的。
b,浏览器地址栏的地址会变成重定向之后的地址。
(九)转发
request.setAttribute(String name,Object value);
request.getAttribute(String name);//如果name对应的对象不存在则返回null
request.getRequestDispatcher("地址").forward(request,response);
一个web组件(servlet/jsp)完成了部分工作,将剩余的工作转交给另外一个web组件继续完成。
转发涉及的各个web组件,可以共享request和response对象。
一般情况是:servlet获取数据,然后将数据转发过一个jsp,由jsp生成相应的表示逻辑(html)。
(十)转发与重定向的区别
(1)、转发的目的地只能是同一个应用内部的各个组件之间,而重定向的目的地是任意的。
如果想实现组件之间的跳转,如果是同一个应用的内部,应该用转发(因为效率更高)。
如果组件之间要共享request对象,也应该用转发。
(request/response对象的生存时间:在一次请求与响应期间存在,超过该范围,容器会销毁该对象)
(2)、浏览器地址的地址:转发是不变的,而重定向是变的。(浏览器发送一/两次请求)
(3)、转发的各个组件可以共享request对象,而重定向不行。
(4)、转发是一件事情未做完,而重定向是一件事情已经做完。
(十一)表单中文乱码问题
(1)、对于静态页面(html)添加
其中,编码要支持中文
(2)、在servlet类当中添加
request.setCharacterEncoding(编码);
其中,编码要看浏览器打开静态页面时的编码。
response.setContentType("text/html;charset=编码");
(3)、如果要保存到数据库
首先,可以设置数据库保存数据的默认编码:
create database sd1102db2default character set utf8;
在jdbc url后面,添加useUnicode=true&characterEncoding=utf8
作用是,告诉驱动程序,当前数据库保存数据的编码是什么。
这样,驱动程序就可以正确地解码了。
(十二)servlet生命周期(了解)
1)、生命周期的含义:
servlet容器如何去创建servlet实例,分配其资源,
然后调用其service方法,并且销毁其实例的整个过程。
2)、生命周期的四个阶段:
阶段一:实例化
A、当有请求到达容器时,容器会查找请求对应的实例是否存在,若不存在,会创建。
B、有容器启动时,或者新部署某个应用时,会先检查web.xml中,某个servlet配置中,
是否有load-on-startup配置,如果有,会创建其实例。
阶段二:初始化
容器会调用servlet实例的init(ServletConfig config)方法。
在init方法执行时,可以分配资源。初始化方法只会执行一次。
阶段三:就绪
容器调用servlet实例的service()方法。
阶段四:销毁
容器会依据自己的策略来决定什么时候删除,在删除之前,
会调用servlet实例的destroy()方法。释放资源。
3)、生命周期当中涉及的几个接口与类
A、Servlet接口
1)、init(ServletConfig config)
2)、service(ServletRequest sr,ServletResponse rp)
3)、destroy()
B、GenericServlet抽象类
实现了两个方法
1)、init(ServletConfig config)
2)、init():如果自已写的Servlet想override默认的
init(ServletConfig config),建议override init()方法。
3)、destroy():空的实现。
C、HttpServlet抽象类
实现了:
service(HttpServletRequest sr,HttpServletResponse rp)其中
HttpServletRequest是ServletRequest的子接口,
HttpServletResponse是ServletResponse的子接口。
该方法会依据请求方式,调用对应的doGet/doPost方法。
doGet(HttpServletRequest rq,HttpServletResponse re)
doPost(HttpServletRequest rq,HttpServletResponse re)
这两个方法的默认实现是抛出异常。
所以,我们写Servlet,业务逻辑可以放在doGet/doPost。
D、ServletConfig接口
由容器来创建符合该接口要求的实例。可以通过该
实例访问servlet的初始化参数。
String getInitParameter(String paraName);
E、ServletContext接口
1)、ServletContext一般称为Servlet上下文。
当服务器启动时,会为每一个应用创建唯一的一个Servlet上下文。
只有当服务器关闭或者应用被卸载,该对象才会被服务器删除,否则,会一直存在。
2)、ServletContext的作用:
1、依据逻辑路径获取实际的物理路径。
String getRealPath(String url);
2、存取数据
setAttribute(String name,Object obj);
Object getAttribute(String name);
3、获取全局初始化参数
getInitParameter(String);
//即在web.xml中配置的全局参数:
3)、获取ServletContext实例的方式
1、GenericServlet提供的getServletContext()方法。
2、HttpSession.getServletContext()。
3、ServletConfig.getServletContext()。
4)、比较ServletContext,HttpSession,HttpServletRequest的生命周期.
ServletContext:整个应用使用期间都存在,除非应用被卸载或服务器重启或关闭
HttpSession:一次会话期间都存在(服务器默认的最大不活动时间是30分钟)
HttpServletRequest:一次请求响应期间或转发共用一个Request对象时存在
(十三)路径(地址)问题
(1)、四种与路径相关的场景
A、链接
B、表单提交
C、重定向
D、转发
(2)、相对路径与绝对路径
相对路径:不以"/"开头的路径(以上四种都一样)
绝对路径:以"/"开头
对于绝对路径时a,b,c三种情况,路径是从应用名开始,而d路径是从应用名之后开始。
获得应用名的方式:request.getContextPath();
(十四)JSP
(1)、jsp是什么?
java server page(java服务器端页面技术)是sun公司制订的一种服务器动态页面生成技术,
其主要组成部分是html和少量的java代码。jsp文件以.jsp为后缀,不需要编译。
虽然servlet也可以生成动态页面,但是,过于烦琐(需要使用out.println来输出),
另外,维护也不方便。jsp其本质,是用来简化servlet中有关表示逻辑生成的一种技术。
jsp从某种意义上讲,其实就是servlet。
(2)、jsp的组成
A、html(html标签、css、javascript):直接书写。
B、java代码:
1)、java代码片断,语法:
<%
java代码
%>
2)、jsp表达式,语法:
<%=java表达式%>
3)、jsp声明,用于给对应的servlet添加属性和方法,语法:
<%!
int count=0;
%>
C、指令
告诉jsp引擎,在将.jsp源文件转换成.java源文件时,作一些额外的处理。比如导包
1)、page指令
语法:<%@page属性名=属性值%>
import属性:导包
<%@page import="java.util.*,java.text.*"%>
也可以分开写
<%@page import="java.util.*"%>
<%@page import="java.text.*"%>
pageEncoding属性:告诉jsp引擎,当前.jsp源文件保存时采用的编码是什么
contentType属性:生成一个消息头,告诉浏览器以指定的编码来显示页面
<%@page pageEncoding="utf-8"contentType="text/html;charset=utf-8"%>
isELIgnored:true(默认)/false,是否忽略el表达式
isErrorPage:true/false(默认),当前页面是否为错误处理页面。
errorPage:指定错误处理页面。
session:true(默认)/false,当前页面是否支持session。
2)、include指令,语法:<%include file=""%>导入文件
.jsp源文件转换成.java源文件时,将被包含的文件的内容插入到指令所在的位置。
3)、taglib指令,语法:<%@taglib prefix=""uri=""%>导入标签库
D、隐含对象
在jsp源文件中,不用声明和创建,就可以直接使用的对象。
request,response,session,application,out
page相当于this,指向jsp实例自身
config ServletConfig实例
exception只有当isErrorPage=true时,才能使用,表示jsp页面出错之后对应的异常对象
pageContext页面上下文,jsp引擎会为每一个jsp实例创建唯一的一个上下文对象。
主要用途:绑订数据:setAttribute()getAttribute()removeAttribute()
查找其它隐含对象:一般在定义标签时使用。
(3)、jsp如何执行:
到请求到达容器时,容器会将.jsp文件转换成.java文件(由容器当中的一个特定的模块,
即jsp引擎来实现的,.java文件,就是一个servlet源文件)容器编译java文件,然后调用其service()方法。
(4)、.jsp文件如何转换成.java文件
html语句-->放到service()方法里,使用out.write()输出。
java代码片断-->放到service()方法里。
jsp表达式-->放到service()方法里,使用out.print()输出。
指令-->影响到源代码,比如<%@page import=""%>
(5)、活动元素(Action Element)
在jsp实例运行的时候,让jsp引擎做一些额外的处理。
A、
B、
并且将结果合并到当前jsp输出的结果里面。可以向被包含的文件传递参数。
C、
D、
确保在scope所指定的范围(page,request,session,application)
中绑订了一个名叫"user"的对象。
E、
输出property所指定的属性值
F、
给property指定的属性赋值
(6)、注释
:注释内容会执行
<%--注释内容--%>:注释内容不会被执行
最终,两种注释都不会在浏览器端有任何的输出。
(十五)状态管理
(1)、了解http协议的无状态特性
http协议是一个无状态协议,即浏览器向服务器发送请求,服务器处理请求时,
并不关心是哪一个浏览器发送的请求,当响应发送给浏览器之后,会立即关闭连接;
如果浏览器要再发送请求,需要重新建立新的连接。
无状态特性的优点:
效率高:指的是,服务器可以为更多的客户端(浏览器)服务。
缺点:
连接是无状态的(连接在响应结束后,立即关闭),对于
某些web应用(需要状态管理的,比如购物车,需要额外的编程)。
可以这样理解状态管理:
用户多次操作(围绕某个目的,比如购物,购物需要多次购买某些商品,
也可能修改商品的数量,删除商品等等)所涉及的数据进行记录。
(2)、状态管理的技术:
A、将用户的状态记录在客户端(也就是在浏览器所在的机器保存用户的状态),即cookie技术。
b、将用户的状态记录在服务器端,即session技术。
(十六)cookie技术
(1)、什么是cookie
是一种在客户端(浏览器)维护用户状态的状态管理技术。
浏览器在访问服务器时,服务器发送一个消息头(set-cookie)及对应的值给浏览器,
浏览器会将该消息头对应的值保存到内存或者硬盘上。当浏览器下次访问服务器时,
会将这些值再发送给服务器。通过这种方式来维护用户的状态
(也就是说,将用户的状态写到了消息头对应的值里面)。
(2)、如何创建cookie
Cookie cookie=new Cookie(String name,String value);
response.addCookie(cookie);
如果cookie的值是非ascii字符,需要将其转换成ascii表示。
保存时进行编码:
name=URLEncoder.encode(name,"UTF-8");
value=URLEncoder.encode(value,"UTF-8");
显示时进行解码:
URLDecoder.decode(cookie.getValue(),"UTF-8");
(3)、查询cookie
Cookie[]cookies=request.getCookies();
如果没有cookie,则返回null.
Cookie.getName():cookie名字
Cookie.getValue():cookie的值
(4)、cookie的生存时间
默认情况下,cookie会被浏览器保存到内存里,当浏览器关闭,cookie会被删除。
可以通过调用
Cookie.setMaxAge(int seconds):设置cookie生存时间
seconds<0:默认情况
senconds>0:cookie会被保存到硬盘上,浏览器关闭,cookie有可能还存在。
seconds=0:删除cookie。
Cookie c=new Cookie("username","");
c.setMaxAge(0);
response.addCookie(c);
(5)、cookie的路径问题
浏览器在向服务器发送请求之前,先比较cookie的路径与要访问的服务器的地址。
只有符合要求的服务器地址(即要访问的地址是cookie的路径的子路径或者相等),
才会携带对应的cookie。
cookie的路径,在默认情况下,与保存cookie的组件的路径是一致的。
可以通过Cookie.setPath()来设置cookie的路径。
Cookie.setPath("/appname");//此时,该cookie可以被appname包含的所有组件访问。
Cookie.setPath("/");//该cookie可以被部署在同一台服务器上的所有应用中的组件访问。
比如:
保存someKey的组件是saveCookie.jsp,
此时,cookie的路径是/appname/a
访问someKey的组件是getCookie1.jsp,
此时,地址是/appname,因为/appname/a是/appname的子路径,
所以,浏览器不会发送cookie给服务器。
访问someKey的组件是getCookie2.jsp,
此时,地址是/appname/a,因为/appname/a与/appname/a一致,会发送cookie。
访问someKey的组件是getCookie3.jsp
此时,地址是/appname/a/b,也会发送cookie。
(6)、cookie的常见使用
A、记住用户的使用习惯。
B、自动登录
C、购物车(与session一起实现)
(7)、cookie的限制
A、用户可以禁止cookie
B、cookie的数量也有限制(浏览器保存的cookie的数量,比如ie,大约300
C、cookie的大小有限制(大约是4k)。
D、cookie的值只能是字符串,并且需要考虑编码问题。
(十七)session技术
(1)、什么是session?
在服务器端维护用户状态的状态管理技术。
浏览器在访问服务器时,服务器会创建一个对象(session对象),
然后,在默认情况下,服务器会将session对象对应的sessionId
(每一个session对象都有唯一的sessionId)以cookie机制发送给浏览器。
浏览器下次访问服务器时,会将sessionId发送给服务器,
服务器依据sessionId查找对应的session对象。通过这种方式,来维护用户的状态
(相当于每一个用户在服务器端有一个与之对应的session对象,用户的状态就保存在该对象里)。
(2)、如何创建session?
方式一:
HttpSession session=request.getSession();
方式二:
HttpSession session=request.getSession(boolean flag);
当flag为true时:
当请求到达服务器时,服务器会检查请求中是否包含
sessionId,
如果没有,则创建一个session对象。
如果有,依据sessionId查找对应的session对象,
如果找到,则返回,如果找不到,则创建一个新的session对象。
当flag为false时:
当请求到达服务器时,服务器会检查请求中是否包含
sessionId,
如果没有,返回null。
如果有,依据sessionId查找对应的session对象,
如果找到,则返回,如果找不到,返回null。
request.getSession()与request.getSession(true)完全一样。
(3)、session的几个常用方法
session.setAttribute(String name,Object obj);
Object obj=session.getAttribute(String name);//找不到,返回null。
String sessionId=session.getId();
(4)、session对象的生存时间
对于不同的服务器,session对象会有不同的默认生存时间,比如tomcat,是30分钟。
超过指定时间,不去访问session对象,服务器会删除session对象。可以设置最大不活动时间session.setMaxInactiveInterval(int seconds);
也可以使用配置文件,设置超时限制。对于整个服务器设置:
可以在tomcat_home/conf/web.xml
也可以,针对单个应用来设置
WEB-INF/web.xml
(5)、删除session对象
session.invalidate();
(6)、session的优缺点
A、存放的数据可以是任何类型,存放的数据量的大小可以很大。
B、数据存放在服务器端,相比cookie要安全。
C、因为所有状态相关的数据都存放在服务器端,所以,服务器的压力比较大。
经常使用session持久化来临时保存session中的状态。
(十八)URL重写机制
当用户禁止cookie之后,如果实现session机制(如何实现sessionId的跟踪)。
使用url重写机制:
简单地说,所谓url重写,指的是在url地址后面添加sessionId。
具体来讲,要访问某个组件(该组件如果需要使用session机制),
不能够直接在浏览器地址栏输入该组件的地址,而应该使用
服务器生成的该组件的地址(该地址中包含了sessionId)。
当使用服务器生成的地址去访问某个组件时,会将sessionId
传递给服务器,服务器通过sessionId找到对应的session对象。
使用到的方法
//在链接,表单提交
response.encodeURL(String url);
//重定向
response.encodeRedirectURL(String url);
比如:
response.sendRedirect(response.encodeRedirectURL(String url));
(十九)过滤器
(1)、什么是过滤器
在servlet规范当中定义的一种特殊的类,该类的实例可以对servlet容器的调用过程进行拦截处理。
(2)、如何写过滤器
step1写一个类,实现Filter接口。
step2在拦截逻辑写在doFilter()方法里。
step3在web.xml在配置过滤器。
(3)、过滤器的优先级
看web.xml中,
(4)、配置初始化参数
可以在web.xml中使用
(5)、过滤器的优点
A、将一类相同或相似的操作集中封装到过滤器当中,方便代码的维护。
B、可以实现代码的"可插拔性"。即增加或者减少某个模块,只需要添加相应的类及配置文件。
eg.//用过滤器来处理一些中文乱码问题
step1-2:
public class CharacterEncodingFilter implements Filter{
public void destroy(){
System.out.println("CharacterEncoding.destroy()");
}
public void doFilter(ServletRequest request,ServletResponse response,
FilterChain chain)throws IOException,ServletException{
request.setCharacterEncoding("UTF-8");
chain.doFilter(request,response);
}
public void init(FilterConfig filterConfig)throws ServletException{
System.out.println("CharacterEncoding.init()");
}
}
step3:
com.tarena.filter.CharacterEncodingFilter
(二十)监听器
(1)、什么是监听器?
servlet规范中定义的一种特殊的类,该类的实例用于监听两类事件并进行相应的处理是:
第一类是容器创建或者销毁ServletContext,HttpSession,HttpServletRequest这三个对象时产生的事件。
第二类是对这个三个对象调用setAttribute,removeAttribute产生的事件。
(2)、编程
step1写一个java类,选择实现相应的监听器接口
step2在监听器接口方法中,实现事件的处理逻辑
step3在web.xml中配置
(3)、HttpSessionListener接口的使用:
该接口是用于监听session的创建和销毁。比如要显示当前系统在线人数。
(4)、了解其它7个接口
ServletContext接口相关的:
ServletContextListener:监听上下文的创建和销毁
ServletContextAttributeListener:监听在上下文上绑订、解除绑订时产生的事件。
ServletRequest接口相关的:
ServletRequestListener:监听request的创建和销毁
ServletRequestAttributeListener:监听在request上绑订、解除绑订时产生的事件。
HttpSession接口相关的:
HttpSessionAttributeListener:监听在session上绑订、解除绑订时产生的事件。
HttpSessionActivationListener:监听session激活、钝化时产生相应事件。
HttpSessionBindingListener:监听在session上绑订、解除绑订时产生的事件。
但是编程与其它7个不一样:
step1写一个java类,实现HttpSessionBindingListener
step2当将该java类的实例绑订/解除绑订到session对象上时,会产生相应的事件。
eg:
Order implements HttpSessionBindingListener
Order o=new Order();
session.setAttribute("order",o);
eg.
step1-2:
public class CountListener implements HttpSessionListener{
@Override
public void sessionCreated(HttpSessionEvent se){
System.out.println("Session created!");
ServletContext context=se.getSession().getServletContext();
Integer counter=(Integer)context.getAttribute("Session_Counter");
if(counter!=null){
counter++;
se.getSession().getServletContext().setAttribute("Session_Counter",counter);
}else{
counter=new Integer(1);
se.getSession().getServletContext().setAttribute("Session_Counter",counter);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se){
System.out.println("Session destroyed!");
ServletContext context=se.getSession().getServletContext();
Integer counter=(Integer)context.getAttribute("Session_Counter");
if(counter!=null){
counter--;
se.getSession().getServletContext().setAttribute("Session_Counter",counter);
}
}
}
setp3:
com.tarena.listener.CountListener
(二十一)servlet线程安全问题
(1)、为什么说servlet会有线程安全问题
在默认情况下,容器只会为servlet类创建唯一的一个实例。
当有多个请求到达容器时,容器会为每一个请求创建一个线程,
这些线程有可会访问同一个实例。如果这些线程对servlet实例
的属性进行修改操作,就有可能产生线程安全问题。
(2)、如何解决
A、加锁:使用synchronized对方法或者代码块加锁(少用,因为会影响性能)。
B、实现SingleThreadModel接口(标识接口),不建议使用。
因为如果一个servlet实现了该接口,容器会为每一个请求
创建一个该servlet实例。是因为实例创建过多。
C、在servlet类当中,尽量避免修改属性。
(二十二)文件上传(扩展)
step1在表单中添加