• Java开发工程师(Web方向)


    第3章.Servlet应用

    转发与重定向

    转发:浏览器发送资源请求到ServletA后,ServletA传递请求给ServletB,ServletB生成响应后返回给浏览器。

    请求转发:forward:将当前的request和response对象交给指定的web组件处理

    这个过程中浏览器只发了一次请求接收了一次响应(浏览器并不知道转发,地址栏url不变)

    1. 获取转发对象RequestDispatcher--由Servlet容器创建,用于封装由路径所标志的服务器资源;

    2. 然后调用转发对象中的forward方法;(另一个方法为include()--原有的和被转发的web组件均可输出相应信息)

    如何获取RequestDispatcher?两种方法:

    通过HttpServletRequest获取

    通过ServletContext获取(之前提到ServletContext三个作用中的一条:获取转发)

    i.e. ServletForward.java将请求转发给ServletForwardExample.java

    RequestDispatcher rd = request.getRequestDispatcher("/forwardExample"); // 从HttpServletRequest中获取转发对象("/forwardExample"为绝对路径)

    rd.forward(request,response); // 转发

    i.e. 通过ServletContext获取转发对象:两种方法

    1. rd = this.getServletContext().getNamedDispatcher("ServletForwardExample");

       // ("ServletForwardExample"为Servlet名称)

    2. rd = this.getServletContext().getRequestDispatcher("/forwardExample");

    重定向:sendRedirect

    study case: 用户登录

    登录验证完成后,浏览器接收到服务器发来的另外一个地址的响应信息;

    浏览器自动向服务器发送跳转请求,并得到服务器返回的跳转结果。

    通过response对象发送给浏览器一个新url地址,让其重新请求。

    两次请求,两次响应--浏览器地址栏url改变

    i.e. ServletRedirect.java将请求重定向到ServletRedirectExample.java

    ServletRedirect.java中:

    response.sendRedirect("redirectExample"); // ("redirectExample"为相对路径)

    若使用绝对路径"/redirectExample",则重定向到地址localhost:8080/redirectExample

    -- 若想重定向到当前web项目的某资源:相对路径;若想到其他web项目:绝对路径。

    ServletRedirectExample.java中:

    syso: request.getParameter("user");

    // 若直接使用传来的request进行读取数据等操作,错误:两次请求和两次响应

    在Chrome的developer tools中,可以看到两次请求:

    redirect?user=aaa -- response header: location:....../redirectExample

    redirectExample。

    转发&重定向总结:

    浏览器地址栏变化:转发不变;重定向变化。

    请求范围:在同一个web项目中转发;重定向可在不同web项目间重定向

    请求过程:转发为一次,重定向为两次

     

    过滤器与监听器

    过滤器 filter:

    通过自定义的过滤规则来过滤请求与响应。

    用于对用户请求进行预处理、和对请求响应进行后处理的web引用组件。

    对Servlet容器进行请求和响应的对象进行检查和修改。

    过滤器本身并不生成请求和响应对象,只是提供了过滤的功能。

    过滤器在Servlet被调用之前检查request对象,并能够修改request header和request的内容;

    在servlet被调用之后,能够检查response对象,并能够修改response header和response的内容。

    过滤器工作原理:

    客户端发送原始请求到Servlet容器,该请求在到达容器之前会经过过滤器处理,过滤之后请求转发到对应Servlet;Servlet处理完请求后进行了响应,该原始响应发还到过滤器,最后由过滤器将过滤后的请求返回给客户端。

    过滤器使用场景:

    用户认证:过滤一部分非法用户,验证用户是否已经登录,是否拥有访问权限等

    编解码处理:如果请求有乱码,可以通过过滤器进行预处理,以得到正确的编码结果

    数据压缩处理:当请求数据较大时,对请求进行压缩,以减轻服务端的处理压力

    过滤器的生命周期:创建和销毁是由servlet容器负责的

    servlet容器根据部署描述符创建filter的实例对象(只会创建一次);

    调用init();完成初始化工作,为拦截用户请求做好准备(同样只会调用一次);

    (和servlet一样,可以配置一个filterConfig的对象,用来存储filter的配置信息)

    初始化完成后,进入正式的过滤操作,doFilter()(类似于servlet中的service()):对请求和响应做实际处理

    当客户端请求/响应与过滤器相关联的url时,过滤器会执行对应的doFilter()方法。

    在web应用被移除或服务器停止时,会调用destroy()(只会调用一次,将过滤器资源释放)销毁filter对象。

    i.e. TestFilter.java

    new class--name:TestFilter.java--Interface:Filter(servlet)

    --init(FilterConfig); doFilter(); destroy();

    配置web.xml:

    <filter>

    <init-param>   <--!由filterConfig.getInitParameter(param-name)读取-->

    <param-name>filterParam</param-name>

    <param-value>1</param-value>

    </init-param>

    <filter-name>TestFilter</filter-name>

    <filter-class>com.netease.server.example.web.controller.TestServlet</filter-class>

    <filter>

    <filter-mapping>

    <filter-name>TestFilter</filter-name>

    <url-pattern>/hello/world/*</url-pattern>

    <--! 符合该url pattern的请求才会经过该过滤器-->

    </filter-mapping>

    在刚才创建的TestFilter.java中完善三个方法:

    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取在web.xml中配置的filter参数并打印
        String value = filterConfig.getInitParameter("filterParam");
        syso(value);
    }
    
    public void doFilter(ServletRequest request, ServletResponse response, 
      FilterChain chain) throws IOException, ServletException { // 实现登陆过的用户可直接进行访问,否则会跳转到登录页面进行登陆步骤 HttpServletRequest req = (HttpServletRequest) request; // 强制类型转换 HttpSession session = req.getSession(); // 得到会话 // 检查属性,判断是否登陆过 if (session.getAttribute("userName") == null) { HttpServletResponse res = (HttpServletResponse) response; res.sendRedirect("../index.html"); // 跳转到登陆页面 } else { // 访问对应资源 chain.doFilter(request, response);// 请求传递到下一个过滤器或是对应的Servlet } }

    部署,访问/hello/world此时用户为未登录状态,重定向到index.html;

    登陆后在地址栏输入/hello/world不进行重定向,直接返回响应。

    过滤器链:

    若一个请求通过filter-mapping匹配到多个filter,web服务器会根据部署描述符中的先后顺序决定调用顺序。

    FilterChain chain.doFilter(req, res);

     

    监听器:listener

    监听事件发生,在事件发生前后能够做出相应处理的web应用组件

    事件源:当有相应事件发生时,事件源将通知发送到对应的监听器

    监听器:向事件源进行注册

    处理:监听器收到通知后,进行对应操作

    Servlet监听器不是直接注册到事件源上,而是由servlet容器进行注册。开发人员只需配置好即可。

    监听器分类:按监听对象划分

    监听应用程序环境 (ServletContext): ServletContextListener, ServletContextAttributeListener

    监听用户请求对象 (ServletRequest): ServletRequestListener, ServletRequestAttributeListener

    监听用户会话对象 (HTTPSession): HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener (监听session在写入磁盘或从磁盘中重新加载到jvm中时) HttpSessionBindingListener (在session对象进行调用attribute方法和removeAttribute方法时)

    两大类:对象本身的监听器 (对象的创建和销毁时);对象属性的监听器 (当属性有增删改查时)

    监听器应用场景:

    应用监听:每一个用户对应一个session,对session进行监听,可以做到对用户登录的统计

    任务触发:如在招聘网站某用户的简历状态更新了,比如变成了面试成功,便可以向应聘者发送一封邮件通知

    监听器启动顺序:

    与过滤器顺序一样,根据部署描述符中的先后顺序决定。

    i.e. TestListener.java--interface (很多选项,如HttpSessionAttributeListener, ServletContextListener, ServletRequestListener)

    @Override:

    requestDestroyed(ServletRequestEvent);

    requestInitialized(ServletRequestEvent);

    contextInitialized(ServletContextEvent);

    contextDestroyed(ServletContextEvent);

    attributeAdded(HttpSessionBindingEvent);

    attributeRemoved(HttpSessionBindingEvent);

    attributeReplaced(HttpSessionBindingEvent);

    web.xml:在部署描述符中配置这些listener: 

    <listener>
        <listener-class>com.netease.server.example.web.controller.listener.TestListener</listener-class>
    </listener>

    listener自动被注册到相应事件,

    session.setAttribute("userName", userName); 时触发HttpSessionAttributeListener.attributeAdded

     

    Servlet容器先创建各种监听器;再创建过滤器;最后创建Servlet对象。

    Servlet并发处理

    当有多个客户端同时访问服务器的同一个Servlet时:串行处理(效率低),并发处理(Servlet采用)

    Servlet容器接收到来自客户端的请求后;

    将请求发给调度器,由调度器进行统一的请求派发;

    调度器从Servlet容器的线程池 (记得Tomcat的线程池吗) 中选取一个工作线程,把请求派发给该线程,由该线程执行servlet的service方法;

    此时Servlet容器又接收到了另一个请求;

    调度器从工作线程池中选出另外一个工作组线程来服务新的请求;

    容器并不关心请求访问的是否为同一个servlet,当容器收到对同一个servlet的多个请求时,这个servlet的service方法会在多线程中并发执行

    当线程处理完请求后,会被放回线程池中;

    若线程池中的线程都被占用,且有新的请求过来,则这些新请求会做排队处理

    Servlet容器也会配置一个最大排队数量,如果超过这个数量,Servlet容器将会拒绝响应的请求。

    Servlet并发处理的特点:

    单实例:不管有多少请求,只有一个Servlet实例对象

    多线程:同时处理多个请求

    线程不安全:单个实例却有多个线程同时访问:Sync问题

    若要实现线程安全:

    变量的线程安全:

    参数变量本地化--局部变量

    使用同步块:synchronized加锁处理--注意要尽可能缩小加锁的代码块

    属性的线程安全:

    ServletContext可以多线程读取--线程不安全:需要做同步处理

    HTTPSession理论上线程安全,只能在处理同一个请求的线程中被访问

    但是当用户打开属于同一个session的多个浏览器窗口时,对同一个session会进行多次请求,会分配给多个线程处理:需要同步处理

    ServletRequest是线程安全的,因为对于同一个请求,只有一个线程进行处理

    要避免在Servlet中创建线程:servlet本身被多线程执行,会导致情况复杂。

    多个Servlet同时访问外部对象时需要加锁处理

    写代码时尽量避免使用servlet实例变量,无法避免时则需要加上同步处理,而保证性能安全需要缩小同步处理的范围。

     

    i.e. 线程不安全

    ConcurrentServlet.java superclass: HttpServlet

    @Override: init(); doGet(); destroy();

    String name;
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        // 该servlet功能,读取并打印username
        throws ServletException, IOException {
        name = req.getParameter("username");
        PrintWriter out = resp.getWriter();
        out.println(name);
    }

    web.xml部署描述符中配置该servlet。

    正常情况下该servlet运行没问题。当多个请求同时访问时,name这个实例变量就很可能出现错误。

    在out.println()之前加入Thread.sleep(5000);后:

    问题演示--实例变量的线程不安全:

    1. 在地址栏打入/concurrent?username=ddd后,页面等待五秒,返回username ddd,正常

    2. 在地址栏打入/concurrent?username=aaa后,页面等待五秒,返回username aaa,正常

    3.在地址栏打入/concurrent?username=ddd, 立即新开窗口打入/concurrent?username=aaa,两个页面等待后的输出均为username aaa。

    bugfix:加上synchronize同步块:

    String name;
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        // 该servlet功能,读取并打印username
        throws ServletException, IOException {
        synchronsized(this) {
            name = req.getParameter("username");
            PrintWriter out = resp.getWriter();
            out.println(name);
        }
    }

    保证了实例变量的线程安全。 

     

    Servlet应用测试

    本次得分为:36.00/36.00, 本次测试的提交时间为:2017-08-10, 如果你认为本次测试成绩不理想,你可以选择再做一次。
    1单选(2分)

    下面哪个方法不是过滤器的生命周期中的方法?

    • A.doFilter
    • B.init
    • C.destroy
    • D.service�2.00/2.00
    2单选(2分)

    下面哪项说法是错误的?

    • A.客户端的请求可以交由多个过滤器处理
    • B.在部署描述符中,如果过滤器定义在监听器前,则容器会先初始化过滤器�2.00/2.00
    • C.过滤器和监听器都是Web服务端应用组件
    • D.ServletRequestAttributeListener是属于EventListeners
    3单选(2分)

    下面说法正确的是?

    • A.ServletContext的属性是线程安全的
    • B.HttpSession的属性是理论上线程安全�2.00/2.00
    • C.其它选项都是正确的
    • D.ServletRequest的属性不是线程安全
    4单选(2分)

    下面获取请求转发对象(RequestDispatcher)方法错误的是?

    • A.通过ServletRequest对象的getNamedDispatcher方法直接获取�2.00/2.00
    • B.通过ServletContext对象的getNamedDispatcher的方法获取
    • C.通过ServletRequest对象的getRequestDispatcher方法直接获取
    • D.通过ServletContext对象的getRequestDispatcher方法直接获取
    5单选(2分)

    下面关于Web应用组件启动顺序的说法错误的是?

    • A.其它选项都不正确�2.00/2.00
    • B.Web应用组件启动过程中,过滤器的优先级高于Servlet
    • C.Web应用组件启动过程中,监听器的优先级高于过滤器
    • D.Web应用组件启动过程中,监听器的优先级高于Servlet
    6多选(3分)

    下面哪些是Servlet并发处理的特点?

    • A.线程不安全�1.00/3.00
    • B.单实例�1.00/3.00
    • C.线程安全
    • D.多线程�1.00/3.00
    7多选(3分)

    下面哪些方法是可以做到Servlet线程安全?

    • A.注意Servlet中属性的线程安全�0.75/3.00
    • B.尽量避免在Servlet中创建线程�0.75/3.00
    • C.注意Servlet中声明的变量的线程安全,尽量不要使用实例变量�0.75/3.00
    • D.多个Servlet访问的外部对象需要加锁处理�0.75/3.00
    8判断(2分)

    请求转发是一次请求,一次响应

    • A.×
    • B.√�2.00/2.00
    9判断(2分)

    请求重定向是两次请求,两次响应

    • A.×
    • B.√�2.00/2.00
    10判断(2分)

    请求转发的过程中,浏览器的地址栏会发生变化

    • A.√
    • B.×�2.00/2.00
    11判断(2分)

    请求重定向的过程中,浏览器的地址栏不会发生变化

    • A.√
    • B.×�2.00/2.00
    12判断(2分)

    请求转发可以跨不同的Web应用程序

    • A.√
    • B.×�2.00/2.00
    13判断(2分)

    请求重定向可以跨不同的Web应用程序

    • A.×
    • B.√�2.00/2.00
    14判断(2分)

    请求重定向中第二次请求的完成是由浏览器自动完成的

    • A.×
    • B.√�2.00/2.00
    15判断(2分)

    当存在多个监听器的时候,其初始化顺序是按照部署描述符中定义的顺序来初始化监听器的

    • A.×
    • B.√�2.00/2.00
    16判断(2分)

    当多个客户端(一定数量)同时请求同一个Servlet的时候,容器可以同时处理

    • A.√�2.00/2.00
    • B.×
    17判断(2分)

    监听器可以分为EventListeners和LifecycleListeners两种类型

    • A.×
    • B.√�2.00/2.00
  • 相关阅读:
    cookie实例---显示上一次访问的时间与java.lang.IllegalArgumentException: An invalid character [32] was present in the Cookie value
    No Mapping For GET "xxx.do"
    Mybatis 配置文件
    spring整合JUnit测试
    Spring 约束文件配置
    c3p0封装
    Linux下载:wget、yum与apt-get用法及区别
    docker安装各种坑
    动态管理upsteam---nginx_http_dyups_module
    安装nginx环境(含lua)时遇到报错ngx_http_lua_common.h:20:20: error: luajit.h: No such file or directory的解决
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/7324183.html
Copyright © 2020-2023  润新知