2

I have this below aspect and for some reasons that you can see here, had to use @EnableAsync and @Async over aspect method as you can see below:

@Aspect
@Component
@EnableAsync
public class ApiCallLogAspect {

    @Async
    @AfterReturning(value = ("within(com.example..*.web.rest.api..*)"), returning = "returnValue")
    public void endpointAfterReturning(JoinPoint p, Object returnValue) throws InterruptedException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        System.out.println(request.getHeader("authorization"));
    }

}

Now, i get this exception:

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at com.example.web.rest.api.ApiCallLogAspect.endpointAfterReturning(ApiCallLogAspect.java:40) ~[classes/:na]
    at com.example.web.rest.api.ApiCallLogAspect$$FastClassBySpringCGLIB$$f034ee12.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_162]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_162]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_162]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_162]

How can i solve this? is there a way to get request headers from

Rasool Ghafari
  • 4,128
  • 7
  • 44
  • 71
  • 1
    The request is bound to a thread, so executing on a different thread, makes those things in accesible. Instead, retrieve the parts you need and than start the new execution. That way you don't need the full request. – M. Deinum Jul 27 '20 at 13:50
  • How can i do that? could you give me an example code? – Rasool Ghafari Jul 27 '20 at 16:36
  • It is already explained in my comment. Retrieve the header, pass it to a method that executes async (or submit a task using a `TaskExecutor`). It will add a small delay in the setup of the headers, but after that everything is in the background. – M. Deinum Jul 27 '20 at 18:42
  • @M.Deinum Sorry, where should i retrieve header and how can i use it with task executor? I'm not familiar with it. – Rasool Ghafari Jul 29 '20 at 05:10
  • In the aspect. Just remove `@Async`, inject a `TaskExecutor`, then retrieve the information and use `TaskExecutor.submit` or `TaskExecutor, execute` to async do the task. – M. Deinum Jul 29 '20 at 06:25

2 Answers2

2

I solved the problem by these changes below with @M.Deinum comments:

public class ThreadContextHolder {
    private ThreadContextHolder() {
    }

    private static final ThreadLocal<Map<String, Object>> ctx = new ThreadLocal<>();

    public static Map<String, Object> getContext() {
        return ctx.get();
    }

    public static void setContext(Map<String, Object> attrs) {
        ctx.set(attrs);
    }

    public static void removeContext() {
        ctx.remove();
    }
}

and then inject taskExecutor to ApiCallLogAspect class:

@Bean
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    executor.setTaskDecorator(
            runnable -> {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

                Map<String, Object> headers = new HashMap<>();
                for (String header : Collections.list(request.getHeaderNames())) {
                    headers.put(header, request.getHeader(header));
                }

                return () -> {
                    try {
                        ThreadContextHolder.setContext(headers);
                        runnable.run();
                    } finally {
                        ThreadContextHolder.removeContext();
                    }
                };
            });

    executor.initialize();
    return executor;
}

and then change endpointAfterReturning method to it:

@Async
@AfterReturning(value = ("within(com.example..*.web.rest.api..*)"), returning = "returnValue")
public void endpointAfterReturning(JoinPoint p, Object returnValue) throws InterruptedException {
    Map<String, String> headers = new HashMap<>();
    ThreadContextHolder.getContext().forEach((s, o) -> headers.put(s, o.toString()));
    System.out.println(headers.get("authorization"));
}

Now, everything works fine. If this solution has a problem please notify to me.

Rasool Ghafari
  • 4,128
  • 7
  • 44
  • 71
1

I solved this issue by including the following annotations in the pointcut expression

    @Pointcut("within(@org.springframework.stereotype.Repository *)"
        + " || within(@org.springframework.stereotype.Service *)"
        + "|| within(@org.springframework.stereotype.Component *)"
        + " || within(@org.springframework.web.bind.annotation.RestController *)")
public void springBeanPointcut() {
}

and assign it to advice as below

    @Around("myPointcut() && springBeanPointcut()")
    public Object applicationLogger(ProceedingJoinPoint pjp) throws Throwable {
   HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

}
sunil
  • 9
  • 2