注解怎么給屬性動態賦值


學而不思則罔,思而不學則殆



前言

大家都用過Spring的@Value("xxx")注解,如果沒有debug過源碼的同學對這個操作還是一知半解,工作一年了學了反射學了注解,還是不會自己手擼一個注解對屬性賦值的操作。今天就用幾分鍾時間給你講明白這個如何實現!

理想中代碼:

@Compant
public class Bean01 {
  @MyValue("張三") //自定義注解
  String name; 
}

如果學過反射,獲取類屬性上面的自定義注解對象簡直太簡單,那怎么拿到“張三”,並給Bean01這個對象的name賦值呢?
在這里我用spring的形式給大家展示一下,完成這個理想賦值的demo~


思路:

1.spring啟動,通過ComponentScan掃描注解(標簽)加載@Component裝飾的所有bean對象
2.通過Spring的BeanFactory增強,拿到Spring中注冊的類信息
(BeanFactory會把掃描到的類信息放到BeanDefinitionMap中
BeanFactory會把掃描到的類名稱放到BeanDefinitionNames中)
3.獲取BeanDefinition中class信息,通過反射技術,獲取類的屬性,進而判斷有沒有自定義的注解裝飾。
4.使用InvocationHandler,拿到自動義注解的屬性值👉(memberValues : name=“張三”)
5.再通過Class使用反射創建對象,並進行類屬性賦值
6.把賦值后對象注冊到Spring容器中,會添加到Spring的一級緩存
7.進行對象獲取的時候(getBean(xxx)),直接會從一級緩存中獲取。這樣就完成了我們注解賦值的操作~


代碼實現

1.加載spring.xml

首先在/resources目錄下創建spring.xml,目的是開啟對Spring的注解支持

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <context:component-scan base-package="com.*"/>
</beans>



2.創建自定義注解

在這里為了更好的演示,創建兩個自定義注解類

@Target(ElementType.FIELD) // 裝飾在類的屬性上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyValue {
    String value() default "";//給姓名賦值用
}

@Target(ElementType.FIELD) // 裝飾在類的屬性上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyValue2 {
    int age() default 0; //給年齡賦值用
}



3.創建Bean對象

然后我在這里創建兩個Bean的對象,使用@Compent進行注入到spring容器,這樣在BeanFactory中就可以獲取到對象信息(BeanDefinition)

@Component
public class Bean01 {
    @MyValue("張三")
    public String name;

    @MyValue2(age = 11)
    public int age;
}

@Component
public class Bean02 {
    @MyValue("李四")
    public String name;

    @MyValue2(age = 18)
    public int age;
}



4.使用PostProcessor進行擴展(邏輯在第6步)

啟動Spring的時候,@Component所裝飾的Bean對象如果實現了BeanDefinitionRegistryPostProcessor這個類,會在執行時顯示地調用他的兩個方法:
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
二者雖然都調用,但是區別在於其參數。**這里不過多介紹,可以在其他小伙伴的文章中進行學習*

@Component
public class MyPostProcess implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {}
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}



5.添加PostProcessor處理邏輯


