Web工程结构与Servlet

Qunar大讲堂
1.web项目结构
 1.1 web项目结构
2.servlet
 2.1 servlet
 2.2 listener
 2.3 filter
 2.4 加载过程
 2.5 路径映射,转发与重定向
 2.6 cookie

一、Web 工程项目结构

Web工程结构与Servlet_第1张图片
无子 Module 结构图
Web工程结构与Servlet_第2张图片
有子 Module 的工程结构

二、servlet

1.Servlet / GenericServlet / HttpServlet
Web工程结构与Servlet_第3张图片
2.Listener

Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个纵向维度控制程序和数据。

目前 Servlet 中提供了 5 种两类事件的观察者接口,它们分别是:

  • 4 个 EventListeners 类型的:由某个事件触发;当这些Listener的属性被修改时,这些事件将被触发
    ServletContextAttributeListener
    监听对ServletContext属性的操作,比如增加、删除、修改属性

ServletRequestAttributeListener
ServletRequestListener
HttpSessionAttributeListener

  • 2 个 LifecycleListeners 类型的:由生命周期的不同状态触发
    ServletContextListener 创建StandardContext时contextInitialized方法被调用
    当创建ServletContext时,激发contextInitialized(ServletContextEvent sce)方法;当销毁ServletContext时,激发contextDestroyed(ServletContextEvent sce)方法。

HttpSessionListener 创建Session是sessionCreated方法被调用
监听HttpSession的操作。当创建一个Session时,激发session Created(HttpSessionEvent se)方法;当销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se)方法。

Web工程结构与Servlet_第4张图片

Listener是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量。

Listener Demo

下面我们开发一个具体的例子,这个监听器能够统计在线的人数(一次“在线”定义为Session创建到被销毁的时段)。在ServletContext初始化和销毁时,在服务器控制台打印对应的信息。当ServletContext里的属性增加、改变、删除时,在服务器控制台打印对应的信息。

要获得以上的功能,监听器必须实现以下3个接口:

  • HttpSessionListener
    ServletContextListener
    ServletContextAttributeListener
import javax.servlet.ServletContextAttributeEvent;  
import javax.servlet.ServletContextAttributeListener;  
import javax.servlet.ServletContextEvent;  
import javax.servlet.ServletContextListener;  
import javax.servlet.http.HttpSessionEvent;  
import javax.servlet.http.HttpSessionListener;  
  
/** 
 * @author wanlong.ma 
 * 
 */  
public class OnlineUserListener implements HttpSessionListener,  
        ServletContextListener, ServletContextAttributeListener {  
    // 当前在线人数计数器
    private long onlineUserCount = 0;  
  
    public long getOnlineUserCount() {  
        return onlineUserCount;  
    }  
  
    @Override  
    public void attributeAdded(ServletContextAttributeEvent arg0) {  
  
    }  

    @Override  
    public void attributeRemoved(ServletContextAttributeEvent arg0) {  
  
    }  

    @Override  
    public void attributeReplaced(ServletContextAttributeEvent attributeEvent) {  
        System.err.println("...attributeReplaced...");  
    }  

    @Override  
    public void contextDestroyed(ServletContextEvent arg0) {  
  
    }  
  
    @Override  
    public void contextInitialized(ServletContextEvent arg0) {  
  
    }  

    // 当Session被创建时调用,在线人数+1,并调用本地toUpdateCount方法设置一个onlineUserCount属性,以便被获取
    @Override  
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {  
        onlineUserCount ++;  
        toUpdateCount(httpSessionEvent);  
    }  
  
    // 当Session被销毁时调用,在线人数-1;机制同上
    @Override  
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {  
        onlineUserCount --;  
        toUpdateCount(httpSessionEvent);  
    }  
  
    private void toUpdateCount(HttpSessionEvent httpSessionEvent){  
        httpSessionEvent.getSession().setAttribute("onlineUserCount", onlineUserCount);  
    }  
}  

配置web.xml:

   
      com.ee.listener.OnlineUserListener  
  

jsp中通过request.getSession().getAttribute获取我们设置的attribute.

3.Filter
  • 概述
    Filter是一种可以改变进入的请求(Request)和返回的响应(Response)的Header和内容的Java组件。

Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链

