You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带@PlatformRelated注解的Spring代理Bean,支持按@KeyParameter路由

Hey there! Let's build this @PlatformRelated annotation system step by step. The goal is to create a dynamic proxy that routes method calls to different implementations based on the @KeyParameter, while keeping Spring's core features like @Async and @Transactional fully functional for your implementation classes. Here's how to do it:

1. Define Core Annotations

First, let's create the two annotations we need: @PlatformRelated for marking interfaces that need dynamic routing, and @KeyParameter for identifying which parameter holds the routing key.

import java.lang.annotation.*;

// Marks an interface that requires a dynamic routing proxy
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlatformRelated {
}

// Marks the parameter that provides the routing key for implementation selection
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyParameter {
}

Optional: For more flexible key-to-implementation mapping, add an annotation to mark your implementation classes with their corresponding keys:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlatformImplementation {
    String value(); // The key that maps this implementation to the proxy
}

2. Implement the Dynamic Proxy Logic

We'll create a factory bean that generates the proxy, plus an invocation handler that handles the routing logic. This ensures we call Spring-managed implementation beans (so their @Async/@Transactional enhancements are preserved).

import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class PlatformProxyFactoryBean<T> implements FactoryBean<T>, ApplicationContextAware {
    private Class<T> interfaceType;
    private ApplicationContext applicationContext;

    public PlatformProxyFactoryBean(Class<T> interfaceType) {
        this.interfaceType = interfaceType;
    }

    @Override
    public T getObject() {
        return (T) Proxy.newProxyInstance(
                interfaceType.getClassLoader(),
                new Class[]{interfaceType},
                new PlatformInvocationHandler()
        );
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceType;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    private class PlatformInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Extract the routing key from the @KeyParameter marked parameter
            String routingKey = null;
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                if (parameters[i].isAnnotationPresent(KeyParameter.class)) {
                    routingKey = args[i].toString();
                    break;
                }
            }

            if (routingKey == null) {
                throw new IllegalArgumentException("No @KeyParameter found in method parameters");
            }

            // Fetch the corresponding implementation from Spring context
            T implementation = findImplementationByKey(routingKey);
            
            // Invoke the method on the Spring-managed bean (preserves all AOP enhancements)
            return method.invoke(implementation, args);
        }

        private T findImplementationByKey(String key) {
            // Match implementations marked with @PlatformImplementation
            return applicationContext.getBeansOfType(interfaceType).values().stream()
                    .filter(bean -> bean.getClass().isAnnotationPresent(PlatformImplementation.class))
                    .filter(bean -> bean.getClass().getAnnotation(PlatformImplementation.class).value().equals(key))
                    .findFirst()
                    .orElseThrow(() -> new IllegalStateException("No implementation found for key: " + key));
        }
    }
}

3. Register Proxy Beans Automatically

To make Spring automatically create proxy beans for all @PlatformRelated interfaces, we'll use an ImportBeanDefinitionRegistrar to scan and register these proxies with high priority (so they're injected instead of direct implementations).

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

public class PlatformRelatedBeanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // Scan all interfaces marked with @PlatformRelated
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(PlatformRelated.class));

        scanner.findCandidateComponents("your.base.package").forEach(beanDefinition -> {
            String interfaceClassName = beanDefinition.getBeanClassName();
            try {
                Class<?> interfaceType = ClassUtils.forName(interfaceClassName, getClass().getClassLoader());
                // Build the proxy factory bean definition
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PlatformProxyFactoryBean.class);
                builder.addConstructorArgValue(interfaceType);
                // Set highest priority to ensure the proxy is injected first
                builder.addPropertyValue("priority", Integer.MIN_VALUE);
                
                // Register the proxy using the interface's fully qualified name as the bean name
                registry.registerBeanDefinition(interfaceClassName, builder.getBeanDefinition());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Failed to load interface: " + interfaceClassName, e);
            }
        });
    }
}

Then create a configuration class to enable this registrar:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(PlatformRelatedBeanRegistrar.class)
public class PlatformRelatedConfig {
}

4. Sample Usage

Now let's build your sample interface and implementations with Spring features:

// Mark the interface with @PlatformRelated
@PlatformRelated
public interface MyInterface {
    void processRequest(@KeyParameter String platformKey);
}

// Implementation 1 with @Async
@Component
@PlatformImplementation("platform1")
@Async
public class Implement1 implements MyInterface {
    @Override
    public void processRequest(String platformKey) {
        System.out.println("Implement1 handling request for: " + platformKey);
    }
}

// Implementation 2 with @Transactional
@Component
@PlatformImplementation("platform2")
@Transactional
public class Implement2 implements MyInterface {
    @Override
    public void processRequest(String platformKey) {
        System.out.println("Implement2 handling request for: " + platformKey);
    }
}

5. Test the Setup

Inject the interface into a service and test the routing:

@Service
public class TestService {
    private final MyInterface myInterface;

    public TestService(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    public void testRouting() {
        myInterface.processRequest("platform1"); // Routes to Implement1 (async)
        myInterface.processRequest("platform2"); // Routes to Implement2 (transactional)
    }
}

Key Notes:

  • Spring Feature Preservation: The proxy calls fully managed Spring beans, so @Async, @Transactional, and other AOP enhancements work exactly as expected.
  • Priority Handling: The proxy is registered with the highest priority, so Spring will inject it instead of any direct implementation beans when you request the interface type.
  • Flexible Routing: You can modify the findImplementationByKey method to use custom mapping logic (like properties files or database entries) if needed.

内容的提问来源于stack exchange,提问作者Yang Chen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:40:30