學而不思則罔,思而不學則殆
前言
大家都用過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的工廠中。整體思路就是這樣,如有不理解地方請大家指出 一起進步~