Spring MVC with Atmosphere - spring

I have recently started with Atmosphere. I need it to implement it in a Spring MVC application.
Till now I've managed to integrate it with Spring MVC.
I just need to perform a very simple task. I have a counter an instance variable as soon as it reaches 10, a response should be broadcasted to the UI.
Can anyone help me how do I write the code for that in the controller.
I've got the Atmosphere resource into the controller.
AtmosphereArgumentResolver.java
public class AtmosphereArgumentResolver implements HandlerMethodArgumentResolver {
//#Override
public boolean supportsParameter(MethodParameter parameter) {
return AtmosphereResource.class.isAssignableFrom(parameter.getParameterType());
}
//#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
{
HttpServletRequest httpServletRequest= webRequest.getNativeRequest(HttpServletRequest.class);
return Meteor.build(httpServletRequest).getAtmosphereResource();
}
}
HomeController.java
#Controller
public class HomeController {
private int counter = 0;
private final BroadcasterFactory bf;
public BroadcasterFactory broadcasterFactory()
{
return BroadcasterFactory.getDefault();
}
for(int i=0; i<=15; i++)
{
counter ++;
}
// As soon as the counter reaches 10 I need to send a broadcast message to the UI.
}
Can anyone please help? A skeleton code would also help as in which Atmosphere method to use for this?

I will copy/past the code i use in my application :
Controller :
#ManagedService(path = "/websocket/*")
#Singleton
public class LanesWebSocket {
private final Logger logger = LoggerFactory.getLogger(LanesWebSocket.class);
// private ScheduledExecutorService scheduledExecutorService;
private Future<?> scheduleFixedBroadcast;
private final ObjectMapper mapper = new ObjectMapper();
private SupervisionCenterService supervisionCenterService;
#Ready
public void onReady(final AtmosphereResource resource) {
if (this.supervisionCenterService == null)
supervisionCenterService = SpringApplicationContext.getBean(SupervisionCenterService.class);
Broadcaster bc = BroadcasterFactory.getDefault().lookup("lanes",true);
bc.addAtmosphereResource(resource);
scheduleFixedBroadcast = bc.scheduleFixedBroadcast(new Callable<String>() {
#Override
public String call() throws Exception {
try {
return mapper.writeValueAsString(supervisionCenterService.findCenterData());
} catch (Exception e) {
scheduleFixedBroadcast.cancel(true);
e.printStackTrace();
return null;
}
}
}, 1, TimeUnit.SECONDS);
}
And you also need to register the atmosphere servlet :
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
[...]
#Override
protected void registerDispatcherServlet(ServletContext servletContext) {
super.registerDispatcherServlet(servletContext);
initAtmosphereServlet(servletContext);
}
private void initAtmosphereServlet(ServletContext servletContext) {
AtmosphereServlet servlet = new AtmosphereServlet();
Field frameworkField = ReflectionUtils.findField(AtmosphereServlet.class, "framework");
ReflectionUtils.makeAccessible(frameworkField);
ReflectionUtils.setField(frameworkField, servlet, new NoAnalyticsAtmosphereFramework());
ServletRegistration.Dynamic atmosphereServlet =
servletContext.addServlet("atmosphereServlet", servlet);
atmosphereServlet.setInitParameter("org.atmosphere.cpr.packages", "com.myclient.theproduct.supervision.websocket");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcasterCacheClass", UUIDBroadcasterCache.class.getName());
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.shareableThreadPool", "true");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.maxProcessingThreads", "10");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.maxAsyncWriteThreads", "10");
servletContext.addListener(new org.atmosphere.cpr.SessionSupport());
atmosphereServlet.addMapping("/websocket/*");
atmosphereServlet.setLoadOnStartup(3);
atmosphereServlet.setAsyncSupported(true);
}
public class NoAnalyticsAtmosphereFramework extends AtmosphereFramework {
public NoAnalyticsAtmosphereFramework() {
super();
}
#Override
protected void analytics() {
// nothing
}
}
}
Don't ask me the reason of the NoAnalyticsAtmosphereFramework class, it could not work without.
Hope this will help you !

