⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期. Bean的⽣命周期分为以下5个部分:
1. 实例化(为Bean分配内存空间)
2. 属性赋值(Bean注⼊和装配,⽐如 @AutoWired )
3. 初始化
a. 执⾏各种通知,如 BeanNameAware , BeanFactoryAware , ApplicationContextAware 的接⼝⽅法.
b. 执⾏初始化⽅法
▪ xml定义 init-method
▪ 使⽤注解的⽅式 @PostConstruct
▪ 执⾏初始化后置⽅法( BeanPostProcessor )
4. 使⽤Bean
5. 销毁Bean a. 销毁容器的各种⽅法,如 @PreDestroy , DisposableBean 接⼝⽅法, destroy-method.
实例化和属性赋值对应构造⽅法和setter⽅法的注⼊.初始化和销毁是⽤⼾能⾃定义扩展的两个阶段, 可以在实例化之后,类加载完成之前进⾏⾃定义"事件"处理. ⽐如我们现在需要买⼀栋房⼦,那么我们的流程是这样的:
1. 先买房(实例化,从⽆到有)
2. 装修(设置属性)
3. 买家电,如洗⾐机,冰箱,电视,空调等([各种]初始化,可以⼊住);
4. ⼊住(使⽤Bean)
5. 卖房(Bean销毁)
执⾏流程如下图所⽰:
1 代码演⽰
@Component
public class BeanLifeComponent implements BeanNameAware {
private UserComponent userComponent;
public BeanLifeComponent() {
System.out.println("执⾏构造函数");
}
@Autowired
public void setUserComponent(UserComponent userComponent) {
System.out.println("设置属性userComponent");
this.userComponent = userComponent;
}
@Override
public void setBeanName(String s) {
System.out.println("执⾏了 setBeanName ⽅法:" + s);
}
/**
* 初始化
*/
@PostConstruct
public void postConstruct() {
System.out.println("执⾏ PostConstruct()");
}
public void use() {
System.out.println("执⾏了use⽅法");
}
/**
* 销毁前执⾏⽅法
*/
@PreDestroy
public void preDestroy() {
System.out.println("执⾏:preDestroy()");
}
}
执⾏结果
通过运⾏结果观察
1. 先执⾏构造函数
2. 设置属性
3. Bean初始化
4. 使⽤Bean
5. 销毁Bean
2 Spring Boot⾃动配置
SpringBoot的⾃动配置就是当Spring容器启动后, ⼀些配置类, bean对象等就⾃动存⼊到了IoC容器中,不需要我们⼿动去声明, 从⽽简化了开发, 省去了繁琐的配置操作.
SpringBoot⾃动配置, 就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到Spring IoC容器中的.
我们学习主要分以下两个⽅⾯:
1. Spring 是如何把对象加载到SpringIoC容器中的
2. SpringBoot 是如何实现的
2.1 Spring 加载Bean
2.1.1 问题描述
需求: 使⽤Spring管理第三⽅的jar包的配置
引⼊第三⽅的包, 其实就是在该项⽬下,引⼊第三⽅的代码, 我们采⽤在该项⽬下创建不同的⽬录来模拟第三⽅的代码引⼊
数据准备:
1. 创建项⽬spring-autoconfig, 当前项⽬⽬录为 com.example.demo
2. 模拟第三⽅代码⽂件在 com.bite.autoconfig ⽬录下
第三⽅⽂件代码:
@Component
public class BiteConfig {
public void study(){
System.out.println("start study...");
}
}
3. 获取 BiteConfig这个Bean
写测试代码
@SpringBootTest
class SpringAutoconfigApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
BiteConfig biteConfig = applicationContext.getBean(BiteConfig.class,"biteConfig");
System.out.println(biteConfig);
}
}
4. 运⾏程序:
观察⽇志: No qualifying bean of type 'com.bite.autoconfig.BiteConfig' available
没有 com.bite.autoconfig.BiteConfig 这个类型的Bean
2.1.2 原因分析
Spring通过五⼤注解和 @Bean 注解可以帮助我们把Bean加载到SpringIoC容器中, 以上有个前提就是这些注解类需要和SpringBoot启动类在同⼀个⽬录下 ( @SpringBootApplication 标注的类 就
是SpringBoot项⽬的启动类).
启动类所在⽬录为: com.example.demo , ⽽ BiteConfig 这个类在
com.bite.autoconfig 下, 所以SpringBoot并没有扫描到.
当我们引⼊第三⽅的Jar包时, 第三⽅的Jar代码⽬录肯定不在启动类的⽬录下, 如何告诉Spring帮我们管理这些Bean呢?
2.1.3 解决⽅案
我们需要指定路径或者引⼊的⽂件, 告诉Spring, 让Spring进⾏扫描到.
常⻅的解决⽅案有两种:
1. @ComponentScan 组件扫描
2. @Import 导⼊(使⽤@Import导⼊的类会被Spring加载到IoC容器中)
我们通过代码来看如何解决
2.1.3.1 @ComponentScan
通过 @ComponentScan 注解, 指定Spring扫描路径
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.bite.autoconfig")
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
可以指定扫描多个包
@ComponentScan({"com.bite.autoconfig","com.example.demo"})
运⾏程序:
可以看到, 这次 biteConfig Bean获取到了
2.1.3.2 @Import
@Import 导⼊主要有以下⼏种形式:
1. 导⼊类
2. 导⼊ ImportSelector 接⼝实现类
1. 导⼊类
@Import(BiteConfig.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
运⾏程序:
可以看到, 这种⽅式也可以告诉Spring加载 biteConfig
问题: 如果⼜多了⼀些配置项呢?
Component
public class BiteConfig2 {
public void study2(){
System.out.println("start study2...");
}
}
我们可以采⽤导⼊多个类
@Import({BiteConfig.class, BiteConfig2.class})
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
很明显, 这种⽅式也很繁琐.
所以, SpringBoot依然没有采⽤.
2. 导⼊ImportSelector接⼝实现类
ImportSelector 接⼝实现类
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//需要导⼊的全限定类名
return new String[]
{"com.bite.autoconfig.BiteConfig","com.bite.autoconfig.BiteConfig2"};
}
}
启动类:
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
运⾏程序:
可以看到, 我们采⽤这种⽅式也可以导⼊第三⽅依赖提供的Bean.
问题:
但是他们都有⼀个明显的问题, 就是使⽤者需要知道第三⽅依赖中有哪些Bean对象或配置类. 如果漏掉其中⼀些Bean, 很可能导致我们的项⽬出现⼤的事故.
这对程序员来说⾮常不友好.
依赖中有哪些Bean, 使⽤时需要配置哪些bean, 第三⽅依赖最清楚, 那能否由第三⽅依赖来做这件事呢?
• ⽐较常⻅的⽅案就是第三⽅依赖给我们提供⼀个注解, 这个注解⼀般都以@EnableXxxx开头的注解,注解中封装的就是 @Import 注解
1. 第三⽅依赖提供注解
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导⼊哪些类
public @interface EnableBiteConfig {
}
注解中封装 @Import 注解, 导⼊ MyImportSelector.class
2. 在启动类上使⽤第三⽅提供的注解
@EnableBiteConfig
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
3. 运⾏程序
可以看到, 这种⽅式也可以导⼊第三⽅依赖提供的Bean.
并且这种⽅式更优雅⼀点. SpringBoot采⽤的也是这种⽅式
3. 总结
1. Bean的作⽤域共分为6种: singleton, prototype, request, session, application和websocket.
2. Bean的⽣命周期共分为5⼤部分: 实例化, 属性复制, 初始化, 使⽤和销毁
3. SpringBoot的⾃动配置原理源码⼝是 @SpringBootApplication 注解, 这个注解封装了3个注
解
◦ @SpringBootConfiguration 标志当前类为配置类
◦ @ComponentScan 进⾏包扫描(默认扫描的是启动类所在的当前包及其⼦包)
◦ @EnableAutoConfiguration
▪ @Import 注解 : 读取当前项⽬下所有依赖jar包中 META-INF/spring.factories ,META-
INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 两个 ⽂件⾥⾯定义的配置类(配置类中定义了 @Bean 注解标识的⽅法)
▪ @AutoConfigurationPackage : 把启动类所在的包下⾯所有的组件都注⼊到 Spring容 器中