zoukankan      html  css  js  c++  java
  • 【Spring】2.如何给面试官讲SpringBean的声明周期

    在这里插入图片描述
    环境:IDEA+ Maven,不太熟练搭建环境的可参考此文IDEA+Spring+Maven,不太属性Maven的参考彻底搞定Maven

    在了解Spring底层之前我们先学会如何用,上文 说过Spring3之前大部分的配置都是依靠XML的格式来进行配置的,已经很古老且累赘了。因此本博客以后都会用Spring注解的方式来实现同样的功能。

    远古xml

    1. maven中的pom.xml引入依赖。
    2. 写好一个Person类
    3. 在resources目录下创建好一个bean.xml。
    4. 写个测试类
      其中远古xml格式跟注解格式的唯一却别就是3,
    5. pom.xml
        <dependencies>
       	<dependency>
       		<groupId>org.springframework</groupId>
       		<artifactId>spring-context</artifactId>
       		<version>5.0.6.RELEASE</version>
       	</dependency>
       	<dependency>
       		<groupId>junit</groupId>
       		<artifactId>junit</artifactId>
       		<version>4.12</version>
       		<scope>test</scope>
       	</dependency>
       </dependencies>
    

    核心思想:无非也就是用人家的东西就先把人家核心的jar包导入到Maven依赖中比如上面spring-context会自动把Core Container到入到我们的依赖中。这样才可以玩IOC跟DI。
    在这里插入图片描述
    2. Person类

    public class Person {
       private String name;
       private Integer age;
       public Person() {
           super();
       }
       public String getName() {
           return name;
       }
       public void setName(String name) {
           this.name = name;
       }
       public Person(String name, Integer age) {
           super();
           this.name = name;
           this.age = age;
       }
       public Integer getAge() {
           return age;
       }
       @Override
       public String toString() {
           return "Person [name=" + name + ", age=" + age + "]";
       }
       public void setAge(Integer age) {
           this.age = age;
       }
    }
    
    1. 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="person" class="com.sowhat.Person">
       	<property name="name" value="SoWhat"></property>
       	<property name="age" value="19"></property>
       </bean>
    </beans>
    
    
    1. 测试类
      MainTest
    public class MainTest1 {
       public static void main(String args[]) {
           // 把beans.xml的类加载到容器
           ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
           // 从容器中获取bean
           Person person = (Person) app.getBean("person");
           System.out.println(person);
       }
    }
    

    在这里插入图片描述

    注解版

    要会用 @Bean,@Configuration,@Scopre,不会的参考 Spring学习网站

    1. @Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的 bean 的id为方法名
    2. @Configuration 指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成BeanDefinition和服务请求
    3. @Scope可简单的理解为设置一个Bean的作用域。

    定义Bean

    // 	配置类 == 配置文件 beans.xml
    @Configuration
    public class MainConfig {
    	// 给容器中注册一个bean, 类型为返回值的类型, 名字是方法名
    	@Bean // 等价于配置beans.xml文件中的<bean>/<bean>
    	public Person person(){
    		return new Person("SoWhat",20);
    	}
    }
    

    测试代码:

    public class MainTest2 { 
    	public static void main(String args[]){
    		ApplicationContext app = new AnnotationConfigApplicationContext(MainConfig.class);
    		//从容器中获取bean
    		Person person = (Person) app.getBean("person");
    		System.out.println(person);
    		String[] namesForBean = app.getBeanNamesForType(Person.class);
    		for(String name:namesForBean){
    			System.out.println(name);
    		}
    	}
    }
    

    默认情况下bean的名称和方法名称相同,你也可以使用name属性来指定,因为如果Spring中国呢我们定义了多个Person类,名字是不可以相同的!

    	@Bean("XXX") // 等价于配置i文件中的<bean>/<bean>
    	public Person person(){
    		return new Person("SoWhat",20);
    	}
    

    在这里插入图片描述
    Spring中Bean 默认都是单例模式在容器中注册的,我们可以通过@Scope来设置。

    1. prototype: 多实例:IOC容器启动并不会去调用方法创建对象放在容器中,而是 每次获取的时候才会调用方法创建对象。
    2. singleton: 单实例(默认):IOC容器启动会调用方法创建对象放到IOC容器中
      以后每交获取就是直接从容器(理解成从map.get对象)中拿。如果想在用的时候才实例化可以用@Lazy来修饰。
    3. request: 主要针对WEB应用,同一次请求创建一个实例(用得不多,了解即可).
    4. session: 同一个session创建一个实例(用得不多,了解即可)
    @Configuration
    public class MainConfig {
    	@Bean
    	@Scope("prototype")
    	public Person person(){
    		return new Person("SoWhat",20);
    	}
    }
    	@Test
    	public void test01(){
    		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(MainConfig.class);
    		String[] names = app.getBeanDefinitionNames();
    		for(String name:names){
    			System.out.println(name);
    		}
    		//从容器中分别取两次person实例, 看是否为同一个bean
    		Object bean1 = app.getBean("person"); // Person p1 = new Person();
    		Object bean2 = app.getBean("person"); // Person p2 = new Person();
    		System.out.println(bean1 == bean2);
    		// 是否是同一个Bean。
    	}
    

    Bean的描述:有时候提供bean的详细信息也是很有用的,bean的描述可以使用@Description来提供

    	@Bean("XXX")
    	@Description("just sowhat test")
    	public Person person(){
    		return new Person("SoWhat",20);
    	}
    

    @ComponentScan

    @ComponentScan 这个存在的意义是设置我们类的扫描范围,将那些Bean添加到我们容器中。学习点一般就是三个:

    1. 指定扫描范围
    2. 扫描过滤器
    3. 自定义过滤规则
    4. 继承TypeFilter接口 实现match自定义方法。

    @Conditional

    Spring4新添加的一个容器:其实主要是进行条件性注册,只有该注解返回true的时候才会将该Bean添加到容器中。
    一般@Conditional@Bean组合使用,然后Conditional里面指定类,该类继承自Condition接口实现matches方法。具体用法参考 Conditional

    容器导入组件方式

    给容器中注册组件的方式

    1. @Bean: [导入第三方的类或包的组件],比如Person为第三方的类, 需要在我们的IOC容器中使用,但是相对来说比较简单。
    2. 包扫描 + 组件的标注注解(@ComponentScan: @Controller, @Service @Reponsitory @ Componet),一般是针对 我们自己写的类。
    3. @Import:快速给容器导入一个组件,比如三方jar导入到bean中,
    1. @Import(要导入到容器中的组件):容器会自动注册这个组件,bean的id为 全类名
    2. ImportSelector:是一个接口,返回需要导入到容器的组件的全类名数组
    3. ImportBeanDefinitionRegistrar:可以手动添加组件到IOC容器, 所有Bean的注册可以使用BeanDifinitionRegistry
      写JamesImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar接口即可
    1. 使用Spring提供的FactoryBean(工厂bean)进行注册
    @Import

    其中 1跟2 的方式比较常见不再重复,先说下3的方式,import 只能作用在类上,@Import注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷,一般有3种实现:
    在这里插入图片描述

    1. 直接填class数组方式:

    用法:@Import({ 类名.class , 类名.class… })

    1. ImportSelector方式【重点】

    这种方式的前提就是一个类要实现ImportSelector接口,重写接口中的selectImports方法,返回全类名的 bean

    1. ImportBeanDefinitionRegistrar方式 更自由的方式注入Bean
    public class SoWhatImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    	//AnnotationMetadata:当前类的注解信息
    	//BeanDefinitionRegistry:BeanDefinition注册类
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    		boolean bean1 = registry.containsBeanDefinition("com.sowhat.cap6.bean.Dog");
    		boolean bean2 = registry.containsBeanDefinition("com.sowhat.cap6.bean.Cat");
    		// 如果Dog和Cat同时存在于我们IOC容器中,那么创建Pig类, 加入到容器
    		// 对于我们要注册的bean, 给bean进行封装,
    		if(bean1 && bean2){
    			RootBeanDefinition beanDefinition = new RootBeanDefinition(Pig.class);
    			registry.registerBeanDefinition("pig", beanDefinition);
    			//  注意此时 name 是 pig 不是全类名!
    		}
    	}
    }
    

    总结:

    1. 第一种用法:@Import({ 要导入的容器中的组件 } ):容器会自动注册这个组件,id默认是全类名
    2. 第二种用法:ImportSelector:返回需要导入的组件的全类名数组,springboot底层用的特别多【重点 】
    3. 第三种用法:ImportBeanDefinitionRegistrar:手动注册bean到容器
    4. 以上三种用法方式皆可混合在一个@Import中使用,特别注意第一种和第二种都是以全类名的方式注册,而第三中可自定义方式。
    5. @Import注解本身在SpringBoot中用的很多,特别是其中的第二种用法ImportSelector方式在SpringBoot中使用的特别多,尤其要掌握。
    1. FactoryBean
      FactoryBean 作用:首先它是一个 Bean,但又不仅仅是一个 Bean。它是一个能生产或修饰对象生成的工厂 Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何 Bean 的实例。
        @Bean
        public SoWhatFactoryBean SoWhatFactoryBean() {
            return new SoWhatFactoryBean();
        }
    -----------
    public class SoWhatFactoryBean implements FactoryBean<Monkey> {
        @Override
        public Monkey getObject() throws Exception {
            return new Monkey();
        }
        @Override
        public Class<?> getObjectType() {
           return Monkey.class;
        }
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    PS: FactoryBeanBeanFactory 区别了解哦

    1. Object bean1 = app.getBean(“SoWhatFactoryBean”); 直接获得 Monkey
    2. Object bean2 = app.getBean(“&SoWhatFactoryBean”);// 直接获得 SoWhatFactoryBean

    在这里插入图片描述

    Bean初始化跟销毁方法

    Spring IoC初始化跟销毁Bean的过程,大致分为Bean定义、Bean初始化、Bean的生存期跟Bean的销毁4个部分。

    其中Bean的定义过程大致如下:

    1.Spirng 通过我们的配置比如@ComponentScan 定义的扫描路径去找到所有带有@Component的类,这是一个资源定位的过程。
    2.找到资源后就要解析,将定义的信息保存起来,注意此时并没初始化Bean,没有Bean的实例,只是有了Bean的定义
    3.然后就会把Bean定义发布到Spring IoC容器中,此时IOC容器只有Bean的定义,还没有Bean的实例生成

    上面的3步只是资源定位跟Bean的定义发布到IOC容器中,接下来就是实例生成跟依赖注入了。一般情况而言发布就默认跟随着实例化跟依赖注入不过,如果我们设置了lazyInit=true则只有在用到的时候才会实例化。 Spring Bean 初始化大致流程如下:
    在这里插入图片描述
    如果仅仅是实例化跟依赖注入当然简单,问题是我们要完成自定义的要求,Spring在完成了依赖注入以后,提供了一系列接口跟配置来完成Bean的初始化过程。看下整个IOC容器初始化Bean的流程。

    一般情况下我们自定义Bean的初始化跟销毁方法下面三种:

    1. 通过xml或者@Bean配置

    通过xml或者@Bean(initMethod="init", destroyMethod="destory")来实现。

    1. 使用 JSR250 规则定义的(java规范)两个注解来实现
    1. @PostConstruct: 在Bean创建完成,且属于赋值完成后进行初始化,属于JDK规范的注解
    2. @PreDestroy: 在bean将被移除之前进行通知, 在容器销毁之前进行清理工作
    3. 提示: JSR是由JDK提供的一组规范
    1. 通过继承实现类方法
    1. 实现InitializingBean接口的afterPropertiesSet()方法,当beanFactory创建好对象,且把bean所有属性设置好之后,会调这个方法,相当于初始化方法。
    2. 实现DisposableBeandestory()方法,当bean销毁时,会把单实例bean进行销毁
      PS:
      对于实例的bean,,可以正常调用初始化和销毁方法
      对于多实例的bean,容器只负责调用时候初始化, 但不会管理bean, 容器关闭时不会调用销毁方法

    在这里插入图片描述

    1. 接口跟方法默认都是针对单个Bean而言的哦。不过BeanPostProcessor是针对所以Bean而言的,
    2. 即使定义了ApplicationContextAware接口,有时候并不会被调用,要根据IOC容器而定。

    end:其中Spring的关闭AnnotationConfigApplicationContext.close()–>doClose–>destroyBeans无非就是在我们关闭这个容器前将存储的各种Bean从Map中清空。
    Bean生命周期 验证代码如下:

    public class Bike implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware, ApplicationContextAware
    {
    	public Bike()
    	{
    		System.out.println("Bike constructor..............");
    	}
    
    	@Override
    	public void setBeanName(String beanName)
    	{
    		System.out.println("调用BeanNameAware的setBeanName");
    	}
    
    	@Override
    	public void setBeanFactory(BeanFactory beanFactory) throws BeansException
    	{
    		System.out.println("调用BeanFactoryAware的setBeanFactory");
    	}
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    	{
    		System.out.println("调用ApplicationContextAware的setApplicationContext");
    	}
    
    	public void init()
    	{
    		System.out.println("Bike 调用@Bean initMethod");
    	}
    
    	public void destory()
    	{
    		System.out.println("Bike 调用@Bean destroyMethod");
    	}
    
    	@PostConstruct
    	public void init1()
    	{
    		System.out.println("Bike 调用注解 @PostConstruct");
    	}
    
    	@PreDestroy
    	public void destory1()
    	{
    		System.out.println("Bike 调用注解 @PreDestroy");
    	}
    
    	@Override
    	public void destroy() throws Exception
    	{
    		System.out.println("Bike 调用 DisposableBean 的 destroy 方法");
    
    	}
    
    	@Override
    	public void afterPropertiesSet() throws Exception
    	{
    		System.out.println("Bike 调用 InitializingBean 的 afterPropertiesSet() 方法");
    	}
    
    	public static void main(String[] args)
    	{
    		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
    		System.out.println("IOC容器创建完成........");
    		app.close();
    	}
    }
    

    系统自带原始的

    1. 在xml中用指定方法
      在这里插入图片描述
    2. 在注解中指定方法:
    public class Bike {
    	public Bike(){
    		System.out.println("Bike constructor..............");
    	}
    	public void init(){
    		System.out.println("Bike .....init.....");
    	}
    	public void destory(){
    		System.out.println("Bike.....destory");
    	}
    }
    -----
    	@Bean(initMethod="init", destroyMethod="destory")
    	public Bike bike(){
    		return new Bike();
    	}
    	public static void main(String[] args) {
    		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap7MainConfigOfLifeCycle.class);
    		System.out.println("IOC容器创建完成........");
    		app.close();
    	}
    

    在这里插入图片描述
    结论 无论是1 还是2 方法顺序如下:

    1. 执行自定义类构造器
    2. 执行 指定的 init 方法
    3. 载入容器中
    4. 容器close前会执行执行指定的destroy方法

    销毁流程

    销毁工作相对简单 app.close()会关闭容器,关闭前清除Bean,执行流程如下:

    1. doClose()
    2. destroyBeans()
    3. getBeanFactory().destroySingletons();
    4. 最终的目标map清除工作,this.containedBeanMap.clear();this.dependentBeanMap.clear(); this.dependenciesForBeanMap.clear();

    创建

    1. 单实例创建
      BeanPostProcessor原理:
      可从容器类跟进顺序为:
    1. AnnotationConfigApplicationContext–>refresh()–>
      finishBeanFactoryInitialization(beanFactory)—>
      beanFactory.preInstantiateSingletons()–>
      760行getBean(beanName)—>
      199行doGetBean(name, null, null, false)–>
      317行createBean(beanName, mbd, args)–>
      501行doCreateBean(beanName, mbdToUse, args)–>
      541行createBeanInstance(beanName, mbd, args)(完成bean创建)–>
      578行populateBean(beanName, mbd, instanceWrapper)(属性赋值)–>
      579行initializeBean(beanName, exposedObject, mbd)(Bean初始化)->
      1069行到1710行,后置处理器完成对init方法的前后处理.

    最终得到如下如下

    createBeanInstance(beanName, mbd, args)(完成bean创建)
    populateBean(beanName, mbd, instanceWrapper); 给bean进行属性赋值
    initializeBean() //初始化Bean方法内容如下,后置处理器对init方法的前后处理
    {
      applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      invokeInitMethods(beanName, wrappedBean, mbd) //执行自定义初始化
      applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
    }
    

    从以上分析不难发现,bean的生命周期为bean的创建, 初始化, 当容器关闭时对单实例的bean进行销毁。
    并且跟踪源码可以看到在调用我们的InitMethod函数前后一定会执行一些增强方法。
    在这里插入图片描述
    3. 对于多实例子 没用@Scope(“prototype”)只会在调用的时候才会创建,未纳入Bean初始化过程中。

    @Bean、@Autowired、@Resource、@Inject

    1. @Bean 作用在方法上,将方法返回的实例,注册到容器中。
    2. @Autowired 是直接让容器给我一个这个类型的实例,若需按名字,则配合使用@Qualifier,默认依赖对象必须存在,若允许不存在,需指定required=false
    3. @Resource 默认ByName(按名字注入),若找不到对应的Bean,会继续按类型去找;但一旦指定了name,那么只会按名字去找Bean
    4. @Inject是Javax提供的依赖注入,效果与Autowired一样可以装配bean,不支持Primary功能,不支持Autowired false
    5. Autowired属于spring的, 不能脱离spring, @Resource和@Inject都是JAVA规范
      一般使用@Autowired

    BeanPostProcessor

    spring中带Processor的类还有很多,但是本质上都和这几个差不多,都是用来处理XXX在XXX的时候XXX。大白话讲就是,在A创建/发生/实例化/初始化之前,你想干什么。有点类似AOP的思想,在中间横叉一道。

    bean的前/后置增强处理器,在bean初始化之前调用进行拦截,在bean初始化前后进行一些处理工作
    使用BeanPostProcessors如何控制Bean的生命周期;
    实现BeanPostProcessors的两个接口即可

    1. postProcessBeforeInitialization()
    2. postProcessAfterInitialization()

    还有一些系统自带的注解实现了BeanPostProcessor,比如@Autowired 底层都是继承的AutowiredAnnotationBeanPostProcessor
    在这里插入图片描述

    重要 Aware

    Spring的依赖注入的最大亮点就是你所有的Bean对Spring容器的存在是没有意识的。即你可以将你的容器替换成别的容器,例如Goggle Guice,这时Bean之间的耦合度很低。

    但是在实际的项目中,我们不可避免的要用到Spring容器本身的功能资源,这时候Bean必须要意识到Spring容器的存在,才能调用Spring所提供的资源,这就是所谓的Aware。

    Aware翻译过来是知道的,已感知的,意识到的,所以这些接口从字面意思应该是能感知到所有Aware前面的含义。其实SpringAware本来就是Spring设计用来框架内部使用的,若我们使用了SpringAware,你的Bean将会和Spring框架耦合。

    结论:比如BeanNameAware接口是为了让自身Bean能够感知到,获取到自身在Spring容器中的id属性。同理,其他的Aware接口也是为了能够感知到自身的一些属性。
    比如实现了ApplicationContextAware接口的类,能够获取到ApplicationContext,实现了BeanFactoryAware接口的类,能够获取到BeanFactory对象。
    在这里插入图片描述
    常用如下:

    Aware子接口 功能
    BeanNameAware 获取容器中Bean的名字
    BeanFactoryAware 获取当前BeanFactory,进而可以调用容器服务
    ApplicationContextAware 跟BeanFactory类似不过是增强版
    MessageSourceAware 获取MessageSource相关文本信息
    ApplicationEventPublisherAware 发布事件
    ResourceLoaderAware 获取资源加载器,获取外部资源文件

    总结:Spring底层的组件可以注入到自定义的bean中,ApplicationContextAware是利用ApplicationContextAwareProcessor来处理的, 其它XXXAware也类似, 都有相关的Processor来处理, 其实就是后置处理器来处理; XXXAware---->功能使用了XXXProcessor来处理的, 这就是后置处理器的作用;ApplicaitonContextAware—>ApplicationContextProcessor后置处理器来处理的

    demo
    @Component
    public class Light implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
        private ApplicationContext applicationContext;
    
        @Override
        public void setBeanName(String name) {
            System.out.println("当前bean的名字:" + name);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println("传入的IOC容器: " + applicationContext);
            this.applicationContext = applicationContext;
        }
    
        @Override
        public void setEmbeddedValueResolver(StringValueResolver resolver) {
            String result = resolver.resolveStringValue("你好${os.name}, 计算#{3*8}");
            System.out.println("解析的字符串为---" + result);
        }
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Light.class);
            System.out.println(app);
        }   
    }
    

    在这里插入图片描述

    参考

    Spring学习网站
    Spring框架介绍跟使用
    江南一点雨Spring
    aware简单说

  • 相关阅读:
    bzoj 1195: [HNOI2006]最短母串 爆搜
    bzoj 4066: 简单题 kd-tree
    NOI冲刺计划2
    bzoj 3572: [Hnoi2014]世界树 虚树 && AC500
    bzoj 3153: Sone1 Toptree
    CTSC && APIO 总结
    bzoj 4031: [HEOI2015]小Z的房间 轮廓线dp
    bzoj 1902: Zju2116 Christopher lucas定理 && 数位DP
    BZOJ 1754: [Usaco2005 qua]Bull Math
    BZOJ 1648: [Usaco2006 Dec]Cow Picnic 奶牛野餐
  • 原文地址:https://www.cnblogs.com/sowhat1412/p/12734068.html
Copyright © 2011-2022 走看看