Java Web基础

Servlet

Servlet 是服务端的 Java 应用程序,用于处理HTTP请求,做出相应的响应。

Servlet 是 java web 得以实现的基础,任何 java web 的框架都是通过对 servlet 加以封装实现的。

图片说明
当客户端向服务器发出HTTP请求时,首先会由服务器中的 Web 容器(如Tomcat)对请求进行路由,交给该URL对应的 Servlet 进行处理,Servlet 所要做的事情就是返回适当的内容给用户。

在这里,我顺便谈谈自己对 JSP 和 Servlet 两者关系的理解。JSP 页面实际是被转换成了 Servlet 的形式运行,也就是说,我们可以将不同的 JSP 页面视为不同的 Servlet,理论上它们中的每一个都能够对客户端请求做出响应,只不过这些 Servlet 可能会将请求转发给其它 Servlet 进行处理,进而由其它 Servlet 做出响应。

要创建自己的 Servlet,可以通过以下三种方式来实现

  • 实现 Servlet 接口
  • 继承 GenericServlet 类
  • 继承 HttpServlet 类
    图片说明

它们的 package 分别是:javax.servlet.Servletjavax.servlet.GenericServletjavax.servlet.http.HttpServlet

特别需要说明的是,所有的 Servlet 都必须实现 javax.servlet.Servlet 接口。若我们开发的 Servlet程序与HTTP协议无关,那么可以直接继承 javax.servlet.GenericServlet抽象类 ;否则,若我们开发的Servlet程序和HTTP协议有关,那么可以直接继承 javax.servlet.http.HttpServlet 抽象类。

servlet container

在完成了 servlet 以及 web.xml 的编写之后需要使用一个叫做 servlet container 的东西才能让服务运行起来。著名的 servlet container 有 tomcat,jetty。它们主要负责维护其生命周期并按照 web.xml 的规则将 http 请求分发到指定的 servlet 进行处理。

Servlet的生命周期与执行流程

当 Servlet 在容器中运行时,其实例的创建及销毁等都不是由程序员所决定的,而是由Web容器进行控制的。一个Servlet对象的创建有两个时机:用户请求之时或应用启动之时。

 (1) 客户端第一次请求某个Servlet时,容器创建该Servlet实例,这也是大部分Servlet创建实例的时机;
 (2) Web应用启动时立即创建Servlet实例,即 load-on-startup Servlet。 

  但每个Servlet都遵循一个生命周期,即:Servlet 实例化–>Servlet 初始化—>服务—>销毁。具体流程如下图所示:

  • 创建Servlet实例;

  • Web容器调用Servlet的init()方法,对Servlet进行初始化。特别地,在Servlet的生命周期中,仅执行一次init()方法;

  • Servlet 被初始化后,将一直存在于Web容器中,用于响应客户端请求。默认的请求处理、响应方式是调用与HTTP请求的方法相应的do方法;

  • Web容器决定销毁Servlet时,先调用Servlet的 destroy() 的方法回收资源,通常在关闭Web应用之时销毁 Servlet。和 init() 方法类似,destroy()方法在Servlet的生命周期中也仅执行一次。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确保在调用destroy()方法时,这些线程已经终止或完成。

              Servlet生命周期.png-235.9kB

      Ps:本图来源于CSDN博友何静媛JAVA学习篇–Servlet详解一文。

Servlet的执行流程

 Servlet的执行流程如下图所示:

  (1) User Agent 向 Servlet容器(Tomcat)发出Http请求;
  (2) Servle容器接收 User Agent 发来的请求;
  (3) Servle容器根据web.xml文件中Servlet相关配置信息,将请求转发到相应的Servlet;
  (4) Servlet容器创建一个 HttpServlet对象,用于处理请求;
  (5) Servlet容器创建一个 HttpServletRequest对象,将请求信息封装到这个对象中;
  (6) Servlet容器创建一个HttpServletResponse对象;
  (7) Servlet容器调用HttpServlet对象的service方法,并把HttpServltRequest对象与HttpServltResponse对象作为参数传给 HttpServlet 对象;
  (8) HttpServlet调用HttpServletRequest对象的有关方法,获取Http请求信息;
  (9) HttpServlet调用JavaBean对象(业务逻辑组件)处理Http请求;
  (10) HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据;
  (11) Servlet容器将HttpServlet的响应结果传给 User Agent。

             servlet执行流程.png-14.8kB


3、load-on-servlet

  load-on-servlet 指的是应用启动时就创建的Servlet,这些Servlet通常是用于后台服务的Servlet或者需要拦截很多请求的Servlet。也就是说,这些Servlet常常作为应用的基础Servlet使用,提供重要的后台服务。例如:

