面試官:展開說說,Spring中Bean物件是如何通過註解注入的?

小傅哥發表於2021-08-04


作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!?

章節目錄(手寫Spring,讓你瞭解更多)

一、前言

寫程式碼,就是從能用到好用的不斷折騰!

你聽過擾動函式嗎?你寫過斐波那契(Fibonacci)雜湊嗎?你實現過梅森旋轉演算法嗎?怎麼 沒聽過這些寫不了程式碼嗎!不會的,即使沒聽過你一樣可以寫的了程式碼,比如你實現的資料庫路由資料總是落在1庫1表它不雜湊分佈、你實現的抽獎系統總是把運營配置的最大紅包發出去提高了運營成本、你開發的秒殺系統總是在開始後的1秒就掛了貨品根本給不出去。

除了一部分僅把編碼當成搬磚應付工作外的程式設計師,還有一部分總是在追求極致的碼農。寫程式碼還能賺錢,真開心! 這樣的碼農總是會考慮?還有沒有更好的實現邏輯能讓程式碼不僅是能用,還要好用呢?其實這一點的追求到完成,需要大量擴充套件性學習和深度挖掘,這樣你設計出來的系統才更你考慮的更加全面,也能應對各種複雜的場景。

二、目標

在目前 IOC、AOP 兩大核心功能模組的支撐下,完全可以管理 Bean 物件的註冊和獲取,不過這樣的使用方式總感覺像是刀耕火種有點難用。因此在上一章節我們解決需要手動配置 Bean 物件到 spring.xml 檔案中,改為可以自動掃描帶有註解 @Component 的物件完成自動裝配和註冊到 Spring 容器的操作。

那麼在自動掃描包註冊 Bean 物件之後,就需要把原來在配置檔案中通過 property name="token" 配置屬性和Bean的操作,也改為可以自動注入。這就像我們使用 Spring 框架中 @Autowired@Value 註解一樣,完成我們對屬性和物件的注入操作。

三、方案

其實從我們在完成 Bean 物件的基礎功能後,後續陸續新增的功能都是圍繞著 Bean 的生命週期進行的,比如修改 Bean 的定義 BeanFactoryPostProcessor,處理 Bean 的屬性要用到 BeanPostProcessor,完成個性的屬性操作則專門繼承 BeanPostProcessor 提供新的介面,因為這樣才能通過 instanceof 判斷出具有標記性的介面。所以關於 Bean 等等的操作,以及監聽 Aware、獲取 BeanFactory,都需要在 Bean 的生命週期中完成。那麼我們在設計屬性和 Bean 物件的注入時候,也會用到 BeanPostProcessor 來完成在設定 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值。整體設計結構如下圖:

  • 要處理自動掃描注入,包括屬性注入、物件注入,則需要在物件屬性 applyPropertyValues 填充之前 ,把屬性資訊寫入到 PropertyValues 的集合中去。這一步的操作相當於是解決了以前在 spring.xml 配置屬性的過程。
  • 而在屬性的讀取中,需要依賴於對 Bean 物件的類中屬性的配置了註解的掃描,field.getAnnotation(Value.class); 依次拿出符合的屬性並填充上相應的配置資訊。這裡有一點 ,屬性的配置資訊需要依賴於 BeanFactoryPostProcessor 的實現類 PropertyPlaceholderConfigurer,把值寫入到 AbstractBeanFactory的embeddedValueResolvers集合中,這樣才能在屬性填充中利用 beanFactory 獲取相應的屬性值
  • 還有一個是關於 @Autowired 對於物件的注入,其實這一個和屬性注入的唯一區別是對於物件的獲取 beanFactory.getBean(fieldType),其他就沒有什麼差一點了。
  • 當所有的屬性被設定到 PropertyValues 完成以後,接下來就到了建立物件的下一步,屬性填充,而此時就會把我們一一獲取到的配置和物件填充到屬性上,也就實現了自動注入的功能。

四、實現

1. 工程結構

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

工程原始碼公眾號「bugstack蟲洞棧」,回覆:Spring 專欄,獲取完整原始碼

自動掃描注入佔位符配置和物件的類關係,如圖 15-2

