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源碼時知道這些有什麼作用,具體他們是怎麼實現的,會在源碼中講解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章