AspectJ在具有匹配参数值的注释上定义切入点

时间:2019-09-23 17:58:22

标签: aop aspectj

我有一个使用Process annotation定义的过程。该注释具有称为name的属性。流程中包含任务。这些任务由另一个Task的{​​{3}}定义。该注释具有属性processName。 我有一个annotation,其中name Generic 。此过程的任务是generic processTask1Task2,这三个任务都以processName Generic 。 我可以使用aspectj来将所有具有相同processName的任务归为“'Process'''下吗?同样,在调用Task3时,也需要触发其中的所有任务。 我正在尝试的代码当前在GenericProcess.execute中。 感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

也许您应该提到您的应用程序甚至没有运行,它退出并出现错误。通常,这几乎是一团糟。也许在将它发布到GitHub之前,您应该已经对其进行了测试并查看了日志。您还应该发布显示错误的日志输出,而不仅仅是说“它不起作用”。我不得不解决许多问题:

  • 首先,我删除(或更名)了 application.yml logback.xml ,因为我既没有H2数据库,又没有创建项目,在控制台上也没有看到任何日志输出。当然,这些设置可能对您有用,对我而言它们却无效。

  • 然后在您的Application中有Process process = context.getBean(Process.class);毫无意义,因为Process不是bean类,而是注释。您从other answer那里获取了我的代码,进行了许多更改,然后再也无法解决。现在您的应用程序如下所示:

package com.spring.aspect.interfaces;

import com.spring.aspect.interfaces.process.GenericProcess;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
  private static final Logger log = LoggerFactory.getLogger(Application.class);

  @Bean
  public ModelMapper modelMapper() {
    return new ModelMapper();
  }

  public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
    GenericProcess process = context.getBean(GenericProcess.class);
    log.info("Generic process = {}", process);
    process.execute();
  }
}
  • 在任务方面Task{1,2,3},切入点execution(public com.spring.aspect.interfaces.entity.Job com.spring.aspect.interfaces.process.GenericProcessImpl.process(..)) && args(context)无效,因为既没有GenericProcessImpl.process(..)方法也没有Context参数。相反,您拥有的是没有参数的void execute()方法。

  • 此外,该方法仅来自已实现的GenericProcess接口,即我们可以使用该接口代替特定的实现作为切入点。

  • 如果您只想记录某些内容,我也认为不需要使用昂贵的@Around建议,@Before就足够了。那么呢?

package com.spring.aspect.interfaces.task;

import com.spring.aspect.interfaces.annotation.Provided;
import com.spring.aspect.interfaces.annotation.Required;
import com.spring.aspect.interfaces.annotation.Task;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Task(name = "Task1", processName = "Generic")
@Required(values = { "param1" })
@Provided(values = { "param2" })
@Aspect
public class Task1 {
  Logger logger = LoggerFactory.getLogger(Task1.class.getName());

  @Before("execution(public void com.spring.aspect.interfaces.process.GenericProcess+.execute())")
  public void task(JoinPoint thisJoinPoint) {
    logger.info("{}", thisJoinPoint);
  }
}
package com.spring.aspect.interfaces.task;

import com.spring.aspect.interfaces.annotation.Provided;
import com.spring.aspect.interfaces.annotation.Required;
import com.spring.aspect.interfaces.annotation.Task;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Task(name = "Task2", processName = "Generic")
@Required(values = { "param2" })
@Provided(values = { "param3" })
@Aspect
public class Task2 {
  Logger logger = LoggerFactory.getLogger(Task2.class.getName());

  @Before("execution(public void com.spring.aspect.interfaces.process.GenericProcess+.execute())")
  public void task(JoinPoint thisJoinPoint) {
    logger.info("{}", thisJoinPoint);
  }
}
package com.spring.aspect.interfaces.task;

import com.spring.aspect.interfaces.annotation.Required;
import com.spring.aspect.interfaces.annotation.Task;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Task(name = "Task3", processName = "Generic")
@Required(values = { "param1", "param2" })
@Aspect
public class Task3 {
  Logger logger = LoggerFactory.getLogger(Task3.class.getName());

  @Before("execution(public void com.spring.aspect.interfaces.process.GenericProcess+.execute())")
  public void task(JoinPoint thisJoinPoint) {
    logger.info("{}", thisJoinPoint);
  }
}
  • 最后但并非最不重要的一点,在TaskAspect中使用@annotation(com.spring.aspect.interfaces.annotation.Task),它将匹配带有@Task批注的方法。但是您有带有该批注的类,因此应改为使用@within(com.spring.aspect.interfaces.annotation.Task)

  • 比起尝试通过Tasktarget(task)注释绑定到自变量要容易得多,而是只使用@within(task),即可获得注释匹配和参数绑定一次。

  • 还记得我们将任务切入点从上面的@Around更改为@Before吗?对于@Before,AspectJ不会生成方法,而是将代码直接编织到目标类中。您也不能依靠它来完成@Around的建议,因此我对上一个问题的回答是有效的,但有点不干净。但是,有一个特殊的切入点指示符adviceexecution()。其目的是拦截其他方面(甚至同一方面)的执行建议。这更干净,更通用。在这种情况下,它甚至是唯一起作用的东西。

  • 最后,您正在使用args(proceedingJoinPoint),我不知道为什么。您是否尝试将切入点从截获的Task{1,2,3}通知绑定到TaskAspect通知?因为AspectJ将执行建议的联接点绑定到类型为JoinPointProceedingJoinPoint的现有第一个参数,所以它将无法正常工作。也许它将绑定到第二个连接点参数。无论如何,我们不需要它,所以让我们将其删除。这真的是人为的,哇。

  • 还有一件事,为什么要使用task.getClass().getName()而不是新引入的@Task批注属性来记录信息?

