How to intercept meta annotations (annotated annotations) in Spring AOP - spring

Suppose I want to find all classes annotated with #Controller, I would create this pointcut:
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerPointcut() {}
But those controllers annotated with #RestController can not be found.
Since RestController itself is annoatated with #Controller.
Any idea on how to find classes annotated either with #Controller or #RestController without having to create two Pointcuts ?
===== edit ====
My real intention here is as follows:
parent annotation:
public #interface ParentAnnotation {}
child annotation (annotated with #ParentAnnotation):
#ParentAnnotation
public #interface ChildAnnotation {}
class A:
#ParentAnnotation
public class MyClassA {}
class B:
#ChildAnnotation
public class MyClassB {}
Now I want to find both MyClassA and MyClassB through #ParentAnnotation.
There's no question for finding MyClassA, but MyClassB is indirectly annotated with #ParentAnnotation, is there a generic way to deal such situation?

How about this?
#Pointcut(
"within(#org.springframework.stereotype.Controller *) || " +
"within(#org.springframework.web.bind.annotation.RestController *)" +
)
Or a little shorter, but maybe too fuzzy if there are other classes with matching names in Springs' packages (I have not checked):
#Pointcut("within(#(org.springframework..*Controller) *)")
Update: As for your real question, I understand it now after your edit. This is also possible, but kind of tricky syntactically. Let me rename your annotations into MetaAnnotation and MyAnnotation, okay? Because they are not really parent and child of each other, there is no inheritance involved in the the OOP sense, just nesting.
Annotations:
Please make sure that the annotations have indeed runtime scope. I did not see that in your code.
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
public #interface MetaAnnotation {}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
#MetaAnnotation
public #interface MyAnnotation {}
Java sample classes:
One class is annotated with the meta annotation, one with the annotated annotation and one with no annotation (negative test case):
package de.scrum_master.app;
#MetaAnnotation
public class MyClassA {
public void doSomething() {}
}
package de.scrum_master.app;
#MyAnnotation
public class MyClassB {
public void doSomething() {}
}
package de.scrum_master.app;
public class MyClassC {
public void doSomething() {}
}
Driver application:
Because I am using pure Java + AspectJ without Spring, I am using this little application in order to demonstrate the result.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyClassA().doSomething();
new MyClassB().doSomething();
new MyClassC().doSomething();
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MetaAnnotationInterceptor {
#Before(
"execution(* *(..)) && (" +
"within(#de.scrum_master.app.MetaAnnotation *) || " +
"within(#(#de.scrum_master.app.MetaAnnotation *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
Console log:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
Now if you want to add yet another level of nesting, add a new annotation and annotate the formerly unannotated class with it:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
#MyAnnotation
public #interface MyOtherAnnotation {}
package de.scrum_master.app;
#MyOtherAnnotation
public class MyClassC {
public void doSomething() {}
}
Then extend the pointcut by one more level of nesting/recursion:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MetaAnnotationInterceptor {
#Before(
"execution(* *(..)) && (" +
"within(#de.scrum_master.app.MetaAnnotation *) || " +
"within(#(#de.scrum_master.app.MetaAnnotation *) *) || " +
"within(#(#(#de.scrum_master.app.MetaAnnotation *) *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
The console log changes to:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())
P.S.: The execution(* *(..)) part is only necessary in AspectJ in order to limit pointcut matching to method executions because AspectJ can intercept more events than Spring AOP. So in Spring AOP you can eliminate that part and the braces surrounding the ... || ... || ... part.

Related

How to create a pointcut expression for all annotations composed of a specified annotation [duplicate]

Suppose I want to find all classes annotated with #Controller, I would create this pointcut:
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerPointcut() {}
But those controllers annotated with #RestController can not be found.
Since RestController itself is annoatated with #Controller.
Any idea on how to find classes annotated either with #Controller or #RestController without having to create two Pointcuts ?
===== edit ====
My real intention here is as follows:
parent annotation:
public #interface ParentAnnotation {}
child annotation (annotated with #ParentAnnotation):
#ParentAnnotation
public #interface ChildAnnotation {}
class A:
#ParentAnnotation
public class MyClassA {}
class B:
#ChildAnnotation
public class MyClassB {}
Now I want to find both MyClassA and MyClassB through #ParentAnnotation.
There's no question for finding MyClassA, but MyClassB is indirectly annotated with #ParentAnnotation, is there a generic way to deal such situation?
How about this?
#Pointcut(
"within(#org.springframework.stereotype.Controller *) || " +
"within(#org.springframework.web.bind.annotation.RestController *)" +
)
Or a little shorter, but maybe too fuzzy if there are other classes with matching names in Springs' packages (I have not checked):
#Pointcut("within(#(org.springframework..*Controller) *)")
Update: As for your real question, I understand it now after your edit. This is also possible, but kind of tricky syntactically. Let me rename your annotations into MetaAnnotation and MyAnnotation, okay? Because they are not really parent and child of each other, there is no inheritance involved in the the OOP sense, just nesting.
Annotations:
Please make sure that the annotations have indeed runtime scope. I did not see that in your code.
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
public #interface MetaAnnotation {}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
#MetaAnnotation
public #interface MyAnnotation {}
Java sample classes:
One class is annotated with the meta annotation, one with the annotated annotation and one with no annotation (negative test case):
package de.scrum_master.app;
#MetaAnnotation
public class MyClassA {
public void doSomething() {}
}
package de.scrum_master.app;
#MyAnnotation
public class MyClassB {
public void doSomething() {}
}
package de.scrum_master.app;
public class MyClassC {
public void doSomething() {}
}
Driver application:
Because I am using pure Java + AspectJ without Spring, I am using this little application in order to demonstrate the result.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyClassA().doSomething();
new MyClassB().doSomething();
new MyClassC().doSomething();
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MetaAnnotationInterceptor {
#Before(
"execution(* *(..)) && (" +
"within(#de.scrum_master.app.MetaAnnotation *) || " +
"within(#(#de.scrum_master.app.MetaAnnotation *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
Console log:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
Now if you want to add yet another level of nesting, add a new annotation and annotate the formerly unannotated class with it:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target({ TYPE })
#MyAnnotation
public #interface MyOtherAnnotation {}
package de.scrum_master.app;
#MyOtherAnnotation
public class MyClassC {
public void doSomething() {}
}
Then extend the pointcut by one more level of nesting/recursion:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MetaAnnotationInterceptor {
#Before(
"execution(* *(..)) && (" +
"within(#de.scrum_master.app.MetaAnnotation *) || " +
"within(#(#de.scrum_master.app.MetaAnnotation *) *) || " +
"within(#(#(#de.scrum_master.app.MetaAnnotation *) *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
The console log changes to:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())
P.S.: The execution(* *(..)) part is only necessary in AspectJ in order to limit pointcut matching to method executions because AspectJ can intercept more events than Spring AOP. So in Spring AOP you can eliminate that part and the braces surrounding the ... || ... || ... part.

How to make Spring IoC container available through out project

I feel stupid to even ask for this but I spent days looking for the answer and I'm still with nothing.
I wanna include simple Spring IoC container in my project. All I want it to do is to allow me Injecting/Autowiring some reusable objects in other classes. What I've done so far looks like this:
-> Project structure here <-
Configuration code:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
#ComponentScan(basePackages = "com.example")
public class AppConfig {
#Bean
public Random rand() {
return new Random(42);
}
#Bean
public String string() {
return "Hello World!";
}
}
Main class code:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class Main {
#Autowired
Random rand;
#Autowired
String string;
public static void main(String[] args) {
// workflow
Main main = new Main();
System.out.println(main.string);
}
}
AnotherClass code:
package com.example.deeperpackage;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Random;
public class AnotherClass {
#Autowired
Random rand;
#Autowired
String string;
public void methodToBeCalled() {
// TODO
System.out.println(string);
}
}
How can I make these #Autowired annotations work? Do I have to instantiate container in every single class in which I want to autowire components? I've seen in work a oracle app which used Spring and #Inject to distribute objects to numerous classes and there was no container logic in any class available for me. Just fields with #Inject annotation. How to achieve that?
Simply add the annotation #Component on the classes you want to inject :
#Component
public class AnotherClass {
...
}
But you cannot inject static attributes and when you do new Main(), no Spring context is being created. If you use Spring Boot, you should look at how to write a main with it.
https://spring.io/guides/gs/spring-boot/

Spring aop doesn't run when project starts

I'v implemented a spring-boot aop demo and it runs well, but when I want to use it to load some resource when the project starts, it doesn't work somehow
Aop:
package com.neo.mysql;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Created by li_weia on 2017/7/6.
*/
#Aspect
#Component
public class DynamicDataSourceAspect {
#Before("#annotation(VendorSource)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在#DS注解
if (method.isAnnotationPresent(VendorSource.class)) {
VendorSource annotation = method.getAnnotation(VendorSource.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
#After("#annotation(VendorSource)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
The VendorSource annotation:
package com.neo.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by li_weia on 2017/7/6.
*/
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface VendorSource {
String value() default "vendor-master";
}
It runs well here, I can successfully change datasource by annotation:
package com.neo.web;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
public class UserController {
private final ClassMappingDao siteMapper;
#Autowired(required = false)
public UserController(ClassMappingDao siteMapper) {
this.siteMapper = siteMapper;
}
#RequestMapping("/getSites")
#VendorSource("vendor-read")
public List<SiteEntity> getUsers() {
return siteMapper.getAllSite();
}
}
but it doesn't work here, the aop method is not invoked at all:
package com.neo.component;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Created by li_weia on 2017/7/7.
*/
#Component
public class TestComponent{
private final ClassMappingDao userMapper;
#Autowired(required = false)
public TestComponent(ClassMappingDao userMapper) {
this.userMapper = userMapper;
init();
}
#VendorSource("vendor-read")
public void init() {
List<SiteEntity> sites = userMapper.getAllSite();
for(SiteEntity site: sites){
System.out.println(site.getSite());
}
}
}
You need to fully qualify the annotation, like so:
#Before("execution(public * *(..)) && #annotation(com.neo.mysql.VendorSource)")
private void whatever() {}
Also, as mentioned in my comment above, you need to have spring-boot-starter-aop on classpath. Maybe you already do, but since you didn't say, it's worth mentioning.
Edit:
I didn't notice the real problem before, I wasn't paying attention.
Spring AOP only triggers if you make calls from another class. This is because Spring needs to be able to intercept the call and run the pointcut. Calling the method from constructor is not going to do anything.
You can do a hackish workaround. Create a #PostConstruct void postConstruct() {} method in your class (not constructor), autowire ApplicationContext, and then do MyClassWithInitMethod myClass = context.getBean(MyClassWithInitMethod.class) in the postConstruct method. Then call the method on myClass, and AOP will kick in.
Frankly, I didn't previously check what you are doing in your pointcut, and it's a terrible idea. When multiple threads run, they are going to overwrite the static context, and create a race-condition that you'll then create another question for. Don't do it! Use the factory pattern instead, and inject the DataSourceFactory in the classes that now have the annotation.

Spring AOP annotation based pointcuts for ElementType.TYPE?

I have a service implementation carrying a class-wide #Transactional annotation. I also have an aspect that uses the #Around advice to retry failed transactions. I'm now trying (for type-safety reasons) to make the pointcut definition annotation based:
#Around("#annotation(TransactionRetryable)")
TransactionRetryable.java:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface TransactionRetryable {
}
Service layer:
#Transactional
public class ... {
#Override
#TransactionRetryable
public String forceError() {
throw new RuntimeException(someNastyMessage);
}
In that form, it only applies to method, not whole classes. However, the #Transactional annotation propagates from class level to each method. Is there a way to avoid putting the #TransactionRetryable annotation above each method and simply once above the class like the #Transactional annotation? Desired form:
#Transactional
#TransactionRetryable
public class ... {
#Override
public String forceError() {
throw new RuntimeException(someNastyMessage);
}
This pointcut would advise all public methods of a class annotated with #TransactionRetryable:
#Around("execution(public * *(..)) && within(#your.package.TransactionRetryable *)")

How do I pass arguments to Spring AOP advice with annotated parameters?

I am using Spring 3.1.2.RELEASE with cglib load-time weaving and I am trying to get advice to work with a method that has custom annotations and annotated parameters.
Advice:
#Aspect
public class MyAdvice
{
#Around("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) " +
"&& args(batch) && #args(propertyToLock)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable {
//Do stuff....
pjp.proceed();
}
}
Here is the class that I am testing:
public interface UpdateManager
{
public void processUpdate(MyBatchObject batch);
}
public class UpdateManagerImpl implements UpdateManager
{
#Lock
public void processUpdate(#LockVal("lockValue") MyBatchObject batch)
{
//Do stuff...
}
}
The problem is that I can't get the advice to execute. If I remove the #args and args conditions in the pointcut, the advice fires, but then I have to dig through the ProceedingJoinPoint to get the parameter that I need.
Why isn't the advice firing? Did I do something wrong?
Edit: The following pointcut DOES WORK as a standalone program with Spring:
#Aspect
public class MyAdvice
{
#Around("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) " +
"&& args(batch)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch) throws Throwable {
//Do stuff....
pjp.proceed();
}
}
However, it does NOT work under JBoss 6 using load-time weaving. I suppose my question should be, then, why does it work as a standalone program but not under JBoss 6?
Update: I forgot to mention that #args() is not meant to match a parameter's annotation, but a parameter type's annotation, which is not what you want and which thus I do not use here.
You cannot bind a parameter's annotation via args(), only the parameter itself. This means that you can only access the parameter's annotation via reflection. You need to determine the method signature, create a Method object from it and then iterate over the method parameters' annotations. Here is a full code sample:
package com.mycompany.locking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Lock {}
package com.mycompany.locking;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface LockVal {
String value() default "";
}
package com.mycompany;
public class MyBatchObject {}
package com.mycompany;
public interface UpdateManager {
public void processUpdate(MyBatchObject batch);
}
package com.mycompany;
import com.mycompany.locking.Lock;
import com.mycompany.locking.LockVal;
public class UpdateManagerImpl implements UpdateManager {
#Lock
#Override
public void processUpdate(#LockVal("lockValue") MyBatchObject batch) {
System.out.println("Processing update");
}
public static void main(String[] args) {
UpdateManager updateManager = new UpdateManagerImpl();
updateManager.processUpdate(new MyBatchObject());
}
}
package com.mycompany.aop;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import com.mycompany.MyBatchObject;
import com.mycompany.locking.LockVal;
#Aspect
public class MyAspect {
#Pointcut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal (*), ..)) && args(batch)")
public void lockedMethod(MyBatchObject batch) {}
#Around("lockedMethod(batch)")
public Object lockAndProceed(ProceedingJoinPoint pjp, MyBatchObject batch) throws Throwable {
System.out.println(pjp);
System.out.println(batch);
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Class<?> clazz = methodSignature.getDeclaringType();
Method method = clazz.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
LockVal propertyToLock;
for (Annotation ann : method.getParameterAnnotations()[0]) {
if(LockVal.class.isInstance(ann)) {
propertyToLock = (LockVal) ann;
System.out.println(propertyToLock.value());
}
}
return pjp.proceed();
}
}
When I run UpdateManagerImpl.main, I see the following output, just as expected:
execution(void com.mycompany.UpdateManagerImpl.processUpdate(MyBatchObject))
com.mycompany.MyBatchObject#86f241
lockValue
Processing update
Disclaimer: I am not a Spring guy, I just tested this with plain AspectJ, not Spring AOP.
This is not a solution, but should take you a step further:
I am assuming you made a typo in your annotations, you probably meant #Aspect and not #Advice?
The suggestion that I have would be to try out these:
a. Separate out into named point cuts and the advice that you want to apply on the pointcut:
#PointCut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) && args(batch) && #args(propertyToLock)")
public void mypointcut(Object batch, LockVal propertyToLock){}
#Around("mypointcut(batch, propertyToLock)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable {
//Do stuff....
pjp.proceed();
}
b. It could be that either args expression or #args expression is causing the issue - try keeping one and removing other and seeing which combination works.
c. If this does not narrow things down, one more option could be to explicitly add an argNames expression also, it could be that the argument names are being cleaned out and not being matched up by name at runtime:
#PointCut("execution(#com.mycompany.locking.Lock * *(#com.mycompany.locking.LockVal(*), ..)) && args(batch) && #args(propertyToLock) && argNames="batch,test1,test2")
public void mypointcut(Object batch, LockVal propertyToLock){}

Resources