Spring Boot注册JAX-WS webservice为bean

6
在我的Spring Boot WS应用程序中,我采用了基于合同优先的方法创建了一个JAX-WS Web服务。该Web服务已启动,但我无法在Web服务内部自动装配其他bean。
我该如何在Spring中将Web服务定义为bean?
以下是我的Web服务实现类:
@WebService(endpointInterface = "com.foo.bar.MyServicePortType")
@Service
public class MySoapService implements MyServicePortType {
    @Autowired
    private MyBean obj;

    public Res method(final Req request) {
        System.out.println("\n\n\nCALLING.......\n\n" + obj.toString()); //obj is null here
        return new Res();
    }
}

MyServicePortType由Maven从wsdl文件生成。

当我调用此服务(通过SoapUi),它会给出NullPointerException,因为MyBean对象没有自动装配。

由于我的应用程序是基于Spring Boot构建的,因此没有xml文件。目前,我有一个带有端点配置的sun-jaxws.xml文件。如何在Spring Boot应用程序中进行以下配置?

<wss:binding url="/hello">
    <wss:service>
        <ws:service bean="#helloWs"/>
    </wss:service>
</wss:binding>

以下是我的SpringBootServletInitializer类:

@Configuration
public class WebXml extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
        return application.sources(WSApplication.class);
    }

    @Bean
    public ServletRegistrationBean jaxws() {
        final ServletRegistrationBean jaxws = new ServletRegistrationBean(new WSServlet(), "/jaxws");
        return jaxws;
    }

    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new WSServletContextListener());
    }
}

根据以下链接 https://github.com/vanioinformatika/spring-boot-cxf-integration-example 使用CXF解决了该问题。 - amique
5个回答

8

SpringBeanAutowiringSupport是将bean注入JAX-WS端点类的推荐方式,从当前spring根Web应用程序上下文中。然而,这在spring boot上有所不同,因为servlet context initialization有一些差异。

问题

SpringBootServletInitializer.startup()使用自定义的ContextLoaderListener,并未将创建的应用程序上下文传递给ContextLoader。稍后初始化JAX-WS端点类对象时,SpringBeanAutowiringSupport依赖于ContextLoader来检索当前应用程序上下文,但始终得到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
                }
            });
        }
        ......
    }
}

解决方案

您可以注册一个实现 org.springframework.boot.context.embedded.ServletContextInitializer 接口的 bean,在 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);
    }
}

那么您可以在JAX-WS端点类中实现自动装配。

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

单元测试

在单元测试中,您可以注入当前Spring应用程序上下文,并使用它调用替代构造函数。

@Autowired 
private ApplicationContext context;

private ServicePortType service;

@Before
public void setup() {
    this.service = new ServiceImpl(this.context);
}

经过一整天的努力,我终于找到了你的出色解决方案。谢谢。 - chinh

2

默认情况下,Spring不知道您的JAX-WS端点 - 它们由JAX-WS运行时而不是Spring管理。您可以通过使用SpringBeanAutowiringSupport来克服这一问题。通常情况下,您只需通过对其进行子类化来实现:

public class MySoapService extends SpringBeanAutowiringSupport implements MyServicePortType {
    …
}

您还可以选择直接从使用@PostConstruct注释的方法中调用它:

public class MySoapService implements MyServicePortType {

    @PostConstruct
    public void init() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
    }
}

我已经尝试了两种方法,但都没有解决问题。 - amique
使用CXF,按照以下github.com/vanioinformatika/spring-boot-cxf-integration-example的示例解决了问题。 - amique
@wilkinson,这在sring-boot 1.4中不起作用,它可以使用Parker指定的方法来解决,是否有标准解决方案? - rajadilipkolli
这里我们谈论的是SPRING-BOOT,但为此,我们必须使用这种方法 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.embedded-containerSpringBeanAutowiringSupport - 对于SRING应该可以工作,但对于SpringBoot不行 - 对于后者我使用了@Bean public ServletRegistrationBean jaxWsServletBean() { var bean = new ServletRegistrationBean(new JaxWsServlet(), JaxWsServlet.URL_PATTERN); bean.setLoadOnStartup(1); return bean; } - Andrew Niken
但我仍然有一些问题: @Bean public ServletListenerRegistrationBean jaxWsListenerBean() { var bean = new ServletListenerRegistrationBean(); bean.setListener(new WSServletContextListener()); return bean; } (请注意,这是Java代码) - Andrew Niken

1
你不必从SpringBootServletInitializer扩展你的配置,也不必覆盖configure()或onStartup()方法。你也不需要构建实现WebApplicationInitializer的东西。只需要执行以下几个步骤(你也可以在一个单独的@Configuration类中完成所有这些步骤,带有@SpringBootApplication注解的类只需要知道这个类在哪里 - 例如通过@ComponentScan)。
  1. 在ServletRegistrationBean中注册CXFServlet
  2. 实例化SpringBus
  3. 实例化JAX-WS SEI(服务接口)的实现
  4. 使用SpringBus和实现Bean实例化EndpointImpl
  5. 在EndpointImpl上调用publish()方法
完成。
@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;
    }
}

2
另外,使用https://github.com/codecentric/cxf-spring-boot-starter,您可以省去1和2 - 完整描述在此处:https://blog.codecentric.de/en/2016/10/spring-boot-apache-cxf-spring-boot-starter/。 - jonashackt
此解决方案必须得到官方认可。 - alegria

0
尝试这样看:@ServletComponentScan
@SpringBootApplication
@EnableTransactionManagement
@ServletComponentScan
public class TelecomApplication { .... }

-1

从 @Andy Wilkinson 得到的线索,使用 SpringBeanAutoWiringSupport 可以通过以下方式实现

public class MySoapService extends SpringBeanAutowiringSupport implements MyServicePortType {
    …
}

您还可以选择从使用@PostConstruct注释的方法直接调用它:

@Service
public class MySoapService implements MyServicePortType {

    @Autowired
    ServletContext servletContext;

    @PostConstruct
    public void init() {
        SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,
            servletContext);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接