One of the scopes that Spring provides is request, beans in it are only valid in the context of a request.
That request is normally delegated to a controller after an HTTP request with Spring already setting up everything necessary.
But what if the request comes from a different source, like for example a Java Message Service.
Would it possible to set up a request scope for each processing of a message?
Can I annotate a method with something to mark it as the boundary of a request scope?
Under the cover , Spring just calls RequestContextHolder to set RequestAttributes to ThreadLocal at the beginning when processing the HTTP request , and remove it from ThreadLocal just before that thread finishes processing the request. The request and session scope beans are actually stored in and get from this RequestAttributes.
In the normal web situation ,this RequestAttributes implementation is backed with the HttpServletRequest. However , in the non-web context , there is no HttpServletRequest and so you cannot use the existing implementation. One of the way is to implement a RequestAttributes that is backed by an internal map :
public class InMemoryRequestAttributes extends AbstractRequestAttributes {
protected Map<String, Object> attributes = new HashMap<>();
#Override
public Object getAttribute(String name, int scope) {
return attributes.get(name);
}
#Override
public void setAttribute(String name, Object value, int scope) {
attributes.put(name, value);
}
#Override
public void removeAttribute(String name, int scope) {
attributes.remove(name);
}
#Override
public String[] getAttributeNames(int scope) {
String[] result = new String[attributes.keySet().size()];
attributes.keySet().toArray(result);
return result;
}
#Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
synchronized (this.requestDestructionCallbacks) {
this.requestDestructionCallbacks.put(name, callback);
}
}
#Override
public Object resolveReference(String key) {
return attributes;
}
#Override
public String getSessionId() {
return null;
}
#Override
public Object getSessionMutex() {
return null;
}
#Override
protected void updateAccessedSessionAttributes() {
}
}
Note: It only work with request scope bean. Modify it if you want to support session scope bean too ....
Then set it and remove it from the ThreadLocal just before and finish processing the JMS messages, something likes :
public void receive(String message){
RequestContextHolder.setRequestAttributes(new InMemoryRequestAttributes());
fooBean.processMessage(message);
RequestContextHolder.resetRequestAttributes();
}
Related
We need to call a Bean class using spring remoting and also set dynamic header in the call. We can set custom HttpInvokerRequestExecutor in the HttpInvokerProxyFactoryBean and add header but how to set dynamic header generated on the fly for the request?
In the Config class, declaring the HttpInvokerProxyFactoryBean
#Bean
#Qualifier("service")
public HttpInvokerProxyFactoryBean invoker() {
HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
invoker.setServiceUrl(url);
invoker.setServiceInterface(Service.class);
return invoker;
}
In the invoker class
#Autowired
Service service;
public void invoke(Bean bean) {
service.process(bean);
}
Its been a long time that I used spring remoting but as far as I remember I found a solution to this by subclassing of SimpleHttpInvokerRequestExecutor which is default when you do not set any custom request executor to HttpInvokerProxyFactoryBean.
IMHO you can write a custom request executor which you can set custom header values and a simple helper component which sets the dynamically provided values to the executor before the next request.
CustomHttpInvokerRequestExecutor:
public class CustomHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor {
private Map<String, String> headers;
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
#Override
protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException {
super.prepareConnection(connection, contentLength);
if (headers != null) {
// adding our custom headers
for (String headerName : headers.keySet()) {
connection.setRequestProperty(headerName, headers.get(headerName));
}
// do not want to persist headers for another request!
headers.clear();
}
}
}
CustomRemoteExecutor:
#Component
public class CustomRemoteExecutor {
#Autowired
private HttpInvokerProxyFactoryBean factoryBean;
/*
* May be you should need a synchronized modifier here if there is possibility
* of multiple threads access here at the same time
*/
public void executeInTemplate(Map<String, String> headers, Runnable task) {
CustomHttpInvokerRequestExecutor executor = (CustomHttpInvokerRequestExecutor) factoryBean.getHttpInvokerRequestExecutor();
executor.setHeaders(headers);
task.run();
}
}
And then you can use it by below:
#Bean
#Qualifier("service")
public HttpInvokerProxyFactoryBean invoker() {
HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
invoker.setServiceUrl(testUrl);
invoker.setServiceInterface(Service.class);
// set our custom request executor
CustomHttpInvokerRequestExecutor executor = new CustomHttpInvokerRequestExecutor();
invoker.setHttpInvokerRequestExecutor(executor);
return invoker;
}
#Autowired
CustomRemoteExecutor executor;
#Autowired
Service service;
public void invoke(Bean bean) {
// when you need custom headers
Map<String, String> headers = new HashMap<>();
headers.put("CUSTOM_HEADER", "CUSTOM_VALUE");
headers.put("CUSTOM_HEADER2", "CUSTOM_VALUE2");
executor.executeInTemplate(headers, () -> service.process(bean));
}
There is one drawback here as I also stated in comments, if you execute your proxy service client in a multithreaded environment (server to server requests may be) you should consider to make executeInTemplate method synchronized
An addition to my answer if your service method needs to return some object then you can add another helper method to CustomRemoteExecutor and use it when you need to return something. The method can have the same name here so it can overload the former one which is much better I think.
public <T> T executeInTemplate(Map<String, String> headers, Callable<T> task) {
CustomHttpInvokerRequestExecutor executor = (CustomHttpInvokerRequestExecutor) factoryBean.getHttpInvokerRequestExecutor();
executor.setHeaders(headers);
try {
return task.call();
} catch (Exception e) {
// it is better to log this exception by your preferred logger (log4j, logback
// etc.)
e.printStackTrace();
}
return null;
}
And again you can use like below:
#Autowired
CustomRemoteExecutor executor;
#Autowired
ISampleService service;
public void invoke(Bean bean) {
// when you need custom headers
Map<String, String> headers = new HashMap<>();
headers.put("CUSTOM_HEADER", "CUSTOM_VALUE");
headers.put("CUSTOM_HEADER2", "CUSTOM_VALUE2");
// assume that service.returnSomething() method returns String
String value = executor.executeInTemplate(headers, () -> service.returnSomething(bean));
}
Hope it helps.
I think my problems is architecture-based, but not so sure so I'll try to explain the reality of it.
The software I'm working on is monolithic oriented but fully exposed with SOAP. The architecture is as followed :
The fondamental concept is what we called "Transaction", which is defined by its request and response (used for SOAP). It structures configurable business logic as well (editing documents, workflows, etc)
We have 1 bean configured in application-context.xml for each Transaction (PersonViewTransaction, PersonSelectTransaction, PersonEditTransaction for example)
All of them inherit
from the same abstract class AbstractTransaction which implements controls on the request provided
that AbstractTransaction implements an interface ITransaction
We build a TransactionFactory which, on any call with beanId and request, calls static methods calling every ITransaction's methods for the Transaction identified by beanId.
When a call is made through Webservices, a Servlet will call this factory, and execute the same logic, as a call from the web project (maven) would do calling directly the factory.
In other words :
Definition of ITransaction
/**
* The main interface defining what a transaction is.
*/
public interface ITransaction <Q extends GenericRequest, P extends GenericResponse> {
P businessLogic(Q req);
void executeControls(String transactionId, Q params, PerimetreAppel environnement)
default void controleLicence() throws NassurCoreException
{
//TODO : contrôles de la licence à effectuer.
}
/**
* Log before transaction
*/
default void logEntree() throws NassurCoreException
{
//TODO to be implemented
}
/**
* Log after transaction
*/
default void logSortie() throws NassurCoreException
{
//TODO to be implemented
}
/**
* Checks that the user can call transaction
*/
default void controleDroits() throws NassurCoreException
{
//TODO to be implemented
}
//...
//Some other method not necessarily implemented in each transaction
}
/**
* The abstract defining the default behaviour of a transaction
*/
public abstract class AbstractTransaction<Q extends GenericRequest, P extends GenericResponse> implements ITransaction<Q, P>{
#Autowired
private ControlService controlService;
//.... some other autowired fields
#Autowired
protected UserDetailsService userDetailsService;
protected String idTransaction;
//Every transaction must implement its own logic
P businessLogic(Q params);
#Override
public void initialisation(String trId)
{
this.idTransaction = trId;
}
#Override
#Transactional
public void executeControls(String transactionId, Q params, PerimetreAppel environnement)
{
//Some controls found from database, and executed
// A control consists of a combination between java checks and some Jruby executed from the Service
List<SomeEntity> list = someEntityRepository.findByTransactionId(transactionId);
for(SomeEntity e : list){
controlService.executeUnitControl(e, params);
}
}
}
Transactions
//Example Transaction
public class PersonViewTransaction extends AbstractTransaction<PersonViewRequest, PersonViewResponse>{
#Override
#Transactional
public PersonViewResponse businessLogic(PersonViewRequest req){
//...
}
}
//Example Transaction
public class PersonEditTransaction extends AbstractTransaction<PersonEditRequest, PersonEditResponse> {
#Override
#Transactional
public PersonEditResponse businessLogic(PersonEditRequest req){
//...
}
}
//Example Transaction
public class PersonSearchTransaction extends AbstractTransaction<PersonSearchRequest, PersonSearchResponse> {
#Override
#Transactional
public PersonSearchResponse businessLogic(PersonSearchRequest req){
//...
}
}
Those Beans are declared in application-context.xml.
Factory
public class TransactionFactory
{
public static GenericResponse executeTransaction(String id, GenericRequest req, HttpServletRequest request)
throws SomeExceptions
{
ServletContext servletCtx = request.getSession().getServletContext();
ApplicationContext actualCtx = WebApplicationContextUtils.getWebApplicationContext(servletCtx);
ITransaction tr = (ITransaction<GenericRequest, GenericResponse>) actualCtx.getBean(id.name());
if (tr == null)
{
throw new Exception("internal Error", null);
}
return executeStack(id, tr, req, request);
}
private static GenericResponse executeStack(String id, ITransaction tr, GenericRequest req, HttpServletRequest request)
throws SomeExceptions
{
try
{
tr.initialisation(id);
tr.controleLicence();
tr.logEntree();
tr.controleDroits();
//...Several operations calling ITransaction
tr.executeControls(id, req, provenance);
//Calling transaction logic
GenericResponse resp = tr.businessLogic(req);
//...Many "after-business" other calls
return resp;
}
catch (SomeExceptions e)
{
logger.error(e);
throw e;
}
}
}
By some mystery, when two calls are made simultaneously on the same transactionId, the executeControls throws some weird returns, as if both executions where mixed in the same bean (to me it appears thread-safe but not that sure...) :
List<SomeEntity> list = someEntityRepository.findByTransactionId(transactionId);
for(SomeEntity e : list){
controlService.executeUnitControl(e, params);
}
executeUnitControl checks if the control must apply, and throws an error if needed. To be more into detail, this is based on a JRuby script that takes params as parameter to build configurable and non-Java based business logic.
Thanks a lots for your help, and if there are french people, I'd love to chat with them any time to explain more details if needed !
I have a Spring MVC (v4.1.3) web application with javascript UI. I have implemented a custom DispatcherServlet and configured the same in web.xml
There is a unique screen code which is sent in the HTTP Header of each request made by the UI to server.
In the doService method of my custom dispatcher servlet, I capture the HTTP Header and put the value in a ThreadLocal dto variable. I access this ThreadLocal variable in the service layer for performing some audit logic which is common for all requests.
Code from CustomDispatcherServlet:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
String uiCode = request.getHeader("uiCode");
if ((uiCode != null && !uiCode.trim().isEmpty())) {
UiCodeDto uiCodeDto = new UiCodeDto(uiCode);
final ThreadLocal<UiCodeDto> threadLocalUser = new ThreadLocal<UiCodeDto>();
threadLocalUser.set(uiCodeDto);
}
...
super.doService(request, response);
}
Code from service layer:
UiCodeDto temp = ThreadLocalUtil.getUiCodeDto(Thread.currentThread());
Code of ThreadLocalUtil to retrieve the value from ThreadLocal:
public final class ThreadLocalUtil {
public static UiCodeDto getUiCodeDto(Thread currThread) {
UiCodeDto UiCodeDto = null;
try {
Field threadLocals = Thread.class.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object currentThread = threadLocals.get(currThread);
Field threadLocalsMap = currentThread.getClass().getDeclaredField("table");
threadLocalsMap.setAccessible(true);
threadLocalsMap.setAccessible(true);
Object[] objectKeys = (Object[]) threadLocalsMap.get(currentThread);
for (Object objectKey : objectKeys) {
if (objectKey != null) {
Field objectMap = objectKey.getClass().getDeclaredField("value");
objectMap.setAccessible(true);
Object object = objectMap.get(objectKey);
if (object instanceof UiCodeDto) {
UiCodeDto = (UiCodeDto) object;
break;
}
}
}
} catch (Exception e) {
...
}
return UiCodeDto;
}
}
The problem is as follows -
1. I am getting random values of screen code - which means the value of some http request N is coming in http request N+1.
2. There are null DTOs in ThreadLocal variable with same name - hence, sometimes when I access the ThreadLocal in service layer, I get a null
I need help in understanding the behavior of ThreadLocal in DispatcherServlet - why would it get values of another request in doService method?
Thanks in advance.
Your code is error prone and hard to understand also why would you need a custom DispatcherServlet. A filter seems more suited for this task.
public class UiCodeFilter extends OncePerRequestFilter {
protected void doFilterInternally(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
try {
String uiCode = req.getHeader("uiCode");
if ((uiCode != null && !uiCode.trim().isEmpty())) {
UiCodeDto uiCodeDto = new UiCodeDto(uiCode);
UiCodeHolder.set(uiCodeDta);
}
chain.doFilter(req, res);
} finally {
UiCodeHolder.clear(); // Always clear!
}
}
}
The UiCodeHolder has a static ThreadLocal to keep the value.
public abstract class UiCodeHolder {
static ThreadLocal<UiCodeDto> current = new ThreadLocal<>()
public void set(UiCodeDto uiCode) {
current.set(uiCode);
}
public UiCodeDta get() {
return current.get();
}
public void clear() {
current.remove(); // for older versions use current.set(null);
}
}
In your service you can now simply do UiContextHolder.get() to obtain the correct value. The UiCodeFilter takes care of setting the value and at the end of the request clears the value again to prevent leaking.
This approach doesn't require ugly reflection hooks, is quite easy to understand is is used by Spring, Hibernate and frameworks alike.
A more Spring way of doing this is to use a request-scoped bean to extract and hold the header:
#Component
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UiCodeDto {
private String uiCode;
#Inject
public void setCode(HttpServletRequest req) {
uiCode = req.getHeader("uiCode");
}
public String getUiCode() {
return uiCode;
}
}
And you can use it like a normal bean:
#Service
public class RandomService {
#Inject
UiCodeDto uiCodeDto;
public void handle() {
System.out.println(uiCodeDto.getUiCode());
}
}
I am trying to create a custom http param binding for my restful service. Please see the example below.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #UserAuthHeaderParam String authString){
}
You can see that there is a UserAuthHeaderParam annotation in the function signature. What I want to do is have a custom http param binding other than the standard javax.ws.rs.*Param .
I have try to implement org.glassfish.hk2.api.InjectionResolver which basically extract the value from http header:
public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
#Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{
return "Hello World";
}
...
}
When I call the restful service, the server get below exceptions. It indicates that the framework fails to resolve the param in the function signature:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195),
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found
Please help. Any advise is appreciated. I do make a lot of search on google but fails to make it work. Jersey 2.*. How to replace InjectableProvider and AbstractHttpContextInjectable of Jersey 1.* might be the similar question.
-- UPDATES:
I use AbstractBinder to bind my resolver to UserAuthHeaderParam:
public class MyApplication extends ResourceConfig
{
public MyApplication()
{
register(new AbstractBinder()
{
#Override
protected void configure()
{
// bindFactory(UrlStringFactory.class).to(String.class);
bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
{
}).in(Singleton.class);
}
});
packages("rs");
}
}
Thank you!
If all you want is to pass value directly from the header to the method you don't need to create custom annotations. Let's say you have a header Authorization, then you can easily access it by declaring your method like this:
#GET
public String authFromHeader(#HeaderParam("Authorization") String authorization) {
return "Header Value: " + authorization + "\n";
}
You can test it by calling curl, e.g.
$ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
Header Value: 1234
Given that the answer to your question, how to create custom binding is as follows.
First you have to declare your annotation like this:
#java.lang.annotation.Target(PARAMETER)
#java.lang.annotation.Retention(RUNTIME)
#java.lang.annotation.Documented
public #interface UserAuthHeaderParam {
}
Having your annotation declared you have to define how it will be resolved. Declare the Value Factory Provider (this is where you'll have access to the header parameters - see my comment):
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractHttpContextValueFactory<String>() {
#Override
protected String get(HttpContext httpContext) {
// you can get the header value here
return "testString";
}
};
}
}
Now declare an injection resolver
public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
public UserAuthHeaderParamResolver() {
super(UserAuthHeaderParamValueFactoryProvider.class);
}
}
and a Binder for your configuration
public class HeaderParamResolverBinder extends AbstractBinder {
#Override
protected void configure() {
bind(UserAuthHeaderParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(UserAuthHeaderParamResolver.class)
.to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
.in(Singleton.class);
}
}
now the last thing, in your ResourceConfig add register(new HeaderParamResolverBinder()), like this
#ApplicationPath("rest")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new HeaderParamResolverBinder());
packages("your.packages");
}
}
Given that, you should be now able to use the value as you wanted:
#GET
public String getResult(#UserAuthHeaderParam String param) {
return "RESULT: " + param;
}
I hope this helps.
I don't know how to resolve your exception. However, may I propose you a different way to do the same thing. I hope it helps.
I've faced exactly the same problem: I need extra parameters in the http header (btw, also related to authentication). Besides, I need to send them in every call, since I want to do a "typical" rest implementation, without maintaining a session.
I'm using Jersey 2.7 - but I'd say it should work in 2.0. I've followed their documentation
https://jersey.java.net/documentation/2.0/filters-and-interceptors.html
It's quite clear there, but anyway I copy-paste my implementation below.
It works fine. True there are some other ways to secure a rest service, for example this is a good one:
http://www.objecthunter.net/tinybo/blog/articles/89
But they depend on the application server implementation and the database you use. The filter, in my opinion, is more flexible and easier to implement.
The copy-paste: I've defined a filter for authentication, which applies to every call and it is executed before the service (thanks to #PreMatching).
#PreMatching
public class AuthenticationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
if (headers == null) {
throw new...
}
// here I get parameters from the header, via headers.get("parameter_name")
// In particular, I get the profile, which I plan to use as a Jersey role
// then I authenticate
// finally, I inform the Principal and the role in the SecurityContext object, so that I can use #RolesAllowed later
requestContext.setSecurityContext(new SecurityContext() {
#Override
public boolean isUserInRole(final String arg0) {
//...
}
#Override
public boolean isSecure() {
//...
}
#Override
public Principal getUserPrincipal() {
//...
}
#Override
public String getAuthenticationScheme() {
//...
}
});
}
}
You have to include this filter class in your implementation of ResourceConfig,
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
// my init
// my packages
register(AuthenticationRequestFilter.class); // filtro de autenticación
// other register
}
}
Hope it helps!
If your need is to retrieve all the http headers binding into one object, a solution could be to use the #Context annotation to get javax.ws.rs.core.HttpHeaders; which contains the list of all request headers.
#POST
#Path("/user/{userId}/orders")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(#PathParam("userId") String someString, #Context HttpHeaders headers){
// You can list all available HTTP request headers via following code :
for(String header : headers.getRequestHeaders().keySet()){
System.out.println(header);
}
}
here is my actual implementatipn of UserAuthHeaderParamValueFactoryProvider class
import javax.inject.Inject;
import javax.inject.Singleton;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
#Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (classType == null || (!classType.equals(String.class))) {
return null;
}
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
//you can use get any header value.
return getContainerRequest().getHeaderString("Authorization");
}
};
}
I am writing a web application using Spring MVC. I have a interface that looks like this:
public interface SubscriptionService
{
public String getSubscriptionIDForUSer(String userID);
}
The getSubscriptionIDForUser actually makes a network call to another service to get the subscription details of the user. My business logic calls this method in multiple places in its logic. Hence, for a given HTTP request I might have multiple calls made to this method. So, I want to cache this result so that repeated network calls are not made for the same request. I looked at the Spring documentation, but could not find references to how can I cache this result for the same request. Needless to say the cache should be considered invalid if it is a new request for the same userID.
My requirements are as follows:
For one HTTP request, if multiple calls are made to getSubscriptionIDForUser, the actual method should be executed only once. For all other invocations, the cached result should be returned.
For a different HTTP request, we should make a new call and disregard the cache hit, if at all, even if the method parameters are exactly the same.
The business logic might execute its logic in parallel from different threads. Thus for the same HTTP request, there is a possibility that Thread-1 is currently making the getSubscriptionIDForUser method call, and before the method returns, Thread-2 also tries to invoke the same method with the same parameters. If so, then Thread-2 should be made to wait for the return of the call made from Thread-1 instead of making another call. Once the method invoked from Thread-1 returns, Thread-2 should get the same return value.
Any pointers?
Update: My webapp will be deployed to multiple hosts behind a VIP. My most important requirement is Request level caching. Since each request will be served by a single host, I need to cache the result of the service call in that host only. A new request with the same userID must not take the value from the cache. I have looked through the docs but could not find references as to how it is done. May be I am looking at the wrong place?
I'd like to propose another solution that a bit smaller than one proposed by #Dmitry. Instead of implementing own CacheManager we can use ConcurrentMapCacheManager provided by Spring in 'spring-context' artifact. So, the code will look like this (configuration):
//add this code to any configuration class
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
and may be used:
#Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}
I ended up with solution as suggested by herman in his comment:
Cache manager class with simple HashMap:
public class RequestScopedCacheManager implements CacheManager {
private final Map<String, Cache> cache = new HashMap<>();
public RequestScopedCacheManager() {
System.out.println("Create");
}
#Override
public Cache getCache(String name) {
return cache.computeIfAbsent(name, this::createCache);
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
return cache.keySet();
}
public void clearCaches() {
cache.clear();
}
}
Then make it RequestScoped:
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
Update:
After a while, I have found that my previous solution was incompatible with Spring-actuator. CacheMetricsRegistrarConfiguration is trying to initialize request scoped cache outside the request scope, which leads to exception.
Here is my alternative Implementation:
public class RequestScopedCacheManager implements CacheManager {
public RequestScopedCacheManager() {
}
#Override
public Cache getCache(String name) {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.computeIfAbsent(name, this::createCache);
}
protected Map<String, Cache> getCacheMap() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return new HashMap<>();
}
#SuppressWarnings("unchecked")
Map<String, Cache> cacheMap = (Map<String, Cache>) requestAttributes.getAttribute(getCacheMapAttributeName(), RequestAttributes.SCOPE_REQUEST);
if (cacheMap == null) {
cacheMap = new HashMap<>();
requestAttributes.setAttribute(getCacheMapAttributeName(), cacheMap, RequestAttributes.SCOPE_REQUEST);
}
return cacheMap;
}
protected String getCacheMapAttributeName() {
return this.getClass().getName();
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.keySet();
}
public void clearCaches() {
for (Cache cache : getCacheMap().values()) {
cache.clear();
}
getCacheMap().clear();
}
}
Then register a not(!) request scoped bean. Cache implementation will get request scope internally.
#Bean
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
EHCache comes to mind right off the bat, or you could even roll-your-own solution to cache the results in the service layer. There are probably a billion options on caching here. The choice depends on several factors, like do you need the values to timeout, or are you going to clean the cache manually. Do you need a distributed cache, like in the case where you have a stateless REST application that is distributed amongst several app servers. You you need something robust that can survive a crash or reboot.
You can use Spring Cache annotations and create your own CacheManager that caches at request scope. Or you can use the one I wrote: https://github.com/rinoto/spring-request-cache