@WebServlet(loadOnStartup=1)
public class TimerServlet extends HttpServlet
{
    public void init(ServletConfig config)throws ServletException
    {
        super.init(config);
        Timer t = new Timer(1000,new ActionListener()       // 匿名内部类
        {
            public void actionPerformed(ActionEvent e)
            {
                System.out.println(new Date());
            }
        });
        t.start();
    }
}

  我们看到,这个 load-on-servlet 没有提供 service()方法,这表明它不能响应用户请求,所以无需为它配置URL映射。由于它不能接收用户请求,所以只能在应用启动时实例化。

  特别需要注意的是,loadOnStartup属性用于标记容器是否在启动的时候加载这个servlet。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。其中,正数值越小,启动该servlet的优先级越高。

Servlet的配置

  为了让Servlet能够响应用户请求,必须将Servlet配置到Web应用中。从 J2EE 6(即 Servlet 3.0)开始,配置Servlet有两种方式,即 @WebServlet注解配置 和 传统的 web.xml 配置,这部分内容在我的下一篇博客《Servlet 综述(实践篇)》有详细介绍,此不赘述。


Servlet 与 并发

1、Servlet容器如何同时来处理多个请求

  Servlet 采用多线程来处理多个请求同时访问。更具体地,Servlet 依赖于一个线程池来服务请求,所谓线程池实际上是一系列的工作者线程集合,该集合包含的是一组等待执行任务的线程。此外,Servlet 使用一个调度线程来管理这些工作者线程。

  当Servlet容器收到一个Servlet请求时,调度线程会从线程池中选出一个工作者线程,并将请求传递给该工作者线程,然后由该线程来执行Servlet的service()方法。当这个线程正在执行的时候,如果容器收到另外一个请求,调度线程将同样从线程池中选出另一个工作者线程来服务新的请求,特别需要注意的是,容器并不关心这个请求是否访问的是同一个Servlet。当容器同时收到对同一个Servlet的多个请求时,那么这个Servlet的service()方法将在多线程中并发执行。

  Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat容器,我们可以在其server.xml中通过元素设置线程池中的线程数目。

  • ServletContext:(线程是不安全的)
    ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。
    所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet***享,比方我们可以使用单例模式来处理共享数据。

  • HttpSession:(线程是不安全的)
    HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。
    当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。
    这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。

  • ServletRequest:(线程是安全的)
    对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
    注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用。


2、如何开发线程安全的Servlet

  Servlet容器采用多线程来处理请求,提高性能的同时也造成了线程安全问题。要开发线程安全的 Servlet应该从这几个方面进行:

(1). 变量的线程安全: 多线程并不共享局部变量,所以我们要尽可能的在Servlet中使用局部变量;

(2). 代码块的线程安全: 可以使用Synchronized、Lock 和 原子操作(java.util.concurrent.atomic)来保证多线程对共享变量的协同访问;但是要注意的是,要尽可能得缩小同步代码的范围,尽量不要在service方法和响应方法上直接使用同步,这会严重影响性能;

(3). 属性的线程安全: ServletContext,HttpSession,ServletRequest对象中属性是线程安全的;

(4). 使用线程安全容器: 使用 java.util.concurrent 包下的线程安全容器代替 ArrayList、HashMap 等非线程安全容器;


  更多关于Java并发相关知识请移步我的专栏《 Java并发编程学习笔记》。该专栏全面记录了Java并发编程的相关知识,并结合操作系统、Java内存模型和相关源码对并发编程的原理、技术、设计、底层实现进行深入分析和总结,并持续跟进并发相关技术。


Servlet 与 MVC

  Java Web 应用的结构经历了 Model1 和 Model2 两个时代。在 Model1 模式下,整个 Web 应用几乎全部用JSP页面组成,只用少量的JavaBean来处理数据库连接、访问等操作。从工程化角度来看,JSP 不但充当了表现层角色,还充当了控制器角色,将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低,使得应用极难扩展和维护。

  Model2 已经是基于MVC架构的设计模式。在 Model2 中,Servlet 作为前端控制器,负责接收客户端发送的请求,在 Servlet 中只包含控制逻辑和简单的前端处理;然后,Servlet 调用后端的JavaBean(业务逻辑组件)来处理业务逻辑;最后,根据处理结果转发到相应的JSP页面处理显示逻辑。在 Model2 模式下,模型(Model)由 JavaBean 充当,视图(View)由JSP页面充当,而控制器则由 Servlet 充当。Model2 的流程示意图如下:

              Model2.png-32.7kB

  更具体地,在 Model2(标准MVC)中,角色分工如下:

  • Model:由 JavaBean 充当,所有的业务逻辑、数据库访问都在Model中实现;
  • View:由 JSP 充当,负责收集用户请求参数并将应用处理结果、状态数据呈现给用户;
  • Controller:由 Servlet 充当,作用类似于调度员,即所有用户请求都发送给 Servlet,Servlet 调用 Model 来处理用户请求,并根据处理结果调用 JSP 来呈现结果;或者Servlet直接调用JSP将应用处理结果展现给用户。

