@AspectJ pointcut pour toutes les méthodes d'une classe avec une annotation spécifique

127

Je veux surveiller toutes les méthodes publiques de toutes les classes avec une annotation spécifiée (par exemple @Monitor) (note: l'annotation est au niveau de la classe). Quel pourrait être un point de départ possible pour cela? Remarque: j'utilise le style Spring AOP de @AspectJ.

Rejeev Divakaran
la source
Celui ci-dessous fonctionne dans une certaine mesure. @Pointcut ("execution (* (@ org.rejeev.Monitor *). * (..))") Cependant maintenant, le conseil est exécuté deux fois. Un indice?
Rejeev Divakaran
Un autre point est que l'annotation @Monitor se trouve sur une interface et qu'une classe l'implémente. La présence d'une interface et d'une classe entraînera-t-elle une double exécution de ces conseils?
Rejeev Divakaran le
6
Vous devriez accepter l'excellente réponse ci-dessous. Cela lui donne une réputation. Il y a très peu de personnes ici sur SO qui peuvent répondre aux questions d'AspectJ.
fool4jesus

Réponses:

162

Vous devez combiner un pointcut de type avec un pointcut de méthode.

Ces pointcuts feront le travail pour trouver toutes les méthodes publiques à l'intérieur d'une classe marquée d'une annotation @Monitor:

@Pointcut("within(@org.rejeev.Monitor *)")
public void beanAnnotatedWithMonitor() {}

@Pointcut("execution(public * *(..))")
public void publicMethod() {}

@Pointcut("publicMethod() && beanAnnotatedWithMonitor()")
public void publicMethodInsideAClassMarkedWithAtMonitor() {}

Conseil le dernier pointcut qui combine les deux premiers et vous avez terminé!

Si vous êtes intéressé, j'ai écrit une feuille de triche avec le style @AspectJ ici avec un exemple de document correspondant ici.

Espen
la source
Merci. La discussion sur les pointcuts d'annotation sur votre aide-mémoire est particulièrement utile.
GregHNZ
1
Comment puis-je faire référence à la classe dans les conseils, comme je le fais avec les conseils normaux en coupe ponctuelle, c'est @Before ("onObjectAction () && this (obj)")
Priyadarshi Kunal
La feuille de triche a été très utile, même si cela fait 5 ans :)
Yadu Krishnan
Juste une question ici, si deux méthodes qui sont dans la hiérarchie et toutes deux tombent sous le pointcut et appartiennent à la même classe, cela s'exécutera-t-il sur les deux? Si oui, consultez stackoverflow.com/questions/37583539/… , car cela ne se produit pas dans mon cas.
HVT7
Je pense que l'exécution publique est redondante parce que vous ne pouvez pas avoir de pointure sur les méthodes privées
amstegraf
58

En utilisant des annotations, comme décrit dans la question.

Annotation: @Monitor

Annotation sur la classe, app/PagesController.java:

package app;
@Controller
@Monitor
public class PagesController {
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody String home() {
        return "w00t!";
    }
}

Annotation sur méthode app/PagesController.java:

package app;
@Controller
public class PagesController {
    @Monitor
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody String home() {
        return "w00t!";
    }
}

Annotation personnalisée, app/Monitor.java:

package app;
@Component
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Monitor {
}

Aspect pour l' 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());
    }
}

Activer AspectJ, servlet-context.xml:

<aop:aspectj-autoproxy />

Inclure les bibliothèques AspectJ, pom.xml:

<artifactId>spring-aop</artifactId>
<artifactId>aspectjrt</artifactId>
<artifactId>aspectjweaver</artifactId>
<artifactId>cglib</artifactId>
Alex
la source
1
Bel exemple. Une question: pourquoi l'annotation Monitordoit-elle être un ressort Component?
mwhs
1
L' Componentannotation est utilisée pour indiquer au conteneur Spring d'appliquer l'inclusion de la classe dans l'objet tisserand AspectJ. Par défaut, Spring regarde seulement Controller, Serviceet d' autres annotations spécifiques, mais non Aspect.
Alex
1
OK merci. Mais je parlais de l' @Componentannotation sur le @interfacepas le Aspect. Pourquoi est-ce nécessaire?
mwhs
2
L' @Componentannotation fait en sorte que Spring le compilera avec le système orienté aspect AspectJ IoC / DI. Je ne sais pas comment le dire différemment. docs.spring.io/spring/docs/3.2.x/spring-framework-reference/…
Alex
Est-ce que cela ne fait que des méthodes "publiques" dans les classes annotées ou est-ce que cela fait toutes les méthodes (quel que soit le niveau d'accès)?
Lee Meador
14

Quelque chose comme ca:

@Before("execution(* com.yourpackage..*.*(..))")
public void monitor(JoinPoint jp) {
    if (jp.getTarget().getClass().isAnnotationPresent(Monitor.class)) {
       // perform the monitoring actions
    }
}

Notez que vous ne devez avoir aucun autre conseil sur la même classe avant celle-ci, car les annotations seront perdues après le proxy.

Bozho
la source
11

Utilisation

@Before("execution(* (@YourAnnotationAtClassLevel *).*(..))")
    public void beforeYourAnnotation(JoinPoint proceedingJoinPoint) throws Throwable {
}
Davide Consonni
la source
4

il devrait suffire de marquer votre méthode d'aspect comme ceci:

@After("@annotation(com.marcot.CommitTransaction)")
    public void after() {

jetez un œil à ceci pour un guide étape par étape à ce sujet.

Marcocast
la source
3

Vous pouvez également définir le point de coupe comme

public pointcut publicMethodInsideAClassMarkedWithAtMonitor() : execution(public * (@Monitor *).*(..));
Shekhar
la source
Des petits execution(public * @Monitor *.*(..))travaux plus simples aussi.
xmedeko
3

Le moyen le plus simple semble être:

@Around("execution(@MyHandling * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
   throws Throwable {
   // perform actions before

   return pjp.proceed();

   // perform actions after
}

Il interceptera l'exécution de toutes les méthodes spécifiquement annotées avec «@MyHandling» dans la classe «YourService». Pour intercepter toutes les méthodes sans exception, il suffit de mettre l'annotation directement sur la classe.

Peu importe la portée privée / publique ici, mais gardez à l'esprit que spring-aop ne peut pas utiliser l'aspect pour les appels de méthode dans la même instance (généralement privés), car il n'utilise pas la classe proxy dans ce cas.

Nous utilisons les conseils @Around ici, mais c'est fondamentalement la même syntaxe avec @Before, @After ou tout autre conseil.

À propos, l'annotation @MyHandling doit être configurée comme ceci:

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface MyHandling {

}
Donatello
la source
qui ne répond pas à la déclaration d'origine, avec ElementType.Type
Alex
oui, ElementType.TYPE permettra également de mettre des annotations directement sur les classes, ce qui, je suppose, résultera en manipulant n'importe quelle méthode de cette classe. Suis-je vrai? Ça marche vraiment?
Donatello
Le // perform actions afterne sera jamais appelé car nous retournons la valeur dans la ligne précédente.
josephpconley
1

Vous pouvez utiliser PerformanceMonitoringInterceptor de Spring et enregistrer les conseils par programme à l'aide d'un 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;
    }
  }
}
Vikram
la source
1

De 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 *);
xmedeko
la source