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