Spring aop: 为子类定义切入点但只调用了一个子类

Spring aop: Pointcut defined for subclasses but only one subclass is invoked

我正在定义一个 JobProcess,方法 Object process( JobContext jobContext ); 带有一个名为 JobProcessImpl 的实现。 每当执行此JobProcessImpl.process方法时,我想用多个subclass进行窥探。 我要执行所有这些子classes。

间谍 class 被定义为基础 class Task 以寻找 JobProcessImpl.process 调用。

在输出中,我总是看到只有来自 AnnotationTask 的日志,而不是来自 ReviewTask 的日志。

如果可能,请告诉我问题是什么。

我尝试了 2 天通过关注各种帖子来解决这个问题。

package com.spring.aspect.dynamicflow.activity;

import com.spring.aspect.dynamicflow.entity.JobContext;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect
public abstract class Task {

    private static final Logger log = LoggerFactory.getLogger( Task.class );

    @Around ( "execution(public java.lang.Object com.spring.aspect.dynamicflow.process.JobProcessImpl.process(..)) " + "&& args(context)" )
    public Object task( JobContext context ) {
        log.info( "This is the base task and needs to overridden by the derived task for the job id: {} ", context.getJobId() );

        return  context;
    }
}

基地类: 注释任务

package com.spring.aspect.dynamicflow.activity;

