• IOC容器的创建


    一、IOC容器创建方式

    Ioc容器的创建时通过ApplicationContext接口的相关实现类进行的。
    在这里插入图片描述
    如上图所示:有三种创建IOC容器的方式。

    ClassPathXmlApplicationContext:从项目的根目录下加载配置文件

    FileSystemXmlApplicationContext:从磁盘中的加载配置文件

    AnnotationConfigApplicationContext:当使用注解配置容器对象时使用此类进行注解读取,创建容器。

    二、IOC容器创建入口

    1、Java中IOC容器创建

    Java中通过如下代码进行配置文件读取:

    ApplicationContext context = new ClassPathXmlApplicationContext("xml路径");
    

    创建ClassPathXmlApplicationContext对象的过程中,调用refresh方法完成容器的创建与初始化。

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            //容器的创建与初始化
            refresh();
        }
    }
    

    2、Web中IOC容器创建

    web项目中,Spring启动是在web.xml配置监听器,如下所示:

     <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
    

    ContextLoaderListener类实现了Tomcat容器的ServletContextListener接口,与其他Servlet监听一样,重写了两个方法:contextInitialized()方法进行web容器初始化,contextDestroyed()方法进行容器销毁。

    //初始化
    @Override
    public void contextInitialized(ServletContextEvent event) {
    	initWebApplicationContext(event.getServletContext());
    }
    //销毁
    @Override
    public void contextDestroyed(ServletContextEvent event) {
    	closeWebApplicationContext(event.getServletContext());
    	ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
    

    初始化的过程中调用ContextLoader中的initWebApplicationContext()方法,代码如下:

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    	//判断ServletContext是否已经存在WebApplication,如果存在则抛出异常	
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
                //抛出异常...
    		}
    
    		Log logger = LogFactory.getLog(ContextLoader.class);
    		servletContext.log("Initializing Spring root WebApplicationContext");
    		if (logger.isInfoEnabled()) {
    			logger.info("Root WebApplicationContext: initialization started");
    		}
    		long startTime = System.currentTimeMillis();
    
    		try {
    			if (this.context == null) {
                    //创建WebApplicationContext
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {
    					if (cwac.getParent() == null) {
                            //得到根上下文的父上下文,然后设置到根上下文,一般的web项目parent为空  
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
                        //从web.xml加载参数,初始化根上下文WebApplicationContext,创建bean工厂和bean对象
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
    			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    			if (ccl == ContextLoader.class.getClassLoader()) {
    				currentContext = this.context;
    			}
    			else if (ccl != null) {
    				currentContextPerThread.put(ccl, this.context);
    			}
    
    			if (logger.isDebugEnabled()) {
    				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
    			}
    			if (logger.isInfoEnabled()) {
    				long elapsedTime = System.currentTimeMillis() - startTime;
    				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
    			}
    
    			return this.context;
    		}
    		//异常处理...
    	}
    

    这个方法中ServletContext是由web容器监听器(ContextLoaderListener)提供。首先判断servlectContext中是否已经存在根上下文,如果存在,则抛出异常;否则通过createWebApplicationContext方法创建新的根上下文。然后通过loadParentContext()方法为其设置父上下文。再通过configureAndRefreshWebApplicationContext为根上下文构建bean工厂和bean对象。最后把上下文存入servletContext,并且存入currentContextPerThread。至此初始化过程完毕,接下来可以获取WebApplicationContext,进而用getBean("bean name")得到bean。

    2.1、创建上下文

    下面首先针对createWebApplicationContext进行分析,createWebApplicationContext方法用于创建跟上下文,其代码如下:

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        //从web.xml配置的contextClass参数中获取上下文类名,如果contextClass为空,则使用默认的。
        Class<?> contextClass = determineContextClass(sc);
        //根上下文必须是ConfigurableWebApplicationContext的子类,否则抛出异常  
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        //根据类名创建类
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    

    determineContextClass方法用于返回根上下文的类名,代码如下:

    protected Class<?> determineContextClass(ServletContext servletContext) {
        //从web.xml获得参数contextClass,在一般的web项目中,此参数为null  
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            //获得根上下文WebApplicationContext的默认实现类的类名,defaultStrategies是Properties类型,在CotnextLoader类开头static语句块中初始化  
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }
    

    Properties类型的初始化静态代码块:

    static {
    		try {
                 //获取当前包下面的ContextLoader.properties文件
    			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
    			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    		}
    		catch (IOException ex) {
    			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    		}
    	}
    

    2.2、初始化根上下文

    初始化上下文的方法configureAndRefreshWebApplicationContext代码如下:

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    	//设置应用程序上下文Id
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                          ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
    
        wac.setServletContext(sc);
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }
    	//获取环境中配置的属性
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }
    
        customizeContext(sc, wac);
        //容器的创建与初始化
        wac.refresh();
    }
    

    综上,对Web中创建IOC容器的流程总结的序列图如下:

    3、refresh方法

    从上面的分析看到,无论是Java或Web,最后的容器创建于初始化都会进入refresh方法中,下面对refresh进行分析,refresh方法在AbstractApplicationContext类中实现,其代码如下:

    // 完成IoC容器的创建及初始化工作
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) 
            // 1: 刷新预处理
            prepareRefresh();
            // 2:
            // a) 创建IoC容器(DefaultListableBeanFactory)
            // b) 加载解析XML文件(最终存储到Document对象中)
            // c) 读取Document对象,并完成BeanDefinition的加载和注册工作
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
            // 3: 对IoC容器进行一些预处理(设置一些公共属性)
            prepareBeanFactory(beanFactory);
    
            try {
                // 4:post-processing对BeanDefinition处理
                postProcessBeanFactory(beanFactory);
    
                // 5: 调用BeanFactoryPostProcessor后置处理器对BeanDefinition处理
                invokeBeanFactoryPostProcessors(beanFactory);
    
                // 6: 注册BeanPostProcessor后置处理器
                registerBeanPostProcessors(beanFactory);
    
                // 7: 初始化一些消息源(比如处理国际化的i18n等消息源)
                initMessageSource();
    
                // 8: 初始化应用事件广播器
                initApplicationEventMulticaster();
    
                // 9: 初始化一些特殊的bean
                onRefresh();
    
                // 10: 注册一些监听器
                registerListeners();
    
                // 11: 实例化剩余的单例bean(非懒加载方式)
                // 1)、bean的实例化(创建)
                // 2)、bean的属性填充
                // 3)、bean的初始化(实现InitializingBean接口的类,在bean标签中的init-method属性)
                // 注意事项:Bean的IoC、DI和AOP都是发生在此步骤
                finishBeanFactoryInitialization(beanFactory);
    
                // STEP 12: 完成刷新时,需要发布对应的事件
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - "
                                + "cancelling refresh attempt: " + ex);
                }
                destroyBeans();
                cancelRefresh(ex);
                throw ex;
            }
            finally {
                resetCommonCaches();
            }
        }
    }
    

    refresh方法是初始化Spring容器的核心代码,共分为12个步骤,具体功能如代码所示,将在后续文章中将对refresh中的步骤进行说明。

  • 相关阅读:
    04_web基础(六)之请求转发与重定向
    04_web基础(五)之cookie与session
    04_web基础(四)之servlet详解
    04_web基础(三)之进一步理解web
    04_web基础(二)之web构建
    本地密码提取工具-LAZAGNE
    免杀工具汇总
    流量取证-提取文件
    CA证书安装以及sslh协议转发
    ssh 端口转发
  • 原文地址:https://www.cnblogs.com/liuyi6/p/10317111.html
Copyright © 2020-2023  润新知