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 工程项目结构

无子 Module 结构图
有子 Module 的工程结构

二、servlet

1.Servlet / GenericServlet / HttpServlet
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)方法。

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:

 <listener>  
      <listener-class>com.ee.listener.OnlineUserListener</listener-class>  
</listener>  

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

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

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

由上图可以看出:在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配置
  <!-- 编码过滤器 -->  
    <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-name>setCharacterEncoding</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
   
  <!-- 请求url日志记录过滤器 -->  
    <filter>  
        <filter-name>logfilter</filter-name>  
        <filter-class>com.company.strutstudy.web.servletstudy.filter.LogFilter</filter-class>  
    </filter>  
    <filter-mapping>  
        <filter-name>logfilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
编码拦截器实现
  public class EncodingFilter implements Filter {  
    private String encoding;  
    private Map<String, String> params = new HashMap<String, String>();  

    // 项目结束时就已经进行销毁  
    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中<url-pattern>的顺序加载
  • 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类,也就是有多个线程在使用这个实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 本文包括:1、Filter简介2、Filter是如何实现拦截的?3、Filter开发入门4、Filter的生命周期...
    廖少少阅读 7,147评论 3 56
  • 这部分主要是与Java Web和Web Service相关的面试题。 96、阐述Servlet和CGI的区别? 答...
    杂货铺老板阅读 1,344评论 0 10
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,018评论 11 349
  • 监听器(listener) 监听器简介 :监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个...
    奋斗的老王阅读 2,411评论 0 53
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139