apache / dubbo

The java implementation of Apache Dubbo. An RPC and microservice framework.
https://dubbo.apache.org/
Apache License 2.0
40.54k stars 26.44k forks source link

[Feature] Expect to add `AutoDubboReference` annotation in spring #14952

Closed HaceraI closed 2 days ago

HaceraI commented 3 days ago

Pre-check

Search before asking

Apache Dubbo Component

Java SDK (apache/dubbo)

Descriptions

It is expected that the official will add an annotation, the main functions are:

Why is this annotation needed?

In some cases, we may integrate multiple microservices into a provider application and publish it for operation (assuming that we develop locally and want to run only one application to access all services, but when publishing to the server, we still need to split multiple modules to run)

I have tried two methods myself, but the effect is not as expected:

  1. Combined annotation method
    @Autowired
    @Documented
    @DubboReference
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoDubboReference {
     //....AliasFor
    }
  2. Custom annotation BeanPostProcessor

    /**
    * <P>AutoServiceReference BeanPostProcessor handler that processes
    * service dependencies injection by checking local Spring Beans first.
    * If not available, it falls back to remote Dubbo service lookup.</P>
    *
    * <p>This processor optimizes performance by avoiding unnecessary
    * remote calls when a local instance is available.</p>
    *
    * <p>Usage:</p>
    * <pre>
    * {@code
    * @AutoServiceReference(service = SomeService.class)
    * private SomeService someService;
    * }
    * </pre>
    *
    * @author Haceral
    */
    public class AutoServiceReferenceProcessor implements BeanPostProcessor {
    @Autowired
    private ApplicationContext applicationContext;
    
    /** Local bean cache; removes items if not accessed for over 1 hour. */
    private final Cache<Class<?>, Object> localBeanCache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.HOURS).build();
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName) throws BeansException {
        for (Field field : bean.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(AutoServiceReference.class)) {
                Class<?> serviceType = field.getAnnotation(AutoServiceReference.class).service();
                Object serviceBean = getOrCreateLocalOrRemoteBean(serviceType);
                setField(field, bean, serviceBean);
            }
        }
        return bean;
    }
    
    /**
     * Retrieve the service bean from local cache or create it via remote RPC.
     * @param serviceType       The type of service to retrieve.
     * @return                  The Spring Bean or Dubbo RPC proxy.
     */
    private Object getOrCreateLocalOrRemoteBean(Class<?> serviceType) {
        try {
            // Attempt to retrieve the service bean from cache, with a fallback to resolving if absent
            return localBeanCache.get(serviceType, () -> resolveServiceBean(serviceType));
        } catch (ExecutionException e) {
            throw new AutoServiceReferenceException(
                "Failed to retrieve or create bean for service: " + serviceType, e.getCause());
        }
    }
    
    /**
     * Resolve the service bean either locally or remotely.
     * @param serviceType       The type of service to retrieve.
     * @return                  The Spring Bean or Dubbo RPC proxy.
     */
    private Object resolveServiceBean(Class<?> serviceType) {
        try {
            return applicationContext.getBean(serviceType);
        } catch (BeansException e) {
            // try to get the service proxy through Dubbo
            Object dubboBean = createDubboProxy(serviceType);
            if (dubboBean == null) {
                throw new AutoServiceReferenceException("Unable to find service: " + serviceType);
            }
            return dubboBean;
        }
    }
    
    /**
     * Create a Dubbo proxy for the service if annotated with DubboReference.
     * @param serviceType       The type of service to retrieve.
     * @return                  The Dubbo proxy instance, or null if unavailable.
     */
    private Object createDubboProxy(Class<?> serviceType) {
        try {
            // rollback to DubboReference if not found in Spring context
            /* ReferenceBean<?> referenceBean = new ReferenceBean<>();
            referenceBean.setInterfaceClass(serviceType);
            referenceBean.setApplicationContext(applicationContext);
            referenceBean.afterPropertiesSet();
            return referenceBean; */
        } catch (Exception ex) {
            throw new AutoServiceReferenceException("Failed to create Dubbo proxy for service: " + serviceType, ex);
        }
    }
    
    /**
     * Use Java reflection API to set the specified field with the given service instance.
     * @param field     The field to inject the service instance.
     * @param bean      The target bean containing the field.
     * @param value     The service instance to inject.
     */
    private void setField(Field field, Object bean, Object value) {
        try {
            field.setAccessible(true);
            field.set(bean, value);
        } catch (IllegalAccessException e) {
            throw new AutoServiceReferenceException("Failed to inject service into field: " + field.getName(), e);
        }
    }
    }

Related issues

No response

Are you willing to submit a pull request to fix on your own?

Code of Conduct

laywin commented 3 days ago

if the provider, consumer in the same jvm, the invoke will be happens in same jvm. you may need't do that.

HaceraI commented 2 days ago

if the provider, consumer in the same jvm, the invoke will be happens in same jvm. you may need't do that.

yes, after testing, you are right, thanks. the reason is that I forgot to add the @EnableDubbo annotation, which led me to think that I needed to handle it manually

wcy666103 commented 2 days ago

Yes, it will use injvm protocol