【问题标题】:Dynamic Proxy Bean with Autowiring capability具有自动装配功能的动态代理 Bean
【发布时间】:2017-01-23 06:44:21
【问题描述】:

在我正在进行的一个基于 Spring 的项目中,有一层用于调用 Web 服务的功能。对于每个 Web 服务操作,使用几乎相同的代码创建一个方法,但具有一些不同的操作特定信息(例如服务名称、操作名称、命名空间等)。

我正在用接口和带注释的方法替换这一层。例如,下面的代码是为 web service("foo") 的 "fetchBar" 操作提供的。

package a.b.c.webservices;

@WebService(service="foo", namespace="...")
public interface FooWebService {

    @WebServiceOperation(operation="fetchBar")
    BarRespons fetchBar(BarRequest request) throws WebServiceException;
}

现在我希望通过某种机制,spring 允许我从某些指定的包创建动态代理 bean,并且我可以使用以下代码来调用 Web 服务。

package a.b.c.business;

import a.b.c.webservices.FooWebService;

public class FooBusiness {

    @Autowired 
    FooWebService fooWebService;


    public Bar getBar() {

        Bar bar = null;            

        BarRequest request; 

        //create request
        BarResponse response = fooWebService.fetchBar(request);
        //extrac bar from response

        return bar;
    }
}

为了实现这一点,我使用java.lang.reflect.Proxy.newProxyInstance 创建了动态bean 实例,方法是提供InvocationHandler 的实现。但是自动装配在提供的invocationHandler 实现及其进一步的依赖项中不起作用。

我尝试了以下方法来实现这一点。

  • 使用ConfigurableListableBeanFactory.registerSingleton 方法实现了BeanFactoryPostProcessor.postProcessBeanFactory 并注册了bean。
  • 实现了ImportBeanDefinitionRegistrar.registerBeanDefinitions 并尝试使用BeanDefinitionRegistry.registerBeanDefinition,但我很困惑如何提供支持自动装配的正确 Bean 定义。

谁能告诉我缺少什么?如果我的方向不正确,请指导我。

