zoukankan      html  css  js  c++  java
  • Spring IOC 父子容器(注解方式启动)

    SpringBoot 启动默认没有父子容器,只有一个容器

    一、调试环境

    依赖使用 Maven 管理,只用导入 spring-context 即可,这里的版本为 5.2.7

    通常使用 spring 有两种配置方式:注解和配置文件

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    
        // 打印 IOC 容器中所有 bean 的名称
        String[] namesForType = applicationContext.getBeanDefinitionNames();
        for (String name : namesForType) {
            System.out.println(name);
        }
    }

    两种配置方式都可以启动 IOC 容器

    注解

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration // 告诉 Spring 这是一个配置类
    @ComponentScan // 默认扫描当前包(com.test.config)及其子包(com.test.config.*)
    public class MainConfig {
    
        // 给容器中注册一个 Bean,类型为返回值的类型,id 默认是用方法名作为 id
        @Bean("person123")
        public Person person() {
            return new Person("lisi", 20, "nice");
        }
    }
    View Code

    配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component,禁用默认规则才可自定义扫描规则 -->
        <context:component-scan base-package="com.test" use-default-filters="false"/>
    
        <bean id="person" class="com.test.bean.Person" scope="prototype">
            <property name="age" value="29"></property>
            <property name="name" value="zhangsan"></property>
        </bean>
    </beans>
    View Code

    一般情况下,我们不会像这样手动去初始化 IOC 容器,而是在 Web 环境下使用,它会自动初始化 IOC 容器,附上调试环境:https://www.cnblogs.com/jhxxb/p/10512553.html

    二、开始源码

    Tomcat 启动后会自动加载”配置类“:https://www.cnblogs.com/jhxxb/p/13596565.html

    第一个被执行的 Spring 程序类应该就是 SpringServletContainerInitializer  类了

    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
        @Override
        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
            List<WebApplicationInitializer> initializers = new LinkedList<>();
    
            if (webAppInitializerClasses != null) {
                for (Class<?> waiClass : webAppInitializerClasses) {
                    // 只处理实体类,接口和抽象类一概不管
                    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                        try {
                            initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                        } catch (Throwable ex) {
                            throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                        }
                    }
                }
            }
    
            if (initializers.isEmpty()) {
                servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
                return;
            }
    
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            // 排序后,循环调用 onStartup 方法进行初始化
            AnnotationAwareOrderComparator.sort(initializers);
            for (WebApplicationInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
    }

    可以看到这里只有我们写的一个配置类,抽象类和接口都被过滤掉了

    往下走,onStartup 的实现在 AbstractDispatcherServletInitializer 类

    public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            super.onStartup(servletContext);
            // 注册 DispatcherServlet,让它去初始化 Spring MVC 的子容器
            registerDispatcherServlet(servletContext);
        }
    
    
    public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
        // 父类方法,super.onStartup(servletContext)
        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            // 注册 ContextLoaderListener 监听器,去初始化 Spring 父容器
            registerContextLoaderListener(servletContext);
        }

    这里可以看出分两步,先初始化 Spring IOC,再初始化 Spring MVC IOC

    注册 ContextLoaderListener,为了后续初始化 Spring IOC

    public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
        protected void registerContextLoaderListener(ServletContext servletContext) {
            WebApplicationContext rootAppContext = createRootApplicationContext();
            if (rootAppContext != null) {
                // 创建 listener,并且把已经创建好的容器放进去
                ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
                // 放入监听器需要的一些上下文,此处没有。一般都为 null 即可。若有需要(自己定制),子类复写此方法即可
                listener.setContextInitializers(getRootApplicationContextInitializers());
                // 把监听器加入进来  这样该监听器就能监听 ServletContext 了,并且执行 contextInitialized 方法
                servletContext.addListener(listener);
            }

    createRootApplicationContext 创建了一个 AnnotationConfigWebApplicationContext,并把配置文件注册了进去

    public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
        @Override
        @Nullable // 返回值允许为 null,这里不分析为 null 的执行流程
        protected WebApplicationContext createRootApplicationContext() {
            Class<?>[] configClasses = getRootConfigClasses();
            if (!ObjectUtils.isEmpty(configClasses)) {
                AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
                // 配置文件可以有多个,会以累加的形式添加进去
                context.register(configClasses);
                return context;
            } else {
                return null;
            }
        }

    注册 DispatcherServlet,为了后续初始化 Spring MVC IOC

    public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
        protected void registerDispatcherServlet(ServletContext servletContext) {
            // Servlet 名称,一般用系统默认的即可,否则自己复写此方法也成
            String servletName = getServletName();
            Assert.hasLength(servletName, "getServletName() must not return null or empty");
    
            // 创建 web 的子容器。创建的代码和上面差不多,也是使用调用者提供的配置文件,创建 AnnotationConfigWebApplicationContext,注:此处不可能为 null
            WebApplicationContext servletAppContext = createServletApplicationContext();
            Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
            // 创建 DispatcherServlet,并且把子容器传进去了。其实就是 new 一个出来,最后加到容器里,就能够执行一些 init 初始化方法了
            FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
            Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    
            // 同样的 getServletApplicationContextInitializers(),一般也为 null 即可
            dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
            // 注册 servlet 到 web 容器里,这样就可以接收 HTTP 请求了
            ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
            if (registration == null) {
                throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. Check if there is another servlet registered under the same name.");
            }
    
            // 1 表示立马执行
            registration.setLoadOnStartup(1);
            // 调用者必须实现
            registration.addMapping(getServletMappings());
            // 默认就是开启了支持异步的
            registration.setAsyncSupported(isAsyncSupported());
    
            // 处理自定义的 Filter 进来,一般我们 Filter 不这么加进来,而是自己 @WebFilter,或者借助 Spring。注:这里添加进来的 Filter 都仅仅只拦截过滤上面注册的 dispatchServlet
            Filter[] filters = getServletFilters();
            if (!ObjectUtils.isEmpty(filters)) {
                for (Filter filter : filters) {
                    registerServletFilter(servletContext, filter);
                }
            }
    
            // 调用者若对 dispatcherServlet 有自己更个性化的参数设置,复写此方法即可
            customizeRegistration(registration);
        }

    初始化 Spring IOC

    继续执行,就来到了 ContextLoaderListener 的初始化方法,附带会初始化 Spring 容器(注:到了此处,就和 web.xml 方式启动一模一样了)

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }

    和 web.xml 不一样的是,使用注解驱动启动的此时,ContextLoaderListener 对象已经持有 WebApplicationContext 的引用(但是还没有放进 ServletContext 里面去)

    往下走,进入父类的 initWebApplicationContext 方法

    public class ContextLoader {
        public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
            // 虽然注解驱动传进来的监听器对象持有 WebApplicationContext 的引用,但是并没有放进 ServletContext 容器
            if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
                throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +
                        "check whether you have multiple ContextLoader* definitions in your web.xml!");
            }
    
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }
            long startTime = System.currentTimeMillis();
    
            try {
                // 这句特别重要,兼容了 web.xml 的方式以及注解驱动的方式。这里是注解驱动的方式,所以此处不会为 null。用 web.xml 的方式的时候,会去详细看 createWebApplicationContext(servletContext) 方法
                if (this.context == null) {
                    this.context = createWebApplicationContext(servletContext);
                }
                // 从上图可以看出:XmlWebApplicationContext(xml驱动) 和 AnnotationConfigWebApplicationContext(注解驱动) 是复合的,都会进来
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                    // 一般刚创建的 context 不会处于激活状态,所以会进来完善一些更多的容器信息。比如刷新容器、设置父容器等
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            // Spring 5.0 开始,默认实现只是返回 null。绝大多数情况下,Spring 容器不用再给设置父容器。这里主要是想多个 Web 应用可以有一个共同的父容器,例如 EJB、EAR
                            ApplicationContext parent = loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        // 读取相应的配置并且刷新 context 对象,这一步极其重要了,因为刷新容器做了太多的事,属于容器的最最最核心逻辑(这里不做分析)
                        configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                // 放进 ServletContext 上下文,避免再次被初始化,也让我们能更加方便的获取到容器
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
                // 此处把容器和当前线程绑定,public static WebApplicationContext getCurrentWebApplicationContext() 这样就可以更加方便的得到容器,类为:ContextLoader
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }
    
                if (logger.isInfoEnabled()) {
                    long elapsedTime = System.currentTimeMillis() - startTime;
                    logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
                }
    
                return this.context;
            } catch (RuntimeException | Error ex) {
                logger.error("Context initialization failed", ex);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
                throw ex;
            }
        }
    
    
        // 最重要的一个方法
        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
            // 一般此处为真,给 ApplicationContext 设置一个 id
            if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
                // 获取 servletContext 中的 contextId 属性 contextId,可在 web.xml 里配置,一般也不用配置,采用 else 里的默认值即可
                String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
                if (idParam != null) {
                    // 存在则设为指定的 id 名
                    wac.setId(idParam);
                } else {
                    // 生成默认 id,一般为 org.springframework.web.context.WebApplicationContext:${contextPath}
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
                }
            }
    
            // 让 Spring 容器关联上 servlet 上下文
            wac.setServletContext(sc);
            // 读取 contextConfigLocation 属性(在 web.xml 配置,但是注解驱动里没有,因此为 null)
            String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
            if (configLocationParam != null) {
                // 设置指定的 spring 文件所在地,支持 classpath 前缀并多文件,以 ,; 为分隔符
                wac.setConfigLocation(configLocationParam);
            }
    // 这里有一个注意的地方,ConfigurableEnvironment 生成的地方 // wac.setConfigLocation(configLocationParam); 时根据 configLocationParam 设置配置参数路径时就会初始化 StandardServletEnvironment(ConfigurableEnvironment 的子类) // StandardServletEnvironment 符合条件,因此会执行 initPropertySources 方法 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 检查 web.xml 是否有一些其余初始化类的配置,极大多数情况都不需要,所以粗暴理解为没什么用 customizeContext(sc, wac); // 容器的核心方法,也是最难的一个方法 // 这里先理解为就是初始化容器,比如加载 bean、拦截器、各种处理器的操作就够了(也是最耗时的一步操作) wac.refresh(); }

    该方法完成之后,看控制台日志:

    Root WebApplicationContext initialized in 435212 ms

    就说明 Spring 根容器初始化完成了

    初始化 Spring MVC IOC

    继续执行,就来到了 DispatcherServlet 的初始化方法,附带会初始化 Spring 子容器(Web 容器),首先要知道 Servlet 初始化的大致步骤:

    1. Servlet 容器加载 Servlet 类,把类的 .class 文件中的数据读到内存中
    2. Servlet 容器中创建一个 ServletConfig 对象。该对象中包含了 Servlet 的初始化配置信息
    3. Servlet 容器创建一个 Servlet 对象(我们也可以手动 new,然后手动添加进去)
    4. Servlet 容器调用 Servlet 对象的 init() 方法进行初始化

    由于设置了 registration.setLoadOnStartup(1),在容器启动完成后就会调用 servlet 的 init()

    DispatcherServlet 继承 FrameworkServlet 继承 HttpServletBean 继承 HttpServlet,在 HttpServletBean 实现了 init()

    先看 HttpServletBean 的 init 方法

    public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
        @Override
        public final void init() throws ServletException {
            // 把 Servlet 的初始化参数封装进来...
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    
            // 这里我们并没有给此 Servlet 初始化的一些参数,所以此处为空,为 false
            // 若进来了,可以看到里面会做一些处理:将这个 DispatcherServlet 转换成一个 BeanWrapper 对象,从而能够以 Spring 的方式来对初始化参数的值进行注入。这些属性如 contextConfigLocation、namespace 等等。
            // 同时注册一个属性编辑器,一旦在属性注入的时候遇到 Resource 类型的属性就会使用 ResourceEditor 去解析。再留一个 initBeanWrapper(bw) 方法给子类覆盖,让子类处真正执行 BeanWrapper 的属性注入工作。
            // 但是 HttpServletBean 的子类 FrameworkServlet 和 DispatcherServlet 都没有覆盖其 initBeanWrapper(bw) 方法,所以创建的 BeanWrapper 对象没有任何作用。
    
            // 备注:此部分把当前 Servlet 封装成一个 BeanWrapper,再把它交给 Spring 容器管理,这部分非常重要,使用 SpringBoot 启动的时候,会看出来这部分代码的重要性
            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;
                }
            }
            // Spring 把这个 init 方法给 final 掉了,然后开了这个口子,子类可以根据自己的需要,在初始化的时候复写这个方法,而不再是 init 方法
            initServletBean();
        }

     继续 initServletBean() 方法,它是由 FrameworkServlet 实现的

    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
        @Override
        protected final void initServletBean() throws ServletException {
            getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
            if (logger.isInfoEnabled()) {
                logger.info("Initializing Servlet '" + getServletName() + "'");
            }
            long startTime = System.currentTimeMillis();
    
            try {
                // 重点,开始初始化 Spring 子容器
                this.webApplicationContext = initWebApplicationContext();
                // 留一个口,给子类去复写初始化所需要的操作,一般都为空实现即可,除非自己要复写 DispatcherServlet
                initFrameworkServlet();
            } catch (ServletException | RuntimeException ex) {
                logger.error("Context initialization failed", ex);
                throw ex;
            }
    
            if (logger.isDebugEnabled()) {
                String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
                logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
            }
    
            // 当看到这句日志,dispatcherServlet 就已经初始化完成,Web 子容器也就初始化完成
            if (logger.isInfoEnabled()) {
                logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
            }
        }

    继续 initWebApplicationContext 方法:创建一个 Web 子容器,并且和上面 Spring 已经创建好了的父容器关联上

    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
        protected WebApplicationContext initWebApplicationContext() {
            // 从 ServletContext 中把上面已经创建好的根容器拿到手
            WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            WebApplicationContext wac = null;
    
            // 上面注册 DispatcherServlet(createDispatcherServlet(servletAppContext)方法)的时候,已经传入了根据配置文件创建好了的子容器,因此,此处肯定是不为 null 的,会进去,和上面一样,完成子容器的初始化、刷新工作,这里就不再解释了
            if (this.webApplicationContext != null) {
                wac = this.webApplicationContext;
                if (wac instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                    if (!cwac.isActive()) {
                        if (cwac.getParent() == null) {
                            // 把根容器,设置为自己的父容器
                            cwac.setParent(rootContext);
                        }
                        // 根据绑定的配置,初始化、刷新容器
                        configureAndRefreshWebApplicationContext(cwac);
                    }
                }
            }
    
            // 若是 web.xml 方式,会走这里,进而走 findWebApplicationContext()
            if (wac == null) {
                wac = findWebApplicationContext();
            }
            if (wac == null) {
                wac = createWebApplicationContext(rootContext);
            }
    
            // 此处注意:下面有解释,refreshEventReceived 属性保证了 onRefresh 方法不会被重复执行
            if (!this.refreshEventReceived) {
                synchronized (this.onRefreshMonitor) {
                    onRefresh(wac);
                }
            }
    
            // 是否需要把我们的容器发布出去,作为 ServletContext 的一个属性值?默认值为 true,一般情况下就让为 true 即可
            if (this.publishContext) {
                // 这个 attr 的 key 的默认值,就是 FrameworkServlet.SERVLET_CONTEXT_PREFIX,保证了全局唯一性
                // 这么一来,我们的根容器、web 子容器其实就都放进 ServletContext 上下文里了,拿取都非常的方便。只是我们一般拿这个容器的情况较少,一般都是拿根容器,比如那个工具类就是获取根容器的
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
            }
    
            return wac;
        }

    FrameworkServlet 策略式的实现了监听方法,监听应用的刷新事件。

    当我们刷新应用的时候(比如上面执行 refresh() 方法,这里就会执行,并且打上标记说已经执行过了),然而 onRefresh() 是一个模版方法,具体实现交给子类,这样子 DispatcherServlet 就可以做初始化 Web 组件的一些事情

    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
        public void onApplicationEvent(ContextRefreshedEvent event) {
            this.refreshEventReceived = true;
            synchronized (this.onRefreshMonitor) {
                onRefresh(event.getApplicationContext());
            }
        }
    
        private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
            @Override
            public void onApplicationEvent(ContextRefreshedEvent event) {
                FrameworkServlet.this.onApplicationEvent(event);
            }
        }

    这就是为何会抽象出 FrameworkServlet 的原因,因为它设计的初衷不仅仅只想支持到 Servlet

    所以此处就不得不说一下,子类自己实现的 onRefresh() 方法:

    public class DispatcherServlet extends FrameworkServlet {
        @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
        // 初始化 Spring MVC 的 9 大组件(至此,才算全部初始化完成了)
        protected void initStrategies(ApplicationContext context) {
            initMultipartResolver(context);
            initLocaleResolver(context);
            initThemeResolver(context);
            initHandlerMappings(context);
            initHandlerAdapters(context);
            initHandlerExceptionResolvers(context);
            initRequestToViewNameTranslator(context);
            initViewResolvers(context);
            initFlashMapManager(context);
        }

    该方法完成之后,看控制台日志:

    INFO (FrameworkServlet.java:547)- Completed initialization in 2015760 ms

    就说明整个 Spring 父子容器全部初始化、启动完成了

    三、其他

    SpringMVC 中的 Servlet 的三个层次:

    1. HttpServletBean 直接继承自 java 的 HttpServlet,其作用是将 Servlet 中配置的参数设置到相应的 Bean 属性上
    2. FrameworkServlet 初始化了 WebApplicationContext
    3. DispatcherServlet 初始化了自身的 9 个组件(重点)

    在任何地方获取 Spring 容器(根容器):

    @Autowired
    private HttpServletRequest request;
    
    @Override
    public Object hello() {
        ApplicationContext ctx1 = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
        WebApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
        System.out.println(ctx1 == ctx2); // true
    
        return "service hello";
    }

    用了 ContextLoader 的静态方法

    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }

    关于 DispatcherServlet:

    DispatcherServlet 创建自己的 WebApplicationContext 并管理这个 WebApplicationContext 里面的 handlers/controllers/view-resolvers

    FrameworkServlet 实现(Spring 4.0 之后才实现的)了 ApplicationContextAware 接口的 setApplicationContext() 方法,可知 DispatcherServlet 的 applicationContext 来自 FrameworkServlet

    setApplicationContext 方法只用于嵌入式的 Servlet 环境。war 环境这里是不会执行的,war 环境下 WebApplicationContext 不会以 Bean 的形式存在于 Spring 容器

    在 DispatcherServlet 的 doService 方法里都有这样的一段代码,方便我们非常方便获取到一些参数,比如 web 子容器等等

    public class DispatcherServlet extends FrameworkServlet {
        @Override
        protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            // Make framework objects available to handlers and view objects.
            request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
            request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
            request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
            request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    关于父子容器:

    优点:能让 web 环境和普通的 Spring 环境达到隔离的效果。web 容器专注于管理 web 相关 Bean,其余 bean 的交给父容器。 

    缺点:父子容器的设计提高了 Spring 初始化、管理 Bean 的复杂度(虽然对使用者一般无感),但万一要用到相关功能时,若不理解原理会有莫名其妙的一些问题,提高了复杂性

    理论上可以有任意多个容器(只是我们一般其它的 Bean 都只放进主容器统一管理,但 Spring 是提供了这样的功能的),比如:

    • 主容器:applicationContext.xml(主文件,包括 JDBC 配置,hibernate.cfg.xml,与所有的 Service 与 DAO 基类)
    • web 子容器:application-servlet.xml(管理 Spring MVC 9 大组件以及相关的Bean)
    • cache 子容器:applicationContext-cache.xml(cache 策略配置,管理和缓存相关的Bean)
    • JMX 子容器:applicationContext-jmx.xml(JMX 相关的 Bean)

    值得注意的是,SpringMVC 在调用 HandlerMapper 进行 url 到 controller 函数方法映射解析的时候,HandlerMapper 会在 SpringMVC 容器中寻找 controller,也就是在子容器中寻找,不会去父容器 spring 容器中寻找的。

    所以如果用父容器来管理 controller 的话,子容器不去管理,在访问页面的时候会出现 404 错误。

    Spring Boot 中只采用一个容器,也就没有这个问题


    https://blog.csdn.net/f641385712/article/details/87883205

    https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet-context-hierarchy

  • 相关阅读:
    Quarts 执行定时任务失败(.job.entity.ScheduleJobEntity cannot be cast to com.)
    Map与String互相转化
    weui中的picker滑动报错
    weui中的picker使用js进行动态绑定数据
    ajax跨域问题解决方案(jsonp的使用)
    pdm文件打开方式
    删除静态页面的html
    js同时获取多个共同class内容标签内容集合
    内外网同时使用
    win10重装系统修改信息
  • 原文地址:https://www.cnblogs.com/jhxxb/p/13598074.html
Copyright © 2011-2022 走看看