Related

Is there a way to integrate Springs #ExceptionHandler with Joinfaces

i wanted to ask if there is a way to enable Springs #ExceptionHandler capabilities with Joinfaces/Primefaces.
For now i'm able to handle global #ControllerAdvice beans, but not if the #ExceptionHandler is inside the #Controller class.
Are there any suggestions on how to solve this topic?
Here is the code i wrote so far
#Slf4j
public class SpringJsfExceptionHandler extends ExceptionHandlerWrapper {
public SpringJsfExceptionHandler(ExceptionHandler wrapped) {
super(wrapped);
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> queue = getUnhandledExceptionQueuedEvents().iterator();
while (queue.hasNext()) {
ExceptionQueuedEvent item = queue.next();
ExceptionQueuedEventContext exceptionQueuedEventContext = (ExceptionQueuedEventContext) item.getSource();
try {
Throwable throwable = exceptionQueuedEventContext.getException();
FacesContext context = FacesContext.getCurrentInstance();
handleException(context, (Exception) throwable);
} finally {
queue.remove();
}
}
}
private void handleException(FacesContext context, Exception throwable) {
WebApplicationContext applicationContext = resolveApplicationContext(context);
Collection<HandlerExceptionResolver> exceptionResolvers = listExceptionHandlerResolvers(applicationContext);
for (HandlerExceptionResolver resolver : exceptionResolvers) {
resolver.resolveException(request(context), response(context), null, throwable);
}
}
private Collection<HandlerExceptionResolver> listExceptionHandlerResolvers(WebApplicationContext context) {
return context.getBeansOfType(HandlerExceptionResolver.class).values();
}
private HttpServletRequest request(FacesContext context) {
return (HttpServletRequest) context.getExternalContext().getRequest();
}
private HttpServletResponse response(FacesContext context) {
return (HttpServletResponse) context.getExternalContext().getResponse();
}
private WebApplicationContext resolveApplicationContext(FacesContext context) {
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
return WebApplicationContextUtils.findWebApplicationContext(request.getServletContext());
}
}
public class SpringJsfExceptionHandlerFactory extends ExceptionHandlerFactory {
public SpringJsfExceptionHandlerFactory() {
}
public SpringJsfExceptionHandlerFactory(ExceptionHandlerFactory wrapped) {
super(wrapped);
}
#Override
public ExceptionHandler getExceptionHandler() {
return new SpringJsfExceptionHandler(getWrapped() != null ? getWrapped().getExceptionHandler() : null);
}
}
This works:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public void handleCalculationException(CalculationException e) {
FacesContext.getCurrentInstance().
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage()));
}
}
This does not work:
#Data
#Controller
#ViewScoped
public class CalculatorController implements Serializable {
#ExceptionHandler
public void handleCalculationException(CalculationException e) {
FacesContext.getCurrentInstance().
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage()));
}
[...]
Thanks in advance
TLDR: No
#ExceptionHandler is part of Spring MVC.
Spring MVC and JSF are separate web frameworks.
Joinfaces allows you to use JSF in a Spring Application, and you can also use Spring MVC in the same application. Every request will however either be handled by Spring MVC (i.e. the DispatcherServlet) or JSF (i.e. the FacesServlet).

Spring `#Autowire` field is `null` eventhough it works fine in other classes

