Spring源码解析之常用拓展点的使用

开篇

  • 本篇文章是分析Spring源码基础的第二篇文章章,第一篇文章请看 Spring源码解析之BeanDefinition
  • 这篇文章主要讲解Spring在创建工厂和实例化Bean时用到的相关的拓展点,以便在后边分析Spring源码时大家知道那些拓展点都有什么功能

阅读本篇文章你可以获得什么

  • Spring中提供了哪几种拓展点
  • Spring中提供的拓展点的使用

为什么要学习Spring拓展点

  • 一些高级的面试提会出现
  • 项目中我们基于扩展点可以做开发
  • 只有明白各种拓展点才能真正明白Spring源码

常用的拓展点的分类

  1. Bean工厂的后置处理器:实现了BeanFactoryPostProcessor及其子类的实现类
  2. Bean的后置处理器:实现了BeanPostProcessor的实现类
  3. 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源码时知道这些有什么作用,具体他们是怎么实现的,会在源码中讲解

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值