I want to capture the argument of #transactional if it is applied at class level.
for e.g. if #transactional applied at method level like :-
class A {
#transactional(readOnly= true)
public void someMethod(){
// some code...
}
}
then I am able to Intercept and capture the formal argument i.e. readOnly with this code like :-
#Aspect
#Component
#Order(0)
public class ReadOnlyRouteInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ReadOnlyRouteInterceptor.class);
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, Transactional transactional) {
if (transactional.readOnly())
//do something
}
However the above code will not work if #transactional applied at class level as :-
#transactional(readOnly= true)
class A {
public void someMethod(){
// some code...
}
}
Now in order to Intercept the #transactional annotation which is applied at class level I have following code :-
#Pointcut("#within(org.springframework.transaction.annotation.Transactional *)")
public void beanAnnotatedWithTransactional() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Pointcut("publicMethod() && beanAnnotatedWithTransactional()")
public void publicMethodInsideAClassMarkedWithATransactional() {}
My actual problem here is I am unable to check the value of readOnly flag if #transactional is applied at class level.
For type level annotation :
#Around("#within(transactional)")
public Object myMethod(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
boolean readOnly = transactional.readOnly();
...
return pjp.proceed();
}
For method level annotation:
#Around("execution(public * *(..)) && #annotation(org.springframework.transaction.annotation.Transactional)")
public Object myMethod(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Transactional annotation = method.getAnnotation(org.springframework.transaction.annotation.Transactional.class);
boolean value = annotation. readOnly();
...
return pjp.proceed();
}
Another (cleaner) option for method level :
#Around("#annotation(transactional)")
public Object myMethod(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
}
or
With more control over tager :
#Around("execution(#org.springframework.transaction.annotation.Transactional public * *(..)) && #annotation("transactional")
public Object myMethod(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
}
You may use the TransactionSynchronizationManager to get reference to the transaction details.
Following code provides the readonly details of the current active transaction.
import org.springframework.transaction.support.TransactionSynchronizationManager;
#Component
#Aspect
public class TestTransactionalAspect {
#Pointcut("#within(org.springframework.transaction.annotation.Transactional)")
public void beanAnnotatedWithTransactional() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Around("publicMethod() && beanAnnotatedWithTransactional()")
public void publicMethodInsideAClassMarkedWithATransactional(ProceedingJoinPoint pjp) {
try {
System.out.println("Intercepted "+pjp.toShortString());
if (TransactionSynchronizationManager.isActualTransactionActive()) {
System.out.println("Is transaction readonly : "+TransactionSynchronizationManager.isCurrentTransactionReadOnly());
}
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Update:
Assuming this is not a Spring Boot project , #EnableTransactionManagement is mandatory for your spring application to work with transactions. Data will get persisted to database without transactions as well.
#EnableTransactionManagement annotation has to be used on
#Configuration classes.
Following is a sample code to enable transaction management in spring
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public FooRepository fooRepository() {
// configure and return a class having #Transactional methods
return new JdbcFooRepository(dataSource());
}
#Bean
public DataSource dataSource() {
// configure and return the necessary JDBC DataSource
}
#Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource());
}
}
Based on the answers above I tried with the following code it is very simple.
/**
* This Aspect advice will be called only if transactional applied at method level
*/
#Around("#annotation(transactional)")
public Object proceedWithMethodLevelAnnotation(ProceedingJoinPoint proceedingJoinPoint,
Transactional transactional) {
if( transactional.readOnly() ) {
//do something
}
/**
* This Aspect advice will be called only if transactional annotation applied at class level
*/
#Around("#within(transactional)")
public Object proceedWithClassLevelAnnotation(ProceedingJoinPoint proceedingJoinPoint,
Transactional transactional)
if( transactional.readOnly() ) {
//do something
}
}
Related
I have two classes
public class ParentTestClass {
public void publicMethodOfParent() {
}
}
#Component
#MyAnnotation
public class ChildTestClass extends ParentTestClass {
public void publicMethodOfChild() {
}
}
With Spring AOP I need to wrap:
all calls for all public methods that are annotated with #MyAnnotation if annotation is put on class level
all methods that are annotated with #MyAnnotation if annotation is on the method level.
Here is my pointcut
#Around("(#within(MyAnnotation) && execution(public * *(..))) || #annotation(MyAnnotation)")
public Object myWrapper(ProceedingJoinPoint invocation) throws Throwable {
// ...
}
This works for public methods of ChildTestClass but ParentTestClass#publicMethodOfParent is not wrapped when I make a call childTestClass.publicMethodOfParent() How can I include parent methods?
Following pointcut expression will intercept the parent methods as well
From the documentation
#Pointcut("within(com.app..*) && execution(public * com.app..*.*(..))")
public void publicMethodsInApp() {
}
#Around("(publicMethodsInApp() && #target(MyAnnotation)) || "
+ "(publicMethodsInApp() && #annotation(MyAnnotation))")
public Object myWrapper(ProceedingJoinPoint invocation) throws Throwable {
//..
}
#target: Limits matching to join points (the execution of methods when
using Spring AOP) where the class of the executing object has an
annotation of the given type.
I use mybatis. My question is how can Spring AOP matches annotation on interface method? Because I want to put some param in annotation and then handle them in afterReturning method.
my annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface CacheClear {
String key() default "";
}
in mapper class:
#CacheClear
List<BizInst> selectAllBizInsts();
and in my aspect:
when use "execute..." it works
#AfterReturning("execution(public * com.dao.*.select*(..))")
public void doAfterReturning(){
System.out.println("after returning");
}
but when use "#annotation(...)" it doesn't work
#AfterReturning("#annotation(com.annotation.CacheClear)")
public void doAfterReturning(){
System.out.println("after returning");
}
You can do something like that for selecting your public dao methods annotated with CacheClear annotation:
#Pointcut("execution(#com.yourPackage.CacheClear * *(..))")
public void methodAnnotatedWithCacheClear( ) {}
#Pointcut("execution(public * com.dao.*.select*(..))")
public void publicDAOMethod() {}
#AfterReturning(pointcut = "methodAnnotatedWithCacheClear() && publicDAOMethod()", returning = "result")
public void doStuff(JoinPoint joinPoint, Object result) {
I have tried the following code , but it does not work:
#Component
#Aspect
#Order(Integer.MAX_VALUE)
public class CacheAspect {
#Around("execution(public * org.springframework.cache.interceptor.CacheInterceptor.invoke(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//CLASS_CACHE.set(signature.getReturnType());
return joinPoint.proceed();
}
}
P.S. I am sure CacheInterceptor is a spring managed bean.
After some experimenting, I find that replacing the spring built in CacheInterceptor with a user defined one can solve my problem.
Here is the code in case someone has similar requirments.
#Configuration
#EnableCaching
#Profile("test")
public class CacheConfig {
#Bean
#Autowired
public CacheManager cacheManager(RedisClientTemplate redisClientTemplate) {
return new ConcurrentMapCacheManager(redisClientTemplate, "test");
}
#Bean
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
#Bean
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new MyCacheInterceptor();
interceptor.setCacheOperationSources(cacheOperationSource());
return interceptor;
}
}
MyCacheInterceptor.java, which shares the same logic with CacheAspect:
public class MyCacheInterceptor extends CacheInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
//CLASS_CACHE.set(signature.getReturnType());
return super.invoke(invocation);
}
}
The spring built in CacheInterceptor bean can be found in ProxyCachingConfiguration class.
Hope it helps.
How to make an aspect that targets all public methods that belong to a class that is marked with specific annotation? In following method1() and method2() should be processed by the aspect and method3() should not be processed by the aspect.
#SomeAnnotation(SomeParam.class)
public class FooServiceImpl extends FooService {
public void method1() { ... }
public void method2() { ... }
}
public class BarServiceImpl extends BarService {
public void method3() { ... }
}
If I put annotations on method level, this aspect will work and match the method calls.
#Around("#annotation(someAnnotation)")
public Object invokeService(ProceedingJoinPoint pjp, SomeAnnotation someAnnotation)
throws Throwable {
// need to have access to someAnnotation's parameters.
someAnnotation.value();
}
I am using Spring and proxy based aspects.
The following should work
#Pointcut("#target(someAnnotation)")
public void targetsSomeAnnotation(#SuppressWarnings("unused") SomeAnnotation someAnnotation) {/**/}
#Around("targetsSomeAnnotation(someAnnotation) && execution(* *(..))")
public Object aroundSomeAnnotationMethods(ProceedingJoinPoint joinPoint, SomeAnnotation someAnnotation) throws Throwable {
... your implementation..
}
This works in Spring Boot 2:
#Around("#within(xyz)")
public Object method(ProceedingJoinPoint joinPoint, SomeAnnotation xyz) throws Throwable {
System.out.println(xyz.value());
return joinPoint.proceed();
}
Note that based on the method argument type (SomeAnnotation xyz), Spring and AspectJ will know which annotation you are looking for, so the xyz does not have to be the name of your annotation.
Using #target and reading the type level annotation with reflection works.
#Around("#target(com.example.SomeAnnotation)")
public Object invokeService(ProceedingJoinPoint pjp) throws Throwable {
I am trying to define a pointcut, that would catch every method that is annotated with (i.e.) #CatchThis. This is my own annotation.
Moreover, I'd like to have access to the first argument of the method, which will be of Long type. There may be other arguments too, but I don't care about them.
EDIT
This is what I have right now. What I don't know is how to pass the first parameter of the method annotated with #CatchThis.
#Aspect
public class MyAspect {
#Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(catchThis)")
public Object logAction(ProceedingJoinPoint pjp, CatchThis catchThis) throws Throwable {
return pjp.proceed();
}
}
Something like this should do:
#Aspect
public class MyAspect{
#Pointcut(value="execution(public * *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(catchThis) && args(.., Long ,..)")
public Object logAction(
ProceedingJoinPoint pjp, CatchThis catchThis, Long long)
throws Throwable {
return pjp.proceed();
}
}