Pointcut @AspectJ para todos os métodos de uma classe com anotação específica

127

Quero monitorar todos os métodos públicos de todas as classes com anotação especificada (por exemplo, @Monitor) (observação: a anotação está no nível da classe). Qual poderia ser um possível argumento para isso? Nota: Estou usando o Spring AOP no estilo @AspectJ.

Rejeev Divakaran
fonte
O abaixo funciona para uma extensão. @Pointcut ("execução (* (@ org.rejeev.Monitor *). * (..))") No entanto, agora o conselho está sendo executado duas vezes. Qualquer pista?
Rejeev Divakaran
Outro ponto é que a anotação @Monitor está em uma interface e uma classe implementa isso. A presença de uma interface e classe causará dupla execução de tais conselhos?
Rejeev Divakaran
6
Você deve aceitar a excelente resposta abaixo. Isso lhe dá reputação. Existem poucas pessoas preciosas aqui no SO que podem responder às perguntas da AspectJ.
fool4jesus

Respostas:

162

Você deve combinar um tipo de corte de ponto com um método de corte de ponto.

Esses apontamentos farão o trabalho para encontrar todos os métodos públicos dentro de uma classe marcada com uma anotação @Monitor:

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

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

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

Aconselhe o último apontador que combina os dois primeiros e pronto!

Se você estiver interessado, escrevi uma folha de dicas com o estilo @AspectJ aqui, com um documento de exemplo correspondente aqui.

Espen
fonte
Obrigado. A discussão dos pontos de anotação da anotação em sua planilha de dicas é particularmente útil.
7113 GregHNZ
1
Como faço para obter referência à classe no conselho da maneira que eu faço com conselhos pointcut normais é @Before ( "onObjectAction () && este (obj)")
Priyadarshi Kunal
O Cheat Sheet foi muito útil, apesar de ter sido 5 anos :)
Yadu Krishnan
Apenas uma pergunta aqui, se dois métodos que estão na hierarquia e ambos se enquadram no pointcut e pertencem à mesma classe, isso será executado nos dois? Se sim, consulte stackoverflow.com/questions/37583539/… , porque isso não está acontecendo no meu caso.
HVT7
Eu sinto que a execução pública é redundante porque você não pode ter um pointcut em métodos privados
amstegraf 24/04
58

Usando anotações, conforme descrito na pergunta.

Anotação: @Monitor

Anotação na aula app/PagesController.java:

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

Anotação no método app/PagesController.java:

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

Anotação personalizada app/Monitor.java:

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

Aspecto para anotação 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());
    }
}

Ativar AspectJ, servlet-context.xml:

<aop:aspectj-autoproxy />

Inclua bibliotecas AspectJ pom.xml:

<artifactId>spring-aop</artifactId>
<artifactId>aspectjrt</artifactId>
<artifactId>aspectjweaver</artifactId>
<artifactId>cglib</artifactId>
Alex
fonte
1
Belo exemplo. Uma pergunta: por que a anotação Monitorprecisa ser uma primavera Component?
mwhs
1
A Componentanotação é usada para dizer ao contêiner Spring para aplicar a inclusão da classe na coisa de tecelão AspectJ. Por padrão, Primavera só olha para Controller, Servicee outras anotações específicas, mas não Aspect.
22413 Alex
1
Ok obrigado. Mas eu estava falando sobre a @Componentanotação no @interfacenão o Aspect. Por que isso é necessário?
mwhs
2
A @Componentanotação faz com que o Spring o compile com o sistema orientado a aspectos AspectJ IoC / DI. Não sei como dizer de forma diferente. docs.spring.io/spring/docs/3.2.x/spring-framework-reference/...
Alex
Isso faz apenas métodos "públicos" nas classes anotadas ou faz todos os métodos (independentemente do nível de acesso)?
21715 Lee Meador
14

Algo parecido:

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

Observe que você não deve ter nenhum outro conselho sobre a mesma classe antes desta, porque as anotações serão perdidas após o proxy.

Bozho
fonte
11

Usar

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

deve ser o suficiente para marcar seu método de aspecto assim:

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

dê uma olhada nisso para obter um guia passo a passo sobre isso.

marcocast
fonte
3

Você também pode definir o corte de ponto como

public pointcut publicMethodInsideAClassMarkedWithAtMonitor() : execution(public * (@Monitor *).*(..));
Shekhar
fonte
execution(public * @Monitor *.*(..))Trabalhos pouco mais simples também.
xmedeko
3

A maneira mais simples parece ser:

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

   return pjp.proceed();

   // perform actions after
}

Ele interceptará a execução de todos os métodos anotados especificamente com '@MyHandling' na classe 'YourService'. Para interceptar todos os métodos sem exceção, basta colocar a anotação diretamente na classe.

Não importa o escopo privado / público aqui, mas lembre-se de que o spring-aop não pode usar o aspecto para chamadas de método na mesma instância (normalmente privadas), porque não usa a classe proxy nesse caso.

Usamos os conselhos @Around aqui, mas é basicamente a mesma sintaxe de @Antes, @Depois ou de qualquer conselho.

A propósito, a anotação @MyHandling deve ser configurada da seguinte maneira:

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

}
Donatello
fonte
que não está respondendo à declaração original, com ElementType.Type
Alex
Sim, o ElementType.TYPE também permitirá colocar anotações diretamente nas classes, o que suponho que resultará para manipular qualquer método nessa classe. Sou verdade? Está realmente funcionando?
Donatello
O // perform actions afternunca será chamado, pois retornamos o valor na linha antes.
Josephpconley
1

Você pode usar o PerformanceMonitoringInterceptor do Spring e registrar programaticamente o conselho usando um processador de beanpost.

@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
fonte
1

De Spring 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
fonte