Servlet规范和Servlet容器

2019-11-11  本文已影响0人  rock_fish
Servlet 规范 的目的

按照面向接口编程的思想,HTTP服务端的业务逻辑类统一实现Servlet接口,使用Servlet容器用来加载和管理业务类(Servlet实现类),容器将httpServer与业务处理类之间做了解耦隔离.
业务开发只需要实现一个 Servlet接口完成业务功能,并把它注册到 Servlet 容器中,剩下的事情就由 容器来处理.开发者不需要关心 Socket 网络通信、不需要关心 HTTP 协议,也不需要关心业务类是如何被实例化和调用的

image.png

Servlet 接口和 Servlet 容器这一整套规范叫作 Servlet 规范,Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器,同时它们也具有 HTTP 服务器的功能.

Servlet 接口

Servlet 接口定义了下面五个方法:

public interface Servlet {
    void init(ServletConfig config) throws ServletException;
    
    ServletConfig getServletConfig();
    
    void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
    
    String getServletInfo();
    
    void destroy();
}

  1. HTTP 协议中的请求是HttpServletRequest类。通过 HttpServletRequest 来获取所有请求相关的信息,包括请求路径、Cookie、HTTP 头、请求参数等;创建和获取 Session。
  2. HTTP 协议中的响应 HttpServletResponse 是用来封装 HTTP 响应的,
Servlet容器
  1. 客户端请求HTTP服务器.
  2. 封装客户请求.
    2.1 HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,
    2.2 调用 Servlet 容器的 service 方法.
  3. Servlet容器检索/创建Servlet类.
    3.1 Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet.
    3.2 如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet.
  4. 用Servlet类做业务处理并返回结果
    4.1 调用 Servlet 的 init 方法来完成初始化.
    4.2 调用 Servlet 的 service 方法来处理请求.
    4.3 结果封装到ServletResponse 对象返回给 HTTP 服务器.
  5. HTTP 服务器会把响应发送给客户端
| -  MyWebApp
      | -  WEB-INF/web.xml        -- 配置文件,用来配置 Servlet 等
      | -  WEB-INF/lib/           -- 存放 Web 应用所需各种 JAR 包
      | -  WEB-INF/classes/       -- 存放你的应用类,比如 Servlet 类
      | -  META-INF/              -- 目录存放工程的一些信息

目录下分别放置了 Servlet 的类文件、配置文件以及静态资源,Servlet 容器通过读取配置文件,就能找到并加载 Servlet

扩展机制

可以根据请求的频率来限制访问,或者根据国家地区的不同来修改响应内容。过滤器的

总结

Servlet 本质上是一个接口,实现了 Servlet 接口的业务类也叫 Servlet。Servlet 接口其实是 Servlet 容器跟具体 Servlet 业务类之间的接口。Servlet 接口跟 Servlet 容器这一整套规范叫作 Servlet 规范,而 Servlet 规范使得程序员可以专注业务逻辑的开发,同时 Servlet 规范也给开发者提供了扩展的机制 Filter 和 Listener。

Filter 和 Listener 的本质区别:
Filter 是干预过程的,它是过程的一部分,是基于过程行为的。
Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致的。

Tomcat的Wrapper组件-Filter-DispatcherServlet-Controller

Tomcat&Jetty在启动时给每个Web应用创建一个全局的上下文环境,这个上下文就是ServletContext,其为后面的Spring容器提供宿主环境。
Tomcat的web.xml配置中,配置了servlet容器的监听器:Spring的ContextLoaderListener,
Tomcat&Jetty在启动过程中触发容器初始化事件,Spring的ContextLoaderListener会监听到这个事件,它的contextInitialized方法会被调用,在这个方法中,Spring会初始化全局的Spring根容器,这个就是Spring的IoC容器,IoC容器初始化完毕后,Spring将其存储到ServletContext中,便于以后来获取。

Tomcat&Jetty在启动过程中还会扫描Servlet,
会扫描到SpringMVC中的DispatcherServlet,这个Servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个Servlet请求。

