大家好,我是三友。
Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的。本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展点跟Spring/SpringBoot整合的。
话不多说,直接进入正题。
由于本文太长,所以就分为上下两部分,剩下的可关注微信公众号三友的java日记,在主页中查看
FactoryBean
提起FactoryBean,就有一道“著名”的面试题“说一说FactoryBean和BeanFactory的区别”。其实这两者除了名字有点像,没有半毛钱关系。。
BeanFactory是Bean的工厂,可以帮我们生成想要的Bean,而FactoryBean就是一种Bean的类型。当往容器中注入class类型为FactoryBean的类型的时候,最终生成的Bean是用过FactoryBean的getObject获取的。
来个FactoryBean的Demo
定义一个UserFactoryBean,实现FactoryBean接口,getObject方法返回一个User对象
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
User user = new User();
System.out.println("调用 UserFactoryBean 的 getObject 方法生成 Bean:" + user);
return user;
}
@Override
public Class<?> getObjectType() {
// 这个 FactoryBean 返回的Bean的类型
return User.class;
}
}
测试类:
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 UserFactoryBean 注册到容器中
applicationContext.register(UserFactoryBean.class);
applicationContext.refresh();
System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));
}
}
结果:
调用 UserFactoryBean 的 getObject 方法生成 Bean:com.sanyou.spring.extension.User@396e2f39
获取到的Bean为com.sanyou.spring.extension.User@396e2f39
从结果可以看出,明明注册到Spring容器的是UserFactoryBean,但是却能从容器中获取到User类型的Bean,User这个Bean就是通过UserFactoryBean的getObject方法返回的。
FactoryBean在开源框架中的使用
1、 在Mybatis中的使用
Mybatis在整合Spring的时候,就是通过FactoryBean来实现的,这也就是为什么在Spring的Bean中可以注入Mybatis的Mapper接口的动态代理对象的原因。
代码如下,省略了不重要的代码。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// mapper的接口类型
private Class<T> mapperInterface;
@Override
public T getObject() throws Exception {
// 通过SqlSession获取接口的动态搭理对象
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
}
getObject方法的实现就是返回通过SqlSession获取到的Mapper接口的动态代理对象。
而@MapperScan注解的作用就是将每个接口对应的MapperFactoryBean注册到Spring容器的。
2、在OpenFeign中的使用
FeignClient接口的动态代理也是通过FactoryBean注入到Spring中的。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
// FeignClient接口类型
private Class<?> type;
@Override
public Object getObject() throws Exception {
return getTarget();
}
@Override
public Class<?> getObjectType() {
return type;
}
}
getObject方法是调用getTarget方法来返回的动态代理。
@EnableFeignClients注解的作用就是将每个接口对应的FeignClientFactoryBean注入到Spring容器的。
一般来说,FactoryBean 比较适合那种复杂Bean的构建,在其他框架整合Spring的时候用的比较多。
@Import注解
@Import注解在项目中可能不常见,但是下面这两个注解肯定常见。
@Import({
SchedulingConfiguration.class})
public @interface EnableScheduling {
}
@Import({
AsyncConfigurationSelector.class})
public @interface EnableAsync {
//忽略
}
@EnableScheduling和@EnableAsync两个注解,一个是开启定时任务,一个是开启异步执行。通过这两个注解可以看出,他们都使用了@Import注解,所以真正起作用的是@Import注解。并且在很多情况下,@EnbaleXXX这种格式的注解,都是通过@Import注解起作用的,代表开启了某个功能。
@Import注解导入的配置类的分类
@Import注解导入的配置类可以分为三种情况:
第一种:配置类实现了 ImportSelector 接口
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
当配置类实现了 ImportSelector 接口的时候,就会调用 selectImports 方法的实现,获取一批类的全限定名,最终这些类就会被注册到Spring容器中。
UserImportSelector实现了ImportSelector,selectImports方法返回User的全限定名,代表吧User这个类注册容器中
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("调用 UserImportSelector 的 selectImports 方法获取一批类限定名");
return new String[]{
"com.sanyou.spring.extension.User"};
}
}
测试:
// @Import 注解导入 UserImportSelector
@Import(UserImportSelector.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 Application 注册到容器中
applicationContext.register(Application.class);
applicationContext.refresh();
System.out.println("获取到的Bean为" + applicationContext.getBean(User.class));
}
}
结果:
调用 UserImportSelector 的 selectImports 方法获取一批类限定名
获取到的Bean为com.sanyou.spring.extension.User@282003e1
所以可以看出,的确成功往容器中注入了User这个Bean
第二种:配置类实现了 ImportBeanDefinitionRegistrar 接口
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
当配置类实现了 ImportBeanDefinitionRegistrar 接口,你就可以自定义往容器中注册想注入的Bean。这个接口相比与 ImportSelector 接口的主要区别就是,ImportSelector接口是返回一个类,你不能对这个类进行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加属性之类的。
来个demo:
实现ImportBeanDefinitionRegistrar接口
public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
//构建一个 BeanDefinition , Bean的类型为 User
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
// 设置 User 这个Bean的属性username的值为三友的java日记
.addPropertyValue("username", "三友的java日记")
.getBeanDefinition();
System.out.println("往Spring容器中注入User");
//把 User 这个Bean的定义注册到容器中
registry.registerBeanDefinition("user", beanDefinition);
}
}
测试:
// 导入 UserImportBeanDefinitionRegistrar
@Import(UserImportBeanDefinitionRegistrar.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 Application 注册到容器中
applicationContext.register(Application.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
}
}
结果:
往Spring容器中注入User
获取到的Bean为com.sanyou.spring.extension.User@6385cb26,属性username值为:三友的java日记
第三种:配置类什么接口都没实现
这种就不演示了,就是一个普普通通的类。
总结
其实不论是什么样的配置类,主要的作用就是往Spring容器中注册Bean,只不过注入的方式不同罢了。
这种方式有什么好处呢?
ImportSelector和ImportBeanDefinitionRegistrar的方法是有入参的,也就是注解的一些属性的封装,所以就可以根据注解的属性的配置,来决定应该返回样的配置类或者是应该往容器中注入什么样的类型的Bean,可以看一下 @EnableAsync 的实现,看看是如何根据@EnableAsync注解的属性来决定往容器中注入什么样的Bean。
@Import的核心作用就是导入配置类,并且还可以根据配合(比如@EnableXXX)使用的注解的属性来决定应该往Spring中注入什么样的Bean。
Bean的生命周期
第一节讲的FactoryBean是一种特殊的Bean的类型,@Import注解是往Spring容器中注册Bean。其实不论是@Import注解,还是@Component、@Bean等注解,又或是xml配置,甚至是demo中的register方法,其实主要都是做了一件事,那就是往Spring容器去注册Bean。
为什么需要去注册Bean?
当然是为了让Spring知道要为我们生成Bean,并且需要按照我的要求来生成Bean,比如说,我要@Autowired一个对象,那么你在创建Bean的过程中,就得给我@Autowired一个对象,这就是一个IOC的过程。所以这就涉及了Bean的创建,销毁的过程,也就是面试常问的Bean的生命周期。我之前写过 Spring bean到底是如何创建的?(上)
、Spring bean到底是如何创建的?(下)两篇文章,来剖析Bean的生命周期的源码,有需要的小伙伴可以看一下。
本节来着重看一下,一个Bean在创建的过程中,有哪些常见的操作Spring在Bean的创建过程中给我们完成,并且操作的顺序是什么样的。
话不多说,直接测试,基于结果来分析。
Bean生命周期的回调
先来测试
创建LifeCycle类
创建了一个LifeCycle,实现了 InitializingBean、ApplicationContextAware、DisposableBean接口,加了@PostConstruct、@PreDestroy注解,注入了一个User对象。
public class LifeCycle implements InitializingBean, ApplicationContextAware, DisposableBean {
@Autowired
private User user;
public LifeCycle() {
System.out.println("LifeCycle对象被创建了");
}
/**
* 实现的 Aware 回调接口
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("Aware接口起作用,setApplicationContext被调用了,此时user=" + user);
}
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct注解起作用,postConstruct方法被调用了");
}
/**
* 实现 InitializingBean 接口
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean接口起作用,afterPropertiesSet方法被调用了");
}
/**
* 通过 {@link Bean#initMethod()}来指定
*
* @throws Exception
*/
public void initMethod() throws Exception {
System.out.println("@Bean#initMethod()起作用,initMethod方法被调用了");
}
@PreDestroy
public void preDestroy() throws Exception {
System.out.println("@PreDestroy注解起作用,preDestroy方法被调用了");
}
/**
* 通过 {@link Bean#destroyMethod()}来指定
*
* @throws Exception
*/
public void destroyMethod() throws Exception {
System.out.println("@Bean#destroyMethod()起作用,destroyMethod方法被调用了");
}
/**
* 实现 DisposableBean 注解
*
* @throws Exception
*/
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean接口起作用,destroy方法被调用了");
}
}
声明LifeCycle
通过@Bean声明了LifeCycle,并且initMethod和destroyMethod属性分别指定到了LifeCycle类的initMethod方法和destroyMethod方法
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public LifeCycle lifeCycle() {
return new LifeCycle();
}
测试
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 LifeCycle 注册到容器中
applicationContext.register(Application.class);
applicationContext.refresh();
// 关闭上下文,触发销毁操作
applicationContext.close();
}
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public LifeCycle lifeCycle() {
return new LifeCycle();
}
@Bean
public User user() {
return new User();
}
}
执行结果:
LifeCycle对象被创建了
Aware接口起作用,setApplicationContext被调用了,此时user=com.sanyou.spring.extension.User@57d5872c
@PostConstruct注解起作用,postConstruct方法被调用了
InitializingBean接口起作用,afterPropertiesSet方法被调用了
@Bean#initMethod()起作用,initMethod方法被调用了
@PreDestroy注解起作用,preDestroy方法被调用了
DisposableBean接口起作用,destroy方法被调用了
@Bean#destroyMethod()起作用,destroyMethod方法被调用了
分析结果
通过测试的结果可以看出,Bean在创建和销毁的过程当我们实现了某些接口或者加了某些注解,Spring就会回调我们实现的接口或者执行的方法。
同时,在执行setApplicationContext的时候,能打印出User对象,说明User已经被注入了,说明注入发生在setApplicationContext之前。
这里画张图总结一下Bean创建和销毁过程中调用的顺序。
红色部分发生在Bean的创建过程,灰色部分发生在Bean销毁的过程中,在容器关闭的时候,就会销毁Bean。
这里说一下图中的Aware接口指的是什么。其余的其实没什么好说的,就是按照这种方式配置,Spring会调用对应的方法而已。
Aware接口是指以Aware结尾的一些Spring提供的接口,当你的Bean实现了这些接口的话,在创建过程中会回调对应的set方法,并传入响应的对象。
这里列举几个Aware接口以及它们的作用
接口 | 作用 |
---|---|
ApplicationContextAware | 注入ApplicationContext |
ApplicationEventPublisherAware | 注入ApplicationEventPublisher事件发布器 |
BeanFactoryAware | 注入BeanFactory |
BeanNameAware | 注入Bean的名称 |
有了这些回调,比如说我的Bean想拿到ApplicationContext,不仅可以通过@Autowired注入,还可以通过实现ApplicationContextAware接口拿到。
通过上面的例子我们知道了比如说@PostConstruct注解、@Autowired注解、@PreDestroy注解的作用,但是它们是如何在不同的阶段实现的呢?接着往下看。
BeanPostProcessor
BeanPostProcessor,中文名 Bean的后置处理器,在Bean创建的过程中起作用。
BeanPostProcessor是Bean在创建过程中一个非常重要的扩展点,因为每个Bean在创建的各个阶段,都会回调BeanPostProcessor及其子接口的方法,传入正在创建的Bean对象,这样如果想对Bean创建过程中某个阶段进行自定义扩展,那么就可以自定义BeanPostProcessor来完成。
说得简单点,BeanPostProcessor就是在Bean创建过程中留的口子,通过这个口子可以对正在创建的Bean进行扩展。只不过Bean创建的阶段比较多,然后BeanPostProcessor接口以及他的子接口InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor就提供了很多方法,可以使得在不同的阶段都可以拿到正在创建的Bean进行扩展。
来个Demo
现在需要实现一个这样的需求,如果Bean的类型是User,那么就设置这个对象的username属性为 ”三友的java日记“。
那么就可以这么写:
public class UserBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof User) {
//如果当前的Bean的类型是 User ,就把这个对象 username 的属性赋值为 三友的java日记
((User) bean).setUsername("三友的java日记");
}
return bean;
}
}
测试:
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//将 UserBeanPostProcessor 和 User 注册到容器中
applicationContext.register(UserBeanPostProcessor.class);
applicationContext.register(User.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println("获取到的Bean为" + user + ",属性username值为:" + user.getUsername());
}
}
测试结果:
获取到的Bean为com.sanyou.spring.extension.User@21a947fe,属性username值为:三友的java日记
从结果可以看出,每个生成的Bean在执行到某个阶段的时候,都会回调UserBeanPostProcessor,然后UserBeanPostProcessor就会判断当前创建的Bean的类型,如果是User类型,那么就会将username的属性设置为 ”三友的java日记“。
Spring内置的BeanPostProcessor
这里我列举了常见的一些BeanPostProcessor的实现以及它们的作用
BeanPostProcessor | 作用 |
---|---|
AutowiredAnnotationBeanPostProcessor | 处理@Autowired、@Value注解 |
CommonAnnotationBeanPostProcessor | 处理@Resource、@PostConstruct、@PreDestroy注解 |
AnnotationAwareAspectJAutoProxyCreator | 处理一些注解或者是AOP切面的动态代理 |
ApplicationContextAwareProcessor | 处理Aware接口注入的 |
AsyncAnnotationBeanPostProcessor | 处理@Async注解 |
ScheduledAnnotationBeanPostProcessor | 处理@Scheduled注解 |
通过列举的这些BeanPostProcessor的实现可以看出,Spring Bean的很多注解的处理都是依靠BeanPostProcessor及其子类的实现来完成的,这也回答了上一小节的疑问,处理@Autowired、@PostConstruct、@PreDestroy注解是如何起作用的,其实就是通过BeanPostProcessor,在Bean的不同阶段来调用对应的方法起作用的。
BeanPostProcessor在Dubbo中的使用
在Dubbo中可以通过@DubboReference(@Reference)来引用生产者提供的接口,这个注解的处理也是依靠ReferenceAnnotationBeanPostProcessor,也就是 BeanPostProcessor 的扩展来实现的。
public class ReferenceAnnotationBeanPostProcessor
extends AbstractAnnotationBeanPostProcessor
implements ApplicationContextAware, BeanFactoryPostProcessor {
// 忽略
}
当Bean在创建的某一阶段,走到了ReferenceAnnotationBeanPostProcessor这个类,就会根据反射找出这个类有没有@DubboReference(@Reference)注解,有的话就构建一个动态搭理注入就可以了。
BeanPostProcessor在Spring Bean的扩展中扮演着重要的角色,是Spring Bean生命周期中很重要的一部分。正是因为有了BeanPostProcessor,你就可以在Bean创建过程中的任意一个阶段扩展自己想要的东西。
BeanFactoryPostProcessor
通过上面一节我们知道 BeanPostProcessor 是对Bean的处理,那么BeanFactoryPostProcessor很容易就猜到是对BeanFactory,也就是Spring容器的处理。
举个例子,如果我们想禁止循环依赖,那么就可以这么写。
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 禁止循环依赖
((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false);
}
}
后面只需要将注入到Spring容器中就会生效。
BeanFactoryPostProcessor是可以对Spring容器做处理的,方法的入参就是Spring的容器,通过这个接口,就对容器进行为所欲为的操作。
由于本文太长,所以就分为上下两部分,剩下的可关注微信公众号三友的java日记,在主业中查看
本文的所有代码,在公众号三友的java日记回复扩展点即可获得
往期热门文章推荐
搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。