I'm trying to setup a simple REST application using Jersey + Jetty and Spring for setting up beans for Autowiring. I am also managing this through Gradle.
I have 2 main endpoints /users
and /greeting
.
A GET
on /greeting
only returns text response and works fine. There is no autowired dependencies there.
However if i do a get on the /users
endpoint, I return the response using an API class which should be injected by spring. That part fails and hence I get a NullPointerException
whenever I call that endpoint.
Another point to note is that I want to use jersey 1.12 and spring 4.2.0. The reason being, this will be a prototype that I will have to plug in elsewhere.
Exception when doing a GET on /users:
java.lang.NullPointerException
at com.hateos.spring.test01.resources.user.controller.UserController.getAllUsers(UserController.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60)
at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$TypeOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:185)
at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75)
at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288)
at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108)
at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1483)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1414)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1363)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1353)
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:414)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:708)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:564)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:317)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:110)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:128)
at org.eclipse.jetty.util.thread.Invocable$InvocableExecutor.invoke(Invocable.java:222)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:294)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:673)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:591)
at java.lang.Thread.run(Thread.java:748)
19-03-20 15:10:59:694 WARN qtp1567581361-16 server.HttpChannel:517 - /users
java.lang.NullPointerException
at com.hateos.spring.test01.resources.user.controller.UserController.getAllUsers(UserController.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60)
at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$TypeOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:185)
at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75)
at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288)
at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108)
at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1483)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1414)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1363)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1353)
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:414)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537)
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:708)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:564)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:317)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:110)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:128)
at org.eclipse.jetty.util.thread.Invocable$InvocableExecutor.invoke(Invocable.java:222)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:294)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:673)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:591)
at java.lang.Thread.run(Thread.java:748)
Project Structure is below(skipping unrelated files):
hateos-spring-test01
|- src
| |- main
| | |- java
| | | |- com.hateos.spring.test01 (nested folders)
| | | |- resources
| | | | |- greeting
| | | | |- user
| | | | |- api
| | | | | |- impl
| | | | | | |- UserAPIImpl.java
| | | | | |- UserAPI.java
| | | | |- controller
| | | | |- UserController.java
| | | | |- exception
| | | | |- model
| | | | |- User.java
| | | | |- UserList.java
| | | |- MyApplication.java
| | |- resources
| | |- hateos-spring-test01.xml
| | |- log4j.properties
| |- test
|- build.gradle
|- gradlew
|- gradlew.bat
|- settings.gradle
build.gradle:
apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
mainClassName = 'com.hateos.spring.test01.MyApplication'
ext {
jettyVersion = '9.4.6.v20170531'
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
}
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile 'org.slf4j:slf4j-api:1.6.6'
compile 'org.slf4j:slf4j-log4j12:1.6.6'
compile "org.codehaus.jackson:jackson-core-asl:1.9.2"
compile "org.codehaus.jackson:jackson-jaxrs:1.9.2"
compile "org.codehaus.jackson:jackson-mapper-asl:1.9.2"
compile "org.codehaus.jackson:jackson-xc:1.9.2"
compile "com.sun.jersey:jersey-client:1.12"
compile "com.sun.jersey:jersey-core:1.12"
compile "com.sun.jersey:jersey-json:1.12"
compile "com.sun.jersey:jersey-server:1.12"
compile "com.sun.jersey:jersey-servlet:1.12"
compile "org.codehaus.jettison:jettison:1.1"
compile "org.eclipse.jetty:jetty-server:${jettyVersion}"
compile "org.eclipse.jetty:jetty-servlet:${jettyVersion}"
compile 'org.springframework:spring-core:4.2.0.RELEASE'
compile 'org.springframework:spring-web:4.2.0.RELEASE'
compile 'org.springframework.hateoas:spring-hateoas:0.25.1.RELEASE'
compile 'org.springframework:spring-beans:4.2.0.RELEASE'
compile 'org.springframework:spring-context:4.2.0.RELEASE'
compile 'org.springframework:spring-context-support:4.2.0.RELEASE'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
spring xml file (hateos-spring-text.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.hateos.spring.test01"/>
<bean id="usersAPI" class="com.hateos.spring.test01.resources.user.api.impl.UserAPIImpl"/>
<bean id="usersController" class="com.hateos.spring.test01.resources.user.controller.UserController" />
</beans>
MyApplication.java
package com.hateos.spring.test01;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApplication {
private static final Logger logger = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
logger.info("Hi guys!");
Server server = new Server(9090);
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
servletContextHandler.setContextPath("/");
server.setHandler(servletContextHandler);
ServletHolder servletHolder = servletContextHandler.addServlet(ServletContainer.class, "/*");
servletHolder.setInitOrder(0);
servletHolder.setInitParameter(
"com.sun.jersey.config.property.packages",
"com.hateos.spring.test01.resources"
);
try {
server.start();
server.join();
} catch (Exception ex) {
logger.error("Error occurred while starting Jetty", ex);
System.exit(1);
} finally {
server.destroy();
}
}
}
/resources/user/api/UserAPI.java
package com.hateos.spring.test01.resources.user.api;
import com.hateos.spring.test01.resources.user.model.User;
import com.hateos.spring.test01.resources.user.model.UserList;
public interface UserAPI {
UserList getAll();
User getById(String userId);
}
/resources/user/api/impl/UserAPIImpl.java
package com.hateos.spring.test01.resources.user.api.impl;
import com.hateos.spring.test01.resources.user.api.UserAPI;
import com.hateos.spring.test01.resources.user.model.User;
import com.hateos.spring.test01.resources.user.model.UserList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class UserAPIImpl implements UserAPI {
private static final Logger logger = LoggerFactory.getLogger(UserAPIImpl.class);
@Override
public UserList getAll() {
UserList userList = new UserList();
for (int index = 0; index < userIds.length; index++) {
userList.getUserList().add(toUser(index));
}
return userList;
}
@Override
public User getById(String userId) {
return findUserById(userId);
}
private User toUser(int index) {
User newUser = null;
if (index < userIds.length) {
newUser = new User();
newUser.setFirstName(firstNames[index]);
newUser.setLastName(lastNames[index]);
newUser.setUserId(userIds[index]);
}
return newUser;
}
private User findUserById(String userId) {
for (int index = 0; index < userIds.length; index++) {
if (userIds[index].equals(userId)) {
return toUser(index);
}
}
return null;
}
private static String[] firstNames = {"Bob", "Jerry", "Milton", "Carrie"};
private static String[] lastNames = {"Builder", "Seinfeld", "Bombay", "Fisher"};
private static String[] userIds = {"001", "153", "5566", "234"};
}
/resources/user/controller/UserController.java
package com.hateos.spring.test01.resources.user.controller;
import com.hateos.spring.test01.resources.user.api.UserAPI;
import com.hateos.spring.test01.resources.user.exception.UserNotFoundException;
import com.hateos.spring.test01.resources.user.model.User;
import com.hateos.spring.test01.resources.user.model.UserList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/users")
@Component
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private UserAPI userAPI;
@Autowired
public void setUserAPI(UserAPI usersAPI) {
logger.info("userAPI is being injected");
this.userAPI = usersAPI;
}
@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public UserList getAllUsers() {
logger.info("getAllUsers is being called");
return this.userAPI.getAll();
}
@GET
@Path("/{uid}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public User getUser(@PathParam("uid") String userId) {
User foundUser = userAPI.getById(userId);
if (foundUser != null) {
return foundUser;
} else {
throw new UserNotFoundException(userId);
}
}
}
EDIT 1 : Console Log at startup
5:06:41 PM: Executing task 'run'...
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :run
19-03-20 17:06:42:452 INFO main test01.MyApplication:15 - Hi guys!
19-03-20 17:06:42:496 INFO main util.log:192 - Logging initialized @446ms to org.eclipse.jetty.util.log.Slf4jLog
19-03-20 17:06:42:618 INFO main server.Server:372 - jetty-9.4.6.v20170531
Mar 20, 2019 5:06:42 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
com.hateos.spring.test01.resources
Mar 20, 2019 5:06:42 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
class com.hateos.spring.test01.resources.greeting.controller.GreetingController
class com.hateos.spring.test01.resources.user.controller.UserController
class com.hateos.spring.test01.resources.shared.RootController
Mar 20, 2019 5:06:42 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Provider classes found:
class com.hateos.spring.test01.resources.shared.controller.BaseExceptionMapper
Mar 20, 2019 5:06:42 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.12 02/15/2012 04:51 PM'
19-03-20 17:06:43:540 INFO main handler.ContextHandler:788 - Started o.e.j.s.ServletContextHandler@4b29d1d2{/,null,AVAILABLE}
19-03-20 17:06:44:316 INFO main server.AbstractConnector:280 - Started ServerConnector@4a003cbe{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
19-03-20 17:06:44:316 INFO main server.Server:444 - Started @2270ms
EDIT 2: This question is different from the linked duplicate. I use jersey and they dont. My solution(given by LppEdd) was to get jersey + spring play well with each other through the initial main function. My question is regarding xml based Spring bean autowiring with Jersey.
Most probably you need to specify the property
associated with UserAPI
<bean id="usersController" class="com.hateos.spring.test01.resources.user.controller.UserController" />
<property name="userAPI" ref="usersAPI" />
</bean>
But, honestly, I'd go for constructor injection. It is a lot cleaner.
@Path("/users")
@Component
class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private final UserAPI userApi;
UserController(final UserAPI userApi) {
this.userApi = userApi;
}
...
}
Using
<bean id="usersController" class="com.hateos.spring.test01.resources.user.controller.UserController" />
<constructor-arg ref="usersAPI" />
</bean>
Although you already use
<context:annotation-config/>
<context:component-scan base-package="com.hateos.spring.test01"/>
So the <bean>
entries in the XML file shouldn't be needed at all.
See comments.
// You forgot about this!
servletContextHandler.addEventListener(new ContextLoaderListener());
servletContextHandler.setInitParameter("contextConfigLocation", "classpath*:**/hateos-spring-text.xml");
// You basically already have this part. Added for clarity
final ServletHolder jerseyServlet = new ServletHolder(SpringServlet.class);
jerseyServlet.setInitOrder(0);
jerseyServlet.setInitParameter(
"com.sun.jersey.config.property.packages",
"com.hateos.spring.test01"
);
servletContextHandler.addServlet(jerseyServlet, "/*");
Should be sufficient.