Loading...

Java Web(二)——Listener/Servlet

  iwehdio的博客园:https://www.cnblogs.com/iwehdio/

学习自:

1、Listener

  • 常用的监听器:

    
    6个常规监听器
        |---ServletContext
                |---ServletContextListener(生命周期监听)
                |---ServletContextAttributeListener(属性监听)
        |---HttpSession
                |---HttpSessionListener(生命周期监听)
                |---HttpSessionAttributeListener(属性监听)
        |---ServletRequest
                |---ServletRequestListener(生命周期监听)
                |---ServletRequestAttributeListener(属性监听)
    
    2个感知监听
        |---HttpSessionBindingListener
        |---HttpSessionActivationListener
         
    
    • 6个常规监听器,分属三类,分别对应JavaWeb三大域对象(服务、会话、请求):ServletContext、HttpSession、ServletRequest。共三对,每一对都包括1个生命周期监听和1个属性监听。
    • 所谓生命周期监听器,就是监听三大域对象的创建和销毁。每当Tomcat创建或销毁三大域对象,都会被这些监听器察觉,然后它们会做相应的操作(调用自身的特定方法)。
    • 属性监听器则专门监听三大域对象get/setAttribute()。每当我们给域对象设置值或者从里面取值,都会被它们监听到,然后还是触发它们特定的方法。

    image-20210129115445888

  • 三大生命周期监听器,各自在何时创建、销毁,顺序是怎么样的?

    1. 在项目启动时ServletContextListener监听到ServletContext对象创建
    2. 每一次请求Tomcat都会创建一个Request,它的创建会被ServletRequestListener监听到
    3. 如果Servlet中调用了request.getSession(),则Tomcat会创建Session(如果根据JSESSIONID找不到对应的),这会被HttpSessionListener监听到
    4. 请求结束,Request销毁,被监听到
    5. 用户30分钟未访问,Session过期销毁,被监听到
    6. 项目关停,ServletContext销毁,被监听到
  • 访问一个Servlet,HttpSessionListener一定会触发吗?(换个角度就是,访问Servlet,Session一定会创建吗?)

    • 只有当在Servlet中调用request.getSession(),且根据JSESSIONID找不到对于的Session时,才会创建新的Session对象,才会被监听到。
    • 第二次请求,浏览器会带上JSESSIONID,此时虽然还是request.getSession(),但是会返回上次那个。根据JSESSIONID去查找Session这个过程是隐式的,我们看到的就是getSession()。
  • 三大属性监听何时触发?

    • get/setAttribute()时,会触发属性监听。
  • 如何实现监听的?

    • 观察者模式。在被监听对象中注册监听器。被监听对象执行某些操作时,通知监听器执行相应的方法。
  • ContextLoaderListener是一个由Spring编写并提供的监听器:

    • 我们搭建SSM框架时,需要做的仅仅是在web.xml中配置它。

    • ContextLoaderListener实现了ServletContextListener(三大生命周期监听之一)。

    • Spring实现了Tomcat提供的ServletContextListener接口,写了一个监听器来监听项目启动。一旦项目启动,会触发ContextLoaderListener中的特定方法。

      image-20210129120012585

    • Tomcat的ServletContext创建时,会调用ContextLoaderListener的contextInitialized(),这个方法内部的initWebApplicationContext()就是用来初始化Spring的IOC容器的。

      • ServletContext对象是Tomcat的
      • ServletContextListener是Tomcat提供的接口
      • ContextLoaderListener是Spring写的,实现了ServletContextListener
      • Spring自己写的监听器,用来创建Spring IOC
    • 怎么注册ContextLoaderListener的?

      • xml中配置了ContextLoaderListener
      • Tomcat会解析web.xml,反射创建ContextLoaderListener。
    • 创建IOC时,要ServletContext对象作什么?

      • 因为,最终IOC容器其实是存放在ServletContext对象(容器)中。
    • 初始化IOC的步骤:

      1. 传入servletContext对象创建空的IOC容器
      2. 以成员变量CONFIG_LOCATION_PARAM为key , 从servletContext对象中得到web.xml中配置的spring-context.xml文件位置
      3. 根据spring-context.xml初始化IOC容器
      4. 将IOC容器存入servletContext

      image-20210129121024222

  • Filter的拦截方式:

    • REQUEST:当⽤户直接访问⻚⾯时,Web容器将会调⽤过滤器。如果⽬标资源是通过RequestDispatcher的include()或forward()⽅法访问时,那么该过滤器就不会被调⽤。
    • INCLUDE:如果⽬标资源是通过RequestDispatcher的include()⽅法访问时,那么该过滤器将被调⽤。除此之外,该过滤器不会被调⽤。
    • FORWARD:如果⽬标资源是通过RequestDispatcher的forward()⽅法访问时,那么该过滤器将被调⽤,除此之外,该过滤器不会被调⽤。
    • ERROR:如果⽬标资源是通过声明式异常处理机制调⽤时,那么该过滤器将被调⽤。除此之外,过滤器不会被调⽤。
  • 监听器涉及三个组件:事件源,事件对象,事件监听器

    • 当事件源发⽣某个动作的时候,它会调⽤事件监听器的⽅法,并在调⽤事件监听器⽅法的时候把事件对象传递进去。

    • 我们在监听器中就可以通过事件对象获取得到事件源,从⽽对事件源进⾏操作。

    • 监听器定义为接⼝,监听的⽅法需要事件对象传递进来,从⽽在监听器上通过事件对象获取得到事件
      源,对事件源进⾏修改。

    • 事件源需要注册监听器(即在事件源上关联监听器对象)。如果触发了相关⽅法的时候,会调⽤监听器的⽅法,并将事件对象传递进去。

    • 事件对象封装了事件源。监听器可以从事件对象上获取得到事件源的对象(信息)。

  • 在Servlet规范中定义了多种类型的监听器,它们⽤于监听的事件源分别 ServletContext,HttpSession和ServletRequest这三个域对象

    • 和其它事件监听器略有不同的是,servlet监听器的注册不是直接注册在事件源上,⽽是由WEB容器负
      责注册,开发⼈员只需在web.xml⽂件中使⽤<listener> 标签配置好监听器。
    • HttpSessionListener、ServletContextListener、ServletRequestListener分别监控着Session、
      Context、Request对象的创建和销毁。
    • ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener
      分别监听着Context、Session、Request对象属性的变化。
  • 除了上⾯的6种Listener,还有两种Linstener监听Session内的对象,分别是HttpSessionBindingListener和HttpSessionActivationListener,实现这两个接⼝并不需要在web.xml⽂件中注册

    • 实现HttpSessionBindingListener接⼝,JavaBean 对象可以感知⾃⼰被绑定到 Session 中和从Session 中删除的事件【和HttpSessionAttributeListener的作⽤是差不多的】
    • 实现HttpSessionActivationListener接⼝,JavaBean 对象可以感知⾃⼰被活化和钝化的事件(当服务器关闭时,会将Session的内容保存在硬盘上【钝化】,当服务器开启时,会将Session的内容在硬盘式重新加载【活化】)

