转载:
Spring上下文和容器
https://blog.csdn.net/qq_40298351/article/details/100995402
Core Container模块是Spring整个架构的根基,其核心概念是BeanFactory,也正是这个概念让Spring成为一个容器,帮助Spring管理Bean,并提供DI(依赖注入)功能来实现对Bean的依赖管理,使用配置方式来达到与业务代码及框架代码的分离。
Context模块即Spring上下文模块(也叫Spring Context模块),是Core Container模块的子模块,它让Spring真正成为一个可执行框架。这个模块扩展实现了BeanFactory,为Spring的扩展和架构继承提供了非常多的可能,比如校验框架、调度框架、缓存框架、模版渲染框架 等等。
一、Spring上下文的设计
Spring Context模式是Spring Core Container模块中的子模块。下面说说核心抽象类的职责。
(1)ApplicationContext是整个容器的基本功能定义类,继承了BeanFactory,这说明容器也是工厂的多态实现。其实它利用了代理的设计方法,内部持有一个BeanFactory实例,这个实例替它执行BeanFactory接口定义的功能。
(2)AbstractApplicationContext是整个容器的核心处理类,是真正的Spring容器的执行者,在内部使用了模版方法,实现了高复用
高扩展,实现了Spring的启动、停止、刷新、事件推送、BeanFactory方法的默认实现及虚拟机回调的注册等。
(3)GenericApplicationContext是Spring Context模块中最容易构建Spring环境的实体类,涵盖了Spring Context的核心功能,在不需要特殊定制的场景下可以实现开箱即用。AnnotationConfigApplicationContext完美利用了GenericApplicationContext的封装性和对外简单性,如果想扩展适合自己业务的轻量级Spring容器,使用GenericApplicationContext这个基类则会非常容易上手。AnnotationConfigApplicationContext的构造方法先传入一个class数组,在创建一个可执行的上下文实例来构造一个可运行的Spring运行环境,使用起来非常简便。
private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) { super(beanFactory); this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { // 实例化注解Bean定义读取实例,并按照class路径扫描Bean实例 this(); // 注册当前这个class数组,解析并添加这个Bean的描述到BeanFactory中 register(annotatedClasses); // 启动Spring容器 refresh(); }
4)AbstractRefreshableApplicationContext是XmlWebApplicationContext的核心父类,如果当前上下文持有BeanFactory,则关闭当前BeanFactory,然后为上下文生命周期的下一个阶段初始化一个新的BeanFactory,并且在创建新容器时仍然保持对其父容器的引用。
二、Spring容器BeanFactory的设计
Spring的核心功能就是实现对Bean的管理,比如Bean的注册、注入、依赖等。而Spring容器提供了依赖注入这个特征,以实现Spring容器对Bean的管理,而且使用IoC实现了对Bean的配置与实际应用代码的隔离。其中,Core Container模块的核心概念就是BeanFactory,它是所有Spring应用的核心。因为Spring的核心模型就是Bean模型,所以需要在管理Spring Bean的基础上保证Spring应用的运行。
BeanFactory接口是Bean容器设计中基本的职责定义接口,定义了按照名称、参数、类型等几个维度获取、判断Bean实例的职能。
三、Spring父子上下文与容器
从ApplicationContext中可以看出,Spring提供了为当前BeanFactory和ApplicationContext设置父子引用的功能方法,BeanFactory像一个单向链表节点一样支持Spring的多容器场景。
/** * Return the parent context, or {@code null} if there is no parent * and this is the root of the context hierarchy. * @return the parent context, or {@code null} if there is no parent */ @Nullable ApplicationContext getParent();
ApplicationContext接口对外提供获取父上下文的方法,既然能对外获取父上下文,那么肯定有上下文属性的设置方法或者初始化方法。最常用的是用构造方法和set方法手工指定。
/** * Create a new AbstractApplicationContext with the given parent context. * @param parent the parent context */ public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); }
在SpringMVC环境中存在Spring父子容器时,子容器可以复用父容器的Bean实例从而避免重复创建。
在使用SpringMVC时,如下配置会出现在web.xml中。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>Hello</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-config.xml</param-value> </init-param> <!-- 服务器已启动就加载 --> <load-on-startup>1</load-on-startup> </servlet>
由于在web.xml中<listener-class>标签的加载早于<servlet>标签的加载,所以ContextLoaderListener在启动后会先创建一个Spring容器,之后在Dispatcher启动时还会实例化一个容器。
HttpServletBean是HttpServlet的子类,它重写了init方法,调用如下方法进行初始化。
@Override protected final void initServletBean() throws ServletException { // ..省略.. try { // 创建Spring Web容器 this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } // ..省略.. }
创建Spring Web容器:
protected WebApplicationContext initWebApplicationContext() { // 从Servlet上下文中属性中获取Listener中的容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; /* 如果父容器是一个web容器并且没有启动, 则此时运行当前容器并且设置它的父容器, 也就是说在XML中配置了两个Spring Servlet,仍然可以互相引用bean */ if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = findWebApplicationContext(); } if (wac == null) { // 创建当前Servlet创建的web容器,并且把父容器创建在其中 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { synchronized (this.onRefreshMonitor) { // 实例化当前容器 onRefresh(wac); } } if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
AbstractRefreshableApplicationContext是Spring Web容器的核心基类,在SpringAbstractAplicationContext启动时调用refreshBeanFactory方法。
@Override protected final void refreshBeanFactory() throws BeansException { //如果当前已经存在工厂,则销毁工厂中的Bean,关闭当前BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //创建Bean工厂 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //设置当前工厂Bean是否允许Bean定义重写覆盖 //设置当前BeanFactory是否允许Bean循环引用 customizeBeanFactory(beanFactory); //按照指定的配置把Bean定义加载到Bean工厂中 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } } protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); } @Nullable protected BeanFactory getInternalParentBeanFactory() { //把父容器中的工厂作为父工厂放在当前容器工厂中 return (getParent() instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent()); }
由于SpringMVC中的容器之间存在关联(也就是父子容器),所以容器之间可以互相访问,子容器也可以共用父容器的Bean。但父容器不能共用子容器的Bean,这是因为当父容器已经启动时,子容器还没有实例化启动,这时如果父容器引用子容器的Bean,则是不可能正常运行的。