In my spring boot ws based application I have created a jax-ws webservice following a contract first approach. The Web service is up but I cannot autowire my other beans ins
By default Spring doesn't know anything about your JAX-WS endpoints – they're managed by the JAX-WS runtime rather than Spring. You can overcome this by using SpringBeanAutowiringSupport You'd typically do so simply by subclassing it:
public class MySoapService extends SpringBeanAutowiringSupport implements MyServicePortType {
…
}
You also have the option of calling it directly from a method annotated with @PostConstruct:
public class MySoapService implements MyServicePortType {
@PostConstruct
public void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
}
Extending SpringBeanAutowiringSupport is the recommended way to get beans injected for JAX-WS endpoint class, from current spring root web application context. However this does not work with spring boot as it's a bit different on servlet context initialization.
SpringBootServletInitializer.startup() uses a custom ContextLoaderListener and does not pass the created application context to ContextLoader. Later when the object of JAX-WS endpoint class being initialized, SpringBeanAutowiringSupport depends on ContextLoader to retrieve the current application context, and always get null.
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
......
}
}
You could register a bean that implements org.springframework.boot.context.embedded.ServletContextInitializer to retrieve the application context during startup().
@Configuration
public class WebApplicationContextLocator implements ServletContextInitializer {
private static WebApplicationContext webApplicationContext;
public static WebApplicationContext getCurrentWebApplicationContext() {
return webApplicationContext;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
}
}
Then you could implement self-autowiring in your JAX-WS endpoint class.
@WebService
public class ServiceImpl implements ServicePortType {
@Autowired
private FooBean bean;
public ServiceImpl() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
WebApplicationContext currentContext = WebApplicationContextLocator.getCurrentWebApplicationContext();
bpp.setBeanFactory(currentContext.getAutowireCapableBeanFactory());
bpp.processInjection(this);
}
// alternative constructor to facilitate unit testing.
protected ServiceImpl(ApplicationContext context) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(new DefaultListableBeanFactory(context));
bpp.processInjection(this);
}
}
In unit tests you could get current spring application context injected, and call alternative constructor with it.
@Autowired
private ApplicationContext context;
private ServicePortType service;
@Before
public void setup() {
this.service = new ServiceImpl(this.context);
}
You don´t have to extend your Configuration from SpringBootServletInitializer, nor override configure() or onStartup() methods. And in no way you have to build something implementing WebApplicationInitializer. There are only few steps to do (you could also do all the steps in a seperate @Configuration-class, the class with @SpringBootApplication only needs to know, where this one is - e.g. via @ComponentScan).
Done.
@SpringBootApplication
public class SimpleBootCxfApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleBootCxfApplication.class, args);
}
@Bean
public ServletRegistrationBean dispatcherServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/soap-api/*");
}
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
@Bean
public WeatherService weatherService() {
return new WeatherServiceEndpoint();
}
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), weatherService());
endpoint.publish("/WeatherSoapService");
return endpoint;
}
}
Taking clue from @Andy Wilkinson using SpringBeanAutoWiringSupport is achieved by using
public class MySoapService extends SpringBeanAutowiringSupport implements MyServicePortType {
…
}
You also have the option of calling it directly from a method annotated with @PostConstruct:
@Service
public class MySoapService implements MyServicePortType {
@Autowired
ServletContext servletContext;
@PostConstruct
public void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
servletContext);
}
}