Spring autowiring fails in RESTeasy service - spring

I have a simple service which fails to autowire bean.
Although getting the same bean through context succeeds.
So the bean creation and registration in repository is working, but autowiring does not.
Changing class of the field in the service (MyRepository -> YourRepository), there is an error thrown that such bean does not exists, so the autowiring mechanism is working.
Any ideas what might be missing?
#Component
#Path("/")
public class RestService {
#Autowired
private MyRepository myRepository; // is not autowired and is null
#GET
#Path("/{param}")
#Produces(MediaType.APPLICATION_JSON)
public Response printMessage(#PathParam("param") String msg) {
return Response.ok(
AppContext.getContext().getBean("myRepository") == myRepository)
.build(); // false
}
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
}
AppContext above is my simple implementation of ApplicationContextAware
Repository
#Repository
public interface MyRepository extends MongoRepository<MyEntity, String> {
}
There is no .xml configuration and spring is initialized through
public class MyInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
container.addListener(new ResteasyBootstrap());
final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
ContextLoaderListener springListener = new ContextLoaderListener(rootContext);
rootContext.register(MyConfiguration.class);
container.addListener(springListener);
}
}
And configuration class
#Configuration
#EnableMongoRepositories("my.package.repository")
#ComponentScan("my.package")
public class MyConfiguration {
#Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
return new MongoTemplate(new MongoClient("localhost"), "db");
}
}
EDIT after 2 comments
I'm using the following library for RESTeasy - spring integration.
Do I need some other?
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring</artifactId>
<version>3.0.8.Final</version>
</dependency>
I think JAX-RS and RESTeasy is working correctly, because service is working and I can access it through web when deployed on JBoss
EDIT for workaround
Service is initialized correctly if I create the following constructor, but it feels more like a workaround
public RestService() {
this.myRepository = MyContext.getContext().getBean(MyRepository.class);
}
And MyContext class for more clarity
#Component
public class MyContext implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}

Spring might be setup correctly, but that doesn't necessary mean that Spring+RestEasy integration is setup correctly.
The code I am posting is the web.xml configuration that I have used (with RestEasy 3.0.6 and Spring 3.2.8) and correctly sets up the integration between RestEasy and Spring and also sets up Spring MVC (everything under /api is handled by RestEasy, everything else is handled by Spring MVC).
<web-app version="2.4"
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">
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>my.package.config.ApplicationConfig</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>web</param-value>
</context-param>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/api</param-value>
</context-param>
<!-- Spring + RESTEasy -->
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<listener>
<listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>
<!-- RESTEasy Servlet-->
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!-- Spring MVC Servlet -->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>my.package.config.MvcConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file></welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error404.jsp</location>
</error-page>
</web-app>

Managed to solve the issue myself.
The correct solution is either to replace
public class MyInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
container.addListener(new ResteasyBootstrap());
final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
ContextLoaderListener springListener = new ContextLoaderListener(rootContext);
rootContext.register(MyConfiguration.class);
container.addListener(springListener);
}
}
with
public class MyInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
container.addListener(new ResteasyBootstrap());
container.addListener(new SpringContextLoaderListener());
}
}
but in this case I'm losing possibility to use AnnotationConfigWebApplicationContext.
Also the initializer can be changed as follows to preserve annotation context.
public class MyInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
container.addListener(new ResteasyBootstrap());
final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
ContextLoaderListener springListener = new ContextLoaderListener(rootContext) {
#Override
protected ContextLoader createContextLoader() {
return new SpringContextLoader();
}
};
rootContext.register(MyConfiguration.class);
container.addListener(springListener);
}
}

Related

Tomcat war deployment gives 404 when using annotations instead of web.xml

