Uploaded image for project: 'CXF'
  1. CXF
  2. CXF-5448

Spring integration via @Configuration & @ComponentScan annotations

    Details

    • Type: Improvement
    • Status: Open
    • Priority: Minor
    • Resolution: Unresolved
    • Affects Version/s: 2.6.11
    • Fix Version/s: None
    • Component/s: Integration
    • Labels:
      None
    • Estimated Complexity:
      Unknown

      Description

      Hi,

      as per dev mailing list thread started by me http://mail-archives.apache.org/mod_mbox/cxf-dev/201312.mbox/%3c1386597934463-5737561.post@n5.nabble.com%3e I would like to share my solution to get rid of XML file with CXF services definition.

      My case is rather simple (read: uncomplete) as I just want to automatically register in CXF @WebService and @WebServiceProvider annotated classes, so that they are exposed via CXFServlet.

      The end developer just needs to annotate her services with e.g. @WebService annotation and also needs to add a following Spring configuration (application code):

      SampleAppConfig.java
      import javax.jws.WebService;
      import javax.xml.ws.WebServiceProvider;
      
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.ComponentScan.Filter;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Import;
      
      @Configuration
      @Import(JaxWsConfig.class)
      @ComponentScan(value = { "package filters" },
          includeFilters = { 
            @Filter(WebService.class), 
            @Filter(WebServiceProvider.class) 
          })
      public class SampleAppConfig {
      }
      

      where JaxWsConfig is a reference to CXF Spring configuration (it should be a part of CXF):

      JaxWsConfig.java
      @Configuration
      @ImportResource({ 
        "classpath:META-INF/cxf/cxf.xml", 
        "classpath:META-INF/cxf/cxf-servlet.xml" 
        })
      public class JaxWsConfig {
      }
      

      The crucial part is Spring bean post processor (that should be also a part of CXF distribution):

      JaxWsBeanPostProcessor.java
      @Named
      public class JaxWsBeanPostProcessor implements BeanPostProcessor {
      
        @Inject
        ListableBeanFactory beanFactory;
        
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
          return bean;
        }
      
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (isWebService(bean)) {
              Bus bus = beanFactory.getBean(Bus.DEFAULT_BUS_ID, Bus.class);
              SpringEndpointImpl endpoint = new SpringEndpointImpl(bus, bean);
              // capitalization is just a nice feature - totally optional
              endpoint.setAddress("/" + StringUtils.capitalize(beanName));
              // adds ALL features registered / discovered by Spring
              Map<String, AbstractFeature> featureMap = beanFactory.getBeansOfType(AbstractFeature.class);
              endpoint.getFeatures().addAll(featureMap.values());
              endpoint.publish();
            }
            
            return bean;
        }
      
        boolean isWebService(Object bean) {
          Class<?> beanClass = bean.getClass();
          return beanClass.getAnnotation(WebService.class) != null
              || beanClass.getAnnotation(WebServiceProvider.class) != null;
        }
      }
      

      And then if you also want to configure / inject your features using CDI (Spring) you do stuff like this (application code):

      MyFeature.java
      @Named
      public class MyFeature extends AbstractFeature {
        
        @Inject
        MyInInterceptor inInterceptor;
      
        @Inject
        MyOutInterceptor outInterceptor;
      
        @Override
        protected void initializeProvider(InterceptorProvider provider, Bus bus) {
          bus.getInInterceptors().add(inInterceptor);
          bus.getOutInterceptors().add(outInterceptor);
        }
      

      Does that make sense?

      Please note that my implementation is simplified but works for me. You should probably add all other possible customizations in JaxWsBeanPostProcessor class.

        Issue Links

          Activity

          Hide
          chris@die-schneider.net Christian Schneider added a comment -

          I like the aproach. The annotation postprocessor cleanly publishes the beans in the spring context. So spring can do its magic in doing the injections into the user code.

          The only problem I see is that we need more flexibility regarding configuration of features and other configs. The current solution to simply pick up all features is probably not good enough for general usage. At least we need some way to override the configuration for some services.

          A different approach would be to define each web service bean in the @Configuration class with some nice API to do all necessary configs. It would be less convenient but more flexible.

          Show
          chris@die-schneider.net Christian Schneider added a comment - I like the aproach. The annotation postprocessor cleanly publishes the beans in the spring context. So spring can do its magic in doing the injections into the user code. The only problem I see is that we need more flexibility regarding configuration of features and other configs. The current solution to simply pick up all features is probably not good enough for general usage. At least we need some way to override the configuration for some services. A different approach would be to define each web service bean in the @Configuration class with some nice API to do all necessary configs. It would be less convenient but more flexible.
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          This looks neat. I think I can even update JAX-RS Spring server to pick up CXF interceptors in a similar fashion.

          How CXF interceptors though can be picked up individually, without the features ?

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - This looks neat. I think I can even update JAX-RS Spring server to pick up CXF interceptors in a similar fashion. How CXF interceptors though can be picked up individually, without the features ?
          Hide
          pbielicki Przemyslaw Bielicki added a comment -

          @Christian - I see your point - it's totally valid. As I said my use case is quite simple (but it will be getting more complex in the future, I suppose)
          I'm not able to "fix" this implementation as I have no complete knowledge on which elements should / should not be customizable. I suppose that CXF should offer the same flexibility to end developers, no matter whether they use XML or annotations. You could also think of keeping the simplest implementations (like the one I presented) for really simple cases - but limitations and risks (e.g. ALL features registered in Spring context will be added to all JAX-WS postprocessed - not sure devs would want that in all cases) should be documented well.

          @Sergey - I'm not sure I understood your question bet let me answer anyway. If you know the name of the bean you want to use you can simply indicate it next to @Inject annotation (you can also check javadoc for @javax.inject.Qualifier - it might be useful):

          @Inject @Named("myInterceptor") InInterceptor inInterceptor;
          

          Please let me know if my answers are clear enough.

          Show
          pbielicki Przemyslaw Bielicki added a comment - @Christian - I see your point - it's totally valid. As I said my use case is quite simple (but it will be getting more complex in the future, I suppose) I'm not able to "fix" this implementation as I have no complete knowledge on which elements should / should not be customizable. I suppose that CXF should offer the same flexibility to end developers, no matter whether they use XML or annotations. You could also think of keeping the simplest implementations (like the one I presented) for really simple cases - but limitations and risks (e.g. ALL features registered in Spring context will be added to all JAX-WS postprocessed - not sure devs would want that in all cases) should be documented well. @Sergey - I'm not sure I understood your question bet let me answer anyway. If you know the name of the bean you want to use you can simply indicate it next to @Inject annotation (you can also check javadoc for @javax.inject.Qualifier - it might be useful): @Inject @Named( "myInterceptor" ) InInterceptor inInterceptor; Please let me know if my answers are clear enough.
          Hide
          chris@die-schneider.net Christian Schneider added a comment -

          I like the idea of defining features the spring way when using spring. Or in the way of the framework of choice. It allows to inject other resources into features. I think we should favour this way instead of the cxf proprietary ways. I am planning to create a wiki page with ideas how to ideally define features, endpoints, .. in the various frameworks. This could then be the basis to shape cxf configuration for cxf 4.

          Show
          chris@die-schneider.net Christian Schneider added a comment - I like the idea of defining features the spring way when using spring. Or in the way of the framework of choice. It allows to inject other resources into features. I think we should favour this way instead of the cxf proprietary ways. I am planning to create a wiki page with ideas how to ideally define features, endpoints, .. in the various frameworks. This could then be the basis to shape cxf configuration for cxf 4.
          Hide
          pbielicki Przemyslaw Bielicki added a comment -

          News is even better, I think. @Inject and other standard annotations will also work in any other CDI container, including Java EE containers. The only Spring specific part here is the @Configuration and bean post processor which will not be needed in Java EE managed environments (e.g. JBoss will publish all JAX-WS services automatically and it also support javax.inject annotations)

          Show
          pbielicki Przemyslaw Bielicki added a comment - News is even better, I think. @Inject and other standard annotations will also work in any other CDI container, including Java EE containers. The only Spring specific part here is the @Configuration and bean post processor which will not be needed in Java EE managed environments (e.g. JBoss will publish all JAX-WS services automatically and it also support javax.inject annotations)
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          Przemyslaw, thanks for the clarification, it looks neat, yes.
          Note though I find it hard to see how this can be generalized, can work at the features level, yes, but unlikely at the individual provider level.
          But looks good nonetheless, , I'd have no problems supporting something like that at JAX-RS level too.

          Sergey

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - Przemyslaw, thanks for the clarification, it looks neat, yes. Note though I find it hard to see how this can be generalized, can work at the features level, yes, but unlikely at the individual provider level. But looks good nonetheless, , I'd have no problems supporting something like that at JAX-RS level too. Sergey
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          Hi, what exactly does ComponentScan Filter achieve in this case ? I'm thinking of optimizing similarly the JAX-RS variant, but I'm confused about the "if (isWebService(bean))" check.

          Another question: does the configuration bean, 'JaxWsConfig' have to have 'JaxWs' prefix or can it be named say "CxfConfig" or similarly, I used your example to move away ImportResource & Configuration from the JAX-RS server, it looks cleaner and def more reusable, but curious about the bean name convention

          Thanks

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - Hi, what exactly does ComponentScan Filter achieve in this case ? I'm thinking of optimizing similarly the JAX-RS variant, but I'm confused about the "if (isWebService(bean))" check. Another question: does the configuration bean, 'JaxWsConfig' have to have 'JaxWs' prefix or can it be named say "CxfConfig" or similarly, I used your example to move away ImportResource & Configuration from the JAX-RS server, it looks cleaner and def more reusable, but curious about the bean name convention Thanks
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          PropertySource and Environment annotations can probably be used to customize things like the address, etc, though they would be used in a subclass I guess.

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - PropertySource and Environment annotations can probably be used to customize things like the address, etc, though they would be used in a subclass I guess.
          Hide
          pbielicki Przemyslaw Bielicki added a comment - - edited

          ComponentScan scans Java classes in defined packages. Spring normally just looks for @Component, @Named, etc. default annotations (check ComponentScan javadoc for details) it knows about. @Filter is a way to tell Spring to look for additional (custom) annotations and to treat classes annotated with them as managed beans.

          Now, isWebService(...) is needed because Spring calls postProcessBeforeInitialization(...) and postProcessAfterInitialization(...) for EVERY bean defined in Spring context. I don't know the way to declare BeanPostProcessor for only specific type of beans. That's why this additional check is necessary.

          Naming convention is up to you

          Show
          pbielicki Przemyslaw Bielicki added a comment - - edited ComponentScan scans Java classes in defined packages. Spring normally just looks for @Component, @Named, etc. default annotations (check ComponentScan javadoc for details) it knows about. @Filter is a way to tell Spring to look for additional (custom) annotations and to treat classes annotated with them as managed beans. Now, isWebService(...) is needed because Spring calls postProcessBeforeInitialization(...) and postProcessAfterInitialization(...) for EVERY bean defined in Spring context. I don't know the way to declare BeanPostProcessor for only specific type of beans. That's why this additional check is necessary. Naming convention is up to you
          Hide
          chris@die-schneider.net Christian Schneider added a comment -

          This sounds very interesting. Could we also use this to scan for jaxrs ressources like Sergey used in the new autodetection feature (<jaxrs:server address="/" base-packages="mypackage">)? I think it may be nice to leave the scanning to spring and just supply a filter the user can specify.

          Show
          chris@die-schneider.net Christian Schneider added a comment - This sounds very interesting. Could we also use this to scan for jaxrs ressources like Sergey used in the new autodetection feature (<jaxrs:server address="/" base-packages="mypackage">)? I think it may be nice to leave the scanning to spring and just supply a filter the user can specify.
          Hide
          pbielicki Przemyslaw Bielicki added a comment -

          You can scan anything that is in your classpath.

          Show
          pbielicki Przemyslaw Bielicki added a comment - You can scan anything that is in your classpath.
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment - - edited

          So I wonder, should @Bean annotation be used instead then, which is what is used in the code contributed to the JAX-RS frontend ? There we use no Filter (whose purpose is obvious) and it still works, and if we do then we can achieve the optimization, i.e, the ApplicationContext will only list the matched beans ?

          Re the naming convention: this is good, means we can have eventually a share-able Configuration bean applicable to all the frontends

          Sergey

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - - edited So I wonder, should @Bean annotation be used instead then, which is what is used in the code contributed to the JAX-RS frontend ? There we use no Filter (whose purpose is obvious) and it still works, and if we do then we can achieve the optimization, i.e, the ApplicationContext will only list the matched beans ? Re the naming convention: this is good, means we can have eventually a share-able Configuration bean applicable to all the frontends Sergey
          Hide
          pbielicki Przemyslaw Bielicki added a comment -

          no Sergey, it won't work like this because you normally have plenty of other beans in your context e.g. Bus, interceptors, transaction managers, etc.

          BeanPostProcessor can/will post process every bean that is in your context. You could create a separate context with only JAX-WS services but it does not make any sense (e.g. you won;t be able to inject anything to you services as all other beans will be in a separate context)

          Show
          pbielicki Przemyslaw Bielicki added a comment - no Sergey, it won't work like this because you normally have plenty of other beans in your context e.g. Bus, interceptors, transaction managers, etc. BeanPostProcessor can/will post process every bean that is in your context. You could create a separate context with only JAX-WS services but it does not make any sense (e.g. you won;t be able to inject anything to you services as all other beans will be in a separate context)
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          Are you sure ? Though it is def a new thing to me, I'll need to write few tests. The user who contributed said it was working fine, what are the faults in this code in your opinion (don't worry about JAX-RS specific deps), I'm curious about the actual approach:

          http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/spring/SpringResourceServer.java

          Note, I'm fine with fixing it and aligning with your version, I'm just not convinced yet that that code will miss on Spring bus, etc, it should not.

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - Are you sure ? Though it is def a new thing to me, I'll need to write few tests. The user who contributed said it was working fine, what are the faults in this code in your opinion (don't worry about JAX-RS specific deps), I'm curious about the actual approach: http://svn.apache.org/repos/asf/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/spring/SpringResourceServer.java Note, I'm fine with fixing it and aligning with your version, I'm just not convinced yet that that code will miss on Spring bus, etc, it should not.
          Hide
          pbielicki Przemyslaw Bielicki added a comment -

          I was sure before your comment, but OK, the only way to be 100% sure is to test it... Anyway, in my case, the bean post processor receives all beans that are in my context, including bus and the rest, that's why the filtering is necessary.

          Javadoc of BeanPostProcessor interface says "ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory. "

          So, as long as you have @Named annotation on your post processor it will apply to all beans. If you want to have specific pos processors for your custom bean types you need to specify it in the bean definition. This is how I understand it and how it works on my side.

          Show
          pbielicki Przemyslaw Bielicki added a comment - I was sure before your comment, but OK, the only way to be 100% sure is to test it... Anyway, in my case, the bean post processor receives all beans that are in my context, including bus and the rest, that's why the filtering is necessary. Javadoc of BeanPostProcessor interface says "ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory. " So, as long as you have @Named annotation on your post processor it will apply to all beans. If you want to have specific pos processors for your custom bean types you need to specify it in the bean definition. This is how I understand it and how it works on my side.
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          Sure, thanks, I guess whatever works best for a user in a given case should be used ; thanks for the example so far anyway
          Cheers, Sergey

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - Sure, thanks, I guess whatever works best for a user in a given case should be used ; thanks for the example so far anyway Cheers, Sergey
          Hide
          davidkarlsen David J. M. Karlsen added a comment - - edited

          Did anything happen to this? I think this is even more applicable now as Spring Boot /@Configuration is spreading. I see the boot support in http://cxf.apache.org/docs/springboot.html - but when you are trying to fit JAX-WS and JAX-RS services into the same app, under separate url-prefixes and buses it won't work - so making the programmatic approach easier would be a great benefit. In my case I have in one app:

          • inbound JAX-RS (on one bus, with some distinct Features, some overlapping with with other bus'es)
          • inbound JAX-WS (on another bus, some distinct Features, some common with other bus'es)
          • outbound JAX-WS clients (yet another bus, with yet distinct Features, and also some overlapping ones with the others)
          Show
          davidkarlsen David J. M. Karlsen added a comment - - edited Did anything happen to this? I think this is even more applicable now as Spring Boot /@Configuration is spreading. I see the boot support in http://cxf.apache.org/docs/springboot.html - but when you are trying to fit JAX-WS and JAX-RS services into the same app, under separate url-prefixes and buses it won't work - so making the programmatic approach easier would be a great benefit. In my case I have in one app: inbound JAX-RS (on one bus, with some distinct Features, some overlapping with with other bus'es) inbound JAX-WS (on another bus, some distinct Features, some common with other bus'es) outbound JAX-WS clients (yet another bus, with yet distinct Features, and also some overlapping ones with the others)
          Hide
          sergey_beryozkin Sergey Beryozkin added a comment -

          As we've discussed on the mailing list, the JAX-WS and JAX-RS starters are meant to be simple. Supporting all these variations with multiple servlets with only the annotations will inevitably lead to some ambiguities, so one can always write a custom SpringBoot application.

          As a side note, +1 to supporting a SpringBoot JAX-WS starter shipped by CXF to support the auto-discovery of JAX-WS services. As far as CXF features are concerned the JAX-RS starter (indirectly via its SpringScan code) checks CXF Provider annotations

          Show
          sergey_beryozkin Sergey Beryozkin added a comment - As we've discussed on the mailing list, the JAX-WS and JAX-RS starters are meant to be simple. Supporting all these variations with multiple servlets with only the annotations will inevitably lead to some ambiguities, so one can always write a custom SpringBoot application. As a side note, +1 to supporting a SpringBoot JAX-WS starter shipped by CXF to support the auto-discovery of JAX-WS services. As far as CXF features are concerned the JAX-RS starter (indirectly via its SpringScan code) checks CXF Provider annotations

            People

            • Assignee:
              Unassigned
              Reporter:
              pbielicki Przemyslaw Bielicki
            • Votes:
              1 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:

                Development