import com.spring.aspect.dynamicflow.entity.JobContext;
import com.spring.aspect.dynamicflow.entity.TaskStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class AnnotationTask extends Task {

    private static final Logger log = LoggerFactory.getLogger( AnnotationTask.class );

    @Override
    public Object task( JobContext context ) {
        log.info( "AnnotationTask's task" );

        /*
         * do some validation if annotation is completed or not
         */

        log.info( "Setting that the annotation is done." );
        context.setAnnotationTaskStatus( TaskStatus.COMPLETED );
        return "AnnotationTask Completed";
    }

审阅任务

package com.spring.aspect.dynamicflow.activity;

import com.spring.aspect.dynamicflow.entity.JobContext;
import com.spring.aspect.dynamicflow.entity.TaskStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class ReviewTask extends Task {

    private static final Logger log = LoggerFactory.getLogger( ReviewTask.class );

    @Override
    public Object task( JobContext context ) {
        log.info( "ReviewTask's task" );

        /*
         * do some validation if annotation is completed or not
         */
        log.info( "not completing the review task due to some reason" );
        context.setReviewTaskStatus( TaskStatus.IN_PROGRESS );
        return "ReviewTask Not Completed";
    }
}

我希望通过使用 TaskAspects 监视这些任务来执行这些任务。

package com.spring.aspect.dynamicflow.aspect;

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;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TaskAspects {

    private static final Logger log = LoggerFactory.getLogger( TaskAspects.class );

    @Around( "within(com.spring.aspect.dynamicflow.activity.Task+) ")
    public Object handleTask( ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info( "Handling the task aspects." );
        log.info( "" + proceedingJoinPoint.getThis() );

        return proceedingJoinPoint.proceed();
    }

}

我也有一个枚举 classes(给出代码完成)

package com.spring.aspect.dynamicflow.entity;

public enum TaskStatus {
    IN_PROGRESS, COMPLETED
}

工作流程

package com.spring.aspect.dynamicflow.process;

import com.spring.aspect.dynamicflow.entity.JobContext;

public interface JobProcess {

    Object process( JobContext jobContext );
}

JobProcessImpl

package com.spring.aspect.dynamicflow.process;

import com.spring.aspect.dynamicflow.entity.JobContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class JobProcessImpl implements JobProcess {

    Logger logger = LoggerFactory.getLogger( JobProcessImpl.class );
    @Override
    public Object process( JobContext jobContext ) {
        logger.info( "Shouldn't get printed Processing the job with jobid {}", jobContext.getJobId() );
        return null;
    }
}

我一发布这个,就尝试用

更改 ReviewTask 和 AnnotationTask
@Override
    public Object task( ProceedingJoinPoint proceedingJoinPoint, JobContext context ) throws Throwable {
        log.info( "ReviewTask's task" );

        /*
         * do some validation if annotation is completed or not
         */
        log.info( "not completing the review task due to some reason" );
        context.setReviewTaskStatus( TaskStatus.IN_PROGRESS );
        return proceedingJoinPoint.proceed();
    }

这解决了我的问题。

You are right, I just noticed that TaskAspects not get triggered at all. I do not see any logs from TaskAspects. Can I use AspectJ inside spring-boot without any spring-aop? Thanks for taking a look and notifying. Much appreaciated.

基本上,您只需从要通过 AspectJ 使用的方面删除 @Component 注释,然后将这些方面添加到文件 src/main/resources/org/aspectj/aop.xml(假设您使用 Maven 构建您的项目):

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

  <weaver options="-verbose -showWeaveInfo">
    <!-- only weave classes in our application-specific packages -->
    <include within="com.spring.aspect.dynamicflow..*"/>
  </weaver>

  <aspects>
    <aspect name="com.spring.aspect.dynamicflow.aspect.TaskAspects"/>
    <aspect name="com.spring.aspect.dynamicflow.activity.AnnotationTask"/>
    <aspect name="com.spring.aspect.dynamicflow.activity.ReviewTask"/>
  </aspects>

</aspectj>

然后您使用 -javaagent:/path/to/aspectjweaver.jar、e.g.something 开始您的应用程序,例如:

-javaagent:"c:\Users\me\.m2\repository\org\aspectj\aspectjweaver.9.4\aspectjweaver-1.9.4.jar"

您还可以在 Java 命令行上同时使用 AspectJ Weaver 和 Spring Instrument 这两个代理,例如

-javaagent:"c:\Users\me\.m2\repository\org\aspectj\aspectjweaver.9.4\aspectjweaver-1.9.4.jar" -javaagent:"c:\Program Files\Java\spring-framework-5.1.9.RELEASE\libs\spring-instrument-5.1.9.RELEASE.jar"

为了您的方便和我自己的示例项目,我在 GitHub repository. There is a branch for Spring AOP and another branch for AspectJ LTW 中分享了我的工作示例。这些分支之间的差异如下所示:

diff --git a/src/main/java/com/spring/aspect/dynamicflow/Application.java b/src/main/java/com/spring/aspect/dynamicflow/Application.java
index 2a7021e..3a7636f 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/Application.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/Application.java
@@ -8,6 +8,16 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.ConfigurableApplicationContext;
 
+/**
+ * Run this from your IDE with
+ *
+ * a) either just -javaagent:/path/to/aspectjweaver.jar and without @EnableLoadTimeWeaving
+ *
+ * b) or with both -javaagent:/path/to/aspectjweaver.jar -javaagent:/path/to/spring-instrument.jar,
+ * either with or without @EnableLoadTimeWeaving. What benefit this has, I don't know.
+ *
+ * See also my extensive comment in Application class.
+ */
 @SpringBootApplication
 public class Application {
   private static final Logger log = LoggerFactory.getLogger(Application.class);
diff --git a/src/main/java/com/spring/aspect/dynamicflow/ApplicationConfig.java b/src/main/java/com/spring/aspect/dynamicflow/ApplicationConfig.java
index b4698e1..649a6ca 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/ApplicationConfig.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/ApplicationConfig.java
@@ -3,8 +3,34 @@ package com.spring.aspect.dynamicflow;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.EnableLoadTimeWeaving;
 
+import static org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving.ENABLED;
+
+/**
+ * Remarks about AspectJ load-time weaving(LTW) in Spring:
+ *
+ * According to the Spring manual it should be enough to put spring-instrument.jar is on the JVM command line
+ * in combination with @EnableLoadTimeWeaving. Actually this does help Spring detect the AspectJ weaver,
+ * I can see the aspects loaded. But obviously this happens too late after the application classes are
+ * already loaded, so the aspects do not have any effect. I even added a static block
+ * static { logger.info("JobProcessImpl class was loaded"); } to JobProcessImpl in order to check it an
+ * the log output occurs right before the aspects are being activated, which of course is too late.
+ *
+ * LTW works if
+ *
+ * a) either I have both Java agents aspectjweaver.jar and spring-instrument.jar on JVM command line
+ * in combination with @EnableLoadTimeWeaving (but then it tries to weave twice, I can see errors in the log)
+ * or without @EnableLoadTimeWeaving (no errors in the log)
+ *
+ * b) or if I only use aspectjweaver.jar without @EnableLoadTimeWeaving.
+ *
+ * The latter is no surprise because AspectJ is independent of Spring and of course works even if Spring is
+ * unaware of its presence. But if I want to advertise its presence via @EnableLoadTimeWeaving, I do not
+ * understand why spring-instrument.jar is not enough, as described in the Spring manual.
+ */
 @Configuration
 @EnableAspectJAutoProxy
 @ComponentScan("com.spring.aspect.dynamicflow")
+//@EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
 public class ApplicationConfig {}
diff --git a/src/main/java/com/spring/aspect/dynamicflow/activity/AnnotationTask.java b/src/main/java/com/spring/aspect/dynamicflow/activity/AnnotationTask.java
index 3c6d5c4..bbdd5b1 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/activity/AnnotationTask.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/activity/AnnotationTask.java
@@ -6,9 +6,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
 
-@Component
 @Aspect
 public class AnnotationTask extends Task {
   private static final Logger log = LoggerFactory.getLogger(AnnotationTask.class);
diff --git a/src/main/java/com/spring/aspect/dynamicflow/activity/ReviewTask.java b/src/main/java/com/spring/aspect/dynamicflow/activity/ReviewTask.java
index ece0ff6..f364da2 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/activity/ReviewTask.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/activity/ReviewTask.java
@@ -6,9 +6,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
 
-@Component
 @Aspect
 public class ReviewTask extends Task {
   private static final Logger log = LoggerFactory.getLogger(ReviewTask.class);
diff --git a/src/main/java/com/spring/aspect/dynamicflow/activity/Task.java b/src/main/java/com/spring/aspect/dynamicflow/activity/Task.java
index 3f1f9ce..93b3b73 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/activity/Task.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/activity/Task.java
@@ -6,9 +6,7 @@ import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
 
-@Component
 @Aspect
 public abstract class Task {
   private static final Logger log = LoggerFactory.getLogger(Task.class);
diff --git a/src/main/java/com/spring/aspect/dynamicflow/aspect/TaskAspects.java b/src/main/java/com/spring/aspect/dynamicflow/aspect/TaskAspects.java
index 3bff7b5..a09d9d6 100644
--- a/src/main/java/com/spring/aspect/dynamicflow/aspect/TaskAspects.java
+++ b/src/main/java/com/spring/aspect/dynamicflow/aspect/TaskAspects.java
@@ -5,9 +5,7 @@ import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
 
-@Component
 @Aspect
 public class TaskAspects {
   private static final Logger log = LoggerFactory.getLogger(TaskAspects.class);
diff --git a/src/main/resources/org/aspectj/aop.xml b/src/main/resources/org/aspectj/aop.xml
new file mode 100644
index 0000000..56342b4
--- /dev/null
+++ b/src/main/resources/org/aspectj/aop.xml
@@ -0,0 +1,15 @@
+<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
+<aspectj>
+
+  <weaver options="-verbose -showWeaveInfo">
+    <!-- only weave classes in our application-specific packages -->
+    <include within="com.spring.aspect.dynamicflow..*"/>
+  </weaver>
+
+  <aspects>
+    <aspect name="com.spring.aspect.dynamicflow.aspect.TaskAspects"/>
+    <aspect name="com.spring.aspect.dynamicflow.activity.AnnotationTask"/>
+    <aspect name="com.spring.aspect.dynamicflow.activity.ReviewTask"/>
+  </aspects>
+
+</aspectj>

顺便说一句,我也很快尝试了 Spring AOP 方面(Task 子类)和 AspectJ 方面(TaskAspects,真是个可怕的名字,为什么不呢?TasksAspectTaskInterceptor?)。尽管 Spring 手册和邮件列表上的许多用户都说,即使没有额外的配置也可以很容易地将这两种方法结合起来,但我无法按照我想要的方式实现 运行。所以现在我没有解决方案。可能我只是犯了一个小错误。我是AspectJ专家,但实际上从来没有使用过Spring或Spring AOP,抱歉。


更新: 我忘了在我的存储库中提到我也解决了我之前在评论中提到的这个问题:

If you really use AspectJ LTW scenario, your TaskAspects aspect gets triggered too often because within(com.spring.aspect.dynamicflow.activity.Task+) in AspectJ not only intercepts method executions but also object and class initialisation, field access, method calls to other classes etc. So either you use AspectJ LTW and it prints too much or (...)

如果您使用该切入点,则确实如此。日志输出(时间戳、日志通道信息等。从日志输出中截断)将是:

Handling the task aspects.
  staticinitialization(com.spring.aspect.dynamicflow.activity.Task.<clinit>)
Handling the task aspects.
  call(Logger org.slf4j.LoggerFactory.getLogger(Class))
Handling the task aspects.
  set(Logger com.spring.aspect.dynamicflow.activity.Task.log)
Handling the task aspects.
  staticinitialization(com.spring.aspect.dynamicflow.activity.AnnotationTask.<clinit>)
Handling the task aspects.
  call(Logger org.slf4j.LoggerFactory.getLogger(Class))
Handling the task aspects.
  set(Logger com.spring.aspect.dynamicflow.activity.AnnotationTask.log)
Handling the task aspects.
  execution(com.spring.aspect.dynamicflow.activity.Task())
Handling the task aspects.
  execution(com.spring.aspect.dynamicflow.activity.AnnotationTask())
Handling the task aspects.
  execution(Object com.spring.aspect.dynamicflow.activity.AnnotationTask.task(ProceedingJoinPoint, JobContext))
Handling the task aspects.
  get(Logger com.spring.aspect.dynamicflow.activity.AnnotationTask.log)
Handling the task aspects.
  call(void org.slf4j.Logger.info(String))
AnnotationTask's task
Handling the task aspects.
  get(Logger com.spring.aspect.dynamicflow.activity.AnnotationTask.log)
Handling the task aspects.
  call(void org.slf4j.Logger.info(String))
  Setting that the annotation is done.
Handling the task aspects.
  get(TaskStatus com.spring.aspect.dynamicflow.entity.TaskStatus.COMPLETED)
Handling the task aspects.
  call(void com.spring.aspect.dynamicflow.entity.JobContext.setAnnotationTaskStatus(TaskStatus))
Handling the task aspects.
  call(Object org.aspectj.lang.ProceedingJoinPoint.proceed())
Handling the task aspects.
  staticinitialization(com.spring.aspect.dynamicflow.activity.ReviewTask.<clinit>)
Handling the task aspects.
  call(Logger org.slf4j.LoggerFactory.getLogger(Class))
Handling the task aspects.
  set(Logger com.spring.aspect.dynamicflow.activity.ReviewTask.log)
Handling the task aspects.
  execution(com.spring.aspect.dynamicflow.activity.Task())
Handling the task aspects.
  execution(com.spring.aspect.dynamicflow.activity.ReviewTask())
Handling the task aspects.
  execution(Object com.spring.aspect.dynamicflow.activity.ReviewTask.task(ProceedingJoinPoint, JobContext))
Handling the task aspects.
  get(Logger com.spring.aspect.dynamicflow.activity.ReviewTask.log)
Handling the task aspects.
  call(void org.slf4j.Logger.info(String))
ReviewTask's task
Handling the task aspects.
  get(Logger com.spring.aspect.dynamicflow.activity.ReviewTask.log)
Handling the task aspects.
  call(void org.slf4j.Logger.info(String))
  Setting that the review is done.
Handling the task aspects.
  get(TaskStatus com.spring.aspect.dynamicflow.entity.TaskStatus.IN_PROGRESS)
Handling the task aspects.
  call(void com.spring.aspect.dynamicflow.entity.JobContext.setReviewTaskStatus(TaskStatus))
Handling the task aspects.
  call(Object org.aspectj.lang.ProceedingJoinPoint.proceed())
Processing the job with jobid 11

我还从日志行之间删除了 AspectJ weaver 日志消息(其中一些是错误的)。您会看到 29 倍的“处理任务方面”,而不是仅 2 倍,这就是我为您将切入点更改为 within(com.spring.aspect.dynamicflow.activity.Task+) && execution(* task(..)) 的原因。现在日志输出看起来符合预期:

Handling the task aspects.
  execution(Object com.spring.aspect.dynamicflow.activity.AnnotationTask.task(ProceedingJoinPoint, JobContext))
AnnotationTask's task
  Setting that the annotation is done.
Handling the task aspects.
  execution(Object com.spring.aspect.dynamicflow.activity.ReviewTask.task(ProceedingJoinPoint, JobContext))
ReviewTask's task
  Setting that the review is done.
Processing the job with jobid 11