Spring Bean
# Spring Bean
# Spring Bean 基础
# 装配Bean的方法
# 通过xml配置装配Bean
# 通过注解装配Bean
更多的时候已经不再推荐使用XML 的方式去装配Bean ,更多的时候会考虑使用注解C annotation )的方式去装配Bean。使用注解的方式可以减少XML 的配置, 注解功能更为强大, 它既能实现XML 的功能, 也提供了自动装配的功能,采用了自动装配后, 程序员所需要做的决断就少了,更加有利于对程序的开发,这就是“约定优于配置” 的开发原则。
在Spring 中,它提供了两种方式来让Spring IoC 容器发现Bean。
组件扫描: 通过定义资源的方式, 让Spring IoC 容器扫描对应的包,从而把Bean装配进来。
自动装配: 通过注解定义,使得一些依赖关系可以通过注解完成。
使用@Component 装配Bean:
@Component(value="role")
public class Role{
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
}
问题:
其一, 对于@ComponentScan 注解, 它只是扫描所在包的Java 类, 但是更多的时候真正需要的是可以扫描所指定的类;
其二, 上面只注入了一些简单的值,而没有注入对象,同样在现实的开发中可以注入对象是十分重要的,也是常见的场景。
自动装配-@Autowired
Spring 是先完成Bean 的定义和生成,然后寻找需要注入的资源。也就是当Spring 生成所有的Bean 后,如果发现这个注解,它就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。
所谓自动装配技术是一种由Spring 自己发现对应的Bean, 自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired 上, 这个时候Spring 会根据类型去寻找定义的Bean 然后将其注入。
使用@Bean 装配Bean
以上都是通过@Component 装配Bean , 但是**@Component 只能注解在类上, 不能注解到方法上**。对于Java 而言, 大部分的开发都需要引入第三方的包( jar 文件) ,而且往往并没有这些包的源码, 这时候将无法为这些包的类加入@Component 注解,让它们变为开发环境的Bean 。
这个时候Spring 给予一个注解@Bean , 它可以注解到方法之上, 并且将方法返回的对象作为Spring 的Bean,存放在IoC 容器中。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
# @Component和@Bean的区别?
作用对象不同: @Component 注解作用于类,而@Bean**注解作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的实例,当我需要用它的时候还给我。
@Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
# Bean的作用域
在默认的情况下, Spring IoC容器只会为配置的Bean 生成一个实例,而不是多个。
有时候我们希望Action 是多个实例,每当我们请求的时候就产生一个独立的对象,而不是默认的一个,这样多个实例就可以在不同的线程运行了,就没有并发问题了。
Spring 提供了4 种作用域,它会根据情况来决定是否生成新的对象。
单例( singleton ):它是默认的选项,在整个应用中, Spring 只为其生成一个Bean的实例。
原型( prototype ):当每次注入,或者通过Spring IoC 容器获取Bean 时, Spring 都会为它创建一个新的实例。
会话( session ):在Web 应用中使用,就是在会话过程中Spring 只创建一个实例。
请求( request ):在Web 应用中使用的,就是在一次请求中Spring 会创建一个实例,但是不同的请求会创建不同的实例。
使用方法:使用@Scope注解
# 单例bean的线程安全问题?
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
在Bean对象中尽量避免定义可变的成员变量(不太现实)。
在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
# Spring Bean 的生命周期
# 总览
Spring 对 Bean 进行实例化(相当于 new XXX())
Spring 将值和引用注入进 Bean 对应的属性中
如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给 setBeanName 方法
- 作用是通过 Bean 的引用来获得 Bean ID,一般业务中是很少有用到 Bean 的 ID 的
如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanDactory 方法,并把 BeanFactory 容器实例作为参数传入。
- 作用是获取 Spring 容器,如 Bean 通过 Spring 容器发布事件等
如果 Bean 实现了 ApplicationContextAware 接口,Spring 容器将调用 setApplicationContext 方法,把应用上下文作为参数传入
- 作用与 BeanFactory 类似都是为了获取 Spring 容器,不同的是 Spring 容器在调用 setApplicationContext 方法时会把它自己作为 setApplicationContext 的参数传入,而 Spring 容器在调用 setBeanFactory 前需要使用者自己指定(注入)setBeanFactory 里的参数 BeanFactory
如果 Bean 实现了 BeanPostProcess 接口,Spring 将调用 postProcessBeforeInitialization 方法-前置处理
- 作用是在 Bean 实例创建成功后对其进行增强处理,如对 Bean 进行修改,增加某个功能
如果 Bean 实现了 InitializingBean 接口,Spring 将调用 afterPropertiesSet 方法,作用与在配置文件中对 Bean 使用 init-method 声明初始化的作用一样,都是在 Bean 的全部属性设置成功后执行的初始化方法。
如果 Bean 实现了 BeanPostProcess 接口,Spring 将调用 postProcessAfterInitialization 方法-后置处理
- postProcessBeforeInitialization 是在 Bean 初始化前执行的,而 postProcessAfterInitialization 是在 Bean 初始化后执行的
经过以上的工作后,Bean 将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
如果 Bean 实现了 DispostbleBean 接口,Spring 将调用它的 destory 方法,作用与在配置文件中对 Bean 使用 destory-method 属性的作用一样,都是在 Bean 实例销毁前执行的方法。
# 四个阶段
- Bean的实例化阶段-主要是在createBeanInstance()方法中,调用类的构造器方法来创建一个Bean实例。用户可以自定义一个类,继承InstantiationAwareBeanPostProcessorAdapter,重写它的两个方法,对Bean的实例化前后做一些额外的操作,例如打印日志。
public class MyInstantiationAwareBeanPostProcessorAdapter extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if (beanName.equals("car")) {
System.out.println(beanName + "在实例化之前");
}
return super.postProcessBeforeInstantiation(beanClass, beanName);
}
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (beanName.equals("car")) {
System.out.println(beanName + "在实例化之后");
}
return super.postProcessAfterInstantiation(bean, beanName);
}
}
属性赋值阶段-主要是在populateBean()方法中,对Bean的各项属性进行赋值。
Bean的初始化阶段-主要调用用户自定义的初始化方法init-Method()。用户可以自定义一个类,继承BeanPostProcessor,重写它的两个方法,对Bean的初始化前后做一些额外的操作,例如打印日志。
public class NdBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("NdBeanPostProcessor 在" + beanName + "对象初始化之前调用......");
if (beanName.equals("car")) {
return new CglibInterceptor().getIntance(bean.getClass());
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("NdBeanPostProcessor 在" + beanName + "对象初始化之后调用......");
return bean;
}
}
- Bean销毁阶段,用户可以自定义destroyMethod()方法,在Bean被销毁时被调用。
# Spring Bean 注册
注册 Spring Bean 实际上是将 BeanDefinition 注册到 IoC 容器中。
# XML 配置元信息
Spring 的传统配置方式。在 <bean> 标签中配置元数据内容。
缺点是当 JavaBean 过多时,产生的配置文件足以让你眼花缭乱。
# 注解配置元信息
使用 @Bean、@Component、@Import 注解注册 Spring Bean。
# Java API 配置元信息
- 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be anDefinitionRegistry)
- 配置类方式:AnnotatedBeanDefinitionReader#register(Class...)
# Spring Bean 实例化
Spring Bean 实例化方式:
常规方式
- 通过构造器(配置元信息:XML、Java 注解和 Java API)
- 通过静态工厂方法(配置元信息:XML、Java 注解和 Java API)
- 通过 Bean 工厂方法(配置元信息:XML、Java 注解和 Java API)
- 通过 FactoryBean(配置元信息:XML、Java 注解和 Java API)
特殊方式
- 通过 ServiceLoaderFactoryBean(配置元信息:XML、Java 注解和 Java API )
- 通过 AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
- 通过 BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
# Spring Bean 初始化和销毁
Spring Bean 初始化和销毁的方式有以下几种:
使用 @PostConstruct 和 @PreDestroy 注解分别指定相应的初始化方法和销毁方法。
实现 InitializingBean 接口的 afterPropertiesSet() 方法来编写初始化方法;实现 DisposableBean 接口的 destroy() 方法来编写销毁方法。
自定义初始化方法
- XML 配置:<bean init-method="init" destroy="destroy" ... />
- Java 注解:@Bean(initMethod = "init", destroyMethod = "destroy")
- Java API:AbstractBeanDefinition#setInitMethodName(String) 和 AbstractBeanDefinition#setDestroyMethodName(String) 分别定义初始化和销毁方法
注意:如果同时存在,执行顺序会按照序列执行。
# Spring Bean 垃圾回收
Spring Bean 垃圾回收步骤:
- 关闭 Spring 容器(应用上下文):context.close()
- 执行 GC
- Spring Bean 覆盖的 finalize() 方法被回调