具有自动装配功能的动态代理Bean

Bil*_*rza 8 spring dynamic-proxy

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

我正在使用接口和带注释的方法替换此层.例如,下面的代码是为web服务("foo")的操作"fetchBar"提供的.

package a.b.c.webservices;

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

    @WebServiceOperation(operation="fetchBar")
    BarRespons fetchBar(BarRequest request) throws WebServiceException;
}
Run Code Online (Sandbox Code Playgroud)

现在我想,通过一些机制,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我尝试了以下方法来实现这一目标.

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

任何人都可以告诉我缺少什么吗?如果我没有朝着正确的方向前进,请指导我.

Bil*_*rza 11

以下是我实现创建'WebService'注释接口bean的所有功能的方法,并且还支持代理实现中的自动装配.(包声明和import语句在下面的代码中省略)首先我创建WebServiceWebServiceOperation注释.

WebService注释

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebService {
    String service();
    String namespace();
}
Run Code Online (Sandbox Code Playgroud)

WebService操作注释

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServiceOperation {
    String operation();
}
Run Code Online (Sandbox Code Playgroud)

下一步是WebService从指定的包中扫描所有带注释的接口.Spring提供ClassPathScanningCandidateComponentProvider了包扫描,但它不检测接口.请查看此问题以及更多详细信息的答案.所以我扩展ClassPathScanningCandidateComponentProvider并覆盖了isCandidateComponent方法.

ClassPathScanner

public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider {

    public ClassPathScanner(final boolean useDefaultFilters) {
        super(useDefaultFilters);
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isIndependent();
    }

}
Run Code Online (Sandbox Code Playgroud)

此时,我创建了一个EnableWebServices注释来启用Web服务并提供包含带WebService注释的接口的Web服务包.

EnableWebServices Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
    WebServiceProxyConfig.class, 
    WebServiceProxyBeansRegistrar.class
})

public @interface EnableWebServices {

    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

}
Run Code Online (Sandbox Code Playgroud)

此注释可以应用于Configuration带有扫描接口的包的一些带注释的类,如下所示.

@EnableWebServices({
    "a.b.c.webservices",
    "x.y.z.webservices"
})
Run Code Online (Sandbox Code Playgroud)

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

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

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

public class TheWebServiceInfo {
    private String service;
    private String namespace;
    private String operation;
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

实现InvocationHandler传递给Proxy.newProxyInstance(以及一些其他信息)以创建代理对象.我需要为每个带WebService注释的接口分离代理对象.我现在将创建一个代理实例创建的工厂,名称为'WebServiceProxyBeanFactory'.此工厂创建的实例将成为相应的带WebService注释接口的bean .

稍后,我将公开'WebServiceProxy'和WebServiceProxyBeanFactorybean.在'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);
    }

}
Run Code Online (Sandbox Code Playgroud)

如果你还记得,早些时候我已经WebServiceProxyConfigEnableWebServices注释中导入了.WebServiceProxyConfig用于暴露WebServiceProxyWebServiceProxyBeanFactory作为bean.

WebServiceProxyConfig

@Configuration
public class WebServiceProxyConfig {

    @Bean
    public WebServiceProxy webServiceProxy() {
        return new WebServiceProxy();
    }

    @Bean(name = "webServiceProxyBeanFactory")
    public WebServiceProxyBeanFactory webServiceProxyBeanFactory() {
        return new WebServiceProxyBeanFactory();
    }

}
Run Code Online (Sandbox Code Playgroud)

现在一切都到位了.是时候编写一个钩子来开始扫描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();
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

在这个类中,我提取了EnableWebServices注释中提供的所有包.对于每个提取的包,我曾经ClassPathScanner扫描过.(这里可以改进逻辑以仅过滤带WebService注释的接口).对于每个检测到的接口,我已经注册了bean定义.请注意我已经使用classLoader和接口类型来webServiceProxyBeanFactory调用它createWebServiceProxyBean.这个工厂方法,在春天之后调用时,将返回与接口类型相同的bean,因此注册了具有正确类型的bean.可以使用接口类型将此bean注入任何位置.而且,WebServiceProxy可以注入和使用任何其他bean.因此,自动装配也将按预期工作.