I have two different aspects. How do I ensure that when calling method from one aspect, it will still go through proxy chain?
Here is relevant code:
Inner aspect:
#Around("withinReplicatedRepository() && entityMethod() && insertMethod()")
public Object trackInsert(ProceedingJoinPoint jp) throws Throwable {
return trackChange(jp, ChangeType.INSERT, jp.getArgs()[0]);
}
Outer aspect:
#Around("withinReplicatedRepository() && entityMethod() && autoSaveRepository() && saveMethod()")
public Object saveEntity(ProceedingJoinPoint jp) throws Throwable {
TransactionUtil.ensureTransactional();
Object entity = jp.getArgs()[0];
AutoSaveRepository repository = (AutoSaveRepository)jp.getTarget();
if (repository.exists(entity)) {
repository.update(entity);
} else {
repository.insert(entity);
}
return null;
}
Usage:
AutoSaveRepository<MyEntity> repo = ...;
repo.save(entity);
My problem is that jp.getTarget() will return original class, thus repository.insert() will not be captured by trackInsert.
You can try
AopContext.currentProxy()
See Javadoc.
The prerequisite is that you activate proxy exposure, though:
In XML you can do this since Spring 3.0.3 via<aop:aspectj-autoproxy expose-proxy="true"/>.
In annotation-style config your can do it since 4.3.1 via #EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true),see Javadoc
Related
I am writing a Spring Boot Application. I want to audit methods with my annotation #AuditMetod: For example I have method foo() with the annotation:
#AuditMetod(name = "SomeValue")
foo() {...}
I want to handle and audit such methods like this (the simplest example):
auditMethod(Method method) {
if (method.hasAnnotation(AuditMethod.class)) {
System.out.println (method.getName() + " was called at " + new Date())
}
}
upd
Thanks to #Karthikeyan #Swapnil Khante and #misha2048 I understood, that I need to use AOP. But I have 2 problems:
The only method in Aspect class in not being called and I don't see the inscription "----------ASPECT METHOD IS CALLED-----------" in log
How can I check in aspect method what method it is intercepting. To get an instance of Method class.
Now I have the following code:
Controller:
#PostMapping
#LoggingRest(executor = "USER", method = "CREATE", model = "SUBSCRIPTION")
public ResponseEntity<?> create(#Valid #RequestBody SubscriptionRequestDto dto) {
...
}
Aspect:
`#Aspect
#Slf4j
#Component
public class AuditAspect {
#Pointcut(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
log.info("----------ASPECT METHOD IS CALLED------------");
}`
And annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LoggingRest {
String executor() default "SYSTEM";
String method() default "";
String model() default "";
}
Auditing is a cross-cutting concern and can be handled using AOP.
Another solution would be to use a low-level solution by writing a custom annotation and using a Spring interceptorto write your business logic.
To use the Spring interceptor you will need to implement the HandlerInterceptor interface
Example of the annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
boolean active() default true;
}
Interceptor example
#Component
public class AuditInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Audit annotation = handlerMethod.getMethodAnnotation(Audit.class);
if (annotation != null && annotation.active()) {
// your business logic
}
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
check this interceptor example
I think one of the solutions here, as #Karthikeyan mentioned, is to use Spring AOP.
If you are not aware a brief introduction - spring-aop module implements the aspect oriented programming paradigm. We extract some common functionality, that we generally want to apply to some subset of functions/methods, to an entity called Aspect (see class annotated with #Aspect). This class will contain out cross-cutting functionality - such as auditing, for instance we want to audit the methods execution time, lets say. We just put the code to be executed, the condition, which tell the spring what exact beans methods should be affect by this aspect, see below.
For example, if I can audit the method execution duration with the following very simple example (in my case I said that any public method, returning void inside the Class com.example.stackoverflow.BusinessLogicClass must be inspected by this Aspect):
#SpringBootApplication
#EnableAspectJAutoProxy
public class StackoverflowApplication implements ApplicationRunner {
#Autowired
private BusinessLogicClass businessLogicClass;
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
businessLogicClass.test();
}
}
#Aspect
#Component
class MyAspectLogicClass {
#Around("execution(public void com.example.stackoverflow.BusinessLogicClass.*(..))")
public Object hangAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long before = System.currentTimeMillis();
Object returnedValue = proceedingJoinPoint.proceed();
long after = System.currentTimeMillis();
System.out.printf("Retruned in '%s' ms %n", (after - before));
return returnedValue;
}
}
#Component
class BusinessLogicClass {
public void test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In my case, I will get the time before method execution, then by the means of
proceedingJoinPoint.proceed() call I delegate the execution to the real method, and then, once I get the response back, I will get the current system time and calculate the execution time, fairly simple.
I hope I have at least directed you somewhere, if you are looking for documentation, this are the resources I suggest you should look for:
https://docs.spring.io/spring-framework/docs/2.5.x/reference/aop.html offical spring doc (stale a bit, but there are some valuable things to learn)
https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html is more fresh doc
Hope it helped :)
The problem was in right annotation. In Aspect class I tried #Around and everything works as I need.
#Aspect
#Slf4j
#Component
public class AuditAspect {
#Around(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
var method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
log.info("----------ASPECT METHOD IS CALLED------------");
}
}
For getting a Method instance I use fallowing code
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
I am working on a Spring Web reactive project and I am currently implementing the collection of different metrics for requests. It turns out that having more than one #Around annotations interacting with the Mono return object of my rest-controller method cause Spring to throw an IllegalStateException.
I am using Java 11 with spring boot 2.1.5-RELEASE
I stepped through the spring code and tried to find out what exactly is going wrong, and it seems to me that when calling proceed() on the ProceedingJoinPoint the spring aop logic confuses arguments of different annotations. Specifically the getUserAttribute call in AbstractApsectJAdvice line 684 returns null and seems to be provided with a wrong argument. I also tried changing the order of the aspect methods without success.
The following method is the rest handler in question, the #TimedMono and #HostLanguageRequestCounted annotations both add into the reactor chain
#Counted("statistics_incoming_requests")
#TimedMono("statistics_incoming_requestExecutionTime")
#PostMapping("/translate/{id}/{field}")
#HostLanguageRequestCounted("statistics_incoming_hostLanguageRequestCount")
public Mono<TranslationResponseBody> getTranslation(#PathVariable String id,
#PathVariable String field,
#RequestBody TranslationRequestBody requestBody) {
return translateService.translate(id, requestBody.getTargetLanguage(), requestBody.getText(), field);
}
This is the method handling the TimedMono annotation:
#Around(value = "#annotation(timed)")
#Order(2)
#SuppressWarnings("checkstyle:illegalThrows")
public Mono<?> timeExecution(ProceedingJoinPoint pjp, TimedMono timed) throws Throwable {
Long tStart = System.nanoTime();
Object m = pjp.proceed();
long tEnd = System.nanoTime();
if (!(m instanceof Mono)) {
throw (new Exception("Method must return a mono object"));
}
Mono<?> mono = (Mono) m;
Consumer<Object> stopTimer = obj -> {
meterRegistry.timer(timed.value())
.record(tEnd - tStart, TimeUnit.NANOSECONDS);
};
return mono.doOnError(stopTimer).doOnNext(stopTimer);
}
And the method handling the HostLanguageRequestCounted annotation:
#Around(value = "#annotation(hostLanguageRequestCounted)")
#Order(1)
public Object gatherTenantMetrics(ProceedingJoinPoint pjp,
HostLanguageRequestCounted hostLanguageRequestCounted) throws Throwable {
Optional<TranslationRequestBody> body = HostLanguageRequestCountedMetricAspect
.getArgumentOfType(TranslationRequestBody.class, pjp);
return Mono.subscriberContext()
.flatMap(ctx -> {
log.info(ctx.toString());
return Mono.just(ctx.get("host"));
}).flatMap(host -> {
if (host != null && body.isPresent() && body.get().getTargetLanguage() != null) {
String metricKey = hostLanguageRequestCounted.value();
metricKey += "_" + host.toString().toLowerCase();
metricKey += "_" + body.get().getTargetLanguage().toLowerCase();
meterRegistry.counter(metricKey).increment();
}
try {
return (Mono<?>) pjp.proceed();
} catch (Throwable t) {
return Mono.error(t);
}
});
}
private static <T> Optional<T> getArgumentOfType(Class<T> clazz, ProceedingJoinPoint pjp) {
return Arrays.stream(pjp.getArgs())
.filter((arg) -> clazz.isAssignableFrom(arg.getClass()))
.map((obj) -> (T) obj).findFirst();
}
in which the exception is thrown (on return (Mono) pjp.proceed();)
The exception thrown is the following:
java.lang.IllegalStateException: Required to bind 2 arguments, but only bound 1 (JoinPointMatch was NOT bound in invocation)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.argBinding(AbstractAspectJAdvice.java:605)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
The next entry in the stack trace is the line in my code.
Is this an actual bug in spring or am I doing something wrong ? When I remove one of the annotations, the other one will execute without problems.
I'm trying to implement performance logging based off of this post: http://www.baeldung.com/spring-performance-logging. I'd like to log each controller endpoint and every database request. If you would like to see the full project, you can find it here. When I hit the endpoints nothing is logged. Placing a breakpoint within the interceptor class it doesn't stop either. I've already set my logging for the packages to the trace level. What am I missing? I believe it's something to with the #PointCut but after looking at the docs I believe I have it correct.
Interceptor
public class PerformanceMonitorInterceptor extends AbstractMonitoringInterceptor
{
#Override
protected Object invokeUnderTrace(MethodInvocation methodInvocation, Log log) throws Throwable
{
String name = createInvocationTraceName(methodInvocation);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.trace(String.format("Method %s execution start at %s", name, LocalDateTime.now()));
try
{
return methodInvocation.proceed();
}
finally
{
stopWatch.stop();
log.trace(String.format("Method %s execution took %dms (%s)", name,
stopWatch.getTotalTimeMillis(), DurationFormatUtils
.formatDurationWords(stopWatch.getTotalTimeMillis(), true, true)));
}
}
}
Configuration
#Configuration
#EnableAspectJAutoProxy
#Aspect
public class ContactControllerPerfLogConfig
{
#Bean
public PerformanceMonitorInterceptor performanceMonitorInterceptor()
{
return new PerformanceMonitorInterceptor();
}
// Any public method on the ContactController
#Pointcut("execution(public * org.example.phonebookexample.app.contact.ContactController.*(..))")
public void contactControllerMonitor()
{
}
#Bean
public Advisor contactControllerMonitorAdvisor(
PerformanceMonitorInterceptor performanceMonitorInterceptor)
{
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("org.example.phonebookexample.app.contact.ContactControllerPerfLogConfig.contactControllerMonitor()");
return new DefaultPointcutAdvisor(pointcut, performanceMonitorInterceptor);
}
}
I never understood the importance of AbstractTraceInterceptor if you are going to customize it.
I always felt too much code for me which can be achieved by a simple #Around advice if you already have #Aspect in place.
#Around("execution(* com..*.*(..))")
public Object logStartAndEnd(ProceedingJoinPoint pjp) throws Throwable{
long startTime = System.currentTimeMillis();
String className = pjp.getTarget().getClass().getCanonicalName();
String methodName = pjp.getSignature().getName();
log.info("started method : " + className+"."+methodName);
Object obj;
try {
obj = pjp.proceed();
log.info("finished method : " + className+"."+methodName);
return obj;
} catch (Throwable e) {
throw e;
}finally {
log.info("Method "+className+"."+methodName+" execution lasted:"+((System.currentTimeMillis() - startTime )/1000f)+" seconds");
}
}
AbstractTraceInterceptor implements MethodInterceptor and has its invoke() method implemented as follows:
public Object invoke(MethodInvocation invocation) throws Throwable {
Log logger = getLoggerForInvocation(invocation);
if (isInterceptorEnabled(invocation, logger)) {
return invokeUnderTrace(invocation, logger);
}
else {
return invocation.proceed();
}
}
Therefore the logger for the Interceptor class needs to be set to TRACE. For the basic PerformanceMonitorInterceptor, that will be org.springframework.aop.interceptor.PerformanceMonitorInterceptor. Since you've written your own interceptor, you'll have to set logging level for your own class.
Take a look at JamonPerformanceMonitorInterceptor for an example of an alternative approach, allowing to track all invocations regardless of logging level, if required.
For the sake of completeness, I'll also post an xml configuration example, because when it comes to AOP, Spring java configs are not so elegant compared to xml configs:
<bean id="performanceMonitorInterceptor" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
<aop:config>
<aop:pointcut id="contactControllerMonitor" expression="execution(public * org.example.phonebookexample.app.contact.ContactController.*(..))" />
<aop:advisor id="contactControllerMonitorAdvisor" pointcut-ref="contactControllerMonitor" advice-ref="performanceMonitorInterceptor"/>
</aop:config>
This config can be imported into your java configuration as follows:
#ImportResource("classpath:/aop-config.xml")
public class MainConfig { ... }
I created an AspectJ aspect which runs fine within a spring application. Now I want to add caching, using springs Cacheable annotation.
To check that #Cacheable gets picked up, I'm using the name of a non-existing cache manager. The regular run-time behavior is that an exception is thrown. But in this case, no exception is being thrown, which suggests that the #Cacheable annotation isn't being applied to the intercepting object.
/* { package, some more imports... } */
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cache.annotation.Cacheable;
#Aspect
public class GetPropertyInterceptor
{
#Around( "call(* *.getProperty(..))" )
#Cacheable( cacheManager = "nonExistingCacheManager", value = "thisShouldBlowUp", key = "#nosuchkey" )
public Object intercepting( ProceedingJoinPoint pjp ) throws Throwable
{
Object o;
/* { modify o } */
return o;
}
}
Given that my Aspect is working already, how can I make #Cacheable work on top of it?
You can achieve similar results, by using Spring regular dependency injection mechanism and inject a org.springframework.cache.CacheManager into your aspect:
#Autowired
CacheManager cacheManager;
Then you can use the cache manager in the around advice:
#Around( "call(* *.getProperty(..))" )
public Object intercepting( ProceedingJoinPoint pjp ) throws Throwable
{
Cache cache = cacheManager.getCache("aopCache");
String key = "whatEverKeyYouGenerateFromPjp";
Cache.ValueWrapper valueWrapper = cache.get(key);
if (valueWrapper == null) {
Object o;
/* { modify o } */
cache.put(key, o);
return o;
}
else {
return valueWrapper.get();
}
}
Both 'aop:aspectj-autoproxy' and 'mvc:annotation-driven' are present in the XML config.
Both of these classes are defined as a bean inside of the same XML.
Using Spring 3.2.3.RELEASE and Google App Engine 1.8.1 in a local/dev environment.
My pointcut does not execute.
My advice. Declared inside a class annotated with #Aspect.
#Component
#Aspect
public class RequestLimiter {
private MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
#Pointcut("within(#pcs.annotations.LimitRequests com.zdware.pcs.controllers.PingCollectorController)")
public void methodRequestLimited(){}
#Around("methodRequestLimited() && args(req,limitRequests)")
public Object requestGateWay(ProceedingJoinPoint jp, HttpServletRequest req,LimitRequests limitRequests) throws Throwable {
// do stuff
}
}
The method I am using to test in the controller layer.
#Controller
public class PingCollectorController {
#RequestMapping(value="/test")
#LimitRequests(requestTimeLimit = 1, functionName = "Test")
public String test(){
return "test"; // this will return me to a jsp that doesnt exist, but my advice is still not executing.
}
}
Is CGLIB in the classpath? It will be needed to generate the proxy (since your controller does not implement an interface, spring cannot use a simpler JDK proxy).