@Component
public class MyPostProcess implements BeanDefinitionRegistryPostProcessor {
    // 用來存放獲取到存在注解配置的Bean對象
    private Map<Class, Object> beanClassAndObjectMap;
    // 解析完帶有自定義注解的bean class
    private Set<Class> hasMyAnnotationObjects;
    // 用來存放當前操作的class對象
    private Class currentClass;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 拿到BeanDefinition集合並對其初始化准備操作
        initBeanClassAndObjectMap(registry, registry.getBeanDefinitionNames());
        // 開始對包含自定義注解的屬性進行賦值
        executeSetField();
    }

    // 此方法任務是從Spring的Feactory中,拿到所有的class信息,並通過反射進行實例化 存入到map集合中
    private void initBeanClassAndObjectMap(BeanDefinitionRegistry registry, String[] beanDefinitionNames) {
        // 初始化存在注解的BeanDefinition
        beanClassAndObjectMap = new HashMap<>(beanDefinitionNames.length);
        // 初始化用來可解析的Class容器
        hasMyAnnotationObjects = new HashSet<>(beanDefinitionNames.length);

        // 根據beanDefinitionNames進行獲取BeanDefinition
        for (String beanDefinitionName : beanDefinitionNames) {
            // 這個BeanDefinition包含了某個Bean對象的class信息
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            
            // 如果這個Bean的定義信息是被注解修飾過的
            if (beanDefinition instanceof ScannedGenericBeanDefinition) {
                try {
                    //根據BeanDefinition的getBeanClassName方法獲取class信息並且存入map容器
                    Class<?> beanClass = Class.forName(registry.getBeanDefinition(beanDefinitionName).getBeanClassName());
                    beanClassAndObjectMap.put(beanClass, beanClass.newInstance());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /* 遍歷存放Class和實例化完的對象,並進行獲取屬性注解的處理操作 */
    private void executeSetField() {
        beanClassAndObjectMap.keySet().forEach(this::doSetHasAnnotationsFiled);
    }

    /* 開始對包含自定義注解的屬性進行賦值 */
    private void doSetHasAnnotationsFiled(Class beanClass) {
        this.currentClass = beanClass;

        try {
            //通過反射技術,獲取對象的所有屬性對象
            Field[] fields = beanClass.getFields();
            
            for (Field field : fields) {
                // 獲取每個屬性上所有被裝飾的注解
                Annotation[] annotations = field.getAnnotations();

				// 遍歷這些注解對象,用來判斷是否有我自定義的注解
                for (Annotation annotation : annotations) {
                    // 執行注解MyValue處理
                    doMyValueHandler(field, annotation);
                    // 執行注解MyValue2處理
                    doMyValue2Handler(field, annotation);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /* 處理@MyValue注解 */
    private void doMyValueHandler(Field field, Annotation annotation) throws Exception {
        if (annotation.annotationType() == MyValue.class) {
            doHandler(annotation, "value", field);
        }
    }

    /* 處理@MyValue2注解 */
    private void doMyValue2Handler(Field field, Annotation annotation) throws Exception {
        if (annotation.annotationType() == MyValue2.class) {
            doHandler(annotation, "size", field);
        }
    }

    // 真正的解析注解屬性內容地方
    // 獲取注解屬性值並對對象的屬性進行賦值操作
    private void doHandler(Annotation annotation, String value, Field field) throws Exception {
        // 通過注解獲取執行處理對象類
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
        // 獲取注解的屬性信息列表(Map形式)
        Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
        // 設置屬性訪問權限
        memberValues.setAccessible(true);
        // 通過屬性信息列表獲取我的注解屬性值信息
        Map map = (Map) memberValues.get(invocationHandler);
        // 給對象的屬性進行動態賦值操作 (關鍵點)
        field.set(this.beanClassAndObjectMap.get(currentClass), map.get(value));
        // 梳理完注解賦值后的bean對象后,存到set集合中,后期用於把對象存到beanFactory的緩存中
        // 后期getBean()方法獲取到的就是我們自定義填充完屬性的對象
        hasMyAnnotationObjects.add(currentClass);
    }

    /* 遍歷填充完的對象屬性,注冊到BeanFactory中 */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        this.hasMyAnnotationObjects.forEach(dataClass -> {
            beanFactory.registerSingleton(pareBeanName(dataClass), beanClassAndObjectMap.get(dataClass));
        });
    }

    /* 首字母小寫 */
    private String pareBeanName(Class dataClass) {
        return dataClass.getSimpleName().toLowerCase().charAt(0)
                + dataClass.getSimpleName().substring(1);
    }
}



6.啟動Spring

這個時候就只剩下啟動類了:

public static void main(String[] args) {
        ClassPathXmlApplicationContext app =
                new ClassPathXmlApplicationContext("spring.xml");



7.執行效果

可以查看到在BeanFactory中定義的BeanDefinition信息
在這里插入圖片描述



通過代理類對注解進行參數解析
在這里插入圖片描述



可以查看到通過注解賦值成功后的對象
在這里插入圖片描述



執行結果
在這里插入圖片描述




總結:

本文使用Spring其中一個擴展點BeanDefinitionFactoryPostProcessor接口,和注解的技術進行結合。再通過反射的機制查看哪些類的哪些屬性是我們需要去處理的。處理后對象放到統一的容器內,后期再注冊到spring的工廠中。整體思路就是這樣,如有不理解地方請大家指出 一起進步~





免責聲明!

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



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