【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简单说

posted @ 2020-04-09 22:01  sowhat1412  阅读(322)  评论(0编辑  收藏  举报