Fairy已經實現的功能
讀取XML格式配置文件,解析得到Bean
讀取JSON格式配置文件,解析得到Bean
基于XML配置的依賴注入
所以,理所當然,今天該實現基于注解的依賴注入了。
基于XML配置文件方式的依賴注入一直是使用依賴注入的標配。使用配置文件讓開發變的更加靈活,告別了硬編碼和擴展性差的問題。
但是,隨著時間的推移以及大量開發人員的深度使用,越發覺得配置文件顯得非常臃腫和復雜。Spring也是如此的敏銳和貼心,給我們帶了很多的注解,好比我們每天都要用的@Autowired,這樣我們不再需要在XML配置文件中在合適的位置小心翼翼的配置注冊你的Bean了。
尤其是這兩年Spring Boot被越來越多的公司使用,倡導無配置文件開發,大量簡單易用的注解呈現在開發者面前,@GetMapping、@ComponentScan、@RestController……
自定義注解
平時開發,我們能用到很多注解@Override、@Service、@Value,這些都是java或者各個框架定義好的,我們自己也可以聲明自定義注解。
在定義自定義注解之前,需要用到一些元注解,就是java事先定義好的
- @Target-定義注解的位置,
表示支持注解的程序元素的種類,
一些可能的值有TYPE, METHOD, CONSTRUCTOR, FIELD等等
- @Retention-定義注解的時機,
表示注解類型保留時間的長短
,
它接收RetentionPolicy參數,可能的值有SOURCE, CLASS, 以及RUNTIME。
- @Documented-
表示使用該注解的元素應被javadoc或類似工具文檔化
- @Inherited-
表示一個注解類型會被自動繼承
有了這些知識儲備,就可以定義自定義注解JackieAutowired了
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface JackieAutowired {
public String name() default "";
}
RetentionPolicy.RUNTIME表示注解在運行時生效
Target有兩個值,分別表示作用在方法和屬性上
聲明了一個屬性name,default表示默認值,可為空
基于注解實現依賴注入
在FairyApplicationContext中添加對于注解的處理
public FairyApplicationContext(String configLocation, ParseType parseType) {
// 加載xml并轉換為BeanDefinition
this.loadConfigFile(configLocation, parseType);
// 實例化BeanDefinition
this.instanceBeanDefinitions();
// 基于注解的依賴注入
this.annotationInject();
// 實現依賴注入
this.injectObject();
}
使用在方法上的注解處理
private void annotationInject() {
for (String beanName : instanceBeans.keySet()) {
Object bean = instanceBeans.get(beanName);
if (bean != null) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 獲取Setter方法
Method setter = propertyDescriptor.getWriteMethod();
if (setter != null && setter.isAnnotationPresent(JackieAutowired.class)) {
JackieAutowired jackieAutowired = setter.getAnnotation(JackieAutowired.class);
Object value = null;
if (jackieAutowired != null && StringUtils.isNotEmpty(jackieAutowired.name())) {
value = instanceBeans.get(jackieAutowired.name());
} else {
value = instanceBeans.get(propertyDescriptor.getName());
if (value == null) {
for (String key : instanceBeans.keySet()) {
if (propertyDescriptor.getPropertyType().isAssignableFrom(instanceBeans.get(key).getClass())) {
value=instanceBeans.get(key);//類型匹配的話就把此相同類型的
break;//找到了類型相同的bean,退出循環
}
}
}
}
setter.setAccessible(true);
try {
setter.invoke(bean, value);
} catch (Exception e) {
LOG.error("invoke setter invoke failed", e);
}
}
}
} catch (Exception e) {
LOG.error("invoke getBean failed", e);
}
}
}
}
通過內省的方式獲取Bean的屬性和getter setter方法和上篇一致
得到setter方法,通過setter.isAnnotationPresent(JackieAutowired.class)判斷該方法上是否有注解JackieAutowired
讀取注解JackieAutowired屬性name,如果能夠取到值,則直接從Bean的上下文map集合中取出
如果屬性name值沒取到,則讀取當前Bean屬性的名稱,然后依次根據名稱或類型進行加載需要注入的Bean
通過反射的方式注入實例化后的Bean,完成依賴注入
添加JackieAutowired注解
這時候只需要在FairyServiceImpl類中的setFairyDao方法上加上注解JackieAutowired即可
public FairyDao getFairyDao() {
System.out.println("===getFairyDao===: " + fairyDao1.toString());
return fairyDao1;
}
@JackieAutowired
public void setFairyDao(FairyDao fairyDao1) {
System.out.println("===setFairyDao===: " + fairyDao1.toString());
this.fairyDao1 = fairyDao1;
}
配置文件聲明如下
<beans>
<bean id="fairyService" class="com.jackie.fairy.bean.impl.FairyServiceImpl">
</bean>
<bean id="fairyDao" class="com.jackie.fairy.bean.impl.FairyDaoImpl">
</bean>
</beans>
這時候,我們不再需要在FairyServiceImpl中聲明property屬性,也不用聲明ref指向fairyDao了,因為JackieAutowired已經能夠處理他們之間的依賴關系并進行注入了。
運行結果
使用在屬性上的注解處理
private void annotationInject() {
for (String beanName : instanceBeans.keySet()) {
Object bean = instanceBeans.get(beanName);
if (bean != null) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(JackieAutowired.class)) {
JackieAutowired jackieAutowired = field.getAnnotation(JackieAutowired.class);
Object value = null;
if (jackieAutowired != null && StringUtils.isNotEmpty(jackieAutowired.name())) {
value = instanceBeans.get(jackieAutowired.name());
} else {
value = instanceBeans.get(field.getName());
if (value == null) {
for (String key : instanceBeans.keySet()) {
if (field.getType().isAssignableFrom(instanceBeans.get(key).getClass())) {
value = instanceBeans.get(key);
break;
}
}
}
}
field.setAccessible(true);
try {
field.set(bean, value);
} catch (Exception e) {
LOG.error("invoke field.set failed", e);
}
}
}
} catch (Exception e) {
LOG.error("invoke getBean failed", e);
}
}
}
}
通過反射拿到Bean的所有字段Field數組
和上面類似,通過field.isAnnotationPresent(JackieAutowired.class)判斷相應的字段上是否有JackieAutowired注解
找到JackieAutowired注解后,讀取其name屬性,如果有值,則進入上下文map中查找相應的bean實例
如果沒有配置name屬性,則通過屬性的名稱進入上下文map中根據名稱和類型進行遍歷,找到相應的bean實例
* 通過反射的方式注入實例化后的Bean,完成依賴注入
添加JackieAutowired注解
這時候添加的位置在屬性上
@JackieAutowired
private FairyDao fairyDao
其他配置同上一種情況,最終運行結果正常。
至此,Fairy實現了基于JackieAutowired注解的依賴注入。
項目地址https://github.com/DMinerJackie/fairy
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優質文章。
我的博客即將同步至騰訊云+社區,邀請大家一同入駐。
文章列表