@AspectJ pointcut para todos los métodos de una clase con anotaciones específicas

127

Quiero monitorear todos los métodos públicos de todas las clases con una anotación especificada (por ejemplo, @Monitor) (nota: la anotación está en el nivel de la clase). ¿Cuál podría ser un posible punto de corte para esto? Nota: Estoy usando el estilo Spring Asop de @AspectJ.

Rejeev Divakaran
fuente
El siguiente funciona en gran medida. @Pointcut ("ejecución (* (@ org.rejeev.Monitor *). * (..))") Sin embargo, ahora el consejo se ejecuta dos veces. ¿Cualquier pista?
Rejeev Divakaran
Otro punto es que la anotación @Monitor está en una interfaz y allí una clase lo implementa. ¿La presencia de una interfaz y una clase provocará la doble ejecución de tales consejos?
Rejeev Divakaran
66
Debe aceptar la excelente respuesta a continuación. Esto le da reputación. Hay muy pocas personas aquí en SO que pueden responder preguntas de AspectJ.
fool4jesus

Respuestas:

162

Debe combinar un tipo de corte de punto con un método de corte de punto.

Estos puntos de corte harán el trabajo para encontrar todos los métodos públicos dentro de una clase marcada con una anotación @Monitor:

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

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

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

¡Consejo el último corte de punto que combina los dos primeros y ya está!

Si está interesado, he escrito una hoja de trucos con el estilo @AspectJ aquí con un documento de ejemplo correspondiente aquí.

Espen
fuente
Gracias. La discusión de los recortes de puntos de Anotación en su Cheat Sheet es particularmente útil.
GregHNZ
1
¿Cómo obtengo referencia a la clase en el consejo? La forma en que lo hago con los consejos normales de pointcut es @Before ("onObjectAction () && this (obj)")
Priyadarshi Kunal
La Cheat Sheet fue muy útil, a pesar de que han pasado 5 años :)
Yadu Krishnan
Solo una pregunta aquí, si dos métodos que están en jerarquía y ambos caen bajo el punto de corte y pertenecen a la misma clase, ¿esto se ejecutará en ambos? En caso afirmativo, consulte stackoverflow.com/questions/37583539/… , porque esto no está sucediendo en mi caso.
HVT7
Siento que la ejecución pública es redundante porque no puedes tener un punto de corte en métodos privados
amstegraf
58

Usando anotaciones, como se describe en la pregunta.

Anotación: @Monitor

Anotación en clase app/PagesController.java:

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

Anotación sobre el método app/PagesController.java:

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

Anotación personalizada app/Monitor.java:

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

Aspecto de anotación 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());
    }
}

Habilitar AspectJ, servlet-context.xml:

<aop:aspectj-autoproxy />

Incluya las bibliotecas de AspectJ pom.xml:

<artifactId>spring-aop</artifactId>
<artifactId>aspectjrt</artifactId>
<artifactId>aspectjweaver</artifactId>
<artifactId>cglib</artifactId>
Alex
fuente
1
Buen ejemplo Una pregunta: ¿por qué la anotación Monitortiene que ser una primavera Component?
mwhs
1
los Component anotación se usa para indicarle al contenedor Spring que aplique la clase en el aspecto de tejedor AspectJ. Por defecto, sólo se basa en la primavera Controller, Servicey otras anotaciones específicas, pero no Aspect.
Alex
1
OK gracias. Pero estaba hablando del@Component anotación en el @interfaceno el Aspect. ¿Por qué es eso necesario?
mwhs
2
La @Componentanotación hace que Spring lo compile con el sistema orientado a aspectos AspectJ IoC / DI. No sé cómo decirlo de manera diferente.docs.spring.io/spring/docs/3.2.x/spring-framework-reference/…
Alex
¿Esto solo hace métodos "públicos" en las clases anotadas o hace todos los métodos (sin importar el nivel de acceso)?
Lee Meador
14

Algo como eso:

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

Tenga en cuenta que no debe tener ningún otro consejo sobre la misma clase antes de esta, porque las anotaciones se perderán después de la representación.

Bozho
fuente
11

Utilizar

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

debería ser suficiente para marcar su método de aspecto de esta manera:

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

Eche un vistazo a esto para obtener una guía paso a paso sobre esto.

marcocast
fuente
3

También puede definir el punto de corte como

public pointcut publicMethodInsideAClassMarkedWithAtMonitor() : execution(public * (@Monitor *).*(..));
Shekhar
fuente
execution(public * @Monitor *.*(..))Trabajos un poco más simples también.
xmedeko
3

La forma más simple 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
}

Interceptará la ejecución de todos los métodos específicamente anotados con '@MyHandling' en la clase 'YourService'. Para interceptar todos los métodos sin excepción, simplemente coloque la anotación directamente en la clase.

No importa el alcance privado / público aquí, pero tenga en cuenta que spring-aop no puede usar el aspecto para las llamadas a métodos en la misma instancia (generalmente las privadas), porque en este caso no usa la clase proxy.

Usamos los consejos de @Around aquí, pero es básicamente la misma sintaxis con @Before, @After o cualquier otro consejo.

Por cierto, la anotación @MyHandling debe configurarse así:

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

}
Donatello
fuente
eso no responde a la declaración original, con ElementType.Type
Alex
sí, ElementType.TYPE también permitirá poner anotaciones directamente en las clases, lo que supongo que dará como resultado el manejo de cualquier método en esta clase. ¿Soy verdad? ¿Funciona realmente?
Donatello
El // perform actions afterno se llamará ya que estamos devolviendo el valor en la línea antes.
josephpconley
1

Puede usar el Interceptor de Monitoreo de Desempeño de Spring y registrar programáticamente el consejo usando un procesador de poste de frijoles.

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

De la primavera 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
fuente