Bean的生命周期#
通过第一阶段的学习,我们顺利把bean的创建权反转给了Spring,下面就来看看,IOC是如何控制Bean的生命周期的!
什么是bean的生命周期?
其实,就是下面的过程
Bean创建------> 初始化------> 销毁
在IOC管理bean生命周期的过程中,我们可以插手做什么?
- 我们可以自定义bean的初始化和销毁方法,bean在到达相应的生命周期时,ioc会调用我们指定的方法,施加在bean上
原来的配置文件版本,需要我们写 init-method 和distroy-method
如何实现?
方法1: 使用@Bean
注解完成#
@Bean(initMethod="init",destroyMethod = "destory")
测试类
@Test public void text6(){ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeConfig.class); Object car = applicationContext.getBean("car"); System.out.println(car); applicationContext.close(); }
单实例情况下: 对象创建后并赋值好了后,执行init方法,容器关闭对象销毁
多实例情况下: 对象创建后并赋值好了后,执行init方法,IOC不会管理bean,需要我们自己去销毁bean
用处?
在配置数据源是大量使用,对象创建后需要初始话很多的数据,对象销毁了,很多资源要释放
方法2: 让Bean实现spring提供的两个接口InitializingBean
和DisposableBean
,重写这两个接口的方法#
public interface InitializingBean { // Bean初始化后调用 void afterPropertiesSet() throws Exception; } public interface DisposableBean { // Bean销毁后调用 void destroy() throws Exception; }
- 单例模式下: 在IOC一初始化就进行bean构造,并且执行完了的
afterPropertiesSet()
初始化,容器关闭,bean执行销毁DisposableBean()
方法3: 使用JS205规范提供的两个注解@PostConstruct和@PreDestory
完成#
- @PostConstruct :在bean创建完成并且属性赋值好了后执行本初始化方法
- @PreDestory: 在容器销毁bean之前,通知我们进行清理工作
这两个注解都是标注在方法上的!!!
@Documented @Retention (RUNTIME) @Target(METHOD) public @interface PostConstruct { } @Documented @Retention (RUNTIME) @Target(METHOD) public @interface PreDestroy { }
方法4: 使用Spring提供给我们的组件,接口BeanPostProcessor
#
这个组件很重要,Spring的底层,尤其是AOP底层大量使用它
里面有两个方法
public interface BeanPostProcessor { // 该方法,会在我们说的前三种初始化方法调用之前, 提前调用!!! // 返回值,可以是我们新创建好的这个bean,也可以包装一下bean 再返回 /* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean * initialization callbacks (like InitializingBean's {@code afterPropertiesSet} * or a custom init-method). The bean will already be populated with property values. * The returned bean instance may be a wrapper around the original. */ Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; // 在上面我们所说的三种初始化方法调用之后,立刻调用!!! Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
实现自己的BeanPostprocessor
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization执行了!!!"+" beanname=="+beanName+" bean=="+bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization执行了!!!"+" beanname=="+beanName+" bean=="+bean); return bean; } }
测试结果如下图
划重点!!! ---
BeanPostprocessor
意味bean的后置处理器,前面也说了,它是spring提供给我们的一个辅助类型的组件,我们可以自定义BeanPostProcessor
,在所有满足加入IOC容器的bean初始化方法前后执行BeanPostProcessor
接口里的postProcessAfterInitialization()
和postProcessBeforeInitialization()
方法,完成对bean的增强,AOP的底层使用的 后置处理器 是spring自动给我们加载进来的,他的这种特性,为AOP,动态植入代码的实现,提供的前提
接口BeanPostProcessor
运行原理#
- 遍历得到容器中所有的
BeanPostProcessor
,挨个执行beforeInitialization
,一旦返回值为null,说明ioc在没有这个对象,直接跳出for循环,不会执行BeanPostProcessor.postProcessBeforeInitialization()
方法进行处理
populateBean(beanName,mdb,instanceWrapper);给bean的属性赋值 initlizeBean // 初始化 { applyBeanPostProcessorBeforeInitialization() // 前置处理 invokeInitMethods(beanName,wrappedBean,mdb); // 执行初始化方法 applyBeanPostProcessorAfterInitialization() ; // 后置处理 }
通过这个继承图,可以看到,
BeanPostProcessor
作为后置处理器的顶级接口存在,程序运行打上断点,也能看到我们自定义的MyBeanPostProcessor
,另外需要我们关心的一个实现是InstantiationAwareBeanPosProcessor
这个子接口,AOP的实现,就应用到了它(第二篇博客会记录)
使用配置文件给bean赋值#
<bean id="person" class="com.changwu.bean" scope="prototype"> <property name="age" value="18"/> <property name="name" value="zhangsan"/> </bean>
使用注解的方法,给bean赋值#
@Value
#
1. 基本数值 @Value("changwu") private String name; 2. #{} SPEL表达式 @Value(value ="#{2*2}") private int age; 3. ${} 取出配置文件中的值 @Value(value ="${student.age}") private int age;
其中取出配置文件中的值,要在主配置类上添加
@PropertySource(value = "classpath:/bean.properties")
指明配置文件的路径
@Value+@PropertySource
#
前者使用
${}
取出环境变量中的属性(程序运行后配置文件会加载进环境变量),后者给前者提供定位
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(PropertySources.class) // 可以写多个 public @interface PropertySource { String name() default ""; //支持同时加载多个配置文件 String[] value(); // 忽略文件找不到 boolean ignoreResourceNotFound() default false; //设置编码 String encoding() default ""; /** * Specify a custom {@link PropertySourceFactory}, if any. * <p>By default, a default factory for standard resource files will be used. * @since 4.3 * @see org.springframework.core.io.support.DefaultPropertySourceFactory * @see org.springframework.core.io.support.ResourcePropertySource */ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class; }
自动装配?#
- 什么是自动装配?
自动装配就是,Spring利用依赖注入DI,完成对IOC容器中的各个组件依赖关系的赋值
@Autowired & @Qualifier & @Primary
#
@Autowired
是Spring自己的注解
那些Bean,都在IOC中,通过@Autowired
注解可以完成自动装配
- 默认按照类型(XXX.class)去IOC中找到对应的组件
- 如果存在多个bean,就按照id找(
@Autowired
标记的引用名) - 使用
@Autowired
,默认如果IOC中不存在该bean,就会报错 - 通过设置
@Autowired(required=false)
设置组件不必需存在于,IOC @Autowired
+@Qualifier
明确指定装配的bean的id
@Qualifier("bookDao2") @Autowired private BookDao bookDao;
再强调一遍,如果是包扫描的话,Bean在IOC的id是类名首字母小写,
@Qualifier("XXX")
不能乱写,要么是类名首字母小写,要么是我们通过@Bean("XXX")
指定的id
@Primary
标记在我们手动添加进去的bean上,强制,首选注入!!!
但是,如果同时存在
@Primary
和@Qualifier
依然会装配我们明确指定的Bean
// 构造器, 方法,参数,属性,全能!!! @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { /** * Declares whether the annotated dependency is required. * <p>Defaults to {@code true}. */ boolean required() default true; }
@Autowired
标注在方法上,方法的参数,默认从容器中获取@Autowired
标注在构造器上,构造器需要的参数,默认从容器中获取@Bean
标注的方法,方法的参数,默认从容器中获取,在参数位置的@Autowired
可以省略不写
@Resources
(JSR205) & @Inject
(JSR330)#
java规范注解
@Resources
- 作用: 默认按照组件名称装配
- 缺点: 不能和
@Qulifier
和@Primary
一起使用
@Inject
- 还麻烦! 需要我们导入依赖
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
@Profile
#
Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能
比如在开发环境下,我们使用A数据库, 测试环境使用B数据库, 生产环境使用C数据库
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional(ProfileCondition.class) public @interface Profile { /** * The set of profiles for which the annotated component should be registered. */ String[] value(); }
支持写到方法上!满足
@profile
指定的环境下,方法的中组件的注入才会生效支持写到类上!满足
@profile
指定的环境下,整个类的中组件的注入才会生效
- 准备工作,注册三个数据源
@Configuration @PropertySource("classpath:/dbProperlies.properties") public class MainConfiguration { @Value("${db.user}") private String user; @Value("${db.driverClass}") private String driverClass; @Profile("text") @Bean("TextDBSource") public DataSource dataSourceText(@Value("${db.password}") String pwd) { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/text"); try { dataSource.setDriverClass(driverClass); } catch (PropertyVetoException e) { e.printStackTrace(); } return dataSource; } @Profile("dev") @Bean("DevDBSource") public DataSource dataSourceDev(@Value("${db.password}") String pwd) { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/person"); try { dataSource.setDriverClass(driverClass); } catch (PropertyVetoException e) { e.printStackTrace(); } return dataSource; } @Profile("pro") @Bean("ProductDBSource") public DataSource dataSourceProduct(@Value("${db.password}") String pwd) { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssh1"); try { dataSource.setDriverClass(driverClass); } catch (PropertyVetoException e) { e.printStackTrace(); } return dataSource; } }
2 . 结合@Profile
注解,在组件上做一个标识,只有满足@Profile
标识条件的才会被注册进IOC
- 加了环境标识的bean,只有那个环境被激活,才会注册到容器中,默认是
@Profile("default")
- 没有环境标识的bean,任何条件下都会加载进容器
如何改变环境?#
- 使用代码的方法
@Test public void text13(){ // 创建上下文 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 设置需要激活的环境 applicationContext.getEnvironment().setActiveProfiles("dev"); // 开发环境下注册组件 // 加载主配置类 applicationContext.register(MainConfiguration.class); // 启动刷新容器 applicationContext.refresh(); String[] names = applicationContext.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } applicationContext.close(); }