Spring #Autowire field is null even though it works fine in other classes successfully.
public class SendRunner implements Runnable {
private String senderAddress;
#Autowired
private SubscriberService subscriberService;
public SendRunner(String senderAddress) {
this.senderAddress = senderAddress;
}
#Override
public void run() {
sendRequest();
}
private void sendRequest() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
Also this class is managed as a bean in the dispatcher servlet :
<bean id="SendRunner" class="sms.dating.messenger.connector.SendRunner">
</bean>
In here i'm getting a null pointer exception for subscriberService. What would be the possible reason for this? Thanks in advance.
Can you please try with below code snippet
#Configuration
public class Someclass{
#Autowired
private SubscriberService subscriberService;
Thread subscriberThread = new Thread() {
#Override
public void run() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
};
}
Can you please annotate your SendRunner class with #Component or #Service and include the SendRunner package in componentscanpackage
Your bean not in Spring Managed context, below can be the reasons.
Package sms.dating.messenger.connector not in Component scan.
You are moving out of the Spring context by creating an object with new (see below),
this way you will not get the autowired fields.
SendRunner sendRunner = new SendRunner () ,
sendRunner.sendRequest();
Just check how I implement. Hope this will help.
#RestController
public class RestRequest {
#Autowired
SendRunner sendRunner;
#RequestMapping("/api")
public void Uri() {
sendRunner.start();
}
}
SendRunner class
#Service
public class SendRunner extends Thread{
#Autowired
private SubscriberService subscriberService;
#Override
public void run() {
SendRequest();
}
private void SendRequest() {
System.out.println("Object is " + subscriberService);
String senderAddress = "address";
subscriberService.getSubscriberByMsisdn(senderAddress);
}
}
Below are the logs printed when I hit the REST api.
Object is com.example.demo.SubscriberService#40f33492

Replace RequestResponseBodyMethodProcessor with CustomMethodProcessor using BeanPostProcessor

How can I swap RequestResponseBodyMethodProcessor with CustomRequestResponseBodyMethodProcessor in the BeanPostProcessor postProcessAfterInitialization() method?
I have copied entire code from RequestResponseBodyMethodProcessor and made some modification in my CustomRequestResponseBodyMethodProcessor.
Now I want Spring to use my CustomRequestResponseBodyMethodProcessor, not the inbuilt.
So tried overwriting in postProcessAfterInitialization() by implementing BeanPostProcessor.
In the below forum, where it says "create a new list of it, replace the normal RequestResponseBodyMethodProcessor with your custom implementation", how can I get handle to do this?
For Reference:
http://forum.spring.io/forum/spring-projects/web/130803-how-to-extend-requestresponsebodymethodprocessor-and-configure-it-in-webmvc-config-xm
Pseudo Code:
class BaseInsert {
commonattribute1;
commonattribute1;
}
class ChildInsert extends BaseInsert {
childattribute1;
childattribute2;
}
#PostMapping("/abc")
public Resource<?> insert(#RequestBody BaseInsert baseInsert){
...
}
I changed the code in CustomRequestResponseBodyMethodProcessor to assign ChildInsert in BaseInsert.
Solution 1: I will recommend this solution the most
#Configuration
#EnableWebMvc
public class AdapterConfig extends WebMvcConfigurerAdapter {
private final ApplicationContext applicationContext;
#Autowired
public TrackingAdapterConfig(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver>reso) {
super.addArgumentResolvers(reso);
reso.add( new CustomRequestBodyMethodProcessor(); }
}
public class CustomProcessor extends RequestResponseBodyMethodProcessor {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.getNestedGenericParameterType().getTypeName()
.equalsIgnoreCase(BaseInsert.class.getName()));
}
#Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
BaseInsert request = childInsert;
return super.readWithMessageConverters(webRequest, parameter, request.getClass());
}
}
Solution 2: This is also good solution but less performant because BeanPostProcessor interface has 2 methods 'postProcessBeforeInitialization()' and 'postProcessAfterInitialization()'.
So when you provide your implementation of this BeanPostProcessor interface with the class annotated as '#Configuration'.
postProcessBeforeInitialization() - This method is called every time before beans are created
postProcessAfterInitialization() - This method is called every time after beans are created.This is the place where CustomResolver can be added to list of resolvers
#Configuration
public class TestBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
return o;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equalsIgnoreCase("requestMappingHandlerAdapter")) {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = (RequestMappingHandlerAdapter) bean;
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> modifiedArgumentResolvers = new ArrayList<>(argumentResolvers.size());
for(int i =1; i< argumentResolvers.size();i++){
modifiedArgumentResolvers.add(argumentResolvers.get(i));
}
modifiedArgumentResolvers.add(new TestRequestBodyMethodProcessor(requestMappingHandlerAdapter.getMessageConverters(), new ArrayList<Object>()));
((RequestMappingHandlerAdapter) bean).setArgumentResolvers(null);
((RequestMappingHandlerAdapter) bean).setArgumentResolvers(modifiedArgumentResolvers);
}
return bean;
}
}
public class TestRequestBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
public TestRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
public TestRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters, ContentNegotiationManager manager) {
super(converters, manager);
}
public TestRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
public TestRequestBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
ContentNegotiationManager manager, List<Object> requestResponseBodyAdvice) {
super(converters, manager, requestResponseBodyAdvice);
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
#Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
BaseInsert trans_type_code = ;
Object arg = readWithMessageConverters(webRequest, parameter,
Test.getModelClassObject().getClass());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return adaptArgumentIfNecessary(arg, parameter);
}
#Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null) {
if (checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getMethod().toGenericString());
}
}
return arg;
}
protected boolean checkRequired(MethodParameter parameter) {
return (parameter.getParameterAnnotation(RequestBody.class).required() && !parameter.isOptional());
}
#Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
I tried the Solution 1 from previous post but also need this:
#Autowired
private RequestMappingHandlerAdapter adapter;
#PostConstruct
public void prioritizeCustomArgumentMethodHandlers () {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(adapter.getArgumentResolvers ());
List<HandlerMethodArgumentResolver> customResolvers = adapter.getCustomArgumentResolvers();
argumentResolvers.removeAll(customResolvers);
argumentResolvers.addAll (0, customResolvers);
adapter.setArgumentResolvers (argumentResolvers);
}
Without this code, program doesn´t stop at my custom RequestResponseBodyMethodProcessor.
You can check my post : Override default message when #ResponseBody is null

