Spring拓展点的使用
开篇
- 本篇文章是分析Spring源码基础的第二篇文章章,第一篇文章请看 Spring源码解析之BeanDefinition
- 这篇文章主要讲解Spring在创建工厂和实例化Bean时用到的相关的拓展点,以便在后边分析Spring源码时大家知道那些拓展点都有什么功能
阅读本篇文章你可以获得什么
- Spring中提供了哪几种拓展点
- Spring中提供的拓展点的使用
为什么要学习Spring拓展点
- 一些高级的面试提会出现
- 项目中我们基于扩展点可以做开发
- 只有明白各种拓展点才能真正明白Spring源码
常用的拓展点的分类
- Bean工厂的后置处理器:实现了BeanFactoryPostProcessor及其子类的实现类
- Bean的后置处理器:实现了BeanPostProcessor的实现类
- Import相关:可以是一个类或者实现了某些接口的类
Bean工厂的后置处理器
Bean工厂的后置处理器:实现了BeanFactoryPostProcessor及其子类的实现类
什么是Bean工厂的后置处理器?
- 允许程序员自定义修改应用程序上下文的bean定义信息,调整上下文的底层bean工厂的bean属性值。
- 应用程序上下文在初始化时可以在它们的bean定义中自动检测BeanFactoryPostProcessor类型的bean,并在创建任何其他bean之前先应用它们。
注意:
BeanFactoryPostProcessor只可以与bean定义进行交互并对其进行修改,但不能与bean实例进行交互。
上边是说了Bean工厂后置处理器的定义与执行时机,下边来做一下实际的使用
实现了BeanFactoryPostProcessor的实现类
上篇文章在讲解BeanDefinition时,讲解到了一个属性AutowireCandidate,如果我们有这样一个需求,就是要把工厂中所有的BeanDefinition中的AutowireCandidate改为false。我们应该怎么做到呢?通过实现BeanFactoryPostProcessor就能很简单的达成这个效果。代码如下:
@Test
public void testBeanFactoryPostProcessorModifyBD() {
//初始化
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBeanDefinition("tb1", new RootBeanDefinition(TestBean.class));
context.registerBeanDefinition("my", new RootBeanDefinition(MyBeanFactoryPostProcessor.class));
//刷新 执行后置处理器
context.refresh();
//获取修改后的定义信息
BeanDefinition beanDefinition = context.getBeanDefinition("tb1");
//执行完之后不会报错
// assertFalse(beanDefinition.isAutowireCandidate());
//执行完之后会报错 因为值已经修改成来fase
assertTrue(beanDefinition.isAutowireCandidate());
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//取到容器中所有的Bean定义的名称
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
BeanDefinition beanDefinition = null;
for (int i = 0; i < beanDefinitionNames.length; i++) {
//根据名称获取BeanDefinition
beanDefinition = beanFactory.getBeanDefinition(beanDefinitionNames[i]);
beanDefinition.setAutowireCandidate(false);
}
}
}
上边的场景可能不是很常见,但是如果你读过Mybatis-Spring的源码你应该也能看到这个类。这个类完成了修改BeanClass类型和自动注入的类型,这个源码我在别的文章中提过,感兴趣可以看一下 spring和mybatis整合为什么只定义了接口?为什么设置自动装配模型为BY_TYPE这边文章。来更好的体会一下BeanFactoryPostProcessor这个接口的作用。
实现了BeanDefinitionRegistryPostProcessor的实现类
它和BeanFactoryPostProcessor有什么不同?
BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口,并增加了自己的接口方法。在使用上来说,BeanDefinitionRegistryPostProcessor不只能修改Bean的定义信息,还能动态的向容器中添加定义信息。
现在有这么一个场景,如果程序中存在类A的定义信息,则把类B添加到容器中。那要怎么做呢?暂时不考虑使用@ConditionOnClass的解决方案,代码如下:
@Test
public void testBeanDefinitionRegistryPostProcessorAddBD() {
//初始化
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBeanDefinition("my", new RootBeanDefinition(MyBeanDefinitionRegistryPostProcessor.class));
context.registerBeanDefinition("a", new RootBeanDefinition(TestBean.class));
//刷新 执行后置处理器
context.refresh();
//获取修改后的定义信息
assertNotNull(context.getBeanDefinition("b"));
// 把 context.registerBeanDefinition("a", new RootBeanDefinition(TestBean.class)); 注视后 验证为null
// assertNull(context.getBeanDefinition("b"));
}
public static class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (registry.containsBeanDefinition("a")) {
registry.registerBeanDefinition("b", new RootBeanDefinition(ListeningBean.class));
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//这个方法是父类方法 如果要修改bean的定义信息 可以重写
}
}
上边就是Bean工厂的后置处理器的一个简单使用流程。以后在分析源码的时候会发现Spring底层是怎么使用这种后置处理器来进行工厂的初始化的。尤其主要的是ConfigurationClassPostProcessor
这个明星类的处理逻辑,后边会有专门的章节进行表述。
Bean的后置处理器
上边介绍Bean工厂的后置处理器时,特意强调了一下BeanFactoryPostProcessor只能作用域BD而不能作用在一个Bean上,那如果我们要对Bean进行特殊的处理呢?那就引入了我们现在要将的接口BeanPostProcessor
什么是Bean的后置处理器?
- 工厂钩子,允许自定义新的bean实例,例如使用代理包装它们。
- ApplicationContexts可以在它们的bean定义中自动检测BeanPostProcessor类型的bean,并将它们应用于随后创建的任何bean。
实现了BeanPostProcessor接口的后置处理器
BeanPostProcessor接口中有两个方法:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
-
一般来说,如果你有属性需要填充,或者bean实例化完成之后需要做些什么可以重写
postProcessBeforeInitialization
方法,最典型的应用场景是我们实现了ApplicationContextAware
接口,就可以注入给我们一个一个context对象。 -
如果你要对某个类进行包装,一般是重写
postProcessAfterInitialization
这个方法,在我们实际工作中最典型的例子是AOP的使用,最后返回代理对象时就是借助于这个方法。
没有想到太好的例子就以Spring中ApplicationContextAwareProcessor为例进行讲解,代码如下:
class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
private final StringValueResolver embeddedValueResolver;
/**
* Create a new ApplicationContextAwareProcessor for the given context.
*/
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
@Override
@Nullable
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareInterfaces(bean);
return null;
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
看代码我们可以发现 它主要重写了postProcessBeforeInitialization这个方法。
在方法中判断有没有实现了aware接口,如果实现了则进行调用,把环境变量,或者上下文通过方法注入。
这个方法的执行时机是当对象被初始化完成且调用完属性填充后,便会执行这个方法。
我们自己实现一个重写了postProcessAfterInitialization
方法的类,对我们产生的类进行代理,在方法调用前后进行日志打印。这里我们只看方法的使用,先忽略这种业务场景合不合理,代码如下:
public class BeanPostProcessorTests {
@Test
public void logTest() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestDoImpl.class);
applicationContext.register(MyBeanPostProcessor.class);
applicationContext.refresh();
TestDo testDo = applicationContext.getBean(TestDo.class);
testDo.doSome();
}
}
interface TestDo {
void doSome();
}
class TestDoImpl implements TestDo {
public void doSome() {
System.out.println("TestDo:doSome");
}
}
class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TestDo) {
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{TestDo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("先执行日志打印log");
return method.invoke(bean, args);
}
});
} else {
return bean;
}
}
}
执行结果:
先执行日志打印log
TestDo:doSome
可以看到现在工厂中存在的就是我们自己产生的代理对象了,实际上aop也是这么做的,只是没有像我们把代码写的这么死。
@Import导入
这部分我们来说一下Spring常用的最后一类拓展点通过Import导入,相信大家对这个注解并不陌生。
我们向容器中添加一个BeanDefinition有很多种方式
- 通过xml配置
- 通过api形式显示注册
- 通过包扫描的方式扫描指定路径下的Bean
- 还有就是通过@Import方式进行导入
所以可以说@Import是用来向容器中添加BeanDefinition最后会被实例化成为Bean
来看一下@Import的代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
可以看到通过value可以是三种类型的值
- 普通类
- ImportSelector类型的类
- ImportBeanDefinitionRegistrar类型的类
Import一个普通类
直接针对当前的class创建BD,放入到BeanDefinitionMap中后期用来实例化当前类型的Bean。
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
说明 产生了对象
assertNotNull(context.getBean(TestBean1.class));
}
@Import(TestBean1.class)
static class ConfigurationWithImportAnnotation {
}
static class TestBean1{
}
Import一个实现了ImportSelector接口的类
实现了ImportSelector的类需要重写selectImports方法。在Spring内部会通过反射实例化对象调用这个方法获取返回值,然后通过解析器把我们传的全限定名根据类加载加载出来。也是先变成beanDefinition最后编程bean。具体用法如下
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
说明 产生了对象
assertNotNull(context.getBean(TestBean2.class));
}
@Import(TestBeanImportSelector.class)
static class ConfigurationWithImportAnnotation {
}
static class TestBeanImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"org.springframework.context.annotation.configuration.ImportTests.TestBean2"};
}
}
static class TestBean2 {
}
当然我们还可以获取Import的类上的注解元数据。来做一些判断进行加不加如特定的类。
Import一个实现了ImportBeanDefinitionRegistrar接口的类
这个接口可以让我们直接获取到BeanDefinitionRegistry
注册器,可以根据其他条件动态的想容器中注入BeanDefinition。示例代码如下:
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
说明 产生了对象
assertNotNull(context.getBean(TestBean2.class));
}
@Import(TestImportBeanDefinitionRegistrar.class)
static class ConfigurationWithImportAnnotation {
}
static class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean2.class));
}
}
static class TestBean2 {
}
当然可以进阶使用,先判断有没有某个定义信息,有的话则不添加,伪代码如下:
static class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition("a")) {
registry.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean2.class));
}
}
}
文章到现在为止,已经说完了Spring常用的拓展点的使用。写这篇文章的目的主要是为了后边介绍Spring源码时知道这些有什么作用,具体他们是怎么实现的,会在源码中讲解