I wrote a little application demonstrating how to manually (through constructors) inject dependencies into Jersey resources in an embedded container like Jetty.
The benefits are:
- lightweightness (no need to use Spring, Guice, etc.),
- more control over your application (less magic behind the scenes),
- controlling dependencies in tests (via Dependency Injection)
- running a web app with a simple Java main method (via embedded Jetty)
The example application is called Time Expert. It exposes the current time via a RESTful web service, so when I run it in the production (via Main.java) I can use it as following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
curl -v -H "Accept: text/plain" http://localhost:6666/time HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 21 Server: Jetty(9.0.5.v20130815) It's currently 20:59
Here's how I test it (TimeAcceptanceTest.java) :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Test public void saysTheCurrentTime() { givenTheCurrentTimeIs("20:15"); whenAUserChecksTheTime(); thenTheUserSees("It's currently 20:15!"); }
However, I cannot rely here on the real time because it changes every time I run tests . The problem can be easily solved with the Clock pattern. My tests require a FixedClock which allows me to set the current time to any value:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FixedClock implements Clock { | |
private Date fixedNow; | |
@Override | |
public Date now() { | |
return fixedNow; | |
} | |
public void setNow(Date fixedNow) { | |
this.fixedNow = fixedNow; | |
} | |
} |
So I start my server with a fixed clock:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Before public void startApplication() { clock = new FixedClock(); timeExpert = new TimeExpertServer(clock); timeExpert.start(); }
... and then set it to, say, 20:15:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Test public void saysTheCurrentTime() throws Exception { givenTheCurrentTimeIs("20:15"); whenAUserChecksTheTime(); thenTheUserSees("It's currently 20:15!"); } private void givenTheCurrentTimeIs(String currentTime) throws ParseException { Date currentTimeAsDate = new SimpleDateFormat("HH:mm").parse(currentTime); clock.setNow(currentTimeAsDate); } private void whenAUserChecksTheTime() { actualApplicationTime = timeExpertUser.checkCurrentTime(); } private void thenTheUserSees(String expectedApplicationTime) { assertEquals(expectedApplicationTime, actualApplicationTime); }
Since my Jersey resource class is only aware of the clock interface, it will display 20:15:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Path("time") public class TimeResource { private final Clock clock; public TimeResource(Clock clock) { this.clock = clock; } @GET @Produces(MediaType.TEXT_PLAIN) public String getCurrentTime() { return String.format("It's currently %s!", format(clock.now())); } private String format(Date now) { return new SimpleDateFormat("HH:mm").format(now); } }
In order to create your Jersey resources with manually injected dependencies you have to register org.glassfish.jersey.servlet.ServletContainer with a customized org.glassfish.jersey.server.ResourceConfig. In that config Jersey resources can be newed up with their dependencies:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public void start() { server = new Server(6666); ServletContextHandler handler = new ServletContextHandler(); handler.setContextPath(""); // adds Jersey Servlet with a customized ResourceConfig handler.addServlet(new ServletHolder(new ServletContainer(resourceConfig())), "/*"); server.setHandler(handler); try { server.start(); } catch (Exception e) { throw new RuntimeException("Could not start the server", e); } } private ResourceConfig resourceConfig() { // manually injecting dependencies (clock) to Jersey resource classes return new ResourceConfig().register(new TimeResource(clock)); }
Of course the production provides a real clock (which returns new Date()):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class Main { public static void main(String[] args) { new Main().startTimeExpert(); } private void startTimeExpert() { final TimeExpertServer timeExpert = new TimeExpertServer(new RealClock()); try { timeExpert.start(); timeExpert.join(); } finally { timeExpert.stop(); } } }
Code is available under: https://github.com/unclejamal/TimeMaster
Enjoy!
Tested using:
- Java 1.7,
- Gradle 1.7 (easily convertible to Maven :)),
- Jersey 2.2,
- Jetty 9.0.5.