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



我有一个使用Process注释定义的过程。此注释具有名为name的属性。流程中包含任务。这些任务是使用另一个称为Task的注释定义的。此注释具有属性processName。 我有一个通用过程,name通用。此过程的任务是 任务 1、任务 2 和 任务 3 ,这三个任务都将processName作为通用。 我是否可以使用方面,以便将所有具有相同processName的任务分组到"流程"下?此外,当调用 GenericProcess.execute 时,其中的任务也需要被触发。 我正在尝试的代码目前在 github 中。 感谢您的帮助。

也许你应该提到你的应用程序甚至没有运行,它会退出并出现错误。总的来说,这几乎是一团糟。也许您应该在将其发布到 GitHub 之前对其进行测试并查看日志。您还应该发布显示错误的日志输出,而不仅仅是基本上说"它不起作用"。我必须解决很多问题:

  • 首先,我删除(或更确切地说是重命名(application.ymllogback.xml因为我既没有您的 H2 数据库,您的项目也没有创建一个,也没有在控制台上看到任何日志输出。当然,这些设置可能对您有效,对我来说则不起作用。

  • 那么在你的Application中,有Process process = context.getBean(Process.class);是没有意义的Process因为它不是一个bean类,而是一个注释。你从另一个答案中拿走了我的代码,改变了很多东西,然后什么都不再适合了。现在,您的应用程序如下所示:

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)

  • 比尝试通过target(task)Task注释绑定到参数更容易的是只使用@within(task),同时获得注释匹配和参数绑定。

  • 还记得我们将任务切入点从上面的@Around更改为@Before吗?对于@BeforeAspectJ不生成方法,而是将代码直接编织到目标类中。您也不能依靠它来获得@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编织者输出和一些领先的记录器列(:

.   ____          _            __ _ _
/\ / ___'_ __ _ _(_)_ __  __ _    
( ( )___ | '_ | '_| | '_ / _` |    
\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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:

  • 我现在还将AspectJ LTW配置添加到您的Maven构建中,请参阅我的叉子中的这些提交。
  • 为您创建了一个拉取请求(PR(,您可以接受该请求,以便将我的所有更改放入您的项目中,而无需复制和粘贴。

最新更新