圖 15-2

  • 在整個類圖中以圍繞實現介面 InstantiationAwareBeanPostProcessor 的類 AutowiredAnnotationBeanPostProcessor 作為入口點,被 AbstractAutowireCapableBeanFactory建立 Bean 物件過程中呼叫掃描整個類的屬性配置中含有自定義註解 ValueAutowiredQualifier,的屬性值。
  • 這裡稍有變動的是關於屬性值資訊的獲取,在註解配置的屬性欄位掃描到資訊注入時,包括了佔位符從配置檔案獲取資訊也包括 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);
            
            // ... 佔位符替換屬性值、設定屬性值

            // 向容器中新增字串解析器,供解析@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<StringValueResolver> 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個註解在我們日常使用 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 是實現介面 InstantiationAwareBeanPostProcessor 的一個用於在 Bean 物件例項化完成後,設定屬性操作前的處理屬性資訊的類和操作方法。只有實現了 BeanPostProcessor 介面才有機會在 Bean 的生命週期中處理初始化資訊
  • 核心方法 postProcessPropertyValues,主要用於處理類含有 @Value、@Autowired 註解的屬性,進行屬性資訊的提取和設定。
  • 這裡需要注意一點因為我們在 AbstractAutowireCapableBeanFactory 類中使用的是 CglibSubclassingInstantiationStrategy 進行類的建立,所以在 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues 中需要判斷是否為 CGlib 建立物件,否則是不能正確拿到類資訊的。ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz;

5. 在Bean的生命週期中呼叫屬性注入

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);
            // 在設定 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值
            applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
            // 給 Bean 填充屬性
            applyPropertyValues(beanName, bean, beanDefinition);
            // 執行 Bean 的初始化方法和 BeanPostProcessor 的前置和後置處理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }

        // 註冊實現了 DisposableBean 介面的 Bean 物件
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        // 判斷 SCOPE_SINGLETON、SCOPE_PROTOTYPE
        if (beanDefinition.isSingleton()) {
            registerSingleton(beanName, bean);
        }
        return bean;
    }

    /**
     * 在設定 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 方法中有這一條新增加的方法呼叫,就是在設定 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值 的操作 applyBeanPostProcessorsBeforeApplyingPropertyValues
  • 那麼這個 applyBeanPostProcessorsBeforeApplyingPropertyValues 方法中,首先就是獲取已經注入的 BeanPostProcessor 集合並從中篩選出繼承介面 InstantiationAwareBeanPostProcessor 的實現類。
  • 最後就是呼叫相應的 postProcessPropertyValues 方法以及迴圈設定屬性值資訊,beanDefinition.getPropertyValues().addPropertyValue(propertyValue);

五、測試

1. 事先準備

配置 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("測試結果:" + userService.queryUserInfo());
}
  • 單元測試時候就可以完整的測試一個類注入到 Spring 容器,同時這個屬性資訊也可以被自動掃描填充上。

測試結果

測試結果:小傅哥,北京,亦莊,RejDlI78hu223Opo983Ds

Process finished with exit code 0

  • 從測試結果可以看到現在我們的使用方式已經通過了,有自動掃描類,有註解注入屬性。這與使用 Spring 框架越來越像了。

六、總結

  • 從整個註解資訊掃描注入的實現內容來看,我們一直是圍繞著在 Bean 的生命週期中進行處理,就像 BeanPostProcessor 用於修改新例項化 Bean 物件的擴充套件點,提供的介面方法可以用於處理 Bean 物件例項化前後進行處理操作。而有時候需要做一些差異化的控制,所以還需要繼承 BeanPostProcessor 介面,定義新的介面 InstantiationAwareBeanPostProcessor 這樣就可以區分出不同擴充套件點的操作了。
  • 像是介面用 instanceof 判斷,註解用 Field.getAnnotation(Value.class); 獲取,都是相當於在類上做的一些標識性資訊,便於可以用一些方法找到這些功能點,以便進行處理。所以在我們日常開發設計的元件中,也可以運用上這些特點。
  • 當你思考把你的實現融入到一個已經細分好的 Bean 生命週期中,你會發現它的設計是如此的好,可以讓你在任何初始化的時間點上,任何面上,都能做你需要的擴充套件或者改變,這也是我們做程式設計時追求的靈活性。

七、系列推薦

相關文章