Web工程结构与Servlet_第5张图片

由上图可以看出:在Request进入Servlet之前Filter对其进行预处理,在Response离开Servlet之后也会被Filter进行处理,要注意这个顺序前后是相反的:

web.xml中元素执行的顺序listener -> filter -> struts拦截器 -> servlet
  • 优点
    过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题

  • 过滤器的作用描述
    ☑ 在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest 。
    ☑ 根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
    ☑ 在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。
    ☑ 根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。

  • Filter接口

1.如何驱动
在 web 应用程序启动时,web 服务器将根据 web.xml 文件中的配置信息来创建每个注册的 Filter 实例对象,并将其保存在服务器的内存中

2.方法介绍
init()   init 方法在 Filter 生命周期中仅执行一次,web 容器在调用 init 方法时
destory()  在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
doFilter()  Filter 链的执行

  • Filter示例:实现编码过滤器和请求url日志记录过滤器
web.xml配置
    
      
        setCharacterEncoding  
        com.company.strutstudy.web.servletstudy.filter.EncodingFilter  
          
            encoding  
            utf-8  
          
      
      
        setCharacterEncoding  
        /*  
      
   
    
      
        logfilter  
        com.company.strutstudy.web.servletstudy.filter.LogFilter  
      
      
        logfilter  
        /*  
      
编码拦截器实现
  public class EncodingFilter implements Filter {  
    private String encoding;  
    private Map params = new HashMap();  

    // 项目结束时就已经进行销毁  
    public void destroy() {  
        System.out.println("end do the encoding filter!");  
        params=null;  
        encoding=null;  
    }  
    
    // Filter处理方法:对request和response进行处理
    public void doFilter(ServletRequest req, ServletResponse resp,  
            FilterChain chain) throws IOException, ServletException {  
        //UtilTimerStack.push("EncodingFilter_doFilter:");  
        System.out.println("before encoding " + encoding + " filter!");  
        req.setCharacterEncoding(encoding);  
        // resp.setCharacterEncoding(encoding);  
        // resp.setContentType("text/html;charset="+encoding);  
        chain.doFilter(req, resp);        
        System.out.println("after encoding " + encoding + " filter!");  
        System.err.println("----------------------------------------");  
        //UtilTimerStack.pop("EncodingFilter_doFilter:");  
    }  
   
    // 项目启动时就已经进行读取  
    public void init(FilterConfig config) throws ServletException {  
        System.out.println("begin do the encoding filter!");  
        encoding = config.getInitParameter("encoding");  
        for (Enumeration e = config.getInitParameterNames(); e  
                .hasMoreElements();) {  
            String name = (String) e.nextElement();  
            String value = config.getInitParameter(name);  
            params.put(name, value);  
        }  
    }  
 }  
日志拦截器实现
  public class LogFilter implements Filter {  
    FilterConfig config;  
   
    public void destroy() {  
        this.config = null;  
    }  
   
    public void doFilter(ServletRequest req, ServletResponse res,  
            FilterChain chain) throws IOException, ServletException {  
        // 获取ServletContext 对象,用于记录日志  
        ServletContext context = this.config.getServletContext();  
        System.out.println("before the log filter!");  

        // 将请求转换成HttpServletRequest 请求  
        HttpServletRequest hreq = (HttpServletRequest) req;  
        // 记录日志  
        System.out.println("Log Filter已经截获到用户的请求的地址:"+hreq.getServletPath() );  
        try {  

            // Filter 只是链式处理,这里将请求转发给下一个Filter,请求依然转发到目的地址
            chain.doFilter(req, res);  

        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("after the log filter!");  
    }  
   
    public void init(FilterConfig config) throws ServletException {  
        System.out.println("begin do the log filter!");  
        this.config = config;  
    }  
   
  }  
4.加载过程
web容器加载过程
  • context-param 初始化键值对
  • listener 有多个listener时,以在web.xml中的注册顺序加载
  • filter 有多个filter时,以web.xml中的顺序加载
  • servlet 同一时间web任期中只有一个servlet实体,servlet并发需要自己做

servlet并发处理
  当请求来临时,servlet容器会初始化对应的servlet。如果多个请求同时访问的是同一个servlet,Servlet容器会创建多个线程同时调用servlet的service()方法来处理这些请求,而不是多个servlet实例。
  如果给service方法设置了synchronized关键字,servlet容器则是序列化请求依次通过service方法。
  但如果servlet实现了SingleThreadModel接口(此时,这个servlet只能一次处理一个请求),那么servlet容器会根据请求的数量创建多个servlet的实例(每个servlet实例相当于一个线程),并调用servlet的service方法来处理请求。

5.路径映射、转发与重定向
  • url-pattern类型
    全路径映射:如/aaa/bbb
    路径映射:以/开头、/*结尾
    扩展映射:以×.开头
    默认映射:/

  • 匹配顺序
    精确路径匹配 -> 最长路径匹配 -> 扩展匹配 -> Default Servlet

  • RequestDispatcher接口
    RequestDispatcher接口,定义一个对象,从客户端接收请求,然后将它发给服务器的可用资源(例如Servlet、CGI、HTML文件、JSP文件)。Servlet引擎创建request dispatcher对象,用于封装由一个特定的URL定义的服务器资源。这个接口是专用于封装Servlet的,但是一个Servlet引擎可以创建request dispatcher对象用于封装任何类型的资源。request dispatcher对象是由Servlet引擎建立的,而不是由Servlet开发者建立的。

RequestDispatcher接口中定义了两个方法:forward方法(页面跳转)和include方法(页面包含)。

获取RequestDispatcher对象的方法:

  ServletContext.getRequestDispatcher (参数只能写绝对路径,以“/”开头)
  ServletRequest.getRequestDispatcher (参数可以是绝对路径,也可以是相对路径)

  如:
  request.getRequestDispacther("/test.jsp")
  • 转发(forward)与重定向(sendredirect)
    都是跳转到另一个路径,不过二者有很大的区别。

简单地说,请求转发是当前servlet处理完Request之后,将服务资源(就是由上面的RequestDispatcher接口来负责的)转发给下一个servlet接着处理,是在同一个请求里面完成的,二者共享的是同一个request,整个过程都是在服务端完成的。

重定向并不转发这次请求的服务资源,而是直接向客户端返回响应,响应告诉客户端你必须要再发送一个请求,去访问重定向的页面,紧接着客户端收到这个请求后,立刻发出一个新的请求,去请求重定向的页面,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。

6.cookie

使用cookie来保存登录信息。

  • 会话:用户开一个浏览器,访问一个网站,只要不关闭该浏览器,不管该用户点击多少个超链接,访问多少资源,直到用户关闭浏览器,整个这个过程我们称为一次会话。

  • 为什么要使用cookie?
    1.记录用户的事件
    2.浏览历史记录
    3.用户名和密码的记录

  • cookie特性
    1.cookie是在服务端创建的;
    2.cookie是保存在浏览器这端的;
    3.cookie的生命周期可以通过cookie.setMaxAge(Int value)设置
     value为负时,表示不保存cookie;
     value为正时,表示最大生存秒数;
     value为0时,表示在客户端删除这个cookie.
     如果不设置,默认为-1,关闭浏览器就destroy了;
    4.cookie可以被多个浏览器共享;
    5.一个WEB应用可以保存多个cookie
    6.cookie存放的时候是以明文方式存放的,因此安全比较低,我们可以通过加密后保存,可以用md5(不可逆)算法加密(想起了base64),经过md5加密后存放到数据库,用户输入密码后加密然后再和数据库里面的匹配。

7.Servlet本身支不支持并发处理响应?

同一时间,在同一个应用的Web容器中,一个Servlet只存在一个实例,但是Servlet可以接受并发的请求。这是因为Servlet是无状态的(Servlet中没有保存状态的可变变量),因此一般情况下Servlet中不会发生资源争用,每次请求都会调用Servlet的service方法,在一个新的线程中去处理请求,Servlet容器会自动使用线程池等技术来支持系统的运行。

但是如果继承的Servlet定义了可争用的资源(可变变量等),需要自己做并发控制。

当客户端第一次请求某个Servlet时,Servlet 容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。

你可能感兴趣的:(Web工程结构与Servlet)