package com.spring.aspect.interfaces.aspect;

import com.spring.aspect.interfaces.annotation.Task;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class TaskAspect {
  static Logger logger = LoggerFactory.getLogger(TaskAspect.class.getName());

  @Around("@within(task) && adviceexecution()")
  public Object groupTasks(ProceedingJoinPoint proceedingJoinPoint, Task task) throws Throwable {
    logger.info("Generalizing the task aspects");
    logger.info("  {}", proceedingJoinPoint);
    logger.info("  Task = {}", task.name());
    logger.info("  Process = {}", task.processName());
    return proceedingJoinPoint.proceed();
  }
}

现在,至少在使用-javaagent:"/path/to/aspectjweaver.jar"参数启动应用程序的情况下,应用程序和各个方面才能再次正常工作。现在,您应该看到这样的日志输出(删除AspectJ weaver输出和一些主要的记录器列):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

(...)
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
c.spring.aspect.interfaces.Application   : Started Application in 2.948 seconds (JVM running for 4.934)
c.spring.aspect.interfaces.Application   : Generic process = com.spring.aspect.interfaces.process.GenericProcessImpl@25d0cb3a
c.s.aspect.interfaces.aspect.TaskAspect  : Generalizing the task aspects
c.s.aspect.interfaces.aspect.TaskAspect  :   adviceexecution(void com.spring.aspect.interfaces.task.Task3.task(JoinPoint))
c.s.aspect.interfaces.aspect.TaskAspect  :   Task = Task3
c.s.aspect.interfaces.aspect.TaskAspect  :   Process = Generic
com.spring.aspect.interfaces.task.Task3  : execution(void com.spring.aspect.interfaces.process.GenericProcessImpl.execute())
c.s.aspect.interfaces.aspect.TaskAspect  : Generalizing the task aspects
c.s.aspect.interfaces.aspect.TaskAspect  :   adviceexecution(void com.spring.aspect.interfaces.task.Task1.task(JoinPoint))
c.s.aspect.interfaces.aspect.TaskAspect  :   Task = Task1
c.s.aspect.interfaces.aspect.TaskAspect  :   Process = Generic
com.spring.aspect.interfaces.task.Task1  : execution(void com.spring.aspect.interfaces.process.GenericProcessImpl.execute())
c.s.aspect.interfaces.aspect.TaskAspect  : Generalizing the task aspects
c.s.aspect.interfaces.aspect.TaskAspect  :   adviceexecution(void com.spring.aspect.interfaces.task.Task2.task(JoinPoint))
c.s.aspect.interfaces.aspect.TaskAspect  :   Task = Task2
c.s.aspect.interfaces.aspect.TaskAspect  :   Process = Generic
com.spring.aspect.interfaces.task.Task2  : execution(void com.spring.aspect.interfaces.process.GenericProcessImpl.execute())
c.s.a.i.process.GenericProcessImpl       : Generic Process execution is invoked

更新

  • 我在日志输出方面对TaskAspect做了一些修改。
  • 我还对记录器进行了包装范围的检查,以便可以从测试中注入模拟内容。
  • 随后,我将您的ProcessTest重构到我的TaskAspectTest中,并将其移至与TaskAspect相同的程序包中。看起来如下:
package com.spring.aspect.interfaces.aspect;

import com.spring.aspect.interfaces.process.GenericProcess;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
 * Attention: Run this test with JVM parameter -javaagent:/path/to/aspectjweaver.jar
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskAspectTest {
  @Autowired
  private GenericProcess genericProcess;

  @Mock
  private Logger mockLogger;
  private Logger originalLogger;

  @Before
  public void setup() {
    originalLogger = TaskAspect.logger;
    TaskAspect.logger = mockLogger;
  }

  @After
  public void cleanup() {
    TaskAspect.logger = originalLogger;
  }

  /**
   * The way TaskAspect is currently implemented, its only side effect is logging output,
   * so the only way we can check if the aspect is executed as expected is to inject a
   * mock logger and verify if it was called as often as expected, i.e. once for each
   * Task1, Task2, Task3, with 1+3 log lines per execution.
   */
  @Test
  public void testAspectExecution() {
    genericProcess.execute();
    verify(mockLogger, times(3)).info(anyString());
    verify(mockLogger, times(9)).info(anyString(), any(Object.class));
  }
}

更新2:

  • 我现在还向您的Maven构建中添加了AspectJ LTW配置,请参见these commits in my fork
  • 我为您创建了一个pull request (PR),您可以接受它,以便将所有更改都复制到您的项目中而无需复制和粘贴。