【问题讨论】:

    标签: spring dynamic-proxy


    【解决方案1】:

    您的 InvocationHandler 是 bean 吗?您应该将它创建为一个 bean,而不仅仅是一个让 Autowired 工作的简单对象

    【讨论】:

    • 我将'WebServiceProxy'(实现InvocationHandler)作为bean 公开并在BeanFactoryPostProcessor.postProcessBeanFactory() 中进行了尝试。这里beanFactory.getBean(WebServiceProxy.class) 返回WebServiceProxy 的实例,但它的'Autowired' 注释字段为空。我需要在此处获取“WebServiceProxy”实例以将其传递给Proxy.newProxyInstance()
    • 当然,BFPP 处理 beanFactory 时没有自动装配字段。字段在 BeanPostProcessor.postProcessBeforeInitialization 阶段自动装配,并在 BFPP 之后触发。
    • BFPP阶段不需要创建任何代理。如果你真的认为你想在初始化阶段围绕一些 bean 创建代理,你需要创建你的 BeanPostProcessor,而不是 BeanFactoryPostProcessor,并在 postProcessAfterInitialization 中创建代理
    • 我不想围绕一些现有的 bean 创建代理。我想为“WebService”注释接口创建 bean。
    • 要创建这些 bean,您需要将 WebServices 作为 bean,以便 Spring 知道它们,并且您想使用 BeanPostProcessor 在它们周围创建代理。您要做的是在 postProcessAfterInitialization 中创建一个代理并返回您的代理而不是旧 bean。
    【解决方案2】:

    这是我如何实现创建“WebService”注释接口的所有功能的方法,并且还支持代理实现内部的自动装配。 (以下代码中省略了包声明和导入语句) 首先我创建了WebServiceWebServiceOperation 注释。

    WebService 注释

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface WebService {
        String service();
        String namespace();
    }
    

    WebService操作注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface WebServiceOperation {
        String operation();
    }
    

    下一步是从指定的包中扫描所有WebService 注释的接口。 Spring 提供ClassPathScanningCandidateComponentProvider 用于包扫描,但它不检测接口。请参阅this questionit's answer 了解更多详情。所以我扩展了ClassPathScanningCandidateComponentProvider 并覆盖了isCandidateComponent 方法。

    类路径扫描器

    public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider {
    
        public ClassPathScanner(final boolean useDefaultFilters) {
            super(useDefaultFilters);
        }
    
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return beanDefinition.getMetadata().isIndependent();
        }
    
    }
    

    此时我创建了EnableWebServices注解来启用Web服务并提供包含WebService注解接口的Web服务包。

    启用WebServices注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import({
        WebServiceProxyConfig.class, 
        WebServiceProxyBeansRegistrar.class
    })
    
    public @interface EnableWebServices {
    
        @AliasFor("basePackages")
        String[] value() default {};
    
        @AliasFor("value")
        String[] basePackages() default {};
    
    }
    

    这个注解可以应用到一些Configuration注解的类,带有包来扫描接口,如下所示。

    @EnableWebServices({
        "a.b.c.webservices",
        "x.y.z.webservices"
    })
    

    是时候考虑创建动态代理了,它将根据WebServiceWebServiceOperation 注释中给出的信息调用实际的Web 服务。 Java 提供了一种创建动态代理的机制,需要提供InvocationHandler 接口的实现,并在其invoke 方法中提供逻辑。我将此实现命名为WebServiceProxy

    假设“TheWebServiceCaller”类型的 bean 包含调用 Web 服务的所有讨厌的逻辑。我只是注入它并使用TheWebServiceInfo(从WebServiceWebServiceOperation 注释中提取)调用它的call 方法并请求对象。

    TheWebServiceInfo(假设所有字段都有getter和setter)

    public class TheWebServiceInfo {
        private String service;
        private String namespace;
        private String operation;
    }
    

    WebServiceProxy

    public class WebServiceProxy implements InvocationHandler {
    
        @Autowired
        private TheWebServiceCaller caller;
    
        @Override
        public Object invoke(Object target, Method method, Object[] args) throws Exception {
    
            Object request = (null != args && args.length > 0) ? args[0] : null;
    
            WebService webService = method.getDeclaringClass().getAnnotation(WebService.class);
            WebServiceOperation webServiceOperation = method.getAnnotation(WebServiceOperation.class);
    
            TheWebServiceInfo theInfo = createTheWebServiceInfo(webService, webServiceOperation);
    
            return caller.call(theInfo, request);
        }
    
        private TheWebServiceInfo createTheWebServiceInfo(WebService webService, WebServiceOperation webServiceOperation) {
            TheWebServiceInfo theInfo = new TheWebServiceInfo();
            theInfo.setService(webService.service());
            theInfo.setNamespace(webService.namespace());
            theInfo.setOperation(webServiceOperation.operation());
            return theInfo;
        }
    }
    

    InvocationHandler 的实现被传递给Proxy.newProxyInstance(连同其他一些信息)以创建代理对象。我需要为每个 WebService 带注释的接口提供单独的代理对象。我现在将创建一个工厂来代理实例创建,名称为“WebServiceProxyBeanFactory”。这个工厂创建的实例将成为对应WebService注解接口的bean。

    稍后,我会将“WebServiceProxy”和WebServiceProxyBeanFactory 公开为bean。在“WebServiceProxyBeanFactory”中,我将注入WebServiceProxy 并使用它。请注意createWebServiceProxyBean 使用泛型。这很重要。

    WebServiceProxyBeanFactory

    public class WebServiceProxyBeanFactory {
    
        @Autowired 
        WebServiceProxy webServiceProxy;
    
        @SuppressWarnings("unchecked")
        public <WS> WS createWebServiceProxyBean(ClassLoader classLoader, Class<WS> clazz) {
            return (WS) Proxy.newProxyInstance(classLoader, new Class[] {clazz}, webServiceProxy);
        }
    
    }
    

    如果你还记得,我之前在EnableWebServices 注释中导入了WebServiceProxyConfigWebServiceProxyConfig 用于将WebServiceProxyWebServiceProxyBeanFactory 暴露为bean。

    WebServiceProxyConfig

    @Configuration
    public class WebServiceProxyConfig {
    
        @Bean
        public WebServiceProxy webServiceProxy() {
            return new WebServiceProxy();
        }
    
        @Bean(name = "webServiceProxyBeanFactory")
        public WebServiceProxyBeanFactory webServiceProxyBeanFactory() {
            return new WebServiceProxyBeanFactory();
        }
    
    }
    

    现在一切就绪。是时候编写一个钩子来开始扫描 Web 服务包并将动态代理注册为 bean。我将提供ImportBeanDefinitionRegistrar 的实现。

    WebServiceProxyBeansRegistrar

    @Configuration
    public class WebServiceProxyBeansRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
    
        private ClassPathScanner classpathScanner;
        private ClassLoader classLoader;
    
        public WebServiceProxyBeansRegistrar() {
            classpathScanner = new ClassPathScanner(false);
            classpathScanner.addIncludeFilter(new AnnotationTypeFilter(WebService.class));
        }
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            String[] basePackages = getBasePackages(importingClassMetadata);
            if (ArrayUtils.isNotEmpty(basePackages)) {
                for (String basePackage : basePackages) {
                    createWebServicProxies(basePackage, registry);
                }
            }
        }
    
        private String[] getBasePackages(AnnotationMetadata importingClassMetadata) {
    
            String[] basePackages = null;
    
            MultiValueMap<String, Object> allAnnotationAttributes = 
                importingClassMetadata.getAllAnnotationAttributes(EnableWebServices.class.getName());
    
            if (MapUtils.isNotEmpty(allAnnotationAttributes)) {
                basePackages = (String[]) allAnnotationAttributes.getFirst("basePackages");
            }
    
            return basePackages;
        }
    
        private void createWebServicProxies(String basePackage, BeanDefinitionRegistry registry) {
            try {
    
                for (BeanDefinition beanDefinition : classpathScanner.findCandidateComponents(basePackage)) {
    
                    Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
    
                    WebService webService = clazz.getAnnotation(WebService.class);
    
                    String beanName = StringUtils.isNotEmpty(webService.bean())
                        ? webService.bean() : ClassUtils.getShortNameAsProperty(clazz);
    
                    GenericBeanDefinition proxyBeanDefinition = new GenericBeanDefinition();
                    proxyBeanDefinition.setBeanClass(clazz);
    
                    ConstructorArgumentValues args = new ConstructorArgumentValues();
    
                    args.addGenericArgumentValue(classLoader);
                    args.addGenericArgumentValue(clazz);
                    proxyBeanDefinition.setConstructorArgumentValues(args);
    
                    proxyBeanDefinition.setFactoryBeanName("webServiceProxyBeanFactory");
                    proxyBeanDefinition.setFactoryMethodName("createWebServiceProxyBean");
    
                    registry.registerBeanDefinition(beanName, proxyBeanDefinition);
    
                }
            } catch (Exception e) {
                System.out.println("Exception while createing proxy");
                e.printStackTrace();
            }
    
        }
    
    }
    

    在这个类中,我提取了EnableWebServices注解中提供的所有包。对于每个提取的包,我使用ClassPathScanner 进行扫描。 (这里的逻辑可以被细化为只过滤WebService注解的接口)。对于每个检测到的接口,我都注册了一个 bean 定义。请注意,我使用了webServiceProxyBeanFactory,并使用类加载器和接口类型调用了它的createWebServiceProxyBean。这个工厂方法,后面被spring调用的时候,会返回和接口相同类型的bean,所以注册了正确类型的bean。这个 bean 可以在任何接口类型的地方注入。此外,WebServiceProxy 可以注入和使用任何其他 bean。所以自动装配也将按预期工作。

    【讨论】:

    • 这对我来说很完美。我在registrar 中的getBasePackages() 方法中添加了几行,以确保如果basePackages 属性在EnableWebServices 注释中为空,则注册器使用带有启用器注释的类的包名称:这将获得注解类的包名String importingClassPackageName = ClassUtils.getPackageName(importingClassMetadata.getClassName());
    • 谢谢!我正在实现一个类似的系统,它也涉及动态代理和注入,就像你的例子一样,它也像 Spring Data JPA。这个答案很有帮助。
    • 您可以扩展“AbstractInvocationHandler”而不是实现“InvocationHandler”,否则“toString、hashCode 和 equals”将由您的调用方法处理,从而导致意外行为
    • 谢谢@dagi12,我不知道com.google.common.reflect.AbstractInvocationHandler,所以我处理了WebServiceProxy 中的hashCodeequalstoString(这没有反映在上述解决方案中)。
    • 适合像我一样快要发疯的人。在您的ProxyBeanFactory.createProxyBean() 中,您应该使用java.lang.reflect.Proxy,而不是来自CGLib 的那个。因此,您的代理应该再次实现java.lang.reflect.InvocationHandler,而不是来自 cglib 的代理。因此,您可以使用com.google.common.reflect.AbstractInvocationHandler 处理hashcode, equals` 和toString
    【解决方案3】:

    我正在考虑同样的问题,但在一个稍微轻量级的上下文中。我不需要动态加载所有 web 服务客户端。因此,我使用了 FactoryBean,并在这个工厂 bean 中构建了动态代理。这是服务自动装配的一个示例:

    public class CurrencyServiceWithDynamicProxy extends AbstractFactoryBean<CurrencyService> {
    
        ServiceClientConfiguration clientConfiguration;
    
        Object proxy;
    
        @Autowired
        public CurrencySyncFactoryDynamicProxy(ServiceClientConfigurationProvider serviceClientConfigurationProvider) {
            this.clientConfiguration = serviceClientConfigurationProvider.createClientConfig("currency");
            proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { getObjectType() }, new MyInvocationHandler());
        }
    
        @Override
        public Class<CurrencySync> getObjectType() {
            // TODO Auto-generated method stub
            return CurrencyService.class;
        }
    
        @Override
        public CurrencySync createInstance() throws Exception {
              // do some creational logic
             return (CurrencySync)proxy;
        }
    
        public CurrencySync createService() {
            JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
            factory.setServiceClass(getObjectType());
            factory.getFeatures().add(som features);
    
            return getObjectType().cast(factory.create());
        }   
    }
    

    关于公认的答案,这个工厂示例可以很容易地扩展到更动态的版本。

    【讨论】:

      猜你喜欢
      • 2012-07-14
      • 1970-01-01
      • 2013-11-02
      • 1970-01-01
      • 2017-03-09
      • 2023-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多