自定義註解@MRpcInjection,實現服務的自動注入

實現服務的自動注入

前言

最近在自己寫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。此項目爲本人在學習一些知識後自己動手的產物,目前還存在很多問題,正在不斷的完善中,有想學習相關內容的小夥伴可以一起看看!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章