Servlet一般会延迟加载,当第一个请求达到时,Tomcat&Jetty发现DispatcherServlet还没有被实例化,就调用DispatcherServlet的init方法,DispatcherServlet在初始化的时候会建立自己的容器,叫做SpringMVC 容器,用来持有Spring MVC相关的Bean。同时,Spring MVC还会通过ServletContext拿到Spring根容器,并将Spring根容器设为SpringMVC容器的父容器,请注意,Spring MVC容器可以访问父容器中的Bean,但是父容器不能访问子容器的Bean, 也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是,在Controller里可以访问Service对象,但是在Service里不可以访问Controller对象。

image.png
https://blog.csdn.net/zhanglf02/article/details/89791797
<web-app>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--前端控制器-->
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springMVC.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        //初始化spring容器
    this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

1.tomcat中配置一个spring的监听器
2.tomcat启动时为应用创建了ServletContext, contextInitialized事件中,spring容器创建,并存入ServletContext中.
3.tomcat启动时,扫描到DispatcherServlet这个servlet
4.有请求来的时候,DispatcherServlet会被创建,并调用init方法,init中创建spring mvc的容器,通过ServletcContext拿到spring容器,两个容器建立关联关系, spring容器作为 spring mvc容器的父容器.spring mvc 能访问spring容器,但是spring容器访问不了spring mvc容器为啥要单向可见?


spring的线程上下文类加载器。

  1. 如果Spring的jar包放到 common 或 shared 目录下,那么Spring框架的类就是由CommonClassLoader /SharedClassLoader来加载的;也即 Spring容器的当前类加载器是 Common/Shared ClassLoader 。
  2. Spring 要管理每个web应用程序的bean,getBean时要能访问到WebAppClassLoader 加载路径下的应用程序的类,那么在 Spring容器的当前类加载器Common/SharedClassLoader 加载不了并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class;
  3. WebAppClassLoader 能够加载 web应用目录下的类,也能使用 common 或 shared 目录下的类(因为common/shared是WebAppClassLoader的父加载器);所以这种情况下,spring容器只能通过 WebAppClassLoader 来加载用户程序中的类;怎么拿到 webAppClassLoader,并让他来加载类呢?
  4. 通过线程上下文类加载器来获取。tomcat在调用ContextLoaderListener 的contextInitialized方法之前(spring容器初始化之前) 创建web应用WebAppClassLoader 并将其放置到线程上下文类加载器中。

看初始化spring容器的代码 initWebApplicationContext
如何用org.springframework.web.context.ContextLoader类 来获取和使用线程上下文类加载器来装载bean

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    try {
        // 创建WebApplicationContext
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        // 将其保存到该webapp的servletContext中     
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        // 获取线程上下文类加载器,默认为WebAppClassLoader
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        // 如果spring的jar包放在每个webapp自己的目录中
        // 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是WebAppClassLoader
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            // 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的WebApplicationContext及对应的WebAppClassLoader存下来
            // 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
            currentContextPerThread.put(ccl, this.context);
        }
        
        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        throw err;
    }
}

创建应用上下文类加载器
createWebApplicationContext(servletContext);

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   //beanFactory此时来指定ClassLoader,后续获取Bean的时候就用它
   beanFactory.setBeanClassLoader(this.getClassLoader());
}

this.getClassLoader() 的值是什么 ,代码在org.springframework.core.io.DefaultResourceLoader

    public ClassLoader getClassLoader() {
        return this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader();
    }

继续看 ClassUtils.getDefaultClassLoader()的实现 ,可以发现会返回线程上下文类加载器.

    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
      
        try {
            //如果线程上下文类加载不是null 就返回线程上下文类加载器
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable var3) {
        }

        if (cl == null) {
            //如果线程上下文类加载器是null 就用 当前类加载器
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                try {
                    //兜底用 系统类加载器.
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable var2) {
                }
            }
        }

        return cl;
    }

Spring的getBean方法

  1. org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass 中 获取类加载器,就是线程上下文类加载器 tccl
  2. org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass
    Class<?> beanClass = Class.forName(类名,false,线程上下文类加载器);

3.拿到类型进行实例化.

当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
当使用本类 托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。如Spring.

————————————————
真正理解线程上下文类加载器(多案例分析)
https://blog.csdn.net/yangcheng33/article/details/52631940

上一篇下一篇

猜你喜欢

热点阅读