Filter

Filter 是介于 Web 容器和 Servlet 之间的过滤器,用于过滤未到达 Servlet 的请求或者由 Servlet 生成但还未返回响应。

img

客户端请求从 Web 容器到达 Servlet 之前,会先经过 Filter,由 Filter 对 request 的某些信息进行处理之后交给 Servlet。

同样,响应从 Servlet 传回 Web 容器之前,也会被 Filter 拦截,由 Filter 对 response 进行处理之后再交给 Web 容器。

若要创建自定义 Filter,需要实现 javax.servlet.Filter 接口。

Listener

Listener 是用于监听某些特定动作的***。当特定动作发生时,监听该动作的***就会自动调用对应的方法。

HttpSessionListener 为例:

img

该 Listener 监听 session 的两种状态,即创建和销毁。当 session 被创建时,会自动调用 HttpSessionListener 的 sessionCreated() 方法,我们可以在该方法中添加一些处理逻辑。当 session 被销毁时,则会调用 sessionDestroyed() 方法。

下面是 Servlet 中的 8 个 Listener 接口,可分为三类:

第一类,ServletContext 相关接口

  1. ServletContextListener:用于监听 ServletContext 的启动和销毁。
  2. ServletContextAttributeListener:用于监听 application 范围的属性变化。

第二类,HttpSession 相关接口

  1. HttpSessionListener:用于监听 session 的创建和销毁。
  2. HttpSessionIdListener:用于监听 session 的 id 是否被更改。
  3. HttpSessionAttributeListener:用于监听 session 范围的属性变化。
  4. HttpSessionActivationListener:用于监听绑定在 HttpSession 对象中的 JavaBean 状态。
  5. HttpSessionBindingListener:用于监听对象与 session 的绑定和解绑。

第三类,ServletRequest 相关接口

  1. ServletRequestListener:用于坚挺 ServletRequest 对象的初始化和销毁。
  2. ServletRequestAttributeListener:用于监听 ServletRequest 对象的属性变化。

servlet container

在完成了 servlet 以及 web.xml 的编写之后需要使用一个叫做 servlet container 的东西才能让服务运行起来。著名的 servlet container 有 tomcat,jetty。它们主要负责维护其生命周期并按照 web.xml 的规则将 http 请求分发到指定的 servlet 进行处理。

spring mvc 与 servlet 的关系

前面提到过,java web 的各种框架都是建立在 servlet 体系之上的,SpringMVC 也不例外。和 structs 等老牌的 web 框架类似,它也提供了一个 DispatcherServlet 作为 Front Controller 来拦截所有的请求,并通过自己的请求分发机制将请求分发到 SpringMVC 下实际的 controller 之中。

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>
</web-app>

spring security web 与 filter 的关系

spring security web 都是基于 servlet filter 建立起来的。其使用的方式和 SpringMVC 的 DispatcherServlet 非常类似,有一个 DelegatingFilterProxy 作为全局的 Filter 对所有的请求进行过滤,并在其层次之下实现 Spring Security 所提供的特有的 Filter(当然不再是继承自 java Filter 的类了)层次。

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
    </filter>

    <filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

interceptor

interceptor:是在面向切面编程的,就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法,是基于JAVA的反射机制。比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。

几个区别:

  • 1,servlet 流程是短的,url传来之后,就对其进行处理,之后返回或转向到某一自己指定的页面。它主要用来在 业务处理之前进行控制.
  • 2,filter 流程是线性的, url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收等,而servlet 处理之后,不会继续向下传递。filter功能可用来保持流程继续按照原来的方式进行下去,或者主导流程,而servlet的功能主要用来主导流程。
    filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等
  • 3, servlet,filter都是针对url之类的,而listener是针对对象的操作的,如session的创建,session.setAttribute的发生,在这样的事件发生时做一些事情。
    可用来进行:Spring整合Struts,为Struts的action注入属性,web应用定时任务的实现,在线人数的统计等
  • 4,interceptor 拦截器,类似于filter,不过在struts.xml中配置,不是在web.xml,并且不是针对URL的,而是针对action,当页面提交 action时,进行过滤操作,相当于struts1.x提供的plug-in机制,可以看作,前者是struts1.x自带的filter,而 interceptor 是struts2 提供的filter.

与filter不同点:
(1)不在web.xml中配置,而是在struts.xml中完成配置,与action在一起
( 2 ) 可由action自己指定用哪个interceptor 来在接收之前做事

5,struts2中的过滤器和拦截器的区别与联系:
(1)、拦截器是基于java反射机制的,而过滤器是基于函数回调的。
(2)、过滤器依赖与servlet容器,而拦截器不依赖与servlet容器。
(3)、拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
(4)、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
(5)、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。

执行流程图:

1、servlet:

图片说明

2.filter
图片说明

3.listener

图片说明

  1. interceptor
    图片说明
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务