I am migrating a legacy web services (SOAP) application to Spring Boot 2.x. I get the application to start up successfully, load the context, but none of my (existing) SOAP endpoints are getting mapped.
The application has many different SOAP services defined using the #WebService and #WebMethod annotations. The mapping is currently defined in sun-jaxws.xml and web.xml, but on Spring Boot I'd like to go without any XML and only use java and annotations.
As an example, consider the follwing IndexingService in its current legacy implementation:
sun-jaxws.xml:
<endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
<endpoint implementation="com.myapp.soap.IndexingService" name="indexingService" url-pattern="/soap/indexing"/>
...
</endpoints>
web.xml:
<!-- jax-ws web service servlet context listener -->
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<!-- jax-ws web services servlet -->
<servlet>
<servlet-name>myWSServlet</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myWSServlet</servlet-name>
<url-pattern>/soap/indexing</url-pattern>
</servlet-mapping>
IndexingService.java:
#WebService
public class IndexingService {
#WebMethod(operationName = "add")
public void add(#WebParam(name = "document") String document) {
...
}
}
I included the spring-boot-starter-web-services starter and created a WsConfigurerAdapter implementation:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext context) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(context);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(messageDispatcherServlet, "/soap/*");
}
}
However, after this I am stuck. How do I map the IndexingService annotated with javax.jws.WebService to this messageDispatcherServlet? Is this even possible without XML on Spring Boot?
I'd like to avoid having to rewrite all SOAP services to Spring Boot components if possible, since that will be a significant amount of work for all services.
Finally, I also would like to expose the (generated) WSDL on these SOAP services, since this is also already working. Is there an easy way to achieve this?
Related
I want to remove my web.xml file from my Spring boot project.
I'm trying an hybrid approach (as this guide).
I've this MessageBrokerServlet define in my web.xml as follow:
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
This servlet uses a services-config.xml file about its configuration.
In the guide when I've mapped a servlet with a config location file I must write as follow:
ServletRegistration.Dynamic dispatcher = container
.addServlet("dispatcher", new DispatcherServlet(context));
So I link the servlet with its context file, but my MessageBrokerServlet has not a construction with parameter, so I've written this following code:
// MessageBrokerServlet
XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocation("/WEB-INF/flex/services-config.xml");
MessageBrokerServlet mbs = new MessageBrokerServlet();
mbs.init(context.getServletConfig());
ServletRegistration.Dynamic messageBrokenServlet = container.addServlet("MessageBrokerServlet", mbs);
messageBrokenServlet.setLoadOnStartup(1);
messageBrokenServlet.addMapping("/messagebroker/*");
container.addListener(new ContextLoaderListener(context));
But when I get the servletConfig from context object it is null.
How can I pass to my MessageBrokerServlet the correct servletConfig, so I can linked to services-config.xml file?
The lifecycle of the MessageBrokerServlet is handled by the servlet container. This is no difference (well a little maybe) in a Spring Boot application. So no need to call the init method.
The MessageBrokerServlet has also nothing to do with Spring so trying to load the configuration with Spring and then expect the MessageBrokerServlet to act on it won't do anything either.
With Spring Boot you can simply register a servlet as a bean in the application context and Spring Boot will take care of the registration. To register the servlet wrap it in a ServletRegistrationBean to pass the init parameters and further configuration. See also this section of the Spring Boot Reference guide.
#Bean
public MessageBrokerServlet messageBrokerServlet() {
return new MessageBrokerServlet();
}
#Bean
public ServletRegistrationBean<MessageBrokerServlet> messageBrokerServletRegistration() {
ServletRegistrationBean<MessageBrokerServlet> registration =
new ServletRegistrationBean(messageBrokerServlet(), "/messagebroker/*");
return registration;
}
Place this in your #SpringBootApplication annotated class or a specific #Configuration annotated class.
This mimics exactly what you have posted as the part in your web.xml. If you have additional init-param blocks (you didn't show them) you can use the addInitParameter method to add those to the ServletRegistrationBean.
I'm trying to use Jersey 2 with Spring with help of this article:
How to use Jersey 2 with Spring IoC container
But autowired bean is null when the application tries to call it after the client request.
In applicationContext.xml i have only component-scan setting.
In pom.xml:
<spring.version>4.1.0.RELEASE</spring.version>
<jersey.version>2.12</jersey.version>
#Component
#RequestScoped
#Path("/user")
public class UserREST {
#Autowired
private UserFacade userFacade;
#POST
#Path("/auth")
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_JSON})
public AuthResponse authorize(User user){
return userFacade.authorize(user); // Null is caught here
}
}
-
#Component
public class UserFacade {
public AuthResponse authorize(com.pushock.model.User user){
AuthResponse response = new AuthResponse();
response.setAuthorized(true);
return response;
}
}
What am I doing wrong?
UPD:
Here is my pom.xml https://bitbucket.org/spukhov/memo-ws/src/00724e00e3aa786f62fd0e43fe0606de6ae569df/pom.xml?at=master
Spring managed beans cannot be injected to JAX-RS classes directly, you need to use Jersey extension for integrating it with Spring.
There is a maven dependency which you don't have in your pom.xml
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.12</version>
</dependency>
Refer to Jersey Documentation: Chapter 22. Spring DI and at the bottom of the page, there is a link to sample spring integration Github project.
Another problem I've seen in your project is you didn't show how spring context should be loaded and configured. You need to configure it in your web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
and in case you are using java based approach for spring configuration, you also need to :
servletContext.setInitParameter("contextConfigLocation", "<NONE>");
in your WebApplicationInitializer implementation
I have problem figuring out why My Jersey RESTful entry point can't find the Spring Bean that I configure when the app server starts. It kept getting NullPointerException after trying from
Spring DI - Autowired property is null in a REST service
NullPointerException on #Autowired attribute with Jersey and Spring for REST service
#Autowired is not working with jersey and spring
Integrating both spring mvc and Jersey, getting a null pointer when viewing a jersey endpoint
Jersey 2 + Spring: #Autowired is null
Web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.testing.resource</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Spring-context.xml
<context:annotation-config />
<context:component-scan base-package="com.testing.config, com.testing.repository, com.testing.workflow" />
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:jdbc.properties</value>
</property>
</bean>
Jersey servlet entry point
#Component
#Produces(MediaType.APPLICATION_JSON)
#Path("/{userId}/items")
public class UserResource
{
#Autowired
private UserWorkFlow userWorkFlow;
#GET
public Response getAllItems(#PathParam("userId") String userId)
{
// ** NullPointerException here complains about the UserWorkFlow
return Response.status(200).entity(userWorkFlow.getItemsFor(userId)).build();
}
}
Service layer
I also tried to make an interface for this but it didn't work.
#Service
public class UserWorkFlow
{
#Autowired
private AllItems allItems;
public List<Item> getItemsFor(String id)
{
return allItems.getItemsFor(id);
}
}
Repository and DAO
#Repository
public class AllItems
{
#Autowired
private ItemSql itemSql;
public List<Item> getItemsFor(String id)
{
return itemSql.getAllItemsFor(id);
}
}
MyBatis Mapper (this has a XML associated with it)
public interface UserSql
{
List<Item> getAllItemsFor(#Param("userId") String userId);
}
I also changed to com.sun.jersey from org.glassfish.jersey but didn't work. I am running out of ideas what could be wrong. Can anyone spot what did I do wrong ?
The link I provided for your previous question had links to four fully working examples. You could have easily just grabbed one of the examples and built on top of it.
I will just walk you through one of the examples. Maybe it was too hard to follow. I will use the Jersey 2.x with Spring XML config.
First, make sure you have the dependencies (only showing versions to ones not shown in the pom)
jersey-container-servlet: 2.22.1
spring-web: 3.2.3.RELEASE
commons-logging
jersey-spring3: 2.22.1. (Notice the snapshot project uses jersey-spring*4*. This is not yet released, and will be released in the next Jersey release)
Second, make sure your web.xml is in order
Third, add your applicationContext.xml to the project class-path.
Fouth, the MyApplication class listed in the previous web.xml.
If you follow the example to the T, you will have a working example. It may not be the exact way you want to configure your Spring components, but you will have a working example you can build off of and tweak around to see what works and what doesn't. When you get more comfortable, you can see the other examples (in the first link of this answer) for other configuration styles/options.
I have extended ServiceImpl and DAOImpl classes with SpringBeanAutowiringSupport, It solved my autowired null pointer exception.
I am trying to add Swagger to an existing application that uses Jersey 1.19. For adding Swagger to the application, I have been following this guide: https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-1.X-Project-Setup-1.5.
When I deploy the application on Apache Tomcat, I get the following error:
SEVERE: Conflicting URI templates. The URI template / for root resource class io.swagger.jaxrs.listing.ApiListingResource and the URI template / transform to the same regular expression (/.*)?
The odd thing is that my Jersey servlet is not deployed at the root context, but at the /api/* context as shown in the web.xml file:
<servlet>
<servlet-name>MyApp Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>app.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyApp Service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
My MyApplication class is defined below:
public class MyApplication extends Application {
private final Set<Object> singletons = new HashSet<Object>();
private final Set<Class<?>> classes = new HashSet<Class<?>>();
public MyApplication() {
MyResource resource= new MyResource();
singletons.addAll(Arrays.asList(resource));
BeanConfig beanConfig = new BeanConfig();
beanConfig.setBasePath("/api");
beanConfig.setResourcePackage(getClass().getPackage().getName());
beanConfig.setTitle("REST API");
beanConfig.setVersion("1.0.0");
beanConfig.setScan(true);
classes.add(io.swagger.jaxrs.listing.ApiListingResource.class);
classes.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
return singletons;
}}
I have tried other configurations, such as defining the Swagger servlet in the web.xml file instead of using the BeanConfig, but the same error still occurs. I have gotten Swagger to work this way on a different project that uses Jersey 2, but unfortunately the current project has to remain on Jersey 1.19. Here is the Swagger dependency defined in the pom.xml file:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey-jaxrs</artifactId>
<version>1.5.0</version>
</dependency>
Any help would be appreciated.
Update 2: Looks like version 1.5.8 of swagger-core fixes that issue. See this commit for details.
Update: Instead of adding Swagger resource as sub-resource it much easier to just override #Path mapping. See Solution 2 for details.
I was facing exactly the same problem. The cause of that is Swagger resource being mapped to root #Path("/") public class ApiListingResource.
Solution 1 - No concurring mappings
One simple and inflexible way around it, is not to define any resource mapping to root path #Path("/").
Solution 2 - Override #Path mapping
2.1 Override Swagger classes
ApiListingResource should get a new #Path mapping
package my.api.package.swagger
import javax.ws.rs.Path;
#Path("swagger")
public class ApiListingResource extends io.swagger.jaxrs.listing.ApiListingResource {}
SwaggerSerializers should get new package
package my.api.package.swagger;
import javax.ws.rs.ext.Provider;
#Provider
public class SwaggerSerializers extends io.swagger.jaxrs.listing.SwaggerSerializers {}
2.2 Configure overriden classes
Add my.api.package.swagger instead of io.swagger.jaxrs.listing in Swagger package config.
Solution 3 - Swagger resource as sub-resource
Other solution is to move Swagger to a different path, allowing your resources to be mapped anywhere you like. To achieve this you need to:
3.1 Remove ApiListingResource from provider classes.
if you subclass Application:
public MyApplication() {
//init BeanConfig
//add your resource classes
//classes.add(io.swagger.jaxrs.listing.ApiListingResource.class);
classes.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
}
if you configure via web.xml using com.sun.jersey.config.property.packages param:
<servlet>
<servlet-name>your-rest-api</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
{your_application_packages},
<!--io.swagger.jaxrs.json,-->
<!--io.swagger.jaxrs.listing-->
</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.classnames</param-name>
<param-value>
io.swagger.jaxrs.listing.SwaggerSerializers,
io.swagger.jaxrs.json.JacksonJsonProvider
</param-value>
</init-param>
</servlet>
BTW, I have noticed that GF 3.1.2.2 with Jersey configured using <filter/> in web.xml does not work with Swagger due to Grizzly related bug.
3.2 Add ApiListingResources as a subresource to one of your resources
#Path("/")
class RootResource {
#Context ServletContext context;
#Path("/swagger")
public ApiListingResource swagger() {
return new ApiListingSubResource(context);
}
}
As ApiListingResource is now not managed by Jersey, it does not get ServletContext injected. To overcome this problem you have to pass it as a constructor parameter, and for that subclass ApiListingResource and provide proper constructor:
// context has 'default' visibility
// so we need to stay in the same package
// to be able to access it
package io.swagger.jaxrs.listing;
import javax.servlet.ServletContext;
public class ApiListingSubResource extends ApiListingResource {
public ApiListingSubResource(ServletContext sc) { this.context = sc; }
}
Now your Swagger descriptors will be under http://hostname/app-path/swagger/swagger.json and you will still be able to use the root resource.
It's a little bit longer way , but works! Hope that helps.
TLDR at the bottom:
As per the JBossWS-cxf user guide, for a web service, the web.xml should contain the following
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>MyWebService</servlet-name>
<servlet-class>com.sgb.MyWebService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyWebService</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Jboss also expects a descriptor file named jboss-cxf.xml in WEB-INF directory (instead of cxf.xml) which should contain the jaxws:endpoint tag like so:
<beans xmlns='http://www.springframework.org/schema/beans'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:beans='http://www.springframework.org/schema/beans'
xmlns:jaxws='http://cxf.apache.org/jaxws'
xsi:schemaLocation='http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://cxf.apache.org/jaxws >
<bean id="MyWebService" class="com.sgb.MyWebService" />
<jaxws:endpoint id="POJOEndpoint" implementor="#MyWebService" wsdlLocation="WEB-INF/wsdl/XYZ.wsdl" address="/warfilename">
<jaxws:invoker>
<bean class="org.jboss.wsf.stack.cxf.InvokerJSE" />
</jaxws:invoker>
</jaxws:endpoint>
</beans>
I then create my service implementation class thusly:
package com.sgb;
#javax.jws.WebService(... ... ... )
public class MyWebService implements IMyWebService
{
public CreateResponse create(CreateRequest request)
{
... ... ... <-- an instance of createService is created
return createService.serve(request)
}
}
So far so good. It works fine.
However, as per Spring's reference documentation, the convenient way to instantiate an application context for web applications is by adding a ContextLoaderListener in the web.xml like so.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
So, I could add the above in my web.xml and then annotate MyWebService class with #Service and make sure the package is set up for component-scaning.
And it should become a spring managed bean too.
Problem is, it doesn't.
JbossWS-CXF seems to be instantiating MyWebService due to which the dependencies are not injecte resulting in a nullpointer.
I am able to get the applicationContext programmatically using ClassPathXmlApplicationContext("/WEB-INF/applicationContext.xml")
And then inject/create my dependencies using appContext.getBean()
But I was hoping to inject/autowire the dependencies directly using annotations instead.
TLDR:
What I currently have is this. (This bean is created by jboss and not spring):
#javax.jws.WebService(... ... ... )
public class MyWebService implements IMyWebService
{
private ApplicationContext appContext;
public MyWebService(){
appContext = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext-ws.xml");
}
public CreateResponse create(CreateRequest request)
{
*** Use getBean() here to get my dependency. ***
IXyzService createService = appContext.getBean("createService",IXyzService.class);
return createService.serve(request)
}
}
What I want is this:
#javax.jws.WebService(... ... ... )
#Service <-- <-- <-- ** This is Spring managed bean**
public class MyWebService implements IMyWebService
{
#Resource <-- <-- <-- **Dependency Injected by Spring**
IXyzService createService;
public CreateResponse create(CreateRequest request)
{
return createService.serve(request)
}
}
What is the best way to accomplish this ???
I found out a few days back that this is indeed possible, so editing my previous answer.
The magic glue is this:
#PostConstruct
public void postConstruct(){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
So to summarize, the following pieces are needed:
1) Load the spring context via web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/META-INF/spring/appContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
2) Annotate a method with #PostConstruct in the class that implements the interface generated by CXF like so:
#javax.jws.WebService(... ... ... )
public class MyWebService implements IMyWebService
{
#Resource <-- <-- <-- **Dependency Injected by Spring**
IXyzService createService;
#PostConstruct
public void postConstruct(){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
public CreateResponse create(CreateRequest request)
{
return createService.serve(request)
}
}
The above info is courtesy of this link:How to initialize Spring Framework inside CXF JAX-WS service
Hope this helps...
=== PREVIOUS ANSWER ===
Well. Turns out, it cannot be done - The version I have now working is the correct way - as far as I can tell.
Explanation:
Apache CXF can be used with OR WITHOUT Spring.
JbossCXF (in AS 7.x or EAP 6.x) uses CXF as the default web service stack WITHOUT Spring. So when Jboss invokes the class that implements the web service interface (MyWebService above in my example annotated with #WebService(), the spring container is NOT yet initiated.... as the configuration in web.xml or jboss-cxf.xml does not allow for it.
So, the Spring container needs to be manually started inside the SEI impl class due to which the service class itself cannot be a spring managed bean (obviously).
Hence, the need to instantiate service beans inside the method using the getBean() method.
Once the service Beans are instantiated, their dependencies are automatically managed by the spring container as they are all now spring managed bean.
Hope this helps someone.
SGB