2016-09-15 1 views
3

저는 스프링 기반 프로젝트에서 웹 서비스를 호출하는 기능 레이어를 제공합니다. 각각의 웹 서비스 오퍼레이션에 대해, 메소드는 거의 동일 코드이지만 작동, 서비스 명, 오퍼레이션 명, 네임 스페이스 등 다른 정보를 가지고 생성된다.자동 연결 기능이있는 동적 프록시 Bean

이 레이어를 인터페이스 및 주석이 달린 메서드로 바꿀 것입니다. 예를 들어, 아래 코드는 웹 서비스 ("foo")의 조작 "fetchBar"에 대해 제공됩니다.

package a.b.c.webservices; 

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

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

은 지금은 어떤 메커니즘으로, 봄 날 일부 지정된 패키지 (들)에서 동적 프록시 빈을 만들 수 나는 웹 서비스를 호출하기 위해 다음 코드를 사용할 수 있습니다합니다.

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

그것을 InvocationHandler의 구현을 제공하여이 나는 java.lang.reflect.Proxy.newProxyInstance를 사용하여 동적 콩 인스턴스를 생성 한을 달성하기 위해. 그러나 Autowiring은 invocationHandler의 제공된 구현과 그 이상의 종속성에서는 작동하지 않습니다.

나는 이것을 달성하기 위해 다음과 같은 방법을 시도했다.

  • BeanFactoryPostProcessor.postProcessBeanFactoryConfigurableListableBeanFactory.registerSingleton 메서드를 사용하여 등록 된 콩을 구현했습니다.
  • ImportBeanDefinitionRegistrar.registerBeanDefinitions을 구현하고 BeanDefinitionRegistry.registerBeanDefinition을 사용하려고했지만 Autowiring을 지원하는 올바른 Bean 정의를 제공하는 방법을 혼란스럽게합니다.

누락 된 항목을 알려줄 수 있습니까? 내가 올바른 방향으로 가고 있지 않다면 나를 인도 해주세요.

답변

4

'WebService'주석이 추가 된 인터페이스 빈을 만드는 모든 기능을 구현 한 방법은 다음과 같습니다. 또한 프록시 구현 내 Autowiring을 지원합니다. (패키지 선언 및 import 문은 아래 코드에서 생략되었습니다.) 먼저 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 메소드를 오버로드했습니다. 이 시점에서

ClassPathScanner

public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider { 

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

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

} 

나는 웹 서비스를 활성화하고 WebService 주석 인터페이스를 포함하는 웹 서비스 패키지를 제공하기 위해 EnableWebServices 주석을 만들었습니다.

EnableWebServices 주석

@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 주석에 주어진 정보에서 실제 웹 서비스를 호출합니다 동적 프록시 생성에 대해 생각하는 시간이다. Java는 InvocationHandler 인터페이스 구현을 제공하고 로직을 invoke 메소드로 제공해야하는 동적 프록시를 생성하는 메커니즘을 제공합니다. 나는 이것을 다음과 같이 구현했다. WebServiceProxy

'TheWebServiceCaller'유형의 bean이 웹 서비스를 호출하는 모든 불쾌한 논리를 포함한다고 가정 해보자. 난 그냥 그것을 삽입하고 TheWebServiceInfo (WebServiceWebServiceOperation 주석에서 추출) 및 요청 개체와 함께 call 메서드를 호출 할 수 있습니다.

TheWebServiceInfo

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

WebServiceProxy (모든 필드 getter 및 setter가 있다고 가정)InvocationHandler의 Implementaion은 프록시를 생성하는 (다른 정보와 함께) Proxy.newProxyInstance에 전달

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

사물. 나는 각각의 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 주석에 WebServiceProxyConfig을 가져 왔습니다. WebServiceProxyConfig은 빈으로 WebServiceProxyWebServiceProxyBeanFactory을 표시하는 데 사용됩니다.

WebServiceProxyConfig

@Configuration 
public class WebServiceProxyConfig { 

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

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

} 

이제 모든 장소에 있습니다. 이제 웹 서비스 패키지 검색을 시작하고 동적 프록시를 빈으로 등록하기위한 후크를 작성해야 할 때입니다. ImportBeanDefinitionRegistrar 구현을 제공합니다.이 클래스에서

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

    } 

} 

WebServiceProxyBeansRegistrar, 나는 EnableWebServices 주석에 제공하는 모든 패키지를 추출 하였다. 추출한 각 패키지에 대해 ClassPathScanner을 사용하여 검사했습니다. (여기에서 논리는 WebService 주석이 달린 인터페이스 만 필터링하도록 수정 될 수 있습니다.) 감지 된 각 인터페이스에 대해 bean 정의를 등록했습니다. 내가 webServiceProxyBeanFactory을 사용했고 classLoader와 인터페이스 타입을 가지고 createWebServiceProxyBean이라고 불렀습니다. 이 팩토리 메소드는 봄 이후에 호출 될 때 인터페이스와 동일한 타입의 빈을 반환 할 것이므로 올바른 타입의 빈이 등록된다. 이 bean은 인터페이스 유형으로 어디에서나 주입 할 수 있습니다. 또한 WebServiceProxy은 다른 빈을 삽입하여 사용할 수 있습니다. 따라서 autowiring도 예상대로 작동합니다.

1

InvocationHandler는 bean입니까? Autowired 작업을 수행하는 단순한 객체가 아닌 bean으로 만들어야합니다.

+0

'InvocationHandler'를 구현 한'WebServiceProxy '를 bean으로 노출시키고'BeanFactoryPostProcessor.postProcessBeanFactory()'에서 시도해 보았습니다. 여기서'beanFactory.getBean (WebServiceProxy.class)'는'WebServiceProxy' 인스턴스를 리턴하지만 'Autowired'주석 필드는 null입니다. 여기에 'WebServiceProxy'인스턴스를 가져 와서 Proxy.newProxyInstance()에 전달해야합니다. –

+1

물론 BFPP가 beanFactory를 처리하는 동안 autowired 필드가 없습니다. 필드는 BeanPostProcessor.postProcessBeforeInitialization 단계에서 자동으로 실행되며 BFPP 후에 실행됩니다. –

+0

BFPP 단계에서 프록시를 만들 필요가 없습니다. 초기화 단계에서 일부 빈 주위에 프록시를 작성하려고한다고 생각하면 BeanFactoryPostProcessor가 아닌 BeanPostProcessor를 작성하고 postProcessAfterInitialization에서 프록시를 작성해야합니다. –