萬人之?dāng)常ㄟ^注解給屬性注入配置和Bean對象
本文轉(zhuǎn)載自微信公眾號「bugstack蟲洞?!梗髡咝「蹈?。轉(zhuǎn)載本文請聯(lián)系bugstack蟲洞棧公眾號。
一、前言
寫代碼,就是從能用到好用的不斷折騰!
你聽過擾動函數(shù)嗎?你寫過斐波那契(Fibonacci)散列嗎?你實(shí)現(xiàn)過梅森旋轉(zhuǎn)算法嗎?怎么 沒聽過這些寫不了代碼嗎!不會的,即使沒聽過你一樣可以寫的了代碼,比如你實(shí)現(xiàn)的數(shù)據(jù)庫路由數(shù)據(jù)總是落在1庫1表它不散列分布、你實(shí)現(xiàn)的抽獎系統(tǒng)總是把運(yùn)營配置的最大紅包發(fā)出去提高了運(yùn)營成本、你開發(fā)的秒殺系統(tǒng)總是在開始后的1秒就掛了貨品根本給不出去。
除了一部分僅把編碼當(dāng)成搬磚應(yīng)付工作外的程序員,還有一部分總是在追求極致的碼農(nóng)。寫代碼還能賺錢,真開心! 這樣的碼農(nóng)總是會考慮??還有沒有更好的實(shí)現(xiàn)邏輯能讓代碼不僅是能用,還要好用呢?其實(shí)這一點(diǎn)的追求到完成,需要大量擴(kuò)展性學(xué)習(xí)和深度挖掘,這樣你設(shè)計(jì)出來的系統(tǒng)才更你考慮的更加全面,也能應(yīng)對各種復(fù)雜的場景。
二、目標(biāo)
在目前 IOC、AOP 兩大核心功能模塊的支撐下,完全可以管理 Bean 對象的注冊和獲取,不過這樣的使用方式總感覺像是刀耕火種有點(diǎn)難用。因此在上一章節(jié)我們解決需要手動配置 Bean 對象到 spring.xml 文件中,改為可以自動掃描帶有注解 @Component 的對象完成自動裝配和注冊到 Spring 容器的操作。
那么在自動掃描包注冊 Bean 對象之后,就需要把原來在配置文件中通過 property name="token" 配置屬性和Bean的操作,也改為可以自動注入。這就像我們使用 Spring 框架中 @Autowired、@Value 注解一樣,完成我們對屬性和對象的注入操作。
三、方案
其實(shí)從我們在完成 Bean 對象的基礎(chǔ)功能后,后續(xù)陸續(xù)添加的功能都是圍繞著 Bean 的生命周期進(jìn)行的,比如修改 Bean 的定義 BeanFactoryPostProcessor,處理 Bean 的屬性要用到 BeanPostProcessor,完成個性的屬性操作則專門繼承 BeanPostProcessor 提供新的接口,因?yàn)檫@樣才能通過 instanceof 判斷出具有標(biāo)記性的接口。所以關(guān)于 Bean 等等的操作,以及監(jiān)聽 Aware、獲取 BeanFactory,都需要在 Bean 的生命周期中完成。那么我們在設(shè)計(jì)屬性和 Bean 對象的注入時候,也會用到 BeanPostProcessor 來完成在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值。整體設(shè)計(jì)結(jié)構(gòu)如下圖:
- 要處理自動掃描注入,包括屬性注入、對象注入,則需要在對象屬性 applyPropertyValues 填充之前 ,把屬性信息寫入到 PropertyValues 的集合中去。這一步的操作相當(dāng)于是解決了以前在 spring.xml 配置屬性的過程。
- 而在屬性的讀取中,需要依賴于對 Bean 對象的類中屬性的配置了注解的掃描,field.getAnnotation(Value.class); 依次拿出符合的屬性并填充上相應(yīng)的配置信息。這里有一點(diǎn) ,屬性的配置信息需要依賴于 BeanFactoryPostProcessor 的實(shí)現(xiàn)類 PropertyPlaceholderConfigurer,把值寫入到 AbstractBeanFactory的embeddedValueResolvers集合中,這樣才能在屬性填充中利用 beanFactory 獲取相應(yīng)的屬性值
- 還有一個是關(guān)于 @Autowired 對于對象的注入,其實(shí)這一個和屬性注入的唯一區(qū)別是對于對象的獲取 beanFactory.getBean(fieldType),其他就沒有什么差一點(diǎn)了。
- 當(dāng)所有的屬性被設(shè)置到 PropertyValues 完成以后,接下來就到了創(chuàng)建對象的下一步,屬性填充,而此時就會把我們一一獲取到的配置和對象填充到屬性上,也就實(shí)現(xiàn)了自動注入的功能。
四、實(shí)現(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
- 在整個類圖中以圍繞實(shí)現(xiàn)接口 InstantiationAwareBeanPostProcessor 的類 AutowiredAnnotationBeanPostProcessor 作為入口點(diǎn),被 AbstractAutowireCapableBeanFactory創(chuàng)建 Bean 對象過程中調(diào)用掃描整個類的屬性配置中含有自定義注解 Value、Autowired、Qualifier,的屬性值。
- 這里稍有變動的是關(guān)于屬性值信息的獲取,在注解配置的屬性字段掃描到信息注入時,包括了占位符從配置文件獲取信息也包括 Bean 對象,Bean 對象可以直接獲取,但配置信息需要在 AbstractBeanFactory 中添加新的屬性集合 embeddedValueResolvers,由 PropertyPlaceholderConfigurer#postProcessBeanFactory 進(jìn)行操作填充到屬性集合中。
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 中,最主要的其實(shí)就是這行代碼的操作 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 是實(shí)現(xiàn)接口 InstantiationAwareBeanPostProcessor 的一個用于在 Bean 對象實(shí)例化完成后,設(shè)置屬性操作前的處理屬性信息的類和操作方法。只有實(shí)現(xiàn)了 BeanPostProcessor 接口才有機(jī)會在 Bean 的生命周期中處理初始化信息
- 核心方法 postProcessPropertyValues,主要用于處理類含有 @Value、@Autowired 注解的屬性,進(jìn)行屬性信息的提取和設(shè)置。
- 這里需要注意一點(diǎn)因?yàn)槲覀冊?AbstractAutowireCapableBeanFactory 類中使用的是 CglibSubclassingInstantiationStrategy 進(jìn)行類的創(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;
- }
- // 實(shí)例化 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);
- }
- // 注冊實(shí)現(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 的實(shí)現(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é)
- 從整個注解信息掃描注入的實(shí)現(xiàn)內(nèi)容來看,我們一直是圍繞著在 Bean 的生命周期中進(jìn)行處理,就像 BeanPostProcessor 用于修改新實(shí)例化 Bean 對象的擴(kuò)展點(diǎn),提供的接口方法可以用于處理 Bean 對象實(shí)例化前后進(jìn)行處理操作。而有時候需要做一些差異化的控制,所以還需要繼承 BeanPostProcessor 接口,定義新的接口 InstantiationAwareBeanPostProcessor 這樣就可以區(qū)分出不同擴(kuò)展點(diǎn)的操作了。
- 像是接口用 instanceof 判斷,注解用 Field.getAnnotation(Value.class); 獲取,都是相當(dāng)于在類上做的一些標(biāo)識性信息,便于可以用一些方法找到這些功能點(diǎn),以便進(jìn)行處理。所以在我們?nèi)粘i_發(fā)設(shè)計(jì)的組件中,也可以運(yùn)用上這些特點(diǎn)。
- 當(dāng)你思考把你的實(shí)現(xiàn)融入到一個已經(jīng)細(xì)分好的 Bean 生命周期中,你會發(fā)現(xiàn)它的設(shè)計(jì)是如此的好,可以讓你在任何初始化的時間點(diǎn)上,任何面上,都能做你需要的擴(kuò)展或者改變,這也是我們做程序設(shè)計(jì)時追求的靈活性。