實現服務的自動注入
前言
最近在自己寫rpc 框架,目前已完成服務註冊發佈、註冊中心、服務發現功能,Github 地址爲:https://github.com/caigoumiao/mrpc
在做服務的消費端時,想要實現服務代理的自動注入,不用每次調用服務都暴露代理的生成細節,就像這樣:(一個註解搞定)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MRpcInjection
{
}
@Component
public class TopService
{
@MRpcInjection
private TestService testService;
public void callTestService()
{
User u = testService.getUser(21);
System.out.println(u);
}
}
通過一個註解 @MRpcInject 實現字段testService 自動注入Proxy 對象。
如何實現?
主要分爲下面兩步:
找到被註解標註的field
想法:在每個Bean 初始化完成之後,遍歷其包含的field, 如果是被 @MRpcInject 註解的,則是我們所找的field。
BeanPostProcessor提供了兩個方法:postProcessBeforeInitialization和postProcessAfterInitialization,主要針對bean初始化提供擴展。
- postProcessBeforeInitialization() 會在每一個bean實例化之後、初始化(如afterPropertiesSet方法)之前被調用。
- postProcessAfterInitialization() 則在每一個bean初始化之後被調用。
所以我們自定義Spring 的BeanPostProcessor,然後在postProcessAfterInitialization() 中判斷其包含字段是否有@MRpcInject 註解即可。
具體實現:
public class ClientPostProcessor implements BeanPostProcessor
{
private Logger log = LoggerFactory.getLogger(this.getClass());
private ServiceImporter serviceImporter;
@Override
public Object postProcessAfterInitialization(Object bean , String beanName) throws BeansException
{
log.info("Bean[" + beanName + "] initialized ...");
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields)
{
// 當字段被 @MRpcInjection 註解時,爲此字段自動注入服務代理類
if (field.getAnnotation(MRpcInjection.class) != null)
{
// 找到指定註解的字段,準備注入代理類
}
}
return bean;
}
}
向field 中注入代理類
這裏主要使用field 提供的set() 方法:
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}
第一個參數爲字段所在對象,第二個參數爲注入的值。
具體實現如下:
// 首先得先設置該字段可訪問
field.setAccessible(true);
// 根據服務類型生成服務的動態代理
Class<?> serviceClass = field.getType();
Object serviceProxy = serviceImporter.importService(serviceClass);
// 將該字段值設置爲生成的代理類
field.set(bean , serviceProxy);
測試一下
在確認服務已發佈的情況下,客戶端服務調用:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ClientConfig.class);
context.getBean(TopService.class).callTestService();
輸出結果:(服務提供者的實現結果)
User(name=miao, age=21)
結果表明TestService 已正確注入!!!
後記
查看完整代碼,見https://github.com/caigoumiao/mrpc。此項目爲本人在學習一些知識後自己動手的產物,目前還存在很多問題,正在不斷的完善中,有想學習相關內容的小夥伴可以一起看看!!!