• 过滤、监听
  • ServletConfig、GenericServlet、ServletContext
  • 异步处理提高并发及伸缩性

ServletConfig

在Web容器启动之后,在web.xml或注解中读取相关配置生成一个ServletConfig对象,实例化Servlet类,并且把ServletConfig对象作为参数传入init()方法中,初始化Servlet。一个Servlet对应一个ServletConfig

  • String getServletName()
    返回此 servlet 实例的名称
  • ServletContext getServletContext()
    返回对调用ServletContext在其中执行的ServletContext的引用
  • String getInitParameter(String name)
    获取具有给定名称的初始化参数的值
  • Enumeration getInitParameterNames()
    以String对象的Enumeration形式返回 servlet 初始化参数的名称,如果 servlet 没有初始化参数,则返回空Enumeration
    image.png

GenericServlet

GenericServlet的init方法调用了ServletConfig。同时其本身也实现了ServletConfig接口。
GenericServlet是servlet的封装。定义一个通用的、与协议无关的 servlet。例如增加了一个无参数的init()方法方便自定义初始化方法
image.png
在tomcat首次加载servlet类时会调用init()方法
image.png

ServletContext

Web容器只会为Web创建全局唯一的ServletContext。java ee没有其实现类。tomcat默认实现了ApplicationContext
image.png

Session之于Context

如果采用集群或者分布式
对于在其部署描述符中标记为“分布式”的 Web 应用程序,每个虚拟机将有一个上下文实例。 在这种情况下,上下文不能用作共享全局信息的位置(因为信息不会是真正的全局信息)。 改用数据库等外部资源。

如果采用集群或者分布式。因为一个Session仅仅适用于当前的ServletContext。每个Web都有一个ServletContext。所以不能直接可见。需要采取和ServletContext无关的方式方法来维护HTTP协议在无状态条件下进行有状态的操作。

ServletContext常用API

image.png

  • String getRealPath(String path) 获取与给定虚拟路径对应的真实路径。
    例如,如果path等于/index.html ,则此方法将返回服务器文件系统上的绝对文件路径,请求格式为http://://index。 html将被映射,其中对应于这个 ServletContext 的上下文路径。
  • public URL getResource(String path) 返回映射到给定路径的资源的 URL
    路径必须以/开头,并被解释为相对于当前上下文根,或相对于 Web 应用程序的/WEB-INF/lib目录中 JAR 文件的/META-INF/resources目录。
  • public RequestDispatcher getRequestDispatcher(String path) 返回一个RequestDispatcher对象
  • Set getResourcePaths(String path) 返回 Web 应用程序中最长子路径与提供的路径参数匹配的所有资源路径的目录列表
  • String getServerInfo() 返回运行 servlet 的 servlet 容器的名称和版本
  • InputStream getResourceAsStream(String path) 位于命名路径的资源作为InputStream对象返回
    InputStream的数据可以是任何类型或长度。 必须根据getResource给出的规则指定路径。 如果指定路径不存在资源,则此方法返回null 。

监听器(观察者模式实现)

  • 观察者模式:
    被观察对象与观察者为1:1,* 关系。每当被观察者状态变化时,所有观察者都会得到通知并被自动更新,为行为设计模式
    核心是将观察者与被观察者解耦

监听事件来监听请求中的行为创建的一组类。例如Web容器调用监听器中的方法对Request、Session、Context的事件类(ServletRequestEvent、HttpSessionEvent、ServletContextEvent)进行一些处理。

常见的监听事件Event与接口Listener概览

  • 事件源、事件监听器、事件对象
    1.当事件源上发生操作时,会调用事件监听器的一个方法,同时会传递事件对象过来
    2.在事件监听器中,通过事件对象可以拿到事件源,从而对事件源上的操作进行处理
  • Context:
    ServletContextListener -> ServletContextEvent
    ServletContextAttributeListener -> ServletContextAttributeEvent
  • Session:
    (HttpSessionActivationListener、HttpSessionIdListener、HttpSessionListener) -> HttpSessionEvent
    (HttpSessionAttributeListener、HttpSessionBindingListener) -> HttpSessionBindingEvent
  • Request:
    ServletRequestListener -> ServletRequestEvent
    ServletRequestAttributeListener -> ServletRequestAttributeEvent

ServletContext事件、监听器

ServletContextListener 生命周期监听器

ServletContext 生命周期监听器,监听Web程序初始化或者结束时响应的动作事件. 共2种通知

  • default public void contextInitialized(ServletContextEvent sce) {}
    加载Web应用和初始化参数时通知Listener
  • default public void contextDestroyed(ServletContextEvent sce) {}
    Web应用关闭时通知Listener

ServletContextAttributeListener 属性更改监听器

image.png
共3种通知,分为为
1.Context属性增加通知
2.Context属性移除通知
3.Context属性修改通知

Session事件、监听器

HttpSessionIdListener HttpSession id 更改的通知

image.png
共1种通知,用于接收session id被修改的通知

HttpSessionListener HttpSession 生命周期更改事件、监听器

image.png
共2种通知
1.session初始化通知
2.session结束时通知

HttpSessionAttributeListener HttpSession 属性更改监听器

image.png
共3种通知:新增属性、移除属性、修改属性

HttpSessionBindingListener session对象绑定监听器

image.png

HttpSessionActivationListener Session转移监听器

image.png
用于解决Session不能共享的问题,HttpSession对象由一个JVM转移到另一个JVM

HttpServletRequest 事件、监听器

ServletRequestListener Request生命周期通知监听器

image.png
共有2个方法
1.request被初始化的时候通知
2.request被销毁的时候通知

