How to cache response with Jersey?
Summary of solutions:
Request as method parameter
Interface:
@Path("myentity") public interface MyEntityResource @GET @Produces(MediaType.APPLICATION_JSON) public Response getMyEntity(@Context final Request request); }
Implementation:
public class MyEntityResourceImpl implements MyEntityResource @Override public Response getMyEntity(final Request request) { final MyEntity myEntity = ... // load entity final String eTagValue = ... // calclutate value of ETag final EntityTag eTag = new EntityTag(eTagValue); ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag); if (responseBuilder == null) { return Response.ok(user).tag(eTag).build(); } return responseBuilder.build(); } }
Disadvantages:
implementation detail
Request
is exposedreturn type
Reponse
is genericmissing grammar of return type in WADL
client proxy with unnecessary parameter
Request
Request as instance variable
Interface:
@Path("myentity") public interface MyEntityResource @GET @Produces(MediaType.APPLICATION_JSON) public Response getMyEntity(); }
Implementation:
public class MyEntityResourceImpl implements MyEntityResource @Context private Request request @Override public Response getMyEntity() { final MyEntity myEntity = ... // load entity final String eTagValue = ... // calclutate value of ETag final EntityTag eTag = new EntityTag(eTagValue); ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag); if (responseBuilder == null) { return Response.ok(user).tag(eTag).build(); } return responseBuilder.build(); } }
Disadvantages:
return type
Reponse
is genericmissing grammar of return type in WADL
dependency injection with
@Context
is complicated, see https://stackoverflow.com/questions/33240443
ShallowEtagHeaderFilter as web filter
web.xml:
<filter> <filter-name>etagFilter</filter-name> <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> </filter> <filter-mapping> <filter-name>etagFilter</filter-name> <url-pattern>/api/*</url-pattern> </filter-mapping>
Interface:
@Path("myentity") public interface MyEntityResource @GET @Produces(MediaType.APPLICATION_JSON) public MyEntity getMyEntity(); }
Implementation:
public class MyEntityResourceImpl implements MyEntityResource @Override public MyEntity getMyEntity() { final MyEntity myEntity = ... // load entity return myEntity; } }
Disadvantages:
bad server performance, see JavaDoc
works only on uncommitted response
no support of weak ETag
Custom WriterInterceptor as JAX-RS Interceptor
Interceptor:
public class CustomInterceptor implements WriterInterceptor { @Context private Request request; @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { OutputStream old = context.getOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try { context.setOutputStream(buffer); context.proceed(); byte[] entity = buffer.toByteArray(); String etag = ... // calclutate value of ETag context.getHeaders().putSingle(HttpHeaders.ETAG, etag); ResponseBuilder responseBuilder = request.evaluatePreconditions(eTag); if (responseBuilder == null) { throw new WebApplicationException(responseBuilder.status(Response.Status.NOT_MODIFIED).header(HttpHeaders.ETAG, etag).build()); } old.write(entity); } finally { context.setOutputStream(old); } } }
See also: ServerCacheInterceptor (Resteasy)
Interface:
@Path("myentity") public interface MyEntityResource @GET @Produces(MediaType.APPLICATION_JSON) public MyEntity getMyEntity(); }
Implementation:
public class MyEntityResourceImpl implements MyEntityResource @Override public MyEntity getMyEntity() { final MyEntity myEntity = ... // load entity return myEntity; } }
Disadvantages:
no predefined interceptor for Jersey available
bad server performance
no support of weak ETag
ugly workaround with
WebApplicationException
You can use CacheControl, eTag - follow below example code
// In your jersey method
final EntityTag eTag = new EntityTag(resource.getId() + "_" +
resource.getLastModified().getTime());
final CacheControl cacheControl = new CacheControl();
cacheControl.setMaxAge(-1);
ResponseBuilder builder = request.evaluatePreconditions(
resource.getLastModified(), eTag);
// the resoruce's information was modified, return it
if (builder == null) {
builder = Response.ok(resource);
}
// the resource's information was not modified, return a 304
return builder.cacheControl(cacheControl).lastModified(
resource.getLastModified()).tag(eTag).build();
Replace resource
with your Resource instance.
Recently I've been solving a similar (if not the same) problem. As a side result of it the following library emerged: https://github.com/AndreyLebedenko/dropwizard-caching-filter
The way it can be used is like below:
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/cached")
@ResponseCachedByFilter(10000)
public Object getCached() {
return dao.get();
}
Hope it helps.
You could use any caching mechanism applicable for standard java together with Jersey, like Ehcache.
You only have to pay attention to verify that your data in the backend hasn't changed.
Here is simple example with Ehcache
:
@GET
@Path("{id}")
public List<Data> getData(@PathParam("id") Long id) {
Element element = CacheManager.getInstance().getCache("test").get(id);
if(element == null) {
Data value = fetchElementFromBackend(id);
CacheManager.getInstance().getCache("test").put(new Element(id, value));
return value;
}
return element.getObjectValue();
}