Servlet起步

什么是Servlet

Servlet是sun公司制定的用来扩展web服务器功能的组件规范,通俗理解为遵循Servlet规范开发的实现了某个功能的Java组件。该组件没有 main 方法,不能独立地运行,只能在Servlet容器中运行,容器管理其从创建到销毁的整个过程。

早期web服务器(Apache)不能处理动态页面,为了扩展该功能,web服务器将请求发送给帮助程序(tomcat)处理。tomcat就是Servlet容器, WEB-INF目录下的web.xml部署描述符文件是web应用的配置文件,容器根据该配置来指定Servlet处理具体请求。

Web请求的过程

  1. 浏览器依据ip、port与服务器建立连接
  2. 浏览器将相关数据(如请求参数)打包,然后发送请求
  3. web服务器的通信模块解析请求数据包,发送给Serlvet容器。容器将解析的数据封装到request(HttpServletRequest)对象中,同时创建一个response(HttpServletResponse)对象。
  4. 容器依据请求路径找到Servlet类,加载class文件并创建Servlet对象(如果已经存在则跳过)。然后调用该对象的service()方法,将request(可以获取请求中所有的数据)和response(可以封装服务器的响应数据)作为参数传递进去,执行业务逻辑。
  5. 容器读取response中的处理结果,然后将处理结果发送给通信模块,通信模块将数据打包发送给浏览器。
  6. 浏览器解析响应数据包,生成响应的页面。
  7. 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构架的程序中,拦截器的使用具有更大的弹性,优先使用。

同时配置过滤器和拦截器时的请求处理过程:

 

 

以上皆为个人理解,如有错误之处,欢迎留言指正。
原文地址:https://www.cnblogs.com/kevin2chen/p/6540052.html