ServletRequestAttributeListener ServletRequest 属性更改通知监听器

image.png
共3种类型的通知
1.接收属性已添加到 ServletRequest 的通知
2.接收属性已从 ServletRequest 中删除的通知
3.接收 ServletRequest 上的属性已被替换的通知

过滤器

在Web应用中,过滤器Filter介于Servlet之前,可以过滤、拦截浏览器client端请求。也可以更改Servlet服务端的请求。
image.png
过滤器在doFilter方法中执行过滤。 每个过滤器都可以访问一个 FilterConfig 对象,它可以从中获取它的初始化参数,以及一个对 ServletContext 的引用,它可以使用它来加载过滤任务所需的资源。
image.png

  • public void init(FilterConfig filterConfig)
    servlet 容器在实例化过滤器后只调用一次 init 方法
  • doFilter(ServletRequest request, ServletResponse response,FilterChain chain)
    容器都会调用 Filter 的doFilter方法。 传入此方法的 FilterChain 允许 Filter 将请求和响应传递给链中的下一个实体
  • void destroy()
    Web 容器调用以向过滤器指示它正在停止服务
  • 作用
    1.请求身份认证
    2.数据过滤、替换
    3.转换格式
    4.数据加密
    5.数据压缩
    6.修改字符集
    7.日志记录和审核

FilterConfig

在Filter初始化的时候向Filter中传递初始化参数
image.png

HttpServletRequestWrapper Request便捷实现,装饰者模式

image.png
应用了装饰者模式比单一继承更加灵活

HttpServletResponseWrapper Response便捷实现,装饰者模式

image.png

Servlet3.0的异步处理

  • Servlet2.0
    在Servlet2.0 中。一个Servlet请求为:Servlet接收请求,业务处理后完成处理,将结果返回客户端
    业务处理过程中可能应用程序可能需要在生成响应之前查询数据库或从远程 Web 服务访问数据
    image.png
    应用程序服务器中的 Web 容器通常根据客户端请求使用服务器线程。在高负载条件下,容器需要大量的线程来处理所有客户端请求。可伸缩性限制包括内存不足或耗尽容器线程池。若要创建可缩放的 Web 应用程序,必须确保没有与请求关联的线程处于空闲状态,以便容器可以使用它们来处理新请求。

  • Servlet3.0
    image.png
    如果 servlet 或过滤器在处理请求时遇到潜在的阻塞操作,它可以将操作分配给异步执行上下文,并立即将与请求关联的线程返回到容器,而无需生成响应。阻塞操作在不同线程的异步执行上下文中完成,该线程可以生成响应或将请求分派给另一个 servlet。
    image.png

  • 代码演示

/**
 * @author jiangtao
 * @date 2021/12/16 00:14
 */
@WebServlet(value = "/asynservlet", asyncSupported = true)
public class AsynServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8;");
        PrintWriter writer = resp.getWriter();
        writer.println("Servlet开始" + Thread.currentThread().getName() + "---" + System.currentTimeMillis() + "<br>");
        writer.flush();
        AsyncContext asyncContext = req.startAsync(req, resp);
        asyncContext.setTimeout(10000);
        new Thread(() -> {
            writer.println("新开线程Servlet开始" + Thread.currentThread().getName() + "---" + System.currentTimeMillis() + "<br>");
            writer.flush();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writer.println("新开线程Servlet结束,进程销毁" + Thread.currentThread().getName() + "---" + System.currentTimeMillis() + "<br>");
            writer.flush();
        }).start();
        writer.println("Servlet结束,资源释放" + Thread.currentThread().getName() + "---" + System.currentTimeMillis() + "<br>");
    }
}

访问http://localhost:8080/asynservlet
image.png

  • AsyncContext常用API
    image.png

Servlet3.0服务端主动推送消息给web前端

  • 后端
    由于AsyncContext异步特性,可以调用相应的异步监听器AsyncListener。
    image.png
  • 前端
    通过frame读取异步Servlet发送的消息

异步非阻塞I/O

  • 背景
    为了提高可伸缩性,高并发
    如果客户端通过慢速网络连接提交大型 HTTP POST 请求,则服务器读取请求的速度比客户端提供请求的速度快。使用传统 I/O,与此请求关联的容器线程有时会处于空闲状态,等待请求的其余部分。
  • 使用方式
    1.将请求置于异步模式
    2.从服务方法中的请求和响应对象获取输入流和/或输出流。
    3.将读Listener分配给输入流,将写Listener分配给输出流。
    4.在Listener的回调方法中处理请求和响应。
@WebServlet(urlPatterns={"/asyncioservlet"}, asyncSupported=true)
public class AsyncIOServlet extends HttpServlet {
   @Override
   public void doPost(HttpServletRequest request, 
                      HttpServletResponse response)
                      throws IOException {
      final AsyncContext acontext = request.startAsync();
      final ServletInputStream input = request.getInputStream();
      
      input.setReadListener(new ReadListener() {
         byte buffer[] = new byte[4*1024];
         StringBuilder sbuilder = new StringBuilder();
         @Override
         public void onDataAvailable() {
            try {
               do {
                  int length = input.read(buffer);
                  sbuilder.append(new String(buffer, 0, length));
               } while(input.isReady());
            } catch (IOException ex) { ... }
         }
         @Override
         public void onAllDataRead() {
            try {
               acontext.getResponse().getWriter()
                                     .write("...the response...");
            } catch (IOException ex) { ... }
            acontext.complete();
         }
         @Override
         public void onError(Throwable t) { ... }
      });
   }
}

这个家伙很懒,啥也没有留下😋