I have defined annotation with
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
When I use custom annotation under method which i want to aspect, and then i want to get the parameters(they are Object, not string, int ant byte) of the method signature.
is there simple way to get method parameter with custom annotation of AOP?
A simple demo can as:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MethodTimer {
}
And the aspect handler:
#Aspect
#Slf4j
#Component
public class TimeCounterAspect {
#Around("#annotation(methodTimer)")
public Object logMethodRequests(ProceedingJoinPoint joinPoint, MethodTimer methodTimer)
throws Throwable {
Long start = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
Object[] myArgs = joinPoint.getArgs();
Object obj = null;
try {
obj = joinPoint.proceed();
} catch (Exception e) {
throw e;
} finally {
log.info("Retrieving timeCost: {} ms in Method: {} args: {}",
System.currentTimeMillis() - start, methodName, Arrays.deepToString(myArgs));
}
return obj;
}
}
You can access the arguments via ProceedingJoinPoint:
#Around("execution(#com.path.annotation.YourAnnotation * *(..)) && #annotation(annotation)")
public Object execute(final ProceedingJoinPoint pjp, final YourAnnotation annotation) throws Throwable {
Object result = pjp.proceed();
// Here is the method arguments
Object[] args = pjp.getArgs();
return result;
}
Related
In my aspect method, i need get value of name (param of custom annotation) name = "unit test"
Method call by user:
#Service
#RequiredArgsConstructor
#Slf4j
public class Task {
#CronLogger(name = "unit test")
public void testCronLogger(String param) {
log.info("testCronLogger ...");
}
}
custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CronLogger {
public String name() default "";
}
Aspect method:
#Aspect
#Component
#EnableAspectJAutoProxy
public class CronLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(CronLoggerAspect.class);
#Around("#annotation(CronLogger)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] tab = joinPoint.getArgs();
for (Object object : tab) {
log.debug("CronLogger: {}", object);
}
return joinPoint.proceed();
}
}
Console:
CronLogger: test
testCronLogger ...
How about this (untested, I simply modified your code)?
#Around("#annotation(cronLogger)")
public Object trace(ProceedingJoinPoint joinPoint, CronLogger cronLogger) throws Throwable {
log.debug("CronLogger: {}", cronLogger.name());
return joinPoint.proceed();
}
Please be careful with upper- and lower-case characters. One is an annotation class name, the other a method parameter name.
need get Method and get Annotation of this method.
#Around("#annotation(CronLogger)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
String name = MethodSignature.class.cast(joinPoint.getSignature()).getMethod().getAnnotation(CronLogger.class)
.name();
log.debug("CronLogger: {}", name);
return joinPoint.proceed();
}
this is annotations Code:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
public #interface PreVisit {
String value();
}
this use in the Controller #PreVisit("#pv.hasAccess('xxxxxx')")
#PreVisit("#pv.hasAccess('xxxxxx')")
#RequestMapping(value = "getUser")
public User getUser(Integer userId) {...some code...}
this is pv.hasAccess('xxxxxx') code:
#Service("pv")
public class PageVisit{
public boolean hasAccess(String par){
//return false or true;
}
}
My question:
In Aspect, how do you get methods in annotation parameters and get the result of execution
this is Aspect file codeļ¼
#Aspect
#Component
public class PreVisitAspect {
#Around("#annotation(PreVisit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//How do I get the result of the method execution in the annotation parameter here
//boolean pvResult=#pv.hasAccess('xxxxxx'); pvResult=false or true
//Do something use the pvResult
}
}
You can second argument type PreVisit and you can access the annotation values in the method.
#Aspect
#Component
public class PreVisitAspect {
#Around("#annotation(PreVisit)")
public Object around(ProceedingJoinPoint joinPoint, PreVisit preVisit) throws Throwable {
//How do I get the result of the method execution in the annotation parameter here
//boolean pvResult=#pv.hasAccess('xxxxxx');
//Do something use the pvResult
String value = preVisit.value();
}
}
If you want to use the PageVisit#hasAccess(String), then inject PageVisit into your aspect and invoke the method.
For this you must modify you controller method as below.
#PreVisit("xxxxxx")
#RequestMapping(value = "getUser")
public User getUser(Integer userId) {...some code...}
and your aspect will be.
#Aspect
#Component
public class PreVisitAspect {
#Autowired
private PageVisit pv;
#Around("#annotation(PreVisit)")
public Object around(ProceedingJoinPoint joinPoint, PreVisit preVisit) throws Throwable {
//How do I get the result of the method execution in the annotation parameter here
//boolean pvResult=#pv.hasAccess('xxxxxx');
//Do something use the pvResult
String value = preVisit.value();
boolean hasAccess = pv.hasAccess(value);
}
}
I'm the questioner
I have solved this using Java reflection,as follows:
#Aspect
#Component
public class PreVisitAspect {
#Autowired
private PageVisit pv;
#Around("#annotation(preVisit)")
public Object around(ProceedingJoinPoint joinPoint, PreVisit preVisit) throws Throwable {
String value = preVisit.value();
//String value="#pas.hasAccess('xxxxxx')";
if(value.startsWith("#")){
String beanName=value.substring(value.indexOf("#")+1,value.indexOf("."));
String methodName=value.substring(value.indexOf(".")+1,value.indexOf("("));
String paramsStr=value.substring(value.indexOf("(")+2,value.lastIndexOf(")")-1);
Object[] paramsArr=paramsStr.split("','");
logger.info("beanName:"+beanName);
logger.info("methodName:"+methodName);
logger.info("paramsStr:"+paramsStr);
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
Method method = ReflectionUtils.findMethod(appContext.getBean(beanName).getClass(), methodName,new Class[]{String.class} );
Boolean result = (Boolean)ReflectionUtils.invokeMethod(method, appContext.getBean(beanName),paramsArr);
logger.info(result.toString());
}
.....other Code.....
}
}
tks #Karthikeyan Vaithilingam
I am using Spring Data to access a Mongo database in a Spring Boot application. I'm doing this by extending MongoRepository.
#CrudPublishingRepository
public interface ProfileRepository extends MongoRepository<Profile, String> {
Optional<Profile> findByUserName(String userName);
#Query("{'products.contracts.contractId': ?0}")
List<Profile> findByContractId(String contractId);
}
For some of my repositories (including the ProfileRepository above) I need to perform some actions every time a save or delete action is performed, which is why I created the #CrudPublishingRepository annotation.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Documented
public #interface CrudPublishingRepository {}
I have used Spring AOP to intercept all save and delete methods on Spring's CrudRepository, with the following advices:
#AfterReturning("execution(public * delete(..)) && this(org.springframework.data.repository.CrudRepository)")
public void onDeleteExecuted(JoinPoint pjp) {
onDelete(pjp);
}
#AfterReturning("execution(public * deleteById(..)) && this(org.springframework.data.repository.CrudRepository)")
public void onDeleteByIdExecuted(JoinPoint pjp) {
onDeleteById(pjp);
}
#AfterReturning("execution(public * deleteAll(..)) && this(org.springframework.data.repository.CrudRepository)")
public void onDeleteAllExecuted(JoinPoint pjp) {
onDeleteAll(pjp);
}
#Around(value = "execution(public * save(..)) && this(org.springframework.data.repository.CrudRepository)")
public Object onSaveExecuted(ProceedingJoinPoint pjp) throws Throwable {
return onSave(pjp);
}
the onSave method looks like this:
private Object onSave(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(getTopicName(pjp));
try {
Object result = pjp.proceed();
return result;
} catch (Throwable t) {
throw t;
}
}
And at last, the getTopicName(JoinPoint pjp) function, which is where the problem is:
private Optional<String> getTopicName(JoinPoint pjp) {
Class clazz = pjp.getTarget().getClass();
while (clazz != null) {
for (Class i : pjp.getTarget().getClass().getInterfaces()) {
if (i.getAnnotation(CrudPublishingRepository.class) != null) {
return Optional.of("found it!");
}
}
clazz = clazz.getSuperclass();
}
return Optional.empty();
}
The implementation obviously isn't finished yet, but I would expect it to return Optional.of("found it!"), but it doesn't.
When I debug, I can see that ProfileRepository is one of the interfaces, as expected, but getAnnotations() returns an empty array.
Can anyone offer me a solution or an explanation as to why this is not working?
I am trying to make a custom annotation like ibatis #Select.
Anyway, In conclusion, the goal is
append some data into the parameter which the method has custom annotation
First take a look end point - ArtistNodeRepository.java
#Repository
public interface ArtistNodeRepository {
#CreateNode(tid = "artist")
public Node create(Map data) throws Exception;
}
What want to do with CreateNode annotation is put data.put("type", "artist") into parameter Map.
Here is the Annotation - CreateNode.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#Documented
public #interface CreateNode {
String[] values() default "";
String tid();
}
To controller annotation, I prepared this BeanPostProcessor - NodeAnnotationProcessor.java
#Component
public class NodeAnnotationProcessor implements BeanPostProcessor {
private ConfigurableListableBeanFactory configurableListableBeanFactory;
#Autowired
public NodeAnnotationProcessor(ConfigurableListableBeanFactory configurableListableBeanFactory) {
super();
this.configurableListableBeanFactory = configurableListableBeanFactory;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
this.scanNodeAnnotation(bean, beanName);
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// this.scanNodeAnnotation(bean, beanName);
return bean;
}
protected void scanNodeAnnotation(Object bean, String beanName){
this.configureMethodAction(bean);
}
private void configureMethodAction(Object bean){
Class<?> managedBeanClass = bean.getClass();
ReflectionUtils.MethodCallback methodCallback = new NodeMethodCallback(configurableListableBeanFactory, bean);
ReflectionUtils.doWithMethods(managedBeanClass, methodCallback);
}
}
I am not clear where to put MethodCallback to postProcessBeforeInitialization or postProcessAfterInitialization. In my thought, it would be in after since I am trying to manipulate parameter of the method
Finally, this is the MethodCallback - NodeMethodCallback.java
public class NodeMethodCallback implements ReflectionUtils.MethodCallback {
private Logger logger = LoggerFactory.getLogger(NodeMethodCallback.class);
private ConfigurableListableBeanFactory beanFactory;
private Object bean;
private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
public NodeMethodCallback(ConfigurableListableBeanFactory beanFactory, Object bean) {
this.beanFactory = beanFactory;
this.bean = bean;
}
#Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
logger.info("doWith method info :: " + String.valueOf(bean) + "." + bean.getClass().getName());
/*
What I expected is Printing ArtistNodeRepository Class with create Method
But It prints something like ...
SessionFlashMapManager
DefaultRequestToViewNameTranslator
...
*/
try {
logger.info("When I call you :: " + method.getName()); // I expect method which contains #CreateNode annotation, but it is not ...
Annotation[] methodAnnotations = method.getDeclaredAnnotations();
boolean isTarget = false;
String tid = "";
for(Annotation anno : methodAnnotations) {
logger.info("annotation Class :: " + anno.getClass().getName());
if(isTarget) break;
if(anno instanceof CreateNode) {
logger.info("CreateNode annotation found");
CreateNode createNode = method.getDeclaredAnnotation(CreateNode.class);
tid = createNode.tid();
isTarget = true;
}
}
if(!isTarget) return;
ReflectionUtils.makeAccessible(method);
/*
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
*/
} catch (Exception e ){
logger.error("ERROR", e);
}
}
}
The problem is ... in doWith I could not find ArtistNodeRepository instance.
What should I do with MethodCallback and BeanPostProcessor to achieve the goal?
Good sample codes would be nice as well as good answers.
i think you misunderstood the useage of ReflectionUtils.doWithMethods. it means iterate the class method if match callback. rather than when invoke method callback.
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
i think you can use aspect. like this.
#Around("execution(public * org.springframework.data.jpa.repository.JpaRepository+.*(..))")
// #Around("#annotation(Repository)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CreateNode createNode = method.getAnnotation(CreateNode.class);
if(createNode != null) {
Object[] args = joinPoint.getArgs();
// do your business
}
return joinPoint.proceed();
}
hope can help you
I want to monitor all public methods of all Classes with specified annotation (say #Monitor) (note: Annotation is at class level). What could be a possible pointcut for this?
Note: I am using #AspectJ style Spring AOP.
You should combine a type pointcut with a method pointcut.
These pointcuts will do the work to find all public methods inside a class marked with an #Monitor annotation:
#Pointcut("within(#org.rejeev.Monitor *)")
public void beanAnnotatedWithMonitor() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Pointcut("publicMethod() && beanAnnotatedWithMonitor()")
public void publicMethodInsideAClassMarkedWithAtMonitor() {}
Advice the last pointcut that combines the first two and you're done!
If you're interested, I have written a cheat sheet with #AspectJ style here with a corresponding example document here.
Using annotations, as described in the question.
Annotation: #Monitor
Annotation on class, app/PagesController.java:
package app;
#Controller
#Monitor
public class PagesController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody String home() {
return "w00t!";
}
}
Annotation on method, app/PagesController.java:
package app;
#Controller
public class PagesController {
#Monitor
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody String home() {
return "w00t!";
}
}
Custom annotation, app/Monitor.java:
package app;
#Component
#Target(value = {ElementType.METHOD, ElementType.TYPE})
#Retention(value = RetentionPolicy.RUNTIME)
public #interface Monitor {
}
Aspect for annotation, app/MonitorAspect.java:
package app;
#Component
#Aspect
public class MonitorAspect {
#Before(value = "#within(app.Monitor) || #annotation(app.Monitor)")
public void before(JoinPoint joinPoint) throws Throwable {
LogFactory.getLog(MonitorAspect.class).info("monitor.before, class: " + joinPoint.getSignature().getDeclaringType().getSimpleName() + ", method: " + joinPoint.getSignature().getName());
}
#After(value = "#within(app.Monitor) || #annotation(app.Monitor)")
public void after(JoinPoint joinPoint) throws Throwable {
LogFactory.getLog(MonitorAspect.class).info("monitor.after, class: " + joinPoint.getSignature().getDeclaringType().getSimpleName() + ", method: " + joinPoint.getSignature().getName());
}
}
Enable AspectJ, servlet-context.xml:
<aop:aspectj-autoproxy />
Include AspectJ libraries, pom.xml:
<artifactId>spring-aop</artifactId>
<artifactId>aspectjrt</artifactId>
<artifactId>aspectjweaver</artifactId>
<artifactId>cglib</artifactId>
Something like that:
#Before("execution(* com.yourpackage..*.*(..))")
public void monitor(JoinPoint jp) {
if (jp.getTarget().getClass().isAnnotationPresent(Monitor.class)) {
// perform the monitoring actions
}
}
Note that you must not have any other advice on the same class before this one, because the annotations will be lost after proxying.
Use
#Before("execution(* (#YourAnnotationAtClassLevel *).*(..))")
public void beforeYourAnnotation(JoinPoint proceedingJoinPoint) throws Throwable {
}
it should be enough to mark your aspect method like this:
#After("#annotation(com.marcot.CommitTransaction)")
public void after() {
have a look at this for a step by step guide on this.
You can also define the pointcut as
public pointcut publicMethodInsideAClassMarkedWithAtMonitor() : execution(public * (#Monitor *).*(..));
The simplest way seems to be :
#Around("execution(#MyHandling * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
throws Throwable {
// perform actions before
return pjp.proceed();
// perform actions after
}
It will intercept execution of all methods specifically annotated with '#MyHandling' in 'YourService' class. To intercept all methods without exception, just put the annotation directly on the class.
No matter of the private / public scope here, but keep in mind that spring-aop cannot use aspect for method calls in same instance (typically private ones), because it doesn't use the proxy class in this case.
We use #Around advice here, but it's basically the same syntax with #Before, #After or any advice.
By the way, #MyHandling annotation must be configured like this :
#Retention(RetentionPolicy.RUNTIME)
#Target( { ElementType.METHOD, ElementType.TYPE })
public #interface MyHandling {
}
I share with you a code that can be useful, it is to create an annotation that can be used either in a class or a method.
#Target({TYPE, METHOD})
#Retention(RUNTIME)
#Documented
public #interface AnnotationLogger {
/**
* It is the parameter is to show arguments in the method or the class.
*/
boolean showArguments() default false;
}
#Aspect
#Component
public class AnnotationLoggerAspect {
#Autowired
private Logger logger;
private static final String METHOD_NAME = "METHOD NAME: {} ";
private static final String ARGUMENTS = "ARGS: {} ";
#Before(value = "#within(com.org.example.annotations.AnnotationLogger) || #annotation(com.org.example.annotations.AnnotationLogger)")
public void logAdviceExecutionBefore(JoinPoint joinPoint){
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
AnnotationLogger annotationLogger = getAnnotationLogger(joinPoint);
if(annotationLogger!= null) {
StringBuilder annotationLoggerFormat = new StringBuilder();
List<Object> annotationLoggerArguments = new ArrayList<>();
annotationLoggerFormat.append(METHOD_NAME);
annotationLoggerArguments.add(codeSignature.getName());
if (annotationLogger.showArguments()) {
annotationLoggerFormat.append(ARGUMENTS);
List<?> argumentList = Arrays.asList(joinPoint.getArgs());
annotationLoggerArguments.add(argumentList.toString());
}
logger.error(annotationLoggerFormat.toString(), annotationLoggerArguments.toArray());
}
}
private AnnotationLogger getAnnotationLogger(JoinPoint joinPoint) {
AnnotationLogger annotationLogger = null;
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = joinPoint.getTarget().getClass().
getMethod(signature.getMethod().getName(), signature.getMethod().getParameterTypes());
if (method.isAnnotationPresent(AnnotationLogger.class)){
annotationLogger = method.getAnnotation(AnnotationLoggerAspect.class);
}else if (joinPoint.getTarget().getClass().isAnnotationPresent(AnnotationLoggerAspect.class)){
annotationLogger = joinPoint.getTarget().getClass().getAnnotation(AnnotationLoggerAspect.class);
}
return annotationLogger;
}catch(Exception e) {
return annotationLogger;
}
}
}
From Spring's AnnotationTransactionAspect:
/**
* Matches the execution of any public method in a type with the Transactional
* annotation, or any subtype of a type with the Transactional annotation.
*/
private pointcut executionOfAnyPublicMethodInAtTransactionalType() :
execution(public * ((#Transactional *)+).*(..)) && within(#Transactional *);
You could use Spring's PerformanceMonitoringInterceptor and programmatically register the advice using a beanpostprocessor.
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
public #interface Monitorable
{
}
public class PerformanceMonitorBeanPostProcessor extends ProxyConfig implements BeanPostProcessor, BeanClassLoaderAware, Ordered,
InitializingBean
{
private Class<? extends Annotation> annotationType = Monitorable.class;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Advisor advisor;
public void setBeanClassLoader(ClassLoader classLoader)
{
this.beanClassLoader = classLoader;
}
public int getOrder()
{
return LOWEST_PRECEDENCE;
}
public void afterPropertiesSet()
{
Pointcut pointcut = new AnnotationMatchingPointcut(this.annotationType, true);
Advice advice = getInterceptor();
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
private Advice getInterceptor()
{
return new PerformanceMonitoringInterceptor();
}
public Object postProcessBeforeInitialization(Object bean, String beanName)
{
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)
{
if(bean instanceof AopInfrastructureBean)
{
return bean;
}
Class<?> targetClass = AopUtils.getTargetClass(bean);
if(AopUtils.canApply(this.advisor, targetClass))
{
if(bean instanceof Advised)
{
((Advised)bean).addAdvisor(this.advisor);
return bean;
}
else
{
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.copyFrom(this);
proxyFactory.addAdvisor(this.advisor);
return proxyFactory.getProxy(this.beanClassLoader);
}
}
else
{
return bean;
}
}
}