萬人之?dāng)?,通過注解給屬性注入配置和Bean對象
本文轉(zhuǎn)載自微信公眾號「bugstack蟲洞?!梗髡咝「蹈?。轉(zhuǎn)載本文請聯(lián)系bugstack蟲洞棧公眾號。
一、前言
寫代碼,就是從能用到好用的不斷折騰!
你聽過擾動函數(shù)嗎?你寫過斐波那契(Fibonacci)散列嗎?你實現(xiàn)過梅森旋轉(zhuǎn)算法嗎?怎么 沒聽過這些寫不了代碼嗎!不會的,即使沒聽過你一樣可以寫的了代碼,比如你實現(xiàn)的數(shù)據(jù)庫路由數(shù)據(jù)總是落在1庫1表它不散列分布、你實現(xiàn)的抽獎系統(tǒng)總是把運營配置的最大紅包發(fā)出去提高了運營成本、你開發(fā)的秒殺系統(tǒng)總是在開始后的1秒就掛了貨品根本給不出去。
除了一部分僅把編碼當(dāng)成搬磚應(yīng)付工作外的程序員,還有一部分總是在追求極致的碼農(nóng)。寫代碼還能賺錢,真開心! 這樣的碼農(nóng)總是會考慮??還有沒有更好的實現(xiàn)邏輯能讓代碼不僅是能用,還要好用呢?其實這一點的追求到完成,需要大量擴展性學(xué)習(xí)和深度挖掘,這樣你設(shè)計出來的系統(tǒng)才更你考慮的更加全面,也能應(yīng)對各種復(fù)雜的場景。
二、目標(biāo)
在目前 IOC、AOP 兩大核心功能模塊的支撐下,完全可以管理 Bean 對象的注冊和獲取,不過這樣的使用方式總感覺像是刀耕火種有點難用。因此在上一章節(jié)我們解決需要手動配置 Bean 對象到 spring.xml 文件中,改為可以自動掃描帶有注解 @Component 的對象完成自動裝配和注冊到 Spring 容器的操作。
那么在自動掃描包注冊 Bean 對象之后,就需要把原來在配置文件中通過 property name="token" 配置屬性和Bean的操作,也改為可以自動注入。這就像我們使用 Spring 框架中 @Autowired、@Value 注解一樣,完成我們對屬性和對象的注入操作。
三、方案
其實從我們在完成 Bean 對象的基礎(chǔ)功能后,后續(xù)陸續(xù)添加的功能都是圍繞著 Bean 的生命周期進行的,比如修改 Bean 的定義 BeanFactoryPostProcessor,處理 Bean 的屬性要用到 BeanPostProcessor,完成個性的屬性操作則專門繼承 BeanPostProcessor 提供新的接口,因為這樣才能通過 instanceof 判斷出具有標(biāo)記性的接口。所以關(guān)于 Bean 等等的操作,以及監(jiān)聽 Aware、獲取 BeanFactory,都需要在 Bean 的生命周期中完成。那么我們在設(shè)計屬性和 Bean 對象的注入時候,也會用到 BeanPostProcessor 來完成在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值。整體設(shè)計結(jié)構(gòu)如下圖:
- 要處理自動掃描注入,包括屬性注入、對象注入,則需要在對象屬性 applyPropertyValues 填充之前 ,把屬性信息寫入到 PropertyValues 的集合中去。這一步的操作相當(dāng)于是解決了以前在 spring.xml 配置屬性的過程。
- 而在屬性的讀取中,需要依賴于對 Bean 對象的類中屬性的配置了注解的掃描,field.getAnnotation(Value.class); 依次拿出符合的屬性并填充上相應(yīng)的配置信息。這里有一點 ,屬性的配置信息需要依賴于 BeanFactoryPostProcessor 的實現(xiàn)類 PropertyPlaceholderConfigurer,把值寫入到 AbstractBeanFactory的embeddedValueResolvers集合中,這樣才能在屬性填充中利用 beanFactory 獲取相應(yīng)的屬性值
- 還有一個是關(guān)于 @Autowired 對于對象的注入,其實這一個和屬性注入的唯一區(qū)別是對于對象的獲取 beanFactory.getBean(fieldType),其他就沒有什么差一點了。
- 當(dāng)所有的屬性被設(shè)置到 PropertyValues 完成以后,接下來就到了創(chuàng)建對象的下一步,屬性填充,而此時就會把我們一一獲取到的配置和對象填充到屬性上,也就實現(xiàn)了自動注入的功能。
四、實現(xiàn)
1. 工程結(jié)構(gòu)
- small-spring-step-14
- └── src
- ├── main
- │ └── java
- │ └── cn.bugstack.springframework
- │ ├── aop
- │ │ ├── aspectj
- │ │ │ └── AspectJExpressionPointcut.java
- │ │ │ └── AspectJExpressionPointcutAdvisor.java
- │ │ ├── framework
- │ │ │ ├── adapter
- │ │ │ │ └── MethodBeforeAdviceInterceptor.java
- │ │ │ ├── autoproxy
- │ │ │ │ └── MethodBeforeAdviceInterceptor.java
- │ │ │ ├── AopProxy.java
- │ │ │ ├── Cglib2AopProxy.java
- │ │ │ ├── JdkDynamicAopProxy.java
- │ │ │ ├── ProxyFactory.java
- │ │ │ └── ReflectiveMethodInvocation.java
- │ │ ├── AdvisedSupport.java
- │ │ ├── Advisor.java
- │ │ ├── BeforeAdvice.java
- │ │ ├── ClassFilter.java
- │ │ ├── MethodBeforeAdvice.java
- │ │ ├── MethodMatcher.java
- │ │ ├── Pointcut.java
- │ │ ├── PointcutAdvisor.java
- │ │ └── TargetSource.java
- │ ├── beans
- │ │ ├── factory
- │ │ │ ├── annotation
- │ │ │ │ ├── Autowired.java
- │ │ │ │ ├── AutowiredAnnotationBeanPostProcessor.java
- │ │ │ │ ├── Qualifier.java
- │ │ │ │ └── Value.java
- │ │ │ ├── config
- │ │ │ │ ├── AutowireCapableBeanFactory.java
- │ │ │ │ ├── BeanDefinition.java
- │ │ │ │ ├── BeanFactoryPostProcessor.java
- │ │ │ │ ├── BeanPostProcessor.java
- │ │ │ │ ├── BeanReference.java
- │ │ │ │ ├── ConfigurableBeanFactory.java
- │ │ │ │ ├── InstantiationAwareBeanPostProcessor.java
- │ │ │ │ └── SingletonBeanRegistry.java
- │ │ │ ├── support
- │ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
- │ │ │ │ ├── AbstractBeanDefinitionReader.java
- │ │ │ │ ├── AbstractBeanFactory.java
- │ │ │ │ ├── BeanDefinitionReader.java
- │ │ │ │ ├── BeanDefinitionRegistry.java
- │ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
- │ │ │ │ ├── DefaultListableBeanFactory.java
- │ │ │ │ ├── DefaultSingletonBeanRegistry.java
- │ │ │ │ ├── DisposableBeanAdapter.java
- │ │ │ │ ├── FactoryBeanRegistrySupport.java
- │ │ │ │ ├── InstantiationStrategy.java
- │ │ │ │ └── SimpleInstantiationStrategy.java
- │ │ │ ├── support
- │ │ │ │ └── XmlBeanDefinitionReader.java
- │ │ │ ├── Aware.java
- │ │ │ ├── BeanClassLoaderAware.java
- │ │ │ ├── BeanFactory.java
- │ │ │ ├── BeanFactoryAware.java
- │ │ │ ├── BeanNameAware.java
- │ │ │ ├── ConfigurableListableBeanFactory.java
- │ │ │ ├── DisposableBean.java
- │ │ │ ├── FactoryBean.java
- │ │ │ ├── HierarchicalBeanFactory.java
- │ │ │ ├── InitializingBean.java
- │ │ │ ├── ListableBeanFactory.java
- │ │ │ └── PropertyPlaceholderConfigurer.java
- │ │ ├── BeansException.java
- │ │ ├── PropertyValue.java
- │ │ └── PropertyValues.java
- │ ├── context
- │ │ ├── annotation
- │ │ │ ├── ClassPathBeanDefinitionScanner.java
- │ │ │ ├── ClassPathScanningCandidateComponentProvider.java
- │ │ │ └── Scope.java
- │ │ ├── event
- │ │ │ ├── AbstractApplicationEventMulticaster.java
- │ │ │ ├── ApplicationContextEvent.java
- │ │ │ ├── ApplicationEventMulticaster.java
- │ │ │ ├── ContextClosedEvent.java
- │ │ │ ├── ContextRefreshedEvent.java
- │ │ │ └── SimpleApplicationEventMulticaster.java
- │ │ ├── support
- │ │ │ ├── AbstractApplicationContext.java
- │ │ │ ├── AbstractRefreshableApplicationContext.java
- │ │ │ ├── AbstractXmlApplicationContext.java
- │ │ │ ├── ApplicationContextAwareProcessor.java
- │ │ │ └── ClassPathXmlApplicationContext.java
- │ │ ├── ApplicationContext.java
- │ │ ├── ApplicationContextAware.java
- │ │ ├── ApplicationEvent.java
- │ │ ├── ApplicationEventPublisher.java
- │ │ ├── ApplicationListener.java
- │ │ └── ConfigurableApplicationContext.java
- │ ├── core.io
- │ │ ├── ClassPathResource.java
- │ │ ├── DefaultResourceLoader.java
- │ │ ├── FileSystemResource.java
- │ │ ├── Resource.java
- │ │ ├── ResourceLoader.java
- │ │ └── UrlResource.java
- │ ├── stereotype
- │ │ └── Component.java
- │ └── utils
- │ ├── ClassUtils.java
- │ └── StringValueResolver.java
- └── test
- └── java
- └── cn.bugstack.springframework.test
- ├── bean
- │ ├── IUserService.java
- │ └── UserService.java
- └── ApiTest.java
自動掃描注入占位符配置和對象的類關(guān)系,如圖 15-2
圖 15-2
- 在整個類圖中以圍繞實現(xiàn)接口 InstantiationAwareBeanPostProcessor 的類 AutowiredAnnotationBeanPostProcessor 作為入口點,被 AbstractAutowireCapableBeanFactory創(chuàng)建 Bean 對象過程中調(diào)用掃描整個類的屬性配置中含有自定義注解 Value、Autowired、Qualifier,的屬性值。
- 這里稍有變動的是關(guān)于屬性值信息的獲取,在注解配置的屬性字段掃描到信息注入時,包括了占位符從配置文件獲取信息也包括 Bean 對象,Bean 對象可以直接獲取,但配置信息需要在 AbstractBeanFactory 中添加新的屬性集合 embeddedValueResolvers,由 PropertyPlaceholderConfigurer#postProcessBeanFactory 進行操作填充到屬性集合中。
2. 把讀取到屬性填充到容器
定義解析字符串接口
cn.bugstack.springframework.util.StringValueResolver
- public interface StringValueResolver {
- String resolveStringValue(String strVal);
- }
- 接口 StringValueResolver 是一個解析字符串操作的接口
填充字符串
- public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
- try {
- // 加載屬性文件
- DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
- Resource resource = resourceLoader.getResource(location);
- // ... 占位符替換屬性值、設(shè)置屬性值
- // 向容器中添加字符串解析器,供解析@Value注解使用
- StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
- beanFactory.addEmbeddedValueResolver(valueResolver);
- } catch (IOException e) {
- throw new BeansException("Could not load properties", e);
- }
- }
- private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
- private final Properties properties;
- public PlaceholderResolvingStringValueResolver(Properties properties) {
- this.properties = properties;
- }
- @Override
- public String resolveStringValue(String strVal) {
- return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
- }
- }
- }
- 在解析屬性配置的類 PropertyPlaceholderConfigurer 中,最主要的其實就是這行代碼的操作 beanFactory.addEmbeddedValueResolver(valueResolver) 這是把屬性值寫入到了 AbstractBeanFactory 的 embeddedValueResolvers 中。
- 這里說明下,embeddedValueResolvers 是 AbstractBeanFactory 類新增加的集合 List
embeddedValueResolvers String resolvers to apply e.g. to annotation attribute values
3. 自定義屬性注入注解
自定義注解,Autowired、Qualifier、Value
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
- public @interface Autowired {
- }
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- public @interface Qualifier {
- String value() default "";
- }
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Value {
- /**
- * The actual value expression: e.g. "#{systemProperties.myProp}".
- */
- String value();
- }
3個注解在我們?nèi)粘J褂?Spring 也是非常常見的,注入對象、注入屬性,而 Qualifier 一般與 Autowired 配合使用。
4. 掃描自定義注解
cn.bugstack.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
- public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
- private ConfigurableListableBeanFactory beanFactory;
- @Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
- this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
- }
- @Override
- public PropertyValues postProcessPropertyValues(PropertyValues pvs, Object bean, String beanName) throws BeansException {
- // 1. 處理注解 @Value
- Class<?> clazz = bean.getClass();
- clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;
- Field[] declaredFields = clazz.getDeclaredFields();
- for (Field field : declaredFields) {
- Value valueAnnotation = field.getAnnotation(Value.class);
- if (null != valueAnnotation) {
- String value = valueAnnotation.value();
- value = beanFactory.resolveEmbeddedValue(value);
- BeanUtil.setFieldValue(bean, field.getName(), value);
- }
- }
- // 2. 處理注解 @Autowired
- for (Field field : declaredFields) {
- Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);
- if (null != autowiredAnnotation) {
- Class<?> fieldType = field.getType();
- String dependentBeanName = null;
- Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);
- Object dependentBean = null;
- if (null != qualifierAnnotation) {
- dependentBeanName = qualifierAnnotation.value();
- dependentBean = beanFactory.getBean(dependentBeanName, fieldType);
- } else {
- dependentBean = beanFactory.getBean(fieldType);
- }
- BeanUtil.setFieldValue(bean, field.getName(), dependentBean);
- }
- }
- return pvs;
- }
- }
- AutowiredAnnotationBeanPostProcessor 是實現(xiàn)接口 InstantiationAwareBeanPostProcessor 的一個用于在 Bean 對象實例化完成后,設(shè)置屬性操作前的處理屬性信息的類和操作方法。只有實現(xiàn)了 BeanPostProcessor 接口才有機會在 Bean 的生命周期中處理初始化信息
- 核心方法 postProcessPropertyValues,主要用于處理類含有 @Value、@Autowired 注解的屬性,進行屬性信息的提取和設(shè)置。
- 這里需要注意一點因為我們在 AbstractAutowireCapableBeanFactory 類中使用的是 CglibSubclassingInstantiationStrategy 進行類的創(chuàng)建,所以在 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues 中需要判斷是否為 CGlib 創(chuàng)建對象,否則是不能正確拿到類信息的。ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;
5. 在Bean的生命周期中調(diào)用屬性注入
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
- public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
- private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
- @Override
- protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
- Object bean = null;
- try {
- // 判斷是否返回代理 Bean 對象
- bean = resolveBeforeInstantiation(beanName, beanDefinition);
- if (null != bean) {
- return bean;
- }
- // 實例化 Bean
- bean = createBeanInstance(beanDefinition, beanName, args);
- // 在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值
- applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
- // 給 Bean 填充屬性
- applyPropertyValues(beanName, bean, beanDefinition);
- // 執(zhí)行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置處理方法
- bean = initializeBean(beanName, bean, beanDefinition);
- } catch (Exception e) {
- throw new BeansException("Instantiation of bean failed", e);
- }
- // 注冊實現(xiàn)了 DisposableBean 接口的 Bean 對象
- registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
- // 判斷 SCOPE_SINGLETON、SCOPE_PROTOTYPE
- if (beanDefinition.isSingleton()) {
- registerSingleton(beanName, bean);
- }
- return bean;
- }
- /**
- * 在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值
- *
- * @param beanName
- * @param bean
- * @param beanDefinition
- */
- protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
- for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) {
- if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor){
- PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName);
- if (null != pvs) {
- for (PropertyValue propertyValue : pvs.getPropertyValues()) {
- beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
- }
- }
- }
- }
- }
- // ...
- }
- AbstractAutowireCapableBeanFactory#createBean 方法中有這一條新增加的方法調(diào)用,就是在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值 的操作 applyBeanPostProcessorsBeforeApplyingPropertyValues
- 那么這個 applyBeanPostProcessorsBeforeApplyingPropertyValues 方法中,首先就是獲取已經(jīng)注入的 BeanPostProcessor 集合并從中篩選出繼承接口 InstantiationAwareBeanPostProcessor 的實現(xiàn)類。
- 最后就是調(diào)用相應(yīng)的 postProcessPropertyValues 方法以及循環(huán)設(shè)置屬性值信息,beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
五、測試
1. 事先準(zhǔn)備
配置 Dao
- @Component
- public class UserDao {
- private static Map<String, String> hashMap = new HashMap<>();
- static {
- hashMap.put("10001", "小傅哥,北京,亦莊");
- hashMap.put("10002", "八杯水,上海,尖沙咀");
- hashMap.put("10003", "阿毛,香港,銅鑼灣");
- }
- public String queryUserName(String uId) {
- return hashMap.get(uId);
- }
- }
- 給類配置上一個自動掃描注冊 Bean 對象的注解 @Component,接下來會把這個類注入到 UserService 中。
注解注入到 UserService
- @Component("userService")
- public class UserService implements IUserService {
- @Value("${token}")
- private String token;
- @Autowired
- private UserDao userDao;
- public String queryUserInfo() {
- try {
- Thread.sleep(new Random(1).nextInt(100));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return userDao.queryUserName("10001") + "," + token;
- }
- // ...
- }
- 這里包括了兩種類型的注入,一個是占位符注入屬性信息 @Value("${token}"),另外一個是注入對象信息 @Autowired
2. 屬性配置文件
token.properties
- token=RejDlI78hu223Opo983Ds
spring.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context">
- <bean class="cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer">
- <property name="location" value="classpath:token.properties"/>
- </bean>
- <context:component-scan base-package="cn.bugstack.springframework.test.bean"/>
- </beans>
在 spring.xml 中配置了掃描屬性信息和自動掃描包路徑范圍。
3. 單元測試
- @Test
- public void test_scan() {
- ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
- IUserService userService = applicationContext.getBean("userService", IUserService.class);
- System.out.println("測試結(jié)果:" + userService.queryUserInfo());
- }
- 單元測試時候就可以完整的測試一個類注入到 Spring 容器,同時這個屬性信息也可以被自動掃描填充上。
測試結(jié)果
測試結(jié)果:小傅哥,北京,亦莊,RejDlI78hu223Opo983Ds
Process finished with exit code 0
- 從測試結(jié)果可以看到現(xiàn)在我們的使用方式已經(jīng)通過了,有自動掃描類,有注解注入屬性。這與使用 Spring 框架越來越像了。
六、總結(jié)
- 從整個注解信息掃描注入的實現(xiàn)內(nèi)容來看,我們一直是圍繞著在 Bean 的生命周期中進行處理,就像 BeanPostProcessor 用于修改新實例化 Bean 對象的擴展點,提供的接口方法可以用于處理 Bean 對象實例化前后進行處理操作。而有時候需要做一些差異化的控制,所以還需要繼承 BeanPostProcessor 接口,定義新的接口 InstantiationAwareBeanPostProcessor 這樣就可以區(qū)分出不同擴展點的操作了。
- 像是接口用 instanceof 判斷,注解用 Field.getAnnotation(Value.class); 獲取,都是相當(dāng)于在類上做的一些標(biāo)識性信息,便于可以用一些方法找到這些功能點,以便進行處理。所以在我們?nèi)粘i_發(fā)設(shè)計的組件中,也可以運用上這些特點。
- 當(dāng)你思考把你的實現(xiàn)融入到一個已經(jīng)細分好的 Bean 生命周期中,你會發(fā)現(xiàn)它的設(shè)計是如此的好,可以讓你在任何初始化的時間點上,任何面上,都能做你需要的擴展或者改變,這也是我們做程序設(shè)計時追求的靈活性。