如何为抽象方法创建切入点

How to create pointcut for an abstract method

我正在尝试为在子 class 中实现的抽象方法创建切入点,但 AOP 从未被调用。

这是我的最小值 Java 代码:

package com.example;

public class Service {
    private ParentAbstractClass clazz;

    public Service(ParentAbstractClass clazz) {
        this.clazz = clazz;
    }

    public void process() {
        clazz.method();
    }
}

这是服务 class,它具有要注入的 Class 的抽象并调用一个方法。

我的抽象 class 有一些共同的逻辑和一个实现特定代码,它是一个抽象方法。

package com.example;

import java.util.List;

public abstract class ParentAbstractClass {
    public void method() {
        abstractMethod(List.of("test"));
    }

    public abstract void abstractMethod(List<String> names);
}

这是为抽象方法提供实现的 class。

package com.example;

import java.util.List;

public class ConcreteClass extends ParentAbstractClass {
    @Override
    public void abstractMethod(List<String> names) {
        System.out.println("Look up! AOP should have executed");
    }
}

通过此设置,我正在使用 spring XML 来配置我的 bean。

<bean id = "clazz" class="com.example.ConcreteClass"/>

<bean id="myservice" class="com.example.Service">
    <constructor-arg ref="clazz"/>
</bean>

<bean id = "aspect" class="com.exmple.TxAspect"/>

<aop:config>
    <aop:aspect id="mergeEnableAspect" ref="aspect">
        <aop:pointcut id="mergeServicePointCut"
                      expression="execution(* com.example.ConcreteClass.abstractMethod(..))"/>
        <aop:around pointcut-ref="mergeServicePointCut" method="test" arg-names="pjp"/>
    </aop:aspect>
</aop:config>

最后是 AOP class:

import org.aspectj.lang.ProceedingJoinPoint;

public class TxAspect {
    public void test(ProceedingJoinPoint pjp) {

        System.out.println("I am not going to do anything");

    }
}

在我的 abstractMethod 中,我正在做一些本质上是事务性的事情,我有手动控制事务的业务需求,但是,我的方面 class 从未被调用。有人可以帮我弄清楚我犯了什么错误。

谢谢。

问题是由Spring AOP 实现的约束引起的。这是来自 Spring documentation 的引述:

Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are, by definition, not intercepted. For JDK proxies, only public interface method calls on the proxy can be intercepted. With CGLIB, public and protected method calls on the proxy are intercepted (and even package-visible methods, if necessary). However, common interactions through proxies should always be designed through public signatures.

Note that pointcut definitions are generally matched against any intercepted method. If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with potential non-public interactions through proxies, it needs to be defined accordingly.

If your interception needs include method calls or even constructors within the target class, consider the use of Spring-driven native AspectJ weaving instead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving before making a decision.

所以你有两种可能的方法来解决这个问题:

  1. 仅在其他 bean 调用的方法上使用您的切面(无自调用)
  2. 使用 AspectJ AOP 实现。我准备了一个使用 AspectJ 编译时编织的简单示例,代码托管在 github

AspectJ 方法简述:

修改交易方面如下:

package com.example;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class TxAspect {

    @Around("methodsToBeProfiled()")
    public void test(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("I am not going to do anything");
        pjp.proceed();
    }

    @Pointcut("execution(* com.example.ConcreteClass.abstractMethod(..))")
    public void methodsToBeProfiled(){}
} 

减少XML配置如下:

  <bean id="clazz" class="com.example.ConcreteClass"/>

  <bean id="myservice" class="com.example.Service">
    <constructor-arg ref="clazz"/>
  </bean>

使用以下 Maven 插件编译应用程序:

  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.11</version>
    <dependencies>
      <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjtools</artifactId>
        <version>${ascpectj.version}</version>
      </dependency>
    </dependencies>
    <configuration>
      <source>1.8</source>
      <target>1.8</target>
      <complianceLevel>1.8</complianceLevel>
    </configuration>
    <executions>
      <execution>
        <goals>
          <goal>compile</goal>
          <goal>test-compile</goal>
        </goals>
      </execution>
    </executions>
  </plugin>