• 其实很简单——SpringIOC详解


    Spring核心:IoC

    IoC容器

    Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Bean。[1]

    Spring主要容器包括 BeanFactoryApplicationContext 两种,其中ApplicationContext容器是使用最多的容器,它间接继承了BeanFactory接口,具有BeanFactory容器的全部功能,其继承关系如下:

    图1:ApplicationContext容器类即成关系

    Spring容器的作用是用来存放Bean(需要交给Spring管理的对象)并控制Bean从产生到销毁的整个生命周期,当应用程序需要用到某一个Bean时,通过容器的 getBean()[2] 方法可以很方便的获取到这个对象。根据Spring的不同配置方式存在不同的 SpringContext 容器实现,最常用的两种配置方式,基于XML文件和注解的配置方式都具有自己的具体的容器实现,ClassPathXmlApplicationContext 类对应基于XML文件的配置方式, AnnotationConfigApplicationContext 类对应基于注解的配置方式。以上两种方式可通过如下代码获取Bean对象:

    /**
     * 基于XML的配置方式获取Bean对象
     */
    public static void main(String[] args) {
    	ApplicationContext context = new ClassPathXmlApplicationContext("/config/beans.xml");
      context.getBean("TestBean");
    }
    
    /**
     * 基于注解的配置方式获取Bean对象
     */
    public static void main(String[] args){
      ApplicationContext context = new AnnotationConfigApplicationContext(ConfigBeans.class); 
      context.getBean("TestBean");
    }
    

    关于对 IoC控制反转的理解:

    控制不反转的情况下,编码过程中如果需要使用一个对象,如 Person 类的对象,则通过 new Person() 调用构造函数的方式直接创建对象,这种方式的缺点是每次都需要创建新的对象,使用n次就会创建n个对象,这种方式在特定场景下[3]会产生不必要的性能损失。

    引入了Ioc机制后,所有的对象都会交给IoC容器管理,根据不同的配置,容器来控制对象什么时候创建新对象,什么时候销毁。当需要使用一个 Person 对象时,通过依赖注入的方式添加对应的 Person 对象依赖,而不一定创建新对象。

    通过IoC机制,对象的控制者从开发者变成了IoC容器,即实现了控制反转。

    Bean

    Bean对象由IoC容器根据配置信息创建,是构成应用程序的基础。配置信息称为元数据,Spring框架规定了一系列可设置的元数据属性,Spring官方文档说明了所有可配置的属性[4]

    Property Explained in… DESC
    Class Instantiating Beans 必选项,用来设定Bean的类型
    Name Naming Beans Bean的唯一标识,xml文件中可以使用id/name
    Scope Bean Scopes 设置Bean的作用域
    Constructor arguments Dependency Injection 设定构造函数注入的参数
    Properties Dependency Injection 设定Bean对象的属性
    Autowiring mode Autowiring Collaborators 设定注入模式
    Lazy initialization mode Lazy-initialized Beans 是否懒加载
    Initialization method Initialization Callbacks 设定对象初始化方法
    Destruction method Destruction Callbacks 设定对象销毁方法
    表1:Bean对象可配置的属性

    Bean和IoC容器的关系

    程序启动时IoC容器会通过配置文件读取元数据,保存在 BeanFactory 对象的 beanDefinitionMap 属性中, beanDefinitionMap 是一个 ConcurrentHashMap 类对象,容器根据其中保存的元数据信息,创建相应的Bean对象。创建出来的Bean对象根据配置不同保存到不同的位置,默认状态的Bean都是单例对象,保存在 singletonObjects 属性中。下图是对这个过程的简单描述:[5]

    图2:IoC容器创建Bean的过程

    XML配置文件中定义Bean对象的方式如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
    >
    		<!-- 创建 Bean 对象,所有属性使用默认值 -->
        <bean name="Bean" class="cn.sunyog.entity.XMLBean">
          	<!-- 设置对象的属性值 -->
            <property name="message" value="默认设置的 Bean"/>
        </bean>
    </beans>
    

    Bean的类定义如下:

    public class XMLBean {
        private String message;
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    

    执行测试代码:

    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("/config/beans.xml");
        XMLBean bean = (XMLBean) context.getBean("Bean");//断点
        System.out.println(bean);
        System.out.println(bean.getMessage());
    }
    

    打印结果:

    cn.sunyog.entity.XMLBean@45752059
    默认设置的 Bean
    

    使用以上代码,通过debug的方式进入断点,查看Bean的定义信息和对象保存位置如下:

    图3:元数据信息保存位置

    图4:Bean对象保存位置

    Bean的作用域

    Spring官方文档中规定了Bean的作用域所有可选值,包括:singleton、prototype、request、session、application、websocket六种。具体描述如下[6]

    Scope Description
    singleton 默认作用域,单例模式,整个容器中只有一个Bean对象
    prototype 每次使用时创建新对象,Bean使用完成后销毁
    request 每个HTTP请求包含一个自己专属的Bean对象。只能在web程序中使用。只支持ApplicationContext容器
    session 每个 HTTP Session包含一个自己专属的Bean对象。只能在web程序中使用。只支持ApplicationContext容器
    application 针对ServletContext创建的Bean对象。只能在web程序中使用。只支持ApplicationContext容器
    websocket 针对WebSocket创建的Bean对象。只能在web程序中使用。只支持ApplicationContext容器
    图2:Bean的作用域说明表
    1.XML文件方式配置Bean

    类定义代码:

    public class XMLBean{
        private String message;
    		//无参构造
      	public XMLBean() {
            System.out.println("构造:"+this);
        }
      
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    		//配置文件中说明的初始化方法
        public void initFunc(){
            System.out.println("Bean 初始化方法执行");
        }
    		//配置文件中说明的销毁方法
        public void destroyFund(){
            System.out.println("Bean 销毁方法执行");
        }
    }
    

    XML文件配置信息:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
    >
    		<bean id="Singleton-Bean" class="cn.sunyog.bean.XMLBean"
              lazy-init="true" scope="singleton" 
              init-method="initFunc" destroy-method="destroyFund">
            <property name="message" value="XML Bean,懒加载,singleton模式,已设定初始化方法,已设定销毁方法" />
        </bean>
    </beans>
    

    通过 junit 测试框架测试各各个Bean的创建顺序

    public class XMLBeanFactoryTest {
        private ClassPathXmlApplicationContext context = null;
      
        @Test
        public void singletonBeanTest() {
          	//获取两次 单例懒加载bean
            XMLBean bean = (XMLBean) this.context.getBean("Singleton-Bean");
            XMLBean bean2 = (XMLBean) this.context.getBean("Singleton-Bean");
          	//获取一次默认Bean
            XMLBean bean3 = (XMLBean) this.context.getBean("Bean");
    				//打印Bean对象内存地址和message属性
            printBeanInfo(bean, bean2, bean3);
        }
    
        private void printBeanInfo(XMLBean... bean) {
            for (XMLBean item : bean) {
                System.out.println(item);
            }
            for (XMLBean item : bean) {
                System.out.println(item.getMessage());
            }
        }
    
        @Before
        public void doBefore() {
            this.context = new ClassPathXmlApplicationContext("/config/beans.xml");
        }
    
        @After
        public void doAfter() {
            //销毁所有bean
            this.context.registerShutdownHook();
        }
    }
    

    结果打印及说明:

    构造:cn.sunyog.bean.XMLBean@2758fe70
    构造:cn.sunyog.bean.XMLBean@2db7a79b
    Bean 初始化方法执行
    cn.sunyog.bean.XMLBean@2db7a79b
    cn.sunyog.bean.XMLBean@2db7a79b
    cn.sunyog.bean.XMLBean@2758fe70
    XML Bean,懒加载,singleton模式,已设定初始化方法,已设定销毁方法
    XML Bean,懒加载,singleton模式,已设定初始化方法,已设定销毁方法
    默认设置的 Bean
    
    
    Bean 销毁方法执行
    

    测试代码中调用三次 getBean() 方法,但打印结果结果显示只创建了两个对象。由于名称为 Singleton-Bean 的Bean配置了单例作用域,所以两次获取的Bean其实是同一个对象,因此分别只调用了一次 initFunc() 方法和 destroyFunc() 方法。这里需要注意prototype模式下,Bean对象使用完成后会自动回收,而不是通过IoC容器回收,所以设定的销毁方法会失效。

    另外,测试代码中先获取的 Singleton-Bean 对象,后获取的 Bean 对象,但创建顺序是先创建 Bean 对象,后创建 Singleton-Bean 对象,这是由于 Singleton-Bean 设置了lazy-init属性为true,只有调用 get Bean() 方法时才创建这个Bean。

    2.注解方式配置Bean
    @Configuration
    public class AnnoBeanConfig{
        @Bean(name = "Anno-Bean",initMethod = "initFunc",destroyMethod = "destroyFunc")
        @Scope("singleton") //作用域
        @Lazy   //懒加载
        public AnnotationBean getAnnotationBean(){
            AnnotationBean bean = new AnnotationBean();
            bean.setMessage("基于注解的配置");
            return bean;
        }
    }
    

    测试代码:

    public class AnnoBeanFactoryTest {
        AnnotationConfigApplicationContext context=null;
    
        @Before
        public void doBefore(){
            this.context=new AnnotationConfigApplicationContext(AnnoBeanConfig.class);
        }
    
        @After
        public void doAfter(){
            this.context.registerShutdownHook();
        }
    
        @Test
        public void test(){
            AnnotationBean bean = (AnnotationBean) context.getBean("Anno-Bean");
            this.printBeanInfo(bean);
        }
    
        private void printBeanInfo(AnnotationBean... bean) {
            for (AnnotationBean item : bean) {
                System.out.println(item);
            }
            for (AnnotationBean item : bean) {
                System.out.println(item.getMessage());
            }
        }
    }
    

    打印结果:

    构造:cn.sunyog.bean.AnnotationBean@21be3395
    注解配置的初始化方法
    cn.sunyog.bean.AnnotationBean@21be3395
    基于注解的配置
    
    
    注解配置的销毁方法
    

    Bean的生命周期

    另一种设定Bean的初始化和销毁回调方法的方式。在定义Bean时,实现 InitializingBean 接口的 afterPropertiesSet() 方法来设定初始化回调;实现 DisposableBean接口的 destroy() 方法来设定销毁回调方法。

    配置代码:

    @Bean(name="Simple-Bean",initMethod = "initFunc",destroyMethod = "destroyFunc")
    public SimpleBean getSimpleBean(){
        SimpleBean bean = new SimpleBean();
        bean.setMessage("实现初始化回调和销毁回调方法");
        return bean;
    }
    

    Bean代码:

    public class SimpleBean extends AnnotationBean implements InitializingBean, DisposableBean {
        public void destroy() throws Exception {
            System.out.println("实现 DisposableBean 接口的 销毁回调 方法");
        }
    
        public void afterPropertiesSet() throws Exception {
            System.out.println("实现 InitializingBean 接口的 初始化回调 方法");
        }
    }
    

    测试代码:

    @Test
    public void testSimpleBean(){
        SimpleBean bean = (SimpleBean) context.getBean("Simple-Bean");
        this.printBeanInfo(bean);
    }
    

    打印结果:

    构造:cn.sunyog.bean.SimpleBean@16e7dcfd
    实现 InitializingBean 接口的 初始化回调 方法
    注解配置的初始化方法
    cn.sunyog.bean.SimpleBean@16e7dcfd
    实现初始化回调和销毁回调方法
    
    
    实现 DisposableBean 接口的 销毁回调 方法
    注解配置的销毁方法
    

    也可以通过配置后置处理器的方式设定初始化前、后处理方法,这种方式需要实现 BeanPostProcessor 接口的 postProcessBeforeInitialization()postProcessAfterInitialization() 方法,后置处理器类代码如下:

    public class PostProcessorBean implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
          	//只处理SimpleBean类型
            if (bean instanceof SimpleBean){
                System.out.println(beanName+": 前置处理方法 执行");
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if(bean instanceof SimpleBean){
                System.out.println(beanName+": 后置处理方法 执行");
            }
            return bean;
        }
    }
    

    配置类代码:

    @Bean
    public BeanPostProcessor getPostProcessorBean(){
        return new PostProcessorBean();
    }
    

    执行 SimpleBean 类的测试代码,得到打印结果(和SimpleBean的测试打印结果略有不同):

    构造:cn.sunyog.bean.SimpleBean@79efed2d
    Simple-Bean: 前置处理方法 执行
    实现 InitializingBean 接口的 初始化回调 方法
    注解配置的初始化方法
    Simple-Bean: 后置处理方法 执行
    cn.sunyog.bean.SimpleBean@79efed2d
    实现初始化回调和销毁回调方法
    
    
    实现 DisposableBean 接口的 销毁回调 方法
    注解配置的销毁方法
    

    Bean的整个生命周期的方法调用顺序如下图,图中直角方框表示应用程序调用,圆角方框表示容器调用:

    graph TD A[容器初始化/调用getBean方法] --> B(执行Bean的构造函数) B -->C{是否配置后置处理器} C --已配置--> D(执行后置处理器的postProcessBeforeInitialization方法) D --> E(执行InitializingBean接口的afterPropertiesSet方法) C --未配置--> E E --> F(执行配置的init-method) F --> Z{是否配置后置处理器} Z --已配置--> G(执行后置处理器的postProcessAfterInitialization方法) G --> H[应用程序使用Bean] Z --未配置--> H H --> I[调用容器关闭方法 如registerShutdownHook] I --> J(执行DisposableBean接口的destroy方法) J --> K(执行配置的destroy-method方法) K --> L[容器销毁]
    图5:Bean的生命周期方法调用顺序

    依赖注入DI

    依赖注入是Spring框架的核心功能,通过DI来管理Bean之间的依赖关系。有构造函数注入和set方法注入两种方式实现,构造函数注入使用含参构造函数创建Bean对象,set方法注入调用无参构造函数创建Bean对象或使用无参的工厂方法构造Bean对象,并使用setter方法赋值。

    构造方法注入

    Bean类代码:

    public class AutowireBean {
        private XMLBean child;
    
        public AutowireBean(XMLBean child) {
            this.child = child;
            System.out.println("AutowireBean类 含参构造函数执行");
        }
    
        public AutowireBean() {
            System.out.println("AutowireBean类 无参构造函数执行");
        }
    
        public XMLBean getChild() {
            return child;
        }
    
        public void setChild(XMLBean child) {
            this.child = child;
            System.out.println("AutowireBean类 setter函数执行");
        }
    }
    

    XML配置文件代码:

    <bean name="Bean" class="cn.sunyog.bean.XMLBean">
        <property name="message" value="默认设置的 Bean"/>
    </bean>
    
    <bean id="cons-autowire-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true">
        <constructor-arg name="child" ref="Bean" />
    </bean>
    

    测试代码:

    @Test
    public void consAutowireBeanTest(){
        AutowireBean bean = (AutowireBean) this.context.getBean("cons-autowire-bean");
        PrintTool.printBeanInfo(bean);
    }
    
    /**
     * 打印工具类
     */
    public class PrintTool {
        public static void printBeanInfo(XMLBean... bean) {
            for (XMLBean item : bean) {
                System.out.println(item);
            }
            for (XMLBean item : bean) {
                System.out.println(item.getMessage());
            }
        }
    
        public static void printBeanInfo(AnnotationBean... bean) {
            for (AnnotationBean item : bean) {
                System.out.println(item);
            }
            for (AnnotationBean item : bean) {
                System.out.println(item.getMessage());
            }
        }
    
        public static void printBeanInfo(AutowireBean... bean){
            for (AutowireBean item : bean) {
                XMLBean child = item.getChild();
                printBeanInfo(child);
            }
        }
    }
    

    结果打印:

    构造:cn.sunyog.bean.XMLBean@79be0360
    AutowireBean类 含参构造函数执行
    cn.sunyog.bean.XMLBean@79be0360
    默认设置的 Bean
    

    set注入

    XML配置代码:

    <bean id="set-autowire-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true">
        <property name="child" ref="Bean" />
    </bean>
    

    测试代码:

    @Test
    public void setAutowireBeanTest(){
        AutowireBean bean = (AutowireBean) this.context.getBean("set-autowire-bean");
        PrintTool.printBeanInfo(bean);
    }
    

    结果打印:

    构造:cn.sunyog.bean.XMLBean@79be0360
    AutowireBean类 无参构造函数执行
    AutowireBean类 setter函数执行
    cn.sunyog.bean.XMLBean@79be0360
    默认设置的 Bean
    

    Spring的自动装配

    Spring的自动装配(autowire)在配置文件中设置了bean的autowire属性后生效,auto-wire属性有三种模式,分别是byName(按名称匹配),byType(按类型匹配),constructor(构造函数自动装配)。其中,前两种分别按名称和类型从容器中查找符合条件的Bean,找到后通过set方法入,查找失败会抛出异常;constructor模式则通过构造方法注入。

    注意,在byType模式下要保证这种类型的Bean只进行了一次配置。

    三种模式xml配置如下:

    <bean name="child" class="cn.sunyog.bean.XMLBean">
        <property name="message" value="默认设置的 Bean"/>
    </bean>
    <bean id="autowire-name-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="byName" />
    <bean id="autowire-type-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="byType" />
    <bean id="autowire-cons-bean" class="cn.sunyog.bean.AutowireBean" lazy-init="true" autowire="constructor" />
    

    测试代码:

    @Test
    public void autowireTest(){
        AutowireBean bean1 = (AutowireBean) this.context.getBean("autowire-name-bean");
        AutowireBean bean2 = (AutowireBean) this.context.getBean("autowire-type-bean");
        AutowireBean bean3 = (AutowireBean) this.context.getBean("autowire-cons-bean");
    }
    

    结果打印:

    构造:cn.sunyog.bean.XMLBean@1f36e637
    AutowireBean类 无参构造函数执行
    AutowireBean类 setter函数执行
    AutowireBean类 无参构造函数执行
    AutowireBean类 setter函数执行
    AutowireBean类 含参构造函数执行
    

    1. 摘自:https://www.w3cschool.cn/wkspring/f8pc1hae.html ↩︎

    2. getBean()方法是BeanFactory接口的基本方法,包括几个重载的方法,可以通过名称、类型、名称+类型等方式获取到对应的Bean ↩︎

    3. 如:对象占用内存特别大,或可以反复使用的对象等场景。这种场景可通过设计模式中的单例模式解决,Spring框架中也大量使用单例模式 ↩︎

    4. Spring Core官方文档:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-class ↩︎

    5. 图片来源:https://www.w3cschool.cn/wkspring/8kei1icc.html ↩︎

    6. 表格来源:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes ↩︎

  • 相关阅读:
    java——io、字节流缓冲区拷贝文件、字节缓冲流
    java——斗地主小游戏之洗牌发牌
    java——HashMap、Hashtable
    java——模拟新浪微博用户注册
    [bzoj 1492][NOI2007]货币兑换Cash
    [bzoj 1010][HNOI 2008]玩具装箱
    [bzoj 2875][noi2012]随机数生成器
    [bzoj 4872][六省联考2017]分手是祝愿
    [bzoj 3566][SHOI 2014]概率充电器
    [bzoj 3534][Sdoi2014] 重建
  • 原文地址:https://www.cnblogs.com/logic-hatten/p/13864670.html
Copyright © 2020-2023  润新知