Spring整合Dubbo,@Reference注解源码解析

上文讲了Spring整合Dubbo和@Service相关的逻辑Spring整合Dubbo,@Service注解源码解析

本文就来讲一下Spring是如何通过@Reference注解获取到远程的服务对象

        在将Service注解的时候有这么一行代码,这行代码注册了和Service相关的BeanPostProcessor,那么与之相对的,在和Reference相关的代码中也是注入了一个BeanPostProcessor

registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);

        这行代码核心逻辑就是手动注册了一个ReferenceAnnotationBeanPostProcessor这个类核心逻辑其实和AutowiredAnnotationBeanPostProcessor很像,只不过一个扫描的是和@Reference相关的属性和方法,一个扫描的是和Autowired,@Value,@Inject相关的属性和方法,该类继承了AnnotationInjectedBeanPostProcessor且实现了InstantiationAwareBeanPostProcessorAdapter接口,那么根据Spring中Bean的生命周期流程,会走到postProcessPropertyValues这个方法(每一个Bean在创建的过程中都会经过各个BeanPostProcessor,然后根据指定场景实现他们的功能),这块是和Spring生命周期相关的逻辑,本文不做概述;

    private void registerReferenceAnnotationBeanPostProcessor(BeanDefinitionRegistry registry) {

        // Register @Reference Annotation Bean Processor
        // 注册一个ReferenceAnnotationBeanPostProcessor做为bean,ReferenceAnnotationBeanPostProcessor是一个BeanPostProcessor
        BeanRegistrar.registerInfrastructureBean(registry,
                ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class);

    }

        在Bean生成的过程中,会走到AnnotationInjectedBeanPostProcessor中的postProcessPropertyValues方法中,该方法会去扫描正在生成的Bean的属性上或者set方法上是否有@Reference注解,如果有,则会缓存并且记录为一个注入点,后续会为其注入属性值,路径如下AnnotationInjectedBeanPostProcessor.findInjectionMetadata->buildAnnotatedMetadata(clazz),(寻找注入点的逻辑其实和@Autowired那块的逻辑差不多,甚至还要更简单,学到这边的人应该都了解了Spring源码,所以也不多做概述)

    private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {

        // 哪些Filed上有@Reference注解
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
        // 哪些方法上有@Reference注解
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);
        // 返回的是Dubbo定义的AnnotatedInjectionMetadata,接下来就会使用这个类去进行属性注入
        return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);

    }

        随后便会根据到底是在方法上加了注解还是属性上加了注解分别进入到AnnotatedMethodElement.inject,AnnotatedFieldElement.inject方法中注入,不管是哪种类型,都会进入到ReferenceAnnotationBeanPostProcessor.doGetInjectedBean方法中,下面的方法就是最核心的逻辑,可以看到就是生成了两个BeanName,一个是referencedBeaName,一个是referenceName(下面会解释这两个beanName到底有啥区别),然后后面会根据你的@Reference注解包括里面的属性生成一个ReferenceBean(这是个FactoryBean),然后再将其扔到Spring的容器里面(下文会讲解为什么要将他扔到Spring里面,而不是ref属性?),最后会调用getOrCreateProxy方法返回一个代理对象(返回的其实是ReferenceBean中的ref属性),所以加了@Reference属性最后注入的值,便是这个返回的代理对象ref,在getOrCreateProxy中有一行很关键的代码便是->return referenceBean.get(),再然后便会走到ReferenceConfig.get()方法中(ReferenceBean和ReferenceConfig是继承关系),调用其中的init方法进行服务引入(Dubbo服务引入源码解析
)然后返回ref属性

    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        /**
         * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
         */
        // 得到引入服务的beanName
        // attributes里存的是@Reference注解中的所配置的属性与值
        // injectedType表示引入的是哪个服务接口
        // referencedBeanName的值为  ServiceBean:org.apache.dubbo.demo.DemoService  表示得到该服务Bean的beanName
        // referencedBeanName表示 我现在要引用的这个服务,它导出时对应的ServiceBean的beanName是什么,可以用来判断现在我引用的这个服务是不是我自己导出的
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);

        /**
         * The name of bean that is declared by {@link Reference @Reference} annotation injection
         */
        // @Reference(methods=[Lorg.apache.dubbo.config.annotation.Method;@39b43d60) org.apache.dubbo.demo.DemoService
        // 我要生成一个RefrenceBean,对应的beanName, 根据@Reference注解来标识不同
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);

        // 生成一个ReferenceBean对象
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);

        // 把referenceBean添加到Spring容器中去
        registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);

        cacheInjectedReferenceBean(referenceBean, injectedElement);

        // 创建一个代理对象,Service中的属性被注入的就是这个代理对象
        // 内部会调用referenceBean.get();
        return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
    }
    private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
        if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    wrapInvocationHandler(referenceBeanName, referenceBean));
        } else {                                    // ReferenceBean should be initialized and get immediately
            // 重点
            return referenceBean.get();
        }
    }

问题1,这边来解释一下两个name到底是干嘛的

                referenceBeanName:这个名字可以看到就是ReferenceBean的beanName,这个应该没啥异议,就是他的真名;

                referencedBeanName:这个名字其实是用来做缓存用的,其实他是构造了一个ServiceBeanName,构造这个东西有两个作用:

        1.因为你在解析@Reference之前肯定会先解析@Service注解,然后肯定是先扔到自己的容器中然后再导出到注册中心,然后你在解析@Reference注解的时候就会判断到底你要引入的这个类是远程服务的还是本地服务的,如果是本地服务的(就是上面那个existsServiceBean(referencedBeanName)的判断,这样就不用走服务引入的逻辑,就会快很多)

        2. 在调用registerReferenceBean方法注册ReferenceBean的时候,也会去判断你要注册的这个服务是否是本地服务,如果是的话,你都没有注册ReferenceBean的必要,只需要把这个别名注册一下建立你referenceBeanName->serviceName(并不是ServiceBean的name,@Service对应的被代理类,比如demoService)的关系,后面就能通过这个referenceBeanName直接拿了,因为这个Bean已经通过@Service生成了,可以直接取本地单例池里面的bean

    private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       Class<?> interfaceClass) {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        String beanName = getReferenceBeanName(attributes, interfaceClass);
        // 要引入的服务就是本地提供的一个服务
        if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref"); // ServiceBean --- ref
            // The name of bean annotated @Service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            // register Alias rather than a new bean name, in order to reduce duplicated beans
            // 如果是本地提供的一个服务,那么就@Reference(parameters=[Ljava.lang.String;@72ef8d15) org.apache.dubbo.demo.DemoService
            // 的别名是demoService,不需要是ServiceBean的名字
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
    }

所以他是根据ReferenceBean注解和注解里面的配置生成了一个相同配置的ServiceBeanName

    private String buildReferencedBeanName(AnnotationAttributes attributes, Class<?> serviceInterfaceType) {
        ServiceBeanNameBuilder serviceBeanNameBuilder = create(attributes, serviceInterfaceType, getEnvironment());
        return serviceBeanNameBuilder.build();
    }

        问题2,为啥要将ReferenceBean扔到Spring容器中?

              是为了@Autowired,因为ReferenceBean中实现了FactoryBean,所以你最后get的时候会进行服务导入,然后为其ref属性赋值,这个ref属性虽然是个代理对象,但也是DemoService类型的(代理对象和被代理类是继承关系),你如果在一个类中使用了@Reference注解,那么你本地的单例池里面也会有这个Reference注解生成的ReferenceBean(因为是FactoryBean,所以也会有对应的DemoService),就比如下面那样,那个@Autowire注入的值也应该是有的

  • 41
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值