Expose Togglz Admin console in Spring Boot on management-port

By default Togglz admin console runs on application port (configured by server.port property). I want to expose it on management.port. My question: is it possible?
If you use Togglz >= 2.4.0 then this feature is available out of the box.
For older releases solution is below:
I managed to expose a raw servlet on management.port by wrapping it with MvcEndpoint.
The easiest way to do it to use Spring Cloud module which does all the job for you (for example in the HystrixStreamEndpoint):
public class HystrixStreamEndpoint extends ServletWrappingEndpoint {
public HystrixStreamEndpoint() {
super(HystrixMetricsStreamServlet.class, "hystrixStream", "/hystrix.stream",
true, true);
}
}
In the case of TogglzConsoleServlet there is unfortunately one more hack to do with path's due to the way it extracts prefix from request URI, so the whole solution looks a little bit ugly:
#Component
class TogglzConsoleEndpoint implements MvcEndpoint {
private static final String ADMIN_CONSOLE_URL = "/togglz-console";
private final TogglzConsoleServlet togglzConsoleServlet;
#Autowired
TogglzConsoleEndpoint(final ServletContext servletContext) throws ServletException {
this.togglzConsoleServlet = new TogglzConsoleServlet();
togglzConsoleServlet.init(new DelegatingServletConfig(servletContext));
}
#Override
public String getPath() {
return ADMIN_CONSOLE_URL;
}
#Override
public boolean isSensitive() {
return true;
}
#Override
public Class<? extends Endpoint> getEndpointType() {
return null;
}
#RequestMapping("**")
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request) {
#Override
public String getServletPath() {
return ADMIN_CONSOLE_URL;
}
};
togglzConsoleServlet.service(requestWrapper, response);
return null;
}
private class DelegatingServletConfig implements ServletConfig {
private final ServletContext servletContext;
DelegatingServletConfig(final ServletContext servletContext) {
this.servletContext = servletContext;
}
#Override
public String getServletName() {
return TogglzConsoleEndpoint.this.togglzConsoleServlet.getServletName();
}
#Override
public ServletContext getServletContext() {
return servletContext;
}
#Override
public String getInitParameter(final String name) {
return servletContext.getInitParameter(name);
}
#Override
public Enumeration<String> getInitParameterNames() {
return servletContext.getInitParameterNames();
}
}
}

