首页 > 代码库 > Servlet
Servlet
一 概述
1.服务器
处理请求、给出响应的全部因素构成的整体称作服务器,包含硬件与软件两个方面。
2.Servlet规范
服务器处理请求、响应遵守的原则。
3.Servlet是什么?
Server Applet,运行在服务器端的java应用程序,使用java语言编写,遵守java规范,Servlet规范的核心。
4.Servlet在Web服务器中的地位
Servlet在整个Web服务器中充当控制器的角色,将请求转发给相应的业务逻辑处理。
5.tomcat是什么?
tomcat是服务器的软件方面,是一个实现了Servlet规范与JSP规范的容器,由Apache提供、免费、开放源代码的轻量级应用服务器,适用于中小型系统和并发访问用户不是很多的场合。
6.MIME
Multipurpose Internet Mail Extensions,多用途互联网邮件扩展协议,为每一种类型的文件制定一种规范以便浏览器接收到文件以后调用相应的组件打开。
二 几个重要类
1.ServletContext
- ServletContext,Servlet上下文,是所用Servlet存在的环境,一个Web应用程序,一个实例对象,在整个应用层级起作用。
- JSP内置对象application就是一个ServletContext实例,
- ServletContext在文件上对应于web.xml,web.xml包含整个服务器中用户自定义的全部信息,从此可以看出ServletContext的作用范围。
- 作用:ServletContext主要用于在Web服务器启动时加载各组件到服务器,根据自定义参数初始化服务器。
- 生命周期:ServletContext对象在服务器启动时创建,服务器停止时销毁。
重要方法:
servletContext.setAttribute(name, object);//向servletContext作用域中添加属性,该属性为所有用户共享 servletContext.getAttribute(name);//获取servletContext作用域中指定属性的属性值 servletContext.removeAttribute(name);//从servletContext作用域中删除指定属性 servletContext.getRealPath(path);//根据相对于项目的路径获取资源的绝对路径URL servletContext.getResourceAsStream(path);//获取路径文件的输入流 servletContext.getInitParameter(name);//获取初始化参数的值
对象获取:
ServletContext对象由Web服务器在启动时自动创建,程序员需要做的就是获取该实例对象,以下是几种获取方式。
this.getServletContext();//HttpServlet提供了获取ServletContext实例对象的方法,在doGet或者doPost方法内部 request.getServletContext();//通过request对象获取 HttpSession session = request.getSession(); session.getServletContext();//通过session对象获取
2.ServletConfig
用于初始化Servlet,一个Servlet,一个ServletConfig。
public void init(ServletConfig config) throws ServletException { String initParameter = config.getInitParameter("initParamName"); }
三 Servlet
1.Servlet的工作原理:
Web容器启动时会创建两个与Servlet相关的Map集合,两个集合的key值均为urlPattern,即请求uri,第一个Map的value是Servlet的引用变量,第二个Map的value是Servlet的全限定性类名。请求到达Web容器后,系统先搜索第一个Map集合,如果存在与uri对应的引用变量,则获取该引用变量,如果不存在,继续搜索第二个Map集合,获取对应的全限定类型,创建对象,并把引用变量存到第一个Map集合中。
2.Servlet继承结构
<interface> <interface> <interface>
Servlet ServletConfig Serializable
| | |
--------------------------------------------------
|
<abstract> GenericServlet
|
<abstract> HttpServlet
3.实际开发中Servlet的创建方式
Servlet接口是Servlet规范中定义的,服务器自动调用其中service方法处理请求,该接口有多个抽象方法,很多在实际开发中很少使用,实现该接口需要实现其中的全部抽象方法,因此不采用直接实现Servlet接口的方法创建Servlet。
GenericServlet实现Servlet接口中大多数抽象方法,保留了一个抽象方法service,继承该抽象类创建servlet,必须实现该抽象方法。HTTP中多个请求方法在处理请求前必须做一些固定的前置工作,如果实现该serivce方法就需要在每一个Servlet的service方法中都编写前置工作代码,造成代码冗余。为了解决此问题,tomcat提供了一个GenericServlet的子类HttpServlet,HttpServlet采用固定行为结构的模板方法模式将前置工作固定在一个方法,用户在创建Serlvet时继承HttpServlet,然后重写与请求方式对应的方法即可。(具体参考源码)
HttpServlet源码
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn‘t support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
从中可以看出Get请求在实际执行前做了很多准备工作。
4.生命周期
Servlet默认在调用时创建并初始化,这也是工作原理中第一个Map集合引用变量为空情况出现的原因。对一些必定会被访问并且访问频繁的Servlet可以设定为在容器启动时创建并初始化,提高访问速度。
<load-on-startup>int类型数字</load-on-startup>
小于0:默认值,调用时创建初始化。
等于大于0:在Web服务器启动时创建初始化,值越小,优先级越高。出现相同值时,不会出现异常,容器自定义顺序执行。
5.主要方法:
this.getInitParameter("");//获取初始化参数 this.getServletName();//获取配置文件<servlet-name>标签的内容 this.getServletContext();
6.由于继承HttpServlet创建的Servlet属于自定义类,系统不知晓,必须在配置文件中配置:
<servlet> <servlet-name>bothRegisterServlet</servlet-name> <servlet-class>com.servlet.register.BothRegisterServlet</servlet-class> <init-param> <param-name>xxx</param-name> <param-value>xxxx</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>bothRegisterServlet</servlet-name> <url-pattern>/bothRegisterServlet01</url-pattern> </servlet-mapping>
由于访问Web容器中的Servlet对象时,无论采用哪种方式都只能间接访问,一般采用url访问servlet,那么就需要在访问时使用的url与最终资源servlet之间建立一对一的映射关系,这就是servletMapping存在的原因。
允许为一个Servlet对象配置多个url格式。
四 request
1.什么是request?
Servlet规范定义的一个向Servlet提供客户端请求信息的对象。
2.request中包含哪些信息,或者通过request可以获取哪些信息?
- 请求参数:当表单输入为空时,服务器端获取的内容为空字符串,不为null。
- request作用域内的属性:可以将数据存放到request作用域中,在同一请求中获取。
- 输入流:文件上传时,可以通过request获取上传文件的输入流。
- Cookie:Cookie由服务器生成,保存在客户单,浏览器向服务器发出请求时自动提交。
- Part:代表上传文件的对象。
- 其他内置对象:如session\application。
- 其他与请求密切相关的信息:如请求方式、使用的浏览器、协议、请求url、请求体长度、查询字符串、请求头、客户端IP、端口号等。
3.常用方法:
⑴request.getParameter()与request.getAttribute()对比
- getParameter:获取请求参数的值,返回值String,表单输入为空时,返回值为空字符串,不为null。
- getAttribute:获取作用域范围内的属性,返回类型为属性类型,属性不存在时,返回null。
⑵request.getSession()与request.getSession(boolean create)对比
- getSession():与getSession(true)相同,基于Cookie获取Session对象,如果Cookie不存在,则新建session。
- getSession(false):基于Cookie获取Session对象,如果Cookie不存在,则返回null。
⑶request.setCharacterEncoding():设置请求体的编码方式,即服务器解析浏览器提交的数据时采用的编码方式。由于只有POST请求的数据通过请求体传输,所以只对POST请求有意义,对GET请求无意义。
⑷request.getContentLength():获取请求体的字节长度,只对POST有意义。
⑸request.getQueryString():获取追到URL后面的请求字符串,只对GET请求有意义。
⑹request.getRequestDispatcher().forward(request,response)request.getRequestDispatcher().include(request,response)的区别:
持有响应权的资源不同。
- forward将请求继续向前推进,将响应权转交给后面的资源,自身无法向服务器发出响应,即在自身中使用out.write方法不能向页面输出内容。
- include包含,将后面的资源作为自身的一部分,不仅后面的资源可以发出响应,自身也可以响应,相当于将自身的一部分代码分到后面的资源中。
4.什么是同一请求?
一次请求在结束前,如果未进行重定向操作,进行转发与包含操作,仍然属于同一请求的范畴。
5.同一请求的意义
同一请求共享请求参数与作用域中的属性等信息。
五 response
1.什么是response?
Servlet规范定义的一个向浏览器发出响应的对象,由Servlet容器自动创建。
2.reponse主要作用:
⑴设置响应的MIME类型及编码方式。
由于存在多种响应数据类型,因此服务器在响应前必须指明数据类型,以便浏览器根据指定类型处理接收的数据。
response.setContentType("text/html;charset=UTF-8");//以HTML方式处理
⑵设定响应头,即响应内容的其他方面:
response.setHeader(name,value);
⑶创建并将Cookie保存到客户端:
response.addCookie(Cookie cookie);
⑷重定向:
response.sendRedirect(url);
⑸获取向浏览器输出内容的输出流:
PrintWriter out=reponse.getWriter();
⑹获取文件下载的输出流:
ServletOutputStream sos=response.getOutputStream();
3.响应或者跳转后的代码执行问题
代码继续执行,对响应结果或者request作用域没有影响,因为响应已经结束,请求已转发给其他资源,自身就失去了处理请求的能力,但对session\applicatiion作用域可以产生影响。通常不在响应或者跳转之后对整个处理过程施加影响。
六 Cookie
1.什么是Cookie?
由服务器生成,保存在浏览器端的存储会话信息的对象。
2.创建Cookie
Servlet规范提供了Cookie类,用于创建Cookie对象:
Cookie cookie=new Cookie(name,value);
3.保存Cookie
由服务器创建,保存在客户端,浏览必须允许保存Cookie将cookie:
response.addCookie(cookie);
4.设定Cookie路径:
Cookie在访问服务器时自动提交,这种提交不是无选择地对任何访问路径都提交,而是只对设定的路径提交。在未单独设定Cookie的绑定路径时,Cookie与生成Cookie的访问路径绑定,访问该路径下任何一个资源时,浏览器自动提交Cookie。
可以设定Cookie的绑定路径,使Cookie不受生成路径的限制:
cookie.setPath(String uri);
5.Cookie的生命周期
默认情况下,Cookie保存在浏览器缓存中,浏览器关闭,Cookie销毁。如果希望Cookie中保存的信息长期存在,可以将Cookie保存到本地硬盘中,前提是当前浏览器支持。
cookie.setMaxAge(int expiry);//以秒为单位
- 取值大于0:将Cookie保存到硬盘中,无论浏览器是否关闭,指定时长过去后,Cookie被销毁。
- 等于0:立即销毁Cookie。
- 小于0:默认情况,将Cookie保存在浏览器缓存中,浏览器关闭,Cookie销毁。
6.常用方法:
String name = cookie.getName();//获取Cookie的名称 String value = http://www.mamicode.com/cookie.getValue();//获取Cookie的值 cookie.setValue(newValue);//修改Cookie的值
7.主要应用:
Cookie主要用于保存浏览器关闭以后需要保留的会话信息,如登陆信息,实现免登陆功能。
七 Session
1.HTTP协议的无状态特性
在HTTP协议中,浏览器向服务器发送请求,服务器响应完毕后,连接结束,服务器端没有保存本次请求的任何信息,这就是HTTP协议的无状态特性。如果需要保存会话信息,就必须提供一种解决方案,而Session就是这个解决方案。
2.什么是Session?
Session是用来在服务器端保存会话信息的对象,比如保存用户在网站上的足迹等。
3.什么是同一Session?
在浏览器开启Cookie的情况下,从浏览器第一个访问网站到浏览器关闭的时间内浏览器与服务器所有的互动都是在同一个会话中。如果浏览器关闭了Cookie,那么浏览器每向服务器发送一次请求,都开启一个新的会话,即服务器端都会新建一个Session对象。
4.Session的工作原理
第一次访问时,服务器会自动创建一个Session对象,并为Session对象分配一个唯一的32位的id,同时生成一个Cookie对象,name为JSESSIONID,value为Session的id。在Cookie的有效期内,再次访问服务器时,根据value值获取对应的Session对象,这样就保证了在同一次会话中存在的始终是同一个Session对象。
5.Session的生命周期
Session对象在浏览器第一次访问服务器时创建,如果浏览器长时间不向服务器发送请求,在指定的时长之后,服务器会销毁Session对象。
这里所说的时长不是从Session创建到销毁的时间长度,而是浏览器长时间发送请求,服务器保存Session对象的最大时间长度,通过以下方法设置,以秒为单位:
session.setMaxInactiveInterval(int interval);
6.Session的销毁
通过以下方法,浏览器可以主动销毁Session对象:
session.invalidate();
7.主要方法:
Session主要用于在同一会话中共享数据,所以对Session的主要操作是操作作用域中的属性:
session.setAttribute(name,value);//向作用域中添加属性 session.getAttribute(name);//获取作用域中的属性 session.removeAttribute(name);//从作用域中删除属性
八 请求转发与重定向
1.什么是请求转发?
服务器接收到请求以后,首先对请求进行预处理,然后将请求转发给其他的资源继续处理,这种转发叫做请求转发。
2.什么是重定向?
服务器调用特定的方法向浏览器发送一个资源路径,浏览器访问该路径,这一过程叫做重定向。
3.请求转发与重定向的区别
- 请求转发是服务器调用不同的资源处理同一请求,始终是同一请求。
- 重定向使得浏览器再次向服务器发送请求,前后是两个不同的请求。
九 异步机制
1.什么是异步机制?
为耗时的任务分配一个线程,主线程继续执行后面的代码,执行完毕,将主线程归还线程池,以便执行其他的请求。
2.异步机制产生的原因
Servlet是单例多线程的,允许并发访问的线程数目有限,为此Servlet建立了一个线程池,请求必须从线程池中获取了线程才能访问Servlet。若一个请求长时间占有线程,可能导致后面的请求长时间等待,降低了程序的吞吐能力。如果一个线程从Servlet线程池中获取了线程以后,另外开启一个线程处理耗时的任务,及时将主线程归还线程池,就解决这个问题。
3.异步机制的作用
异步机制的作用主要不是为了提高单次执行速度,而是提高吞吐量,即同一时间段内允许更多的请求访问Servlet。为了提高访问的线程数目,降低每次访问占有Servlet线程的时间,将耗时的任何交个另外一个线程处理,将主线程及时归还线程池。
4.异步机制的基本原理
Servlet接收到请求以后,对请求进行初步处理,然后开启一个异步线程处理具体的业务,Servlet线程继续执行后面的代码,执行完毕后,将Servlet线程归还线程池,以便其他请求使用。等待异步线程执行完毕后一起响应,异步线程执行完毕主要有两个标志:
- 在异步线程内部调用complete方法。
- 超时时间结束。
超时时间不并代表异步线程的生命时长,而是最大生命时长。如果在超时时长内,异步线程调用了complete方法,异步线程提前结束。
异步线程默认的超时时长是10s(Servlet不同版本不同),当主线程执行完毕,同时超时时长结束或者异步线程提前结束,服务器开始向浏览器发送响应,销毁request、response对象,关闭输出流,如果异步线程未执行完毕,那么异步线程中已执行的响应会响应到浏览器,未执行的响应不会响应到浏览器。
5.响应时机
分配给异步线程的时间不是无限的,因此存在一个响应时机的问题。
在主线程执行完毕并且异步线程超时时长用完或者提前结束时响应。
6.异步处理的实现
AsyncContext ac=request.startAsync(); ac.setTimeout(int mills); ac.start(Runnable run);//将异步线程管理对象ac作为参数传入异步线程中,通过该参数可以获取request\response
7.异步机制的使用
由于异步机制的设计目的是为了使请求尽快归还Servlet线程,提高程序的吞吐量,并未显著提高响应速度。异步机制通常不直接用作向浏览器输出响应内容,如在异步线程内部使用out.write直接向浏览器输出,而是用来处理耗时的任务,将处理结果存放到session/application等作用域中。
8.还可以使用AsyncContext对象为异步线程添加监听器,监听异步线程的执行过程。
十 web.xml
1.配置Servlet、Filter、Listener、contextParams等构成应用程序的重要信息。
2.配置欢迎页面,也是网站的首页,可以由多个文件构成,优先采用上面的文件:
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>default.html</welcome-file> </welcome-file-list>
注:在配置文件中书写路径时,只有欢迎列表中的路径不需要在前面加“/”,其他地方的路径都需要在前面加“/”,因为欢迎页面只能放在WebContent根路径下,不能放在WebContent根路径下的文件夹内,其路径相对固定,因此可以简写。
3.配置错误页面:
⑴方式一,根据错误代码配置页面,出现指定的错误代码时显示指定的页面:
<error-page> <error-code>404</error-code> <location>/xxxx</location>//可以放在WebContent目录下任意位置 </error-page>
⑵方式一,根据异常类型配置页面,出现指定的异常时显示指定的页面:
<error-page> <exception-type>java.lang.NullException</exception>//异常类必须写完整的类名 <location>/xxxx</location> </error-page>
4.配置ServletContext的初始化参数:
<context-param> <param-name>paramName</param-name> <param-value>paramValue</param-value> </context-param>
十一 注解式开发
Servlet3.0新增注解式开发,注解式开发就是将在编写在配置文件web.xml中的信息转移到类文件上。
注解方式:
在类名上添加注解:
@WebServlet(urlPatterns={"",""},loadOnStartup=1,initParams={@WebInitParam(name="",value="")},asyncSupported=true)
两种注册方式同时存在,如果映射路径不相同,相当于存在一个集中了所用路径的Servlet,如果存在相同路径,服务器无法启动。
十二 文件上传
1.Servlet3.0提供一个Part类型,该类封装了上传文件信息。
2.文件名存储在一个名为Content-Disposition的请求头中。
3.文件上传的具体实现:
Part part=request.getPart(文件字段名);//获取文件封装对象 String fileName=part.getHeader("Content-Dispostion");//获取文件名 part.write("parentPath/"+fileName);//将文件保存到指定路径,只能使用绝路径
十三 文件下载
1.Servlet提供了ServletOutputStream用作文件下载的输出流。
2.文件下载时必须设置浏览器以附件的形式处理从服务器获取的数据:
response.setHeader("Content-disposition","attachment;filename=xxxx");
如果请求头中设置的文件名含有中文必须转化为ISO-8859-1的编码方式。
3.从服务器获取输入流,利用ServletOutputStream输出流将文件写到用户指定路径:
InputStream is = getServletContext().getResourceAsStream("/Files/upload.txt");
ServletOutputStream os = response.getOutputStream();
有了输入流与输出流,后续操作就是将输入流中的内容写入输出流,这是IO常见的操作。
十四 线程安全问题
1.Servlet线程安全问题产生的原因
Servlet以单例的形式运行在多线程的环境中,其中的实例变量存储在堆中的对象中,而堆是多线程共享的,因此实例变量存在线程安全问题,而静态变量存储在方法区,方法区也是多线程共享的,静态变量也存在线程安全问题,而局部变脸存储在栈中,栈中的数据在内部是共享的,在栈间是不共享的,即一个线程一个栈,因此局部变量时线程安全的。
2.线程安全问题的解决方案:
- 将全局变量转化为局部变量。
- 将修改全局变量的代码添加到同步方法或者同步块中。
- 将全局变量存储到ThrealLocal中,为每一个线程分配一个变量的副本,各个线程相互独立操作。
十五 免登录
1.根据公开与保护程度,将网站上的资源分为开放性资源、权限性资源两种:
- 开放性资源:对所用用户公开,无需权限就可以访问的资源。
- 权限性资源:保存用户个人信息,通过身份验证后才可以访问的资源。
2.免登录的含义
登录网站以后,下次使用同一浏览器访问网站,不需要重新登录。
3.免登录的实现原理
将登录信息(用户名、密码)保存在Cookie中,将Cookie保存在本地,访问网站时浏览器自动提交Cookie,经Filter或者Interceptor验证通过后,无需登录直接访问。
4.免登录的条件
同一个浏览器:免登录需要从本地获取Cookie对象验证信息。
十六 Servlet的组件可插性
Servlet3.0组件具有可查性,指的是Servlet、Filter、Listener可以作为架包插入项目中。
架包创建:
- 创建项目Web Fragment Project。
- 打包jar File。
- 放入lib目录下,作为架包使用。
使用:
可以将一些常用的Servlet组件封装成架包,直接放在lib目录下使用,如解决POST请求时中文乱码的Filter。
十七 动态注册
1.什么是动态注册?
在Web运行时注册Servlet、Filter、Listener。
2.动态注册时机
为了安全,只能在Web服务器启动时注册,即通过ServletContextListener监听器注册。
Servlet