zoukankan      html  css  js  c++  java
  • SpringMVC源码分析


     1、SpringMVC和Servlet

    1.1.SpringMVC和Servlet关系

    Servlet:性能最好,处理Http请求的标准。

    SpringMVC:开发效率高(好多共性的东西都封装好了,是对Servlet的封装,核心的DispatcherServlet最终继承自HttpServlet)

    这两者的关系,就如同MyBatis和JDBC,一个性能好,一个开发效率高,是对另一个的封装。

    1.2.原生Servlet模式和Spring MVC的区别

    2.Servlet讲解实例

    2.1.Servlet中的生命周期方法

    1. 被创建:执行init方法,只执行一次
            * Servlet什么时候被创建?
                * 默认情况下,第一次被访问时,Servlet被创建
                * 可以配置执行Servlet的创建时机。
                    * 在<servlet>标签下配置
                        1. 第一次被访问时,创建
                            * <load-on-startup>的值为负数
                        2. 在服务器启动时,创建
                            * <load-on-startup>的值为0或正整数
                                * Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的
                * 多个用户同时访问时,可能存在线程安全问题。
                * 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
    
    2. 提供服务:执行service方法,执行多次
            * 每次访问Servlet时,Service方法都会被调用一次。
    3. 被销毁:执行destroy方法,只执行一次
            * Servlet被销毁时执行。服务器关闭时,Servlet被销毁
            * 只有服务器正常关闭时,才会执行destroy方法。
            * destroy方法在Servlet被销毁之前执行,一般用于释放资源
    /**
     * Servlet的方法
     */
    public class ServletDemo2 implements Servlet {
        private int age = 3;
    
        /**
         * 初始化方法
         * 在Servlet被创建时,执行。只会执行一次
         * @param servletConfig
         * @throws ServletException
         */
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            System.out.println("init.....");
        }
    
        /**
         * 获取ServletConfig对象
         * ServletConfig:Servlet的配置对象
         * @return
         */
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        /**
         * 提供服务方法
         * 每一次Servlet被访问时,执行。执行多次
         * @param servletRequest
         * @param servletResponse
         * @throws ServletException
         * @throws IOException
         */
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("service.....");
            int number = 3;
        }
    
        /**
         * 获取Servlet的一些信息,版本,作者等等。。
         * @return
         */
        @Override
        public String getServletInfo() {
            return null;
        }
    
        /**
         * 销毁方法
         * 在服务器正常关闭时,执行,执行一次。
         */
        @Override
        public void destroy() {
            System.out.println("destroy.....");
        }
    }

    2.2.思路

    1. 定义一个类,实现Servlet接口

     public class ServletDemo1 implements Servlet

    2. 实现接口中的抽象方法
    3. 配置Servlet

    执行原理:
    1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
    2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
    3. 如果有,则在找到对应的<servlet-class>全类名
    4. tomcat会将字节码文件加载进内存,并且创建其对象
    5. 调用其方法

     2.3.代码实现

    1.定义一个类,实现Servlet接口,实现接口中的抽象方法。

    /**
     * Servlet快速入门
     */
    public class ServletDemo1 implements Servlet {
    
    
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
    
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        //提供服务的方法
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("Hello Servlet");
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    2.配置Servlet

    web.xml

        <!--配置Servlet -->
        <servlet>
            <servlet-name>demo1</servlet-name>
            <servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>   //被访问的类
        </servlet>
    
        <servlet-mapping>
            <servlet-name>demo1</servlet-name>
            <url-pattern>/demo1</url-pattern>    //访问的路径
        </servlet-mapping>
    

     3.前端访问

     4.控制台显示

     3.SpringMVC

    3.1.类的继承关系

    Spring MVC前端控制器DispatcherServlet-->FrameworkServlet-->HttpServletBean-->HttpServlet—>GenericServlet

    3.2.Servlet接口

    public interface Servlet {
    
        //1.init(),初始化servlet对象,完成一些初始化工作。它是由servlet容器控制的,该方法只能被调用一次。
        void init(ServletConfig var1) throws ServletException;
    
       //getServletConfig(),ServletConfig是容器向servlet传递参数的载体。
        ServletConfig getServletConfig();
    
        //2.service(),接受客户端请求对象,执行业务操作,利用响应对象响应客户端请求。
        void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    
       //getServletInfo(),获取servlet相关信息。
        String getServletInfo();
    
        //3.destroy(),当容器监测到一个servlet从服务中被移除时,容器调用该方法,释放资源。
        void destroy();
    }
    

    3.3.GenericServlet

      

    3.4.HttpServlet

    HttpServlet是Servlet规范中的核心类,实现Servlet接口,继承此类用于处理用户请求。

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            HttpServletRequest request;
            HttpServletResponse response;
            try {
                request = (HttpServletRequest)req;
                response = (HttpServletResponse)res;
            } catch (ClassCastException var6) {
                throw new ServletException(lStrings.getString("http.non_http"));
            }
    
            this.service(request, response);
    }

    3.5.HttpServletBean

    HttpServletBean主要配置servlet中初始化参数。继承HttpServlet,并实现无参的init()方法,用于设置在web.xml中配置的contextConfigLocation属性,此属性指定Spring MVC的配置文件地址,默认为WEB-INF/[servlet-name]-servlet.xml,源码如下:

    /**
     * DispatcherServlet第一次加载时调用init方法
     */
    @Override
    public final void init() throws ServletException {
        // 省略日志...
        // 获取在web.xml配置的初始化参数<init-param>,并将其设置到DispatcherServlet中
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        // 调用子类(FrameworkServlet)进行初始化
        // 模版方法,此方法在HttpServletBean本身是空的,但是因为调用方法的对象是DispatcherServlet
        // 所以优先在DispatcherServlet找,找不到再去父类找,最后在FrameworkServlet找到
        initServletBean();
        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
    

    总结HttpServletBean的作用:

    • 获取web.xml的中配置DispatcherServlet的初始化参数,存放到一个参数容器ServletConfigPropertyValues中
    • 根据传进来的this创建BeanWrapper,本质上它就是DispatcherServlet
    • 通过bw.setPropertyValues(pvs, true),把参数设置到bw(即DispatcherServlet)里面去
    • 最后调用子类的initServletBean()

    3.6.FrameworkServlet

    FrameworkServlet主要创建WebApplicationContext上下文,重写了HttpServletBean的initServletBean()方法。

    3.6.1、initServletBean

    该方法只有两句关键代码,其作用一个是调用initWebApplicationContext()方法初始化WebApplicationContext上下文,另一个是调用子类方法initFrameworkServlet()方法,源码如下:

    /**
     * DispatcherServlet第一次加载时调用init方法
     */
    @Override
    public final void init() throws ServletException {
        // 省略日志...
        // 获取在web.xml配置的初始化参数<init-param>,并将其设置到DispatcherServlet中
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        // 调用子类(FrameworkServlet)进行初始化
        // 模版方法,此方法在HttpServletBean本身是空的,但是因为调用方法的对象是DispatcherServlet
        // 所以优先在DispatcherServlet找,找不到再去父类找,最后在FrameworkServlet找到
        initServletBean();
        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }
    

    3.6.2、initWebApplicationContext

    protected WebApplicationContext initWebApplicationContext() {
        // 获取root WebApplicationContext,即web.xml中配置的listener(ContextLoaderListener)
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        // 判断容器是否由编程式传入(即是否已经存在了容器实例),存在的话直接赋值给wac,给springMVC容器设置父容器
        // 最后调用刷新函数configureAndRefreshWebApplicationContext(wac),作用是把Spring MVC配置文件的配置信息加载到容器中去
        if (this.webApplicationContext != null) {
            // context上下文在构造是注入
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // context没有被refreshed,提供一些诸如设置父context、设置应用context id等服务
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        // 在ServletContext中寻找是否有Spring MVC容器,初次运行是没有的,Spring MVC初始化完毕ServletContext就有了Spring MVC容器
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        // 当wac既没有没被编程式注册到容器中的,也没在ServletContext找得到,此时就要新建一个Spring MVC容器
        if (wac == null) {
            // 如果没有WebApplicationContext则创建
            wac = createWebApplicationContext(rootContext);
        }
        // 到这里Spring MVC容器已经创建完毕,接着真正调用DispatcherServlet的初始化方法onRefresh(wac)
        // 此处仍是模板模式的应用
        if (!this.refreshEventReceived) {
            onRefresh(wac);
        }
        // 将Spring MVC容器存放到ServletContext中去,方便下次取出来
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }
        return wac;
    }
    

    3.6.3、createWebApplicationContext

    protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
        return createWebApplicationContext((ApplicationContext) parent);
    }
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        // 实例化容器
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        // 设置容器环境
        wac.setEnvironment(getEnvironment());
        // 设置父容器
        wac.setParent(parent);
        // 加载Spring MVC的配置信息,如:bean注入、注解、扫描等等
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            wac.setConfigLocation(configLocation);
        }
        // 刷新容器,根据Spring MVC配置文件完成初始化操作
        configureAndRefreshWebApplicationContext(wac);
        return wac;
    }
    

    总结FrameworkServlet的作用:

    • 创建Spring MVC的容器,根据配置文件实例化里面各种bean,并将之与spring的容器进行关联
    • 把创建出来的Spring MVC容器存放到ServletContext中
    • 通过模板方法模式调用子类DispatcherServlet的onRefresh()方法

    3.7.DispatcherServlet

    DispatcherServlet是Spring MVC核心,它是J2EE规范前端控制器的实现,负责拦截用户请求,并解析请求进行转发。

    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context); // 文件上传解析
        initLocaleResolver(context); // 本地解析
        initThemeResolver(context); //主题解析
        initHandlerMappings(context); // URL请求映射
        initHandlerAdapters(context); // 初始化Controller类
        initHandlerExceptionResolvers(context); // 异常解析
        initRequestToViewNameTranslator(context); 
        initViewResolvers(context); // 视图解析
        initFlashMapManager(context);
    }
    

    3.8.总结

    • 容器启动时,加载web.xml部署描述文件,扫描到并找到DispatcherServlet核心控制器
    • 调用HttpServletBean的init()方法,把DispatcherServlet初始化参数设置到DispatcherServlet中,并调用子类FrameworkServlet的initServletBean()方法
    • FrameworkServlet的initServletBean()创建Spring MVC容器并初始化,并且和Spring父容器进行关联,使得Spring MVC容器能访问Spring容器中定义的bean,之后调用子类DispatcherServlet的onRefresh()方法
    • DispatcherServlet的onRefresh(ApplicationContext context)对DispatcherServlet的策略组件进行初始化

     

  • 相关阅读:
    Junit单元测试
    Stream流方法引用
    Stream流思想和常用方法
    算法
    函数式接口
    Zookeeper理解
    GreenPlum学习之(Share-nothing)架构
    链表反转问题
    KMP算法的java实现
    KMP详解之二
  • 原文地址:https://www.cnblogs.com/aaaazzzz/p/13170120.html
Copyright © 2011-2022 走看看