2、Servlet

  • 什么是Servlet?

    • Servlet是Server Applet(运行在服务端的小程序)
    • Servlet其实就是⼀个遵循Servlet开发的java类。Servlet是由服务器调⽤的,运⾏在服务器端。
  • 为什么要⽤到Servlet?

  • 我们编写java程序想要在⽹上实现 聊天、发帖、这样⼀些的交互功能,普通的java技术是⾮常难完成的。sun公司就提供了Servlet这种技术供我们使⽤。

  • Servlet的生命周期:

    • 加载Servlet。当Tomcat第⼀次访问Servlet的时候,Tomcat会负责创建Servlet的实例
    • 初始化。当Servlet被实例化后,Tomcat会调⽤init()⽅法初始化这个对象
    • 处理服务。当浏览器访问Servlet的时候,Servlet 会调⽤service()⽅法处理请求
    • 销毁。当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会⾃动调⽤destroy()⽅法,让该
      实例释放掉所占的资源。⼀个Servlet如果⻓时间不被使⽤的话,也会被Tomcat⾃动销毁
    • 卸载。当Servlet调⽤完destroy()⽅法后,等待垃圾回收。如果有需要再次使⽤这个Servlet,会重
      新调⽤init()⽅法进⾏初始化操作。
  • Tomcat其实是Web服务器和Servlet容器的结合体。

    • Web服务器的作用说穿了就是:将某个主机上的资源映射为一个URL供外界访问。
    • Servlet容器,顾名思义里面存放着Servlet对象。
  • 为什么能通过Web服务器映射的URL访问资源?肯定需要写程序处理请求,主要3个过程:

    • 接收请求、处理请求、响应请求。
    • 其中接收请求和响应请求是共性功能,且没有差异性。访问淘宝和访问京东,都是接收www.taobao.com/brandNo=1,响应给浏览器的都是JSON数据。于是,大家就把接收和响应两个步骤抽取成Web服务器。
    • 但处理请求的逻辑是不同的。没关系,抽取出来做成Servlet,交给程序员自己编写。
    • 随着后期互联网发展,出现了三层架构,所以一些逻辑就从Servlet抽取出来,分担到Service和Dao。

    image-20210122193430451

  • Java Web三大组件:

    • Servlet/Filter/Listener的使用。没有main,也没有new,写一个类然后在web.xml中配个标签,它们就这么兀自运行了。

    • 实现的原理简单来说就是“注入”和“回调”。想象一下,Tomcat里有个main方法,假设是这样的:

      image-20210122193851991

    image-20210122194017111

  • Servlet虽然是个接口,但实现类只是个空壳,我们写点业务逻辑就好了。Tomcat往Servlet接口中传入三个对象:ServletConfig、ServletRequest、ServletResponse。

  • ServletConfig:

    • servletConfig对象封装了servlet的一些参数信息。如果需要,我们可以从它获取。

    image-20210122194308294

  • Request/Response:

    • HTTP请求到了Tomcat后,Tomcat通过字符串解析,把各个请求头(Header),请求地址(URL),请求参数(QueryString)都封装进了Request对象中。
    • 至于Response,Tomcat传给Servlet时,它还是空的对象。Servlet逻辑处理后得到结果,最终通过response.write()方法,将结果写入response内部的缓冲区。Tomcat会在servlet处理结束后,拿到response,遍历里面的信息,组装成HTTP响应发给客户端。

    image-20210122194538002

    • Servlet接口5个方法,其中init、service、destroy是生命周期方法。init和destroy各自只执行一次,即servlet创建和销毁时。而service会在每次有新请求到来时被调用。也就是说,我们主要的业务代码需要写在service中。
  • GenericServlet抽象类实现了javax.servlet接口:

    image-20210122194723277

    • 提升了init方法中原本是形参的servletConfig对象的作用域(成员变量),方便其他方法使用
    • init方法中还调用了一个init空参方法,如果我们希望在servlet创建时做一些什么初始化操作,可以继承GenericServlet后,覆盖init空参方法
    • 由于其他方法内也可以使用servletConfig,于是写了一个getServletContext方法
  • 抽象类HttpServlet实现了javax.servlet接口:

    • HttpServlet的service方法已经替我们完成了复杂的请求方法判断。但是针对每一种请求,业务逻辑代码是不同的,HttpServlet无法知晓子类想干嘛,所以就抽出七个方法,并且提供了默认实现:报405、400错误,提示请求不支持。
    • 一个类声明成抽象方法,一般有两个原因:
      • 有抽象方法
      • 没有抽象方法,但是不希望被实例化
    • HttpServlet做成抽象类,仅仅是为了不让new。要求子类重写doGet、doPost等方法。

    image-20210122195214111

    • 如果没有重写,浏览器页面会显示:405(http.method_get_not_supported)。

    image-20210122195235885

  • ServletContext,直译的话叫做“Servlet上下文”。它其实就是个大容器,是个map。服务器会为每个应用创建一个ServletContext对象:

    • ServletContext对象的创建是在服务器启动时完成的
    • ServletContext对象的销毁是在服务器关闭时完成的

    image-20210129111749009

    • ServletContext对象的作用是在整个Web应用的动态资源(Servlet/JSP)之间共享数据。例如在AServlet中向ServletContext对象保存一个值,然后在BServlet中就可以获取这个值。
    • 这种用来装载共享数据的对象,在JavaWeb中共有4个,而且更习惯被成为“域对象”:
      • ServletContext域(Servlet间共享数据)
      • Session域(一次会话间共享数据,也可以理解为多次请求间共享数据)
      • Request域(同一次请求共享数据)
      • Page域(JSP页面内共享数据)
    • 它们都可以看做是map,都有getAttribute()/setAttribute()方法。
    • 每一个动态web工程,都应该在WEB-INF下创建一个web.xml,它代表当前整个应用。Tomcat会根据这个配置文件创建ServletContext对象。
  • 获取ServletContext的方法共5种:

    • ServletConfig#getServletContext();
    • GenericServlet#getServletContext();
    • HttpSession#getServletContext();
    • HttpServletRequest#getServletContext();
    • ServletContextEvent#getServletContext()
  • 配置Filter时可以设置4种拦截方式:

    image-20210129112433964

    • 重定向和REQUEST/FORWARD/INCLUDE/ERROR最大区别在于:
      • 重定向会导致浏览器发送2次请求,FORWARD们是服务器内部的1次请求
    • 因为FORWARD/INCLUDE等请求的分发是服务器内部的流程,不涉及浏览器。
    • ServletContext拥有分发器Dispatcher,用于分发请求:REQUEST/FORWARD/INCLUDE/ERROR。REQUEST是浏览器发起的,而ERROR是发生页面错误时发生的,稍微特殊些。

    image-20210129112630294

    • 最外层那个圈,可以理解成ServletContext,FORWARD/INCLUDE这些都是内部请求。如果在web.xml中配置Filter时4种拦截方式全配上,那么服务器内部的分发跳转都会被过滤。
  • 每一个URL要交给哪个Servlet处理,具体的映射规则都由一个映射器决定:

    image-20210129112758524

    • 所谓的映射器,其实就是Tomcat中一个叫Mapper的类。它里面有个internalMapWrapper方法:

      image-20210129112923007

    • 定义了映射规则:

      1. 对于静态资源,Tomcat最后会交由一个叫做DefaultServlet的类来处理
      2. 对于Servlet ,Tomcat最后会交由一个叫做 InvokerServlet的类来处理
      3. 对于JSP,Tomcat最后会交由一个叫做JspServlet的类来处理

    image-20210129113052123

    • 如果一个Servlet的拦截路径被设置为/*,表示拦截所有,相当于把DefaultServlet、JspServlet以及我们自己写的其他Servlet都“短路”了,它们都失效了。

    • 如果一个Servlet的拦截路径被设置为/,截所有,但不包括JSP。虽然JSP不拦截了,但是静态资源还是不会被拦截。针对这种情况,SpringMVC提供了一个标签,解决上面/无法读取静态资源的问题:

      <!-- 静态资源处理  css js imgs -->
      <mvc:resources location="/resources/**" mapping="/resources"/>
      
  • conf/web.xml指的是Tomcat全局配置web.xml。

    • 它里面配置了两个Servlet:JspServlet的映射路径为*.jsp和DefaultServlet的映射路径为/
    • conf/web.xml中的配置相当于写在了每一个应用的web.xml中。即每个应用默认都配置了JSPServlet和DefaultServlet处理JSP和静态资源。
    • 如果我们在应用的web.xml中为DispatcherServlet配置/,会和DefaultServlet产生路径冲突,从而覆盖DefaultServlet。此时,所有对静态资源的请求,映射器都会分发给我们自己写的DispatcherServlet处理。遗憾的是,它只写了业务代码,并不能IO读取并返回静态资源。JspServlet的映射路径没有被覆盖,所以动态资源照常响应。
    • 如果我们在应用的web.xml中为DispatcherServlet配置/*,虽然JspServlet和DefaultServlet拦截路径还是.jsp和/,没有被覆盖,但无奈的是在到达它们之前,请求已经被DispatcherServlet抢去,所以最终不仅无法处理JSP,也无法处理静态资源。
  • Servlet的细节:

    • ⼀个已经注册的Servlet可以被多次映射。同⼀个Servlet可以被映射到多个URL上。

    • Servlet映射的URL可以使⽤通配符,比如*.jsp/*

    • 为什么Servlet是单例的?

      • 浏览器多次对Servlet的请求,⼀般情况下,服务器只创建⼀个Servlet对象,也就是说,Servlet对象⼀旦创建了,就会驻留在内存中,为后续的请求做服务,直到服务器关闭。
    • 每次访问请求对象和响应对象都是新的:

      • 对于每次访问请求,Servlet引擎都会创建⼀个新的HttpServletRequest请求对象和⼀个新的
        HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调⽤的Servlet的service()⽅
        法,service⽅法再根据请求⽅式分别调⽤doXXX⽅法。
    • 线程安全问题:

      • 当多个⽤户访问Servlet的时候,服务器会为每个⽤户创建⼀个线程。当多个⽤户并发访问Servlet共享资源的时候就会出现线程安全问题。
    • load-on-startup:

      • 如果在<servlet> 元素中配置了⼀个<load-on-startup> 元素,那么WEB应⽤程序在启动时,就会装载并创建Servlet的实例对象、以及调⽤Servlet实例对象的init()⽅法。
  • 读取资源文件:

    • 资源文件在类目录src下:

      • 通过ServletContext对象获取:

        //获取到ServletContext对象
        ServletContext servletContext = this.getServletContext();
        //调⽤ServletContext⽅法获取到读取⽂件的流
        InputStream inputStream = servletContext.getResourceAsStream("/WEBINF/
        classes/zhongfucheng/web/1.png");
        
      • 通过类加载器获取:

        //获取到类装载器
        ClassLoader classLoader = Servlet111.class.getClassLoader();
        //通过类装载器获取到读取⽂件流
        InputStream inputStream =
        classLoader.getResourceAsStream("/zhongfucheng/web/1.png");
        
      • :如果⽂件太⼤,就不能⽤类装载器的⽅式去读取,会导致内存溢出。

    • 资源文件在web目录下:

      • 通过ServletContext对象获取:

        //获取到ServletContext对象
        ServletContext servletContext = this.getServletContext();
        //调⽤ServletContext⽅法获取到读取⽂件的流
        InputStream inputStream = servletContext.getResourceAsStream("2.png");
        
  • HttpServletRequest常⽤⽅法

    • 获得客户机【浏览器】信息
      • getRequestURL⽅法返回客户端发出请求时的完整URL。
      • getRequestURI⽅法返回请求⾏中的资源名部分。
      • getQueryString ⽅法返回请求⾏中的参数部分。
      • getPathInfo⽅法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
      • getRemoteAddr⽅法返回发出请求的客户机的IP地址
      • getRemoteHost⽅法返回发出请求的客户机的完整主机名
      • getRemotePort⽅法返回客户机所使⽤的⽹络端⼝号
      • getLocalAddr⽅法返回WEB服务器的IP地址。
      • getLocalName⽅法返回WEB服务器的主机名
    • 获得客户机请求头
      • getHeader⽅法
      • getHeaders⽅法
      • getHeaderNames⽅法
    • 获得客户机请求参数(客户端提交的数据)
      • getParameter⽅法
      • getParameterValues(String name)⽅法
      • getParameterNames⽅法
      • getParameterMap⽅法
  • 转发和重定向的区别:

    • 实际发⽣位置不同,地址栏不同。
      • 转发是发⽣在服务器的。浏览器的地址栏是没有发⽣变化的,浏览器是不知道该跳转的动作,转发是对浏览器透明的。⼀次转发中request和
        response对象都是同⼀个。
      • 重定向是发⽣在浏览器的。浏览器的地址会发⽣变化的,重定向会发出两个http请求,request域对象是⽆效的。
    • ⽤法不同。
      • 资源地址给服务器⽤的直接从资源名开始写,给浏览器⽤的要把应⽤名写上。
      • request.getRequestDispatcher("/资源名 URI").forward(request,response)
        转发时"/"代表的是本应⽤程序的根⽬录【zhongfucheng】
      • response.send("/web应⽤/资源名 URI")
        重定向时"/"代表的是webapps⽬录
    • 能够去往的URL的范围不⼀样
      • 转发是服务器跳转只能去往当前web应⽤的资源
      • 重定向是服务器跳转,可以去往任何的资源
    • 传递数据的类型不同
      • 转发的request对象可以传递各种类型的数据,包括对象
      • 重定向只能传递字符串
    • 跳转的时间不同
      • 转发时:执⾏到跳转语句时就会⽴刻跳转
      • 重定向:整个⻚⾯执⾏完之后才执⾏跳转

iwehdio的博客园:https://www.cnblogs.com/iwehdio/
posted @ 2021-01-29 17:07  iwehdio  阅读(185)  评论(0编辑  收藏  举报