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



作者:小傅哥
博客: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 生命周期中,你會發現它的設計是如此的好,可以讓你在任何初始化的時間點上,任何面上,都能做你需要的擴展或者改變,這也是我們做程序設計時追求的靈活性。

七、系列推薦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM