如何实现带@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
findImplementationByKeymethod to use custom mapping logic (like properties files or database entries) if needed.
内容的提问来源于stack exchange,提问作者Yang Chen