I am trying to learn Spring framework. When I use Spring annotation for configuration instead of web.xml and deploy war file on Tomcat in Docker container, it gives 404 error. After switching to web.xml and servlet.xml configuration, it gives no error.
My config file:
package com.janfranco.mvctutorial.config;
...
#Configuration
#EnableWebMvc
#ComponentScan
public class AppConfig implements WebMvcConfigurer {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
My servlet file:
package com.janfranco.mvctutorial.config;
...
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { AppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
I use Maven for management. Here is my Dockerfile:
FROM tomcat:8.0.20-jre8
COPY target/mvctutorial.war /usr/local/tomcat/webapps/
How can I use annotations and get rid of xml configs?
Edit:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>mvctutorial</display-name>
<absolute-ordering />
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

CORS Response Filter not invoked Resteasy / JAX-RS 2.0

I am trying to implement a CORS response filer to allow cross-domain reference from my JavaScript front-end. I am using Wildfly 10.0.final which comes with Resteasy that is JAX-RS 2.0 compliment if I understand correctly.
EDIT: added #Provider to the CorsResponseFilter, and as a singleton to the RestServiceConfig.
What do I need to do to get my CorsResponseFilter invoked?
PS. Read these posts, but they didn't help solving the problem.
ContainerRequestFilter ContainerResponseFilter dosent get called
ResourceConfig and Application
CorsResponseFilter.java
#Provider
public class CorsResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
//headers.add("Access-Control-Allow-Origin", "http://podcastpedia.org"); //allows CORS requests only coming from podcastpedia.org
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, X-Codingpedia");
headers.add("Access-Control-Max-Age", "1209600");
}
}
RestServiceConfig.java
public class RestServiceConfig extends Application {
private final Set<Object> singletons = new HashSet<>();
public RestServiceConfig() {
singletons.add(new CorsResponseFilter());
singletons.add(new ApplicationService());
singletons.add(new TweetObsService());
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
web.xml
...
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
</listener-class>
</listener>
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.mycorp.myapp.service.RestServiceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>org.clearbyte.obs.service.CorsResponseFilter</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/service</param-value>
</context-param>
...
Wildfly log console
13:56:01,672 INFO [org.jboss.resteasy.resteasy_jaxrs.i18n] RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.clearbyte.obs.service.RestServiceConfig
13:56:01,672 INFO [org.jboss.resteasy.resteasy_jaxrs.i18n] RESTEASY002200: Adding class resource org.clearbyte.obs.service.TweetObsService from Application class org.clearbyte.obs.service.RestServiceConfig
13:56:01,672 INFO [org.jboss.resteasy.resteasy_jaxrs.i18n] RESTEASY002200: Adding class resource org.clearbyte.obs.service.ApplicationService from Application class org.clearbyte.obs.service.RestServiceConfig
So I've started over from scratch with a new project to eliminate error sources. Thanks for the input on using #Provider and adding OPTIONS. Plus I removed all configuration REST from the web.xml.
#Provider is essential for the Filter to work
ServiceCorsFilter.java
#Provider
public class ServiceCorsFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
responseContext.getHeaders().putSingle("Access-Control-Allow-Origin", "*");
responseContext.getHeaders().putSingle("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, DELETE");
responseContext.getHeaders().putSingle("Access-Control-Allow-Headers", "Content-Type");
}
}
#ApplicationPath makes web.xml configuration obsolete
ServiceConfig.java
#ApplicationPath("service")
public class ServiceConfig extends Application {
private Set<Object> singletons = new HashSet<>();
public ServiceConfig() {
singletons.add(new UserServiceV1());
singletons.add(new ServiceCorsFilter());
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
This is what is left in the web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<display-name>MyApp</display-name>
<!-- No REST related config due the the #Provider and inheritance of Application-->
</web-app>
I would try declaring it like a standard filter, not a param of the servlet dispatcher:
<filter>
<filter-name>CorsHeadersFilter</filter-name>
<filter-class>org.clearbyte.obs.service.CorsResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsHeadersFilter</filter-name>
<url-pattern>/service/*</url-pattern>
</filter-mapping>
Some browsers (namely: Chrome) send an OPTION request before issuing their request.
Since you explicitly specify 'GET' 'PUT' 'POST' 'DELETE', the 'OPTION' call does not get the headers information :)
Adding OPTION to your list should solve the issue

how to autowire bean in the servlet filters in spring application?

I have a spring-boot application.
I have no ApplicationContext.xml or web.xml files in my project. I prefer to avoid them and have everything configured in Java code.
I have read the following the posts about bean injection in servlet filters.
How can I get a Spring bean in a servlet filter?
http://www.deadcoderising.com/2015-05-04-dependency-injection-into-filters-using-delegatingfilterproxy/
spring injection in servlet filter
After reading them, I started to use DelegatingFilterProxy.
My question is how to autowire the bean into filter and avoid using xml files especially for DelegatingFilterProxy configuration.
The code snipped is available from the second post hosted in github.
public class AuditHandler {
public void auditRequest(String appName, ServletRequest request) {
System.out.println(appName + ": Received request from " + request.getRemoteAddr() );
}
}
public class AuditFilter implements Filter {
private final AuditHandler auditHandler;
private String appName;
public AuditFilter(AuditHandler auditHandler) {
this.auditHandler = auditHandler;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
auditHandler.auditRequest(appName, request);
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
appName = filterConfig.getInitParameter("appName");
}
public void destroy() {}
}
ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="auditHandler" class="com.deadcoderising.AuditHandler">
</bean>
<bean id="auditFilter" class="com.deadcoderising.AuditFilter">
<constructor-arg ref="auditHandler"/>
</bean>
</beans>
web.xml
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="true">
<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>
<filter>
<filter-name>auditFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>appName</param-name>
<param-value>di-example</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>auditFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
You should add a FilterRegistrationBean to your main Application class (class annotated with #SpringBootApplication) and let Spring provide instance of the AuditHandler:
#Bean
#Autowired
public FilterRegistrationBean auditFilterRegistration(AuditHandler handler) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new AuditFilter(handler));
filterRegistrationBean.setOrder(3); // ordering in the filter chain
return filterRegistrationBean;
}
If this doesn't work (e.g. your AuditHandler implementation is not annotated properly or it's not on the default package scanning path) you can instruct Spring to provide it (also in your #SpringBootApplication annotated class):
#Bean
public AuditHandler auditHandler() {
return new AuditHandlerImplementation();
}

NoSuchBeanDefinitionException with spring and gwt (requestFactory)

I get this error with a gwt (using requestfactory) and spring
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.calibra.server.service.AccountService] is defined: expected single bean but found 0:
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:271)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1101)
at org.calibra.server.SpringServiceLocator.getInstance(SpringServiceLocator.java:24)
at com.google.web.bindery.requestfactory.server.LocatorServiceLayer.createServiceInstance(LocatorServiceLayer.java:56)
My service locator
public class SpringServiceLocator implements ServiceLocator {
#Override
public Object getInstance(Class<?> clazz) {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(
RequestFactoryServlet.getThreadLocalServletContext());
return context.getBean(clazz);
}
}
My spring service
#Service
public class AccountServiceImpl implements AccountService{
#Override
public void addNewAccount(Account account) {
...
}
#Override
public List<Account> loadAllAccounts() {
...
}
}
Gwt requestContext, reference my spring service
#Service(value=AccountService.class, locator=SpringServiceLocator.class)
public interface AccountRequest extends RequestContext {
Request<Void> addNewAccount(AccountProxy account);
Request<List<AccountProxy>> loadAllAccounts();
}
my 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>gwtRequest</servlet-name>
<servlet-class>com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gwtRequest</servlet-name>
<url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>welcomeGWT.html</welcome-file>
</welcome-file-list>
I don't understand how i can have 0 AccountService beans ?
i tried to add in the dispatcher-servlet
<bean id="accountService" class="org.calibra.server.service.AccountServiceImpl"/>
I got the same result
Any idea?
edit: if somebody have a full complete example, that could be useful.
I think using the ContextLoaderListener alone is not enough as you don't seem to have the DispatcherServlet in use (have you?).
The following lines work for me:
<filter>
<filter-name>springRequestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springRequestContextFilter</filter-name>
<url-pattern>/gwtRequest</url-pattern>
</filter-mapping>
I've seen this question in a couple of other places. You should try explicity defining the AccountServiceImpl as a bean in your applicationContext.xml (not the dispatch-servlet.xml) first and see if you still get the error, if you don't then you know it's that you're missing the component-scan in your application context xml which is what I think is the case.
hope this helps

Spring - context scan fail with requestfactory (gwt)

in a gwt web application i use spring.
if i use in my applicationContext.xml file
<context:component-scan base-package="com.test.**"/>
my bean is not found, i need to manually declare it in the applicationContext file.
my web.xml file
<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>gwtRequest</servlet-name>
<servlet-class>com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>gwtRequest</servlet-name>
<url-pattern>/gwtRequest</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>welcomeGWT.html</welcome-file>
</welcome-file-list>
Is there any reason why component scan fail?
Locator class
public class AccountLocator extends Locator<Account, Long> {
#Autowired
private AccountDAO accountDAO;
...
}
Spring service locator
public class SpringServiceLocator implements ServiceLocator {
#Override
public Object getInstance(Class<?> clazz) {
HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
return context.getBean(clazz);
}
}
Server layer
#Service
public class AccountServiceImpl implements AccountService{
#Autowired
private AccountDAO accountDAO;
...
}
Dao layer
#Repository
public class AccountDAOImpl implements AccountDAO{
...
}

Resources