【Spring】2.如何给面试官讲SpringBean的声明周期
环境:IDEA+ Maven,不太熟练搭建环境的可参考此文IDEA+Spring+Maven,不太属性Maven的参考彻底搞定Maven。
在了解Spring底层之前我们先学会如何用,上文 说过Spring3之前大部分的配置都是依靠XML的格式来进行配置的,已经很古老且累赘了。因此本博客以后都会用Spring注解的方式来实现同样的功能。
远古xml
- maven中的pom.xml引入依赖。
- 写好一个Person类
- 在resources目录下创建好一个bean.xml。
- 写个测试类
其中远古xml格式跟注解格式的唯一却别就是3, - 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;
}
}
- 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>
- 测试类
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学习网站
@Bean
是一个方法级别上的注解,主要用在@Configuration
注解的类里,也可以用在@Component
注解的类里。添加的 bean 的id为方法名- @Configuration 指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成BeanDefinition和服务请求
- @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
来设置。
- prototype: 多实例:IOC容器启动
并不会
去调用方法创建对象放在容器中,而是 每次获取
的时候才会调用方法创建对象。- singleton: 单实例(默认):IOC容器启动会调用方法创建对象放到IOC容器中
以后每交获取就是直接从容器(理解成从map.get对象)中拿。如果想在用的时候才实例化可以用@Lazy
来修饰。- request: 主要针对WEB应用,同一次请求创建一个实例(用得不多,了解即可).
- 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添加到我们容器中。学习点一般就是三个:
- 指定扫描范围
- 扫描过滤器
- 自定义过滤规则
- 继承TypeFilter接口 实现match自定义方法。
@Conditional
Spring4新添加的一个容器:其实主要是进行条件性注册,只有该注解返回true的时候才会将该Bean添加到容器中。
一般@Conditional
跟@Bean
组合使用,然后Conditional里面指定类,该类继承自Condition接口实现matches方法。具体用法参考 Conditional。
容器导入组件方式
给容器中注册组件的方式
@Bean
: [导入第三方的类或包的组件],比如Person为第三方的类, 需要在我们的IOC容器中使用,但是相对来说比较简单。包扫描
+ 组件的标注注解
(@ComponentScan: @Controller, @Service @Reponsitory @ Componet),一般是针对 我们自己写的类。@Import
:快速给容器导入一个组件,比如三方jar
导入到bean中,
- @Import(要导入到容器中的组件):容器会自动注册这个组件,bean的id为 全类名
- ImportSelector:是一个接口,返回需要导入到容器的组件的全类名数组
- ImportBeanDefinitionRegistrar:可以手动添加组件到IOC容器, 所有Bean的注册可以使用BeanDifinitionRegistry
写JamesImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar接口即可
- 使用Spring提供的FactoryBean(工厂bean)进行注册
@Import
其中 1跟2 的方式比较常见不再重复,先说下3的方式,import 只能作用在类上,@Import
注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷,一般有3种实现:
- 直接填class数组方式:
用法:@Import({ 类名.class , 类名.class… })
- ImportSelector方式【重点】
这种方式的前提就是一个类要实现ImportSelector接口,重写接口中的
selectImports
方法,返回全类名的 bean
。
- 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 不是全类名!
}
}
}
总结:
- 第一种用法:@Import({ 要导入的容器中的组件 } ):容器会自动注册这个组件,id默认是
全类名
- 第二种用法:ImportSelector:返回需要导入的组件的
全类名
数组,springboot底层用的特别多【重点 】- 第三种用法:ImportBeanDefinitionRegistrar:手动注册bean到容器
- 以上三种用法方式皆可混合在一个@Import中使用,特别注意第一种和第二种都是以
全类名
的方式注册,而第三中可自定义方式。- @Import注解本身在
SpringBoot
中用的很多,特别是其中的第二种用法ImportSelector
方式在SpringBoot中使用的特别多,尤其要掌握。
- 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: FactoryBean 跟BeanFactory 区别了解哦
- Object bean1 = app.getBean(“SoWhatFactoryBean”); 直接获得 Monkey
- 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的初始化跟销毁方法下面三种:
- 通过xml或者@Bean配置
通过
xml
或者@Bean(initMethod="init", destroyMethod="destory")
来实现。
- 使用 JSR250 规则定义的(java规范)两个注解来实现
@PostConstruct
: 在Bean创建完成,且属于赋值完成后进行初始化,属于JDK规范的注解@PreDestroy
: 在bean将被移除之前进行通知, 在容器销毁之前进行清理工作- 提示: JSR是由JDK提供的一组规范
- 通过继承实现类方法
- 实现
InitializingBean
接口的afterPropertiesSet()
方法,当beanFactory
创建好对象,且把bean所有属性设置好
之后,会调这个方法,相当于初始化方法。- 实现
DisposableBean
的destory()
方法,当bean销毁时,会把单实例bean进行销毁
PS:
对于单
实例的bean,,可以正常调用初始化和销毁方法
对于多实例的bean,容器只负责调用时候初始化, 但不会管理bean, 容器关闭时不会调用销毁方法
- 接口跟方法默认都是针对单个Bean而言的哦。不过
BeanPostProcessor
是针对所以Bean而言的,- 即使定义了
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();
}
}
系统自带原始的
- 在xml中用指定方法
- 在注解中指定方法:
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 方法顺序如下:
- 执行自定义类构造器
- 执行 指定的
init
方法- 载入容器中
- 容器close前会执行执行指定的
destroy
方法
销毁流程
销毁工作相对简单 app.close()
会关闭容器,关闭前清除Bean,执行流程如下:
- doClose()
- destroyBeans()
- getBeanFactory().destroySingletons();
- 最终的目标map清除工作,this.containedBeanMap.clear();this.dependentBeanMap.clear(); this.dependenciesForBeanMap.clear();
创建
- 单实例创建
BeanPostProcessor原理:
可从容器类跟进顺序为:
- 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
- @Bean 作用在方法上,将方法返回的实例,注册到容器中。
- @Autowired 是直接让容器给我一个这个类型的实例,若需按名字,则配合使用@Qualifier,默认依赖对象必须存在,若允许不存在,需指定required=false
- @Resource 默认ByName(按名字注入),若找不到对应的Bean,会继续按类型去找;但一旦指定了name,那么只会按名字去找Bean
- @Inject是Javax提供的依赖注入,效果与Autowired一样可以装配bean,不支持Primary功能,不支持Autowired false
- Autowired属于spring的, 不能脱离spring, @Resource和@Inject都是JAVA规范
一般使用@Autowired
BeanPostProcessor
spring中带Processor的类还有很多,但是本质上都和这几个差不多,都是用来处理XXX在XXX的时候XXX。大白话讲就是,在A创建/发生/实例化/初始化之前,你想干什么。有点类似AOP的思想,在中间横叉一道。
bean的前/后置增强
处理器,在bean初始化之前调用进行拦截,在bean初始化前后进行一些处理工作
使用BeanPostProcessors
如何控制Bean的生命周期;
实现BeanPostProcessors的两个接口即可
- postProcessBeforeInitialization()
- 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);
}
}