Spring Boot Custom #Async Wrap Callable

I am working on an application that supports multi tenancy. The tenant's unqiue identifier is stored in a thread local and can be accessed via some service.
To allow parallel processing, I have created a Callable wrapper, sets the thread local variable:
class TenantAwareCallable<T> implements Callable<T> {
private final String tenantName;
private final Callable<T> delegate;
TenantAwareCallable(Callable<T> delegate, String tenantName) {
this.delegate = delegate;
this.tenantName = tenantName;
}
#Override
public T call() throws Exception {
// set threadlocal
TenantContext.setCurrentTenantName(tenantName);
try {
return delegate.call();
} catch (Exception e) {
// log and handle
} finally {
TenantContext.clear();
}
}
}
This can already be used in the application. But what I would like to have is some custom #Async annotation, like for example #TenantAwareAsync or #TenantPreservingAsync, that wraps the callable, created by Spring in this one and then executes it.
Is there some way to get started with this?
Thanks in advance!
I have a working solution for this, so I think sharing it here might help some people some day.
I solved it not by using the TenantAwareCallable but by customizing the Executor service. I chose to extend Spring's ThreadPoolTaskScheduler (since this is what we use in the project).
public class ContextAwareThreadPoolTaskScheduler extends ThreadPoolTaskScheduler {
#Override
protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
return new ContextAwareThreadPoolTaskExecutor(poolSize, threadFactory, rejectedExecutionHandler);
}
}
The actual setting of the context data is done in a customized ScheduledThreadPoolExecutor:
public class ContextAwareThreadPoolTaskExecutor extends ScheduledThreadPoolExecutor {
public ContextAwareThreadPoolTaskExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
super(poolSize, threadFactory, rejectedExecutionHandler);
}
#Override
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
return new ContextAwareTask<V>(task);
}
#Override
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
return new ContextAwareTask<V>(task);
}
static private class ContextAwareTask<T> implements RunnableScheduledFuture<T> {
private final RunnableScheduledFuture<T> delegate;
private final TenantContextHolder multitenantContextHolder;
private final LoggingContextHolder loggingContextHolder;
private final SecurityContext securityContext;
ContextAwareTask(RunnableScheduledFuture<T> delegate) {
this.delegate = delegate;
multitenantContextHolder = TenantContextHolder.newInstance();
loggingContextHolder = LoggingContextHolder.newInstance();
securityContext = SecurityContextHolder.getContext();
}
#Override
public void run() {
multitenantContextHolder.apply();
loggingContextHolder.apply();
SecurityContextHolder.setContext(securityContext);
delegate.run();
SecurityContextHolder.clearContext();
loggingContextHolder.clear();
multitenantContextHolder.clear();
}
// all other methods are just delegates
}
}
The Holders are basically just objects to store context state and apply it in the new thread.
public class TenantContextHolder {
private String tenantName;
public static TenantContextHolder newInstance() {
return new TenantContextHolder();
}
private TenantContextHolder() {
this.tenantName = TenantContext.getCurrentTenantName();
}
public void apply() {
TenantContext.setCurrentTenantName(tenantName);
}
public void clear() {
TenantContext.clear();
}
}
The custom implementation of the Scheduler can then be configured in the Spring environment.
#Configuration
public class AsyncConfiguration implements AsyncConfigurer {
private ThreadPoolTaskScheduler taskScheduler;
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
if (taskScheduler == null) {
taskScheduler = new ContextAwareThreadPoolTaskScheduler();
}
return taskScheduler;
}
#Override
public Executor getAsyncExecutor() {
return taskScheduler();
}
}

Resources