controller中 加了@PostMapping和切面注解的方法会进入切面的逻辑,没有@PostMapping的方法加上切面注解不会进入切面的逻辑

发生问题的背景

当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 切面将不会生效。

解决方案

  1. test1 方法调用改为通过代理对象调用
    你可以将 test1 方法放在一个单独的服务类中,然后通过自动注入的方式调用,这样可以确保该方法通过代理对象调用。

修改示例

  1. 创建一个新的服务类
@Service
public class TestService {

	@MonitorMethod
    public void test1(){
        
    }
}
  1. 在控制器中自动注入并调用该服务
@Controller
public class Test {
	
	@Autowired
	private TestService  testService;
	@PostMapping("/test")
	@MonitorMethod
	public void test() {
    	testService.test1();
	}
	
	
}

其他解决方案

  1. 确保内部调用也被代理
    如果你不想把 test1 移到另一个服务中,可以使用 AOP 的另一种方式:AspectJ 编译时织入(compile-time weaving)或加载时织入(load-time weaving),但这通常需要更多配置。

通过以上修改,确保 test1 方法被代理对象调用,从而触发切面。这样,@MonitorMethod 注解就能够生效,方法也会被切面拦截。

Spring AOP 的默认行为是这样的:

  1. 在 Controller 中的方法

    • 如果方法有 @RequestMapping@GetMapping@PostMapping 等 Spring MVC 注解,Spring 会为这些方法创建代理,从而使 AOP 切面能够拦截这些方法的调用。
    • 如果方法没有这些注解,并且是在同一个类中直接调用,这些调用不会通过代理对象,因此 AOP 切面无法拦截这些调用。
  2. 在 Service、Component 等注解标注的类中的方法

    • Spring 会为这些类创建代理对象,从而使 AOP 切面能够拦截这些方法的调用,即使方法没有 Spring MVC 注解。
    • 在这些类中,方法调用需要通过代理对象才能被 AOP 切面拦截。如果在同一个类中直接调用方法(不通过代理),切面也不会生效。

确保切面生效的几点建议

  1. 通过代理对象调用方法

    • 如果一个方法需要被切面拦截,确保它通过代理对象调用。例如,将方法移到一个独立的服务类中,通过 @Autowired 注入调用。
  2. 组件扫描

    • 确保你的配置中包含了对 @Service@Component 等注解的组件扫描。通常,在 Spring Boot 应用中,这通过 @SpringBootApplication 注解已经自动配置好了。
  3. 使用 @EnableAspectJAutoProxy 注解

    • 确保在配置类中使用了 @EnableAspectJAutoProxy 注解,以启用 Spring AOP 的代理机制。

AspectJ 和 Spring AOP 是两种实现面向切面编程(AOP)的方式,它们在实现机制和应用场景上有所不同。下面是详细的解释和它们之间的区别:

Spring AOP

Spring AOP 使用动态代理来实现 AOP 功能。它在运行时生成代理对象,主要依赖于 JDK 动态代理或 CGLIB 动态代理。

特点:
  1. 基于代理

    • JDK 动态代理:用于代理实现了接口的类。
    • CGLIB 动态代理:用于代理没有实现接口的类。
  2. 运行时织入

    • 代理对象在运行时生成并应用切面。
    • 只对 Spring 管理的 Bean 生效。
  3. 局限性

    • 不能拦截同一个类中其他方法的内部调用,因为内部调用不会通过代理。
    • 只能拦截 public 方法。

AspectJ

AspectJ 是一个全面的 AOP 框架,提供比 Spring AOP 更强大的功能。它可以通过编译时织入、加载时织入和运行时织入来实现 AOP 功能。

特点:
  1. 编译时织入(Compile-time Weaving)

    • 在编译阶段将切面代码直接织入目标类的字节码中。
  2. 加载时织入(Load-time Weaving,LTW)

    • 类在类加载器加载到 JVM 时,织入切面代码。
    • 需要配置类加载器以支持 LTW。
  3. 运行时织入(Runtime Weaving)

    • 通过代理或字节码修改实现(类似于 Spring AOP)。
  4. 灵活性和功能强大

    • 可以拦截非 public 方法。
    • 可以拦截同一个类中方法的内部调用。
    • 提供更丰富的切点表达式。

Spring AOP 与 AspectJ 的区别

  1. 织入时间

    • Spring AOP:运行时织入。
    • AspectJ:编译时织入、加载时织入和运行时织入。
  2. 实现机制

    • Spring AOP:基于代理,使用 JDK 动态代理和 CGLIB。
    • AspectJ:通过修改字节码实现,可以在编译时或类加载时完成。
  3. 功能和灵活性

    • Spring AOP:主要用于简单的 AOP 场景,只能拦截 public 方法,不能拦截内部方法调用。
    • AspectJ:功能更强大,可以拦截任何方法,支持更复杂的切点表达式和织入方式。

使用 AspectJ 编译时织入

以下是如何配置 AspectJ 编译时织入的步骤:

  1. 添加依赖
    在 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>
    
  2. 创建 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");
        }
    }
    
  3. 配置编译插件
    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 加载时织入的步骤:

  1. 添加依赖
    在 Maven 项目中,添加 AspectJ 依赖(同上)。

  2. 创建 aop.xml 文件
    src/main/resources/META-INF 目录下创建 aop.xml 文件:

    <aspectj>
        <weaver>
            <include within="com.example..*"/>
        </weaver>
        <aspects>
            <aspect name="com.example.LoggingAspect"/>
        </aspects>
    </aspectj>
    
  3. 配置类加载器
    修改 JVM 启动参数以启用 AspectJ 加载时织入:

    -javaagent:/path/to/aspectjweaver.jar
    

总结

  • Spring AOP:简单易用,适合大多数常见的 AOP 场景,但有一些限制,如无法拦截内部方法调用。
  • AspectJ:功能强大,支持复杂的 AOP 场景,包括编译时和加载时织入,但需要更多的配置和理解。

根据你的具体需求和应用场景选择合适的 AOP 实现方式。Spring AOP 更适合简单的应用场景,而 AspectJ 适合需要高级 AOP 功能的复杂场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值