Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have spring cache store the ResponseBody and not the intermediary object

I use spring cache with this method which returns the queried value as JSON:

@RequestMapping("/getById")
@ResponseBody
@Cacheable
public HugeValue getHugeValueFromSlowFoo( @RequestParam(value = "id", defaultValue = "") String id ) {
  return Foo.getById( id );
}

This works fine and the HugeValue-object is stored in the cache (Hazelcast in this case). I want to further improve this because the time taken to create JSON from the HugeValue is quite high. Can I tell spring cache to cache the JSON-ified version of my object ?

I use Jackson with Spring Boot 1.2 and Spring 4.1

like image 270
Marged Avatar asked Jan 12 '15 08:01

Marged


Video Answer


3 Answers

Without really knowing your exact use case (I'm not yet allowed to add comments for asking unfortunately), I try to give a short summary on the ideas I have in mind. They all assume that you use Jackson for json mapping and at least Spring 3.1.

There is no enableResponseBodyCaching feature in SpringMVC as far as I know.

First alternative: Use http caching because it seems like you really want to cache the whole http response. Spring offers a straight forward way of global configuration:

<mvc:interceptors>
    <bean id="webContentInterceptor"
          class="org.springframework.web.servlet.mvc.WebContentInterceptor">
        <property name="cacheSeconds" value="0"/>
        <property name="useExpiresHeader" value="true"/>
        <property name="useCacheControlHeader" value="true"/>
        <property name="useCacheControlNoStore" value="true"/>
    </bean>
</mvc:interceptors>

If you wish to control this per Controller, inherit from Spring AbstractController and set cacheSeconds property according to javaDoc.

True power of http caching comes with a http proxy in front of your server of course.

Second idea: Implement your own subclass of MappingJackson2HttpMessageConverter. In writeInternal() you could add some logic which accesses a cache to retrieve an already mapped version instead of mapping the input object. This approach means that you will hit your services in order to retrieve the java object behind the Json stream. If this is fine for you because there is also caching at some point, this approach is worth a try imho.

Third idea: Do the json mapping on your own in a dedicated wrapper service which provides raw json strings/streams. You can easily inject the Jackson mapper (class name ObjectMapper) and gain full control over the mapping. Annotating this service then allows you to cache the results. In your Controller you only provide a ResponseEntity of the according type you whish to use (String or some stream). This would even prevent deeper service access if there is a cached result present.

Edit: Probably the MappingJackson2JsonView could also get handy. To be honest, I never worked with it before so I can't really say something about its usage.

Hope that helps and/or gives inspiration! Cheers

like image 62
Ben Steinert Avatar answered Oct 21 '22 11:10

Ben Steinert


I think this could work.

public interface HugeValueService{
    public String getHugeValueFromSlowFoo(String id);
}

public class HugeValueServiceImpl implements HugeValueService{
    @Cacheable
    public String getHugeValueFromSlowFoo(String id ) {
      return Foo.getById( id );
    }
}

Your Class
----------
@RequestMapping("/getById")
@ResponseBody
public HugeValue getHugeValueFromSlowFoo( @RequestParam(value = "id", defaultValue = "") String id ) {
  return hugeValueService.getHugeValueFromSlowFoo(id);
}

Good Luck!!!

like image 36
Luis Lee Avatar answered Oct 21 '22 11:10

Luis Lee


Cache the json and put it in the response manually.

public void getHugeValueFromSlowFoo( @RequestParam(value = "id", defaultValue = "") String id, ServletResponse resp ) 
{
  String jsonSerializedHugeValue = Bar.getById(id); // serialize manually with ObjectMapper
  resp.getWriter().write(jsonSerializedHugeValue);
  resp.setContentType("application/json");
  resp.flushBuffer();
}
like image 23
Dariusz Avatar answered Oct 21 '22 11:10

Dariusz