2016-09-15 2 views
3

Dans un projet basé sur le printemps sur lequel je travaille, il existe une couche de fonctionnalités pour appeler le service Web. Pour chaque opération de service Web, une méthode est créée avec presque le même code mais avec des informations différentes, spécifiques à l'opération (par exemple, nom de service, nom d'opération, espaces de noms, etc.).Bean proxy dynamique avec capacité d'auto-apprentissage

Je remplace cette couche par des interfaces et des méthodes annotées. Par exemple, le code ci-dessous est fourni pour l'opération "fetchBar" du service Web ("foo").

package a.b.c.webservices; 

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

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

Maintenant, je veux, avec un mécanisme, le printemps me permettent de créer des grains de proxy dynamiques de certains package spécifié (s) et je peux utiliser le code suivant pour appeler le service 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; 
    } 
} 

Pour y parvenir, j'ai créé des instances dynamiques de haricots en utilisant java.lang.reflect.Proxy.newProxyInstance en lui fournissant la mise en œuvre de InvocationHandler. Mais Autowiring ne fonctionne pas dans l'implémentation fournie de invocationHandler et dans ses autres dépendances.

J'ai essayé les moyens suivants pour y parvenir.

  • Mis en œuvre BeanFactoryPostProcessor.postProcessBeanFactory et les haricots enregistrés en utilisant la méthode ConfigurableListableBeanFactory.registerSingleton.
  • Mis en œuvre ImportBeanDefinitionRegistrar.registerBeanDefinitions et essayé d'utiliser BeanDefinitionRegistry.registerBeanDefinition, mais je suis confus comment fournir une définition de Bean correcte qui prend en charge Autowiring.

Quelqu'un peut-il me dire ce qui manque? S'il vous plaît me guider si je ne vais pas dans la bonne direction.

Répondre

4

Voici comment j'ai implémenté toutes les fonctionnalités qui créent des beans d'interfaces annotées 'WebService' et qui supporte Autowiring dans l'implémentation proxy. (déclaration de paquet et les instructions d'importation sont omis dans le code ci-dessous) Tout d'abord, j'ai créé WebService et WebServiceOperation annotation.

WebService Annotation

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

WebService Opération Annotation

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

L'étape suivante consiste à analyser toutes les interfaces WebService annotés de paquets spécifiés. Spring fournit ClassPathScanningCandidateComponentProvider pour l'analyse des paquets mais ne détecte pas les interfaces. S'il vous plaît voir this question et it's answer pour plus de détails. J'ai donc étendu ClassPathScanningCandidateComponentProvider et surpasser la méthode isCandidateComponent.

ClassPathScanner

public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider { 

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

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

} 

A ce stade, j'ai créé l'annotation EnableWebServices pour activer les services Web et de fournir des packages de services Web qui contiennent WebService interfaces annotés.

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 {}; 

} 

Cette annotation peut être appliquée à une Configuration classe annotée avec des paquets pour analyser les interfaces, comme ci-dessous.

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

Il est temps de penser à la création de proxy dynamique qui invoquera service Web réel à partir des informations données dans WebService et WebServiceOperation annotations. Java fournit un mécanisme pour créer un proxy dynamique qui nécessite de fournir l'implémentation de l'interface InvocationHandler et de fournir la logique dans sa méthode invoke. Supposons qu'un bean de type "TheWebServiceCaller" contienne toute la méchante logique pour appeler un service Web. Je viens de l'injecter et d'appeler sa méthode call avec un TheWebServiceInfo (extrait des annotations WebService et WebServiceOperation) et de demander l'objet.

TheWebServiceInfo (Supposons que tous les champs ont accesseurs)

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; 
    } 
} 

implementaion de InvocationHandler est passé à Proxy.newProxyInstance (ainsi que d'autres informations) pour créer proxy objets. J'ai besoin d'objets proxy séparés pour chaque interface annotée WebService. Je vais maintenant créer une fabrique pour créer des instances de proxy et le nom est 'WebServiceProxyBeanFactory'. Les instances créées par cette fabrique deviennent des beans correspondant aux interfaces annotées WebService.

Un peu plus tard, j'exposerai «WebServiceProxy» et WebServiceProxyBeanFactory comme des beans. Dans 'WebServiceProxyBeanFactory', je vais injecter WebServiceProxy et l'utiliser. Veuillez noter que createWebServiceProxyBean utilise des génériques. C'est important.

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); 
    } 

} 

Si vous vous souvenez, plus tôt, je WebServiceProxyConfig ont importé dans EnableWebServices annotations. est utilisé pour exposer WebServiceProxy et WebServiceProxyBeanFactory comme des haricots.

WebServiceProxyConfig

@Configuration 
public class WebServiceProxyConfig { 

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

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

} 

Maintenant, tout est en place. Il est temps d'écrire un crochet pour commencer à analyser les paquets de services Web et enregistrer les proxies dynamiques en tant que beans. Je vais fournir la mise en œuvre de 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(); 
     } 

    } 

} 

Dans cette classe, j'extrait tous les paquets fournis dans l'annotation EnableWebServices. Pour chaque paquet extrait, j'ai utilisé ClassPathScanner pour numériser. (Ici, la logique peut être affinée pour filtrer uniquement les interfaces annotées WebService). Pour chaque interface détectée, j'ai enregistré une définition de bean. S'il vous plaît noter que j'ai utilisé webServiceProxyBeanFactory et a appelé son createWebServiceProxyBean avec classLoader et le type d'interface. Cette méthode d'usine, lorsqu'elle est invoquée au printemps plus tard, retournera un bean du même type que celui de l'interface, de sorte que le bean avec le type correct est enregistré. Ce bean peut être injecté n'importe où avec le type d'interface. De plus, WebServiceProxy peut injecter et utiliser n'importe quel autre haricot. Donc autowiring fonctionnera aussi comme prévu.

1

Est-ce que votre InvocationHandler est un haricot? Vous devez le créer en tant que bean, pas seulement un simple objet pour obtenir Autowired

+0

J'ai exposé 'WebServiceProxy' (implémente 'InvocationHandler') comme bean et l'ai essayé dans' BeanFactoryPostProcessor.postProcessBeanFactory() '. Ici 'beanFactory.getBean (WebServiceProxy.class)' renvoie l'instance de 'WebServiceProxy' mais ses champs annotés 'Autowired' sont null. J'ai besoin d'obtenir l'instance 'WebServiceProxy' ici pour la passer à 'Proxy.newProxyInstance()'. –

+1

Bien sûr, il n'y a pas de champs autowired pendant que BFPP traite beanFactory. Les champs sont autowired au stade de BeanPostProcessor.postProcessBeforeInitialization et il est déclenché après BFPP. –

+0

Vous n'avez pas besoin de créer de proxy à l'étape de BFPP. Si vous pensez vraiment que vous voulez créer des proxies autour de certains beans à l'étape d'initialisation, vous devez créer votre BeanPostProcessor, pas BeanFactoryPostProcessor, et créer des proxies dans postProcessAfterInitialization –