什么是Servlet
Servlet是sun公司制定的用来扩展web服务器功能的组件规范,通俗理解为遵循Servlet规范开发的实现了某个功能的Java组件。该组件没有 main 方法,不能独立地运行,只能在Servlet容器中运行,容器管理其从创建到销毁的整个过程。
早期web服务器(Apache)不能处理动态页面,为了扩展该功能,web服务器将请求发送给帮助程序(tomcat)处理。tomcat就是Servlet容器, WEB-INF目录下的web.xml部署描述符文件是web应用的配置文件,容器根据该配置来指定Servlet处理具体请求。
Web请求的过程
- 浏览器依据ip、port与服务器建立连接
- 浏览器将相关数据(如请求参数)打包,然后发送请求
- web服务器的通信模块解析请求数据包,发送给Serlvet容器。容器将解析的数据封装到request(HttpServletRequest)对象中,同时创建一个response(HttpServletResponse)对象。
- 容器依据请求路径找到Servlet类,加载class文件并创建Servlet对象(如果已经存在则跳过)。然后调用该对象的service()方法,将request(可以获取请求中所有的数据)和response(可以封装服务器的响应数据)作为参数传递进去,执行业务逻辑。
- 容器读取response中的处理结果,然后将处理结果发送给通信模块,通信模块将数据打包发送给浏览器。
- 浏览器解析响应数据包,生成响应的页面。
- WEB应用程序停止时,Servlet容器将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
简单来说就是tomcat容器通过web.xml文件中的<url-patten>找到对应的servlet,然后调用service()方法处理浏览器请求。
开发servlet步骤
1.在web容器中配置url映射
<servlet> <servlet-name>servletTest</servlet-name> <servlet-class>com.servlet.servletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletTest</servlet-name> <url-pattern>/servlet/test</url-pattern> </servlet-mapping>
2.开发servlet类
定义一个servlet,继承HttpServlet(能够处理HTTP请求),然后覆写doGet/doPost或覆写service()方法。
抽象类Httpservlet同时实现了service、doGet和doPost方法,其中service方法会根据HTTP请求自动调用相应的doxxx方法(默认实现为向客户端返回一个错误)。所以实际开发中如果service方法不需要处理业务逻辑,则只需重写相应的doxxx方法(向客户端发送数据),不用重写service方法。如果需要service处理业务逻辑而重写了service方法,则里面必须包括相应的转发逻辑(转发到其他Servlet组件或调用doxxx)。
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Servlettest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); ServletCofig config = this.getServletConfig(); //可以直接调用 out.println("<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the GET method"); out.println(" </BODY></HTML>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
一个servlet处理多个请求
此时servlet充当控制器的作用,将不同请求分发给不同资源,需要采用url-pattern的扩展名规则。
String uri=request.getRequestURI(); //获取请求路径:appName/list.do,appName/add.do String action= uri.substring(uri.lastIndexOf(“/”)+1,uri.lastIndexOf(“.”)); //根据不同的路径,调用不同的分支处理 if(action.equals("add")){ ... }else if(action.equals("list")){ ... }
Servlet生命周期
1.实例化:容器不管收到多少个请求,只会创建一个servlet对象。
实例化时机:收到请求时,或设置<load-on-startup>1</load-on-startup>于容器启动时创建。
2. 初始化: 容器调用init(ServletConfig config)方法对Serlvet进行初始化,GernericServlet提供了init方法的实现(而且通过this.config=config将ServletConfig对象保存),不用开发者写。Servlet的整个生命周期内,该方法只被调用一次。在init方法内,通过config.getInitParameter("name")或静态方法ServletConfig.getInitParameter("name")可以获取servlet的初始化参数init-param。如果需要重写init方法执行别的操作,最后应调用 super.init() 以确保正确的初始化。
3. 就绪:容器收到请求后调用service()方法。
4. 销毁:只执行一次。
Servlet线程安全问题
重定向与转发
重定向:服务器向浏览器发送一个302状态码及一个Location消息头(值为重定向地址),浏览器收到后立即向重定向地址发送请求。
使用场景:当资源移动到新的位置,需要客户端向新地址发送请求时,或是为了负载均衡,或者只是为了简化用户的操作(提交信息后跳转)。
使用响应对象的API即可实现重定向,重定向过程中涉及到的web组件不共享同一个request和response对象。
out.println("会被清空"); response.sendRedirect("任意url"); System.out.println("该处代码仍会执行,等整个service方法结束才发送响应数据"); //重定向后会清空response对象中的数据,Content-Lenght:0
转发:一个web组件(servlet/jsp)将未完成的请求处理通过容器转交给另外一个web组件继续完成,如servlet获取数据后转发给jsp展现。
转发前后涉及的web组件用的是同一个request和response对象。在服务器内部完成,和浏览器无关。
使用步骤:
//1.绑定数据到request对象 request.setAttribute(String name,Object obj); //Object request.getAttribute(String name); 转发目标组件内获取数据 //2.获得转发器 RequestDispatcher rd=request.getRequestDispatcher(String path); //path:转发目的地,必须是同一个应用内部的绝对路径或相对路径 //3.转发 rd.forward(request,response); other code ... //转发语句之后的代码依然会被执行完
过滤器、监听器与SpringMVC的拦截器
过滤器:是Servlet2.3规范中定义的一种封装了一些功能的小型、可插入web组件,用来拦截Servlet容器的请求过程和响应过程,以便处理请求和响应数据。
使用场景:确认用户是否登陆过,提交的内容是否有敏感词,字符集转变,管理会话属性,将多个相同处理逻辑的模块集中到过滤器中方便代码的维护。
编写过滤器步骤:
1.编写一个java类,实现filter接口,拦截处理逻辑在doFilter方法中实现
/* 敏感词过滤器 */ public class CommentFilter implements Filter{ private FilterConfig config; private illegalWord; //容器启动后创建Filter实例,调用init方法一次。 //容器会将创建好的FilterConfig对象作为参数传入init方法,借此可以获取初始化的配置信息 //可以将FilterConfig作为成员保存在对象中供后续使用 public void init(FilterConfig filterConfig)throws ServletException{ this.config=filterConfig; illegalWord=filterConfig.getInitParameter("illegalWord"); ... } //doFilter是主要方法,可以调用过滤器链的doFilter方法,也可以直接向客户端返回响应信息,或者利用HttpServletResponse的sendRedirect()方法将请求转向到其他资源 //参数chain是过滤器链对象,过滤器链的dofilter()方法会调用下一个过滤器的doFilter方法。若无则调用相应的Serlvet的service方法 public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws IOException,ServletException{ HttpServletRequest req=(HttpServletRequest)request; //强制转到子接口 HttpServletResponse res=(HttpServletResponse)response; req.setCharacterEncoding("UTF-8"); res.setContentType("text/html;charset=utf-8"); PrintWriter out=req.getWriter(); String comment=req.getParameter("coment"); if(coment.indexOf("shit")!=-1){ //res.sendRedirect("error/_error.jsp");//可以直接转发 out.print("<h3>有敏感词</h3>"); //也可以直接响应数据。 }else{ chain.doFilter(req,res); …//other code here will be executed when response comes back; } } //容器删除过滤器实例之前调用,只执行一次 public void destroy(){this.config = null;...} }
2.将过滤器配置到web.xml中
<!-- 配置编码过滤器 --> <filter> <filter-name>setCharacterEncoding</filter-name> <filter-class>com.company.strutstudy.web.servletstudy.filter.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> //存在多个过滤器时,会按照filter-mapping的先后顺序依次调用 <filter-mapping> <filter-name>setCharacterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
监听器:Servlet规范中定义的一种特殊的组件,用来监听Servlet容器产生的特定事件并进行相应的处理,包括容器创建/销毁request、session和ServletContext时的事件,调用reqeust、session和ServletContext的setAttribute、removeAttribute方法时产生的事件。
使用场景:统计在线用户数量/网站访问量,记录访问日志,系统启动时初始化等。
监听器的启动顺序:按照web.xml的配置顺序来启动
容器加载顺序:监听器>过滤器>Servlet
编写监听器步骤:
1.编写一个java类,实现特定的监听器接口(ServletContextListener/ServletContextAttributeListener,HttpSessionListener/HttpSessionAttributeListene,ServletRequestListener/ServletRequestAttributeListener)
//统计网站在线人数 public class CountListener implements HttpSessionListener{ private int count=0; public void sessionCreated(HttpSessionEvent hse){ //其他接口对应的requestInitialized,contextInitialized方法 count++; //通过监听的事件对象获得session对象,然后通过session获得servlet上下文 HttpSession session=hse.getSession(); ServletContext ctx=session.getServletContext(); ctx.setAttribute("count",count) } public void sessionDestroyed(HttpSessionEvent hse){ //requestDestroyed,contextDestroyed方法 //关闭时操作 } }
2.在web.xml中配置该监听器
<listener> <listener-class>web.CountListener</listener-class> </listener>
拦截器:Spring的HandlerMapping处理器支持拦截器应用。拦截器组件是SpringMVC特有的组件,可以在进入Controller之前拦截,也可以在执行Controller之后拦截,还可以在jsp解析完成后向浏览器输出前拦截。
使用场景:日志记录,权限检查,后台处理时间监控,通用逻辑共用(如读取cookie)。
拦截器接口及方法:
public interface HandlerInterceptor { boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle(HttpServletRequest request, HttpServletResponse response,Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception; }
继承HandlerInterceptor接口,三个方法必须同时实现。如果只需要实现某个的回调,可以继承HandlerInterceptorAdapter接口。
preHandle:处理器执行前被调用,第三个参数为处理请求的处理器。
返回值:true表示继续调调其他拦截器或处理器;false表示流程中断,不继续调用其他的拦截器或处理器,此时需要通过response重定向来产生响应,否则页面是白板。
postHandle:处理器执行后、视图处理前调用,可以通过modelAndView对模型数据进行处理或对视图进行处理。
afterCompletion:整个请求处理完毕后调用。如性能监控中可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理。只要preHandle返回true,拦截器的afterCompletion就会被执行。
如何配置:
<mvc:interceptors> <!-- 定义在mvc:interceptors元素下面的Interceptor将拦截所有的请求 --> <bean class="com.test.AllInterceptor"/> <mvc:interceptor> <!-- 定义在mvc:interceptor下面的Interceptor将拦截特定的请求 --> <mvc:mapping path="/test/check.do"/> <!-- 不拦截的请求 --> <mvc:exclude-mapping path="/login/*"> <bean class="com.test.SomeInterceptor"/> </mvc:interceptor> </mvc:interceptors>
SpringMVC拦截器与Filter的区别
二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。
不同之处:
Filter是Servlet规范规定的,由Servlet容器支持的,只能用于Web程序中,只在Servlet前后起作用。
拦截器是在Spring容器内,由Spring框架支持的,既可以用于Web程序,也可以用于Application、Swing程序中。拦截器是一个Spring的组件,能使用Spring里的任何资源。在Spring构架的程序中,拦截器的使用具有更大的弹性,优先使用。
同时配置过滤器和拦截器时的请求处理过程: