发生问题的背景
当spring中@Controller类下面对于下面两个方法 test会进入MonitorMethod的切面的逻辑,而test1不会。
@Controller
public class Test {
@PostMapping("/test")
@MonitorMethod
public void test() {
// test
}
@MonitorMethod
public void test1() {
// test1
}
}
@PostMapping
确实会对方法进行代理,因此注解能够生效。而没有任何 Spring MVC 注解的方法,可能不会被 Spring 管理,因此切面不能拦截到这些方法的执行。
这是因为 Spring AOP 默认使用 JDK 动态代理或者 CGLIB 代理,这取决于目标类实现的接口。当使用 JDK 动态代理时,只有通过代理接口的方法调用才能被代理捕获,而通过内部方法调用不会触发代理。因此,如果 test1
方法在同一个类内部被调用,而不是通过代理对象调用,则 AOP 切面将不会生效。
解决方案
- 将
test1
方法调用改为通过代理对象调用:
你可以将test1
方法放在一个单独的服务类中,然后通过自动注入的方式调用,这样可以确保该方法通过代理对象调用。
修改示例
- 创建一个新的服务类:
@Service
public class TestService {
@MonitorMethod
public void test1(){
}
}
- 在控制器中自动注入并调用该服务:
@Controller
public class Test {
@Autowired
private TestService testService;
@PostMapping("/test")
@MonitorMethod
public void test() {
testService.test1();
}
}
其他解决方案
- 确保内部调用也被代理:
如果你不想把test1
移到另一个服务中,可以使用 AOP 的另一种方式:AspectJ 编译时织入(compile-time weaving)或加载时织入(load-time weaving),但这通常需要更多配置。
通过以上修改,确保 test1
方法被代理对象调用,从而触发切面。这样,@MonitorMethod
注解就能够生效,方法也会被切面拦截。
Spring AOP 的默认行为是这样的:
-
在 Controller 中的方法:
- 如果方法有
@RequestMapping
、@GetMapping
、@PostMapping
等 Spring MVC 注解,Spring 会为这些方法创建代理,从而使 AOP 切面能够拦截这些方法的调用。 - 如果方法没有这些注解,并且是在同一个类中直接调用,这些调用不会通过代理对象,因此 AOP 切面无法拦截这些调用。
- 如果方法有
-
在 Service、Component 等注解标注的类中的方法:
- Spring 会为这些类创建代理对象,从而使 AOP 切面能够拦截这些方法的调用,即使方法没有 Spring MVC 注解。
- 在这些类中,方法调用需要通过代理对象才能被 AOP 切面拦截。如果在同一个类中直接调用方法(不通过代理),切面也不会生效。
确保切面生效的几点建议
-
通过代理对象调用方法:
- 如果一个方法需要被切面拦截,确保它通过代理对象调用。例如,将方法移到一个独立的服务类中,通过
@Autowired
注入调用。
- 如果一个方法需要被切面拦截,确保它通过代理对象调用。例如,将方法移到一个独立的服务类中,通过
-
组件扫描:
- 确保你的配置中包含了对
@Service
、@Component
等注解的组件扫描。通常,在 Spring Boot 应用中,这通过@SpringBootApplication
注解已经自动配置好了。
- 确保你的配置中包含了对
-
使用
@EnableAspectJAutoProxy
注解:- 确保在配置类中使用了
@EnableAspectJAutoProxy
注解,以启用 Spring AOP 的代理机制。
- 确保在配置类中使用了
AspectJ 和 Spring AOP 是两种实现面向切面编程(AOP)的方式,它们在实现机制和应用场景上有所不同。下面是详细的解释和它们之间的区别:
Spring AOP
Spring AOP 使用动态代理来实现 AOP 功能。它在运行时生成代理对象,主要依赖于 JDK 动态代理或 CGLIB 动态代理。
特点:
-
基于代理:
- JDK 动态代理:用于代理实现了接口的类。
- CGLIB 动态代理:用于代理没有实现接口的类。
-
运行时织入:
- 代理对象在运行时生成并应用切面。
- 只对 Spring 管理的 Bean 生效。
-
局限性:
- 不能拦截同一个类中其他方法的内部调用,因为内部调用不会通过代理。
- 只能拦截 public 方法。
AspectJ
AspectJ 是一个全面的 AOP 框架,提供比 Spring AOP 更强大的功能。它可以通过编译时织入、加载时织入和运行时织入来实现 AOP 功能。
特点:
-
编译时织入(Compile-time Weaving):
- 在编译阶段将切面代码直接织入目标类的字节码中。
-
加载时织入(Load-time Weaving,LTW):
- 类在类加载器加载到 JVM 时,织入切面代码。
- 需要配置类加载器以支持 LTW。
-
运行时织入(Runtime Weaving):
- 通过代理或字节码修改实现(类似于 Spring AOP)。
-
灵活性和功能强大:
- 可以拦截非 public 方法。
- 可以拦截同一个类中方法的内部调用。
- 提供更丰富的切点表达式。
Spring AOP 与 AspectJ 的区别
-
织入时间:
- Spring AOP:运行时织入。
- AspectJ:编译时织入、加载时织入和运行时织入。
-
实现机制:
- Spring AOP:基于代理,使用 JDK 动态代理和 CGLIB。
- AspectJ:通过修改字节码实现,可以在编译时或类加载时完成。
-
功能和灵活性:
- Spring AOP:主要用于简单的 AOP 场景,只能拦截 public 方法,不能拦截内部方法调用。
- AspectJ:功能更强大,可以拦截任何方法,支持更复杂的切点表达式和织入方式。
使用 AspectJ 编译时织入
以下是如何配置 AspectJ 编译时织入的步骤:
-
添加依赖:
在 Maven 项目中,添加 AspectJ 依赖:<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.9.6</version> </dependency>
-
创建 Aspect 类:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Before; @Aspect public class LoggingAspect { @Pointcut("execution(* com.example..*(..))") public void applicationPackagePointcut() { // Method is empty because it's just a Pointcut, the implementations are in the advices. } @Before("applicationPackagePointcut()") public void logBefore() { System.out.println("AspectJ before method execution"); } }
-
配置编译插件:
在pom.xml
中配置 AspectJ 编译插件:<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.12.6</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <target>1.8</target> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> <verbose>true</verbose> <showWeaveInfo>true</showWeaveInfo> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
使用 AspectJ 加载时织入
以下是如何配置 AspectJ 加载时织入的步骤:
-
添加依赖:
在 Maven 项目中,添加 AspectJ 依赖(同上)。 -
创建
aop.xml
文件:
在src/main/resources/META-INF
目录下创建aop.xml
文件:<aspectj> <weaver> <include within="com.example..*"/> </weaver> <aspects> <aspect name="com.example.LoggingAspect"/> </aspects> </aspectj>
-
配置类加载器:
修改 JVM 启动参数以启用 AspectJ 加载时织入:-javaagent:/path/to/aspectjweaver.jar
总结
- Spring AOP:简单易用,适合大多数常见的 AOP 场景,但有一些限制,如无法拦截内部方法调用。
- AspectJ:功能强大,支持复杂的 AOP 场景,包括编译时和加载时织入,但需要更多的配置和理解。
根据你的具体需求和应用场景选择合适的 AOP 实现方式。Spring AOP 更适合简单的应用场景,而 AspectJ 适合需要高级 AOP 功能的复杂场景。