Spring AOP无法拦截内部方法调用—— exposeProxy=true用法

假设一个接口里面有两个方法:

package demo.long;

public interface CustomerService {  
    public void doSomething1();  
    public void doSomething2();  
}  

接口实现类如下:

package demo.long.impl;

import demo.long.CustomerService; 

public class CustomerServiceImpl implements CustomerService {  
  
    public void doSomething1() {  
        System.out.println("CustomerServiceImpl.doSomething1()");  
        doSomething2();  
    }  
  
    public void doSomething2() {  
        System.out.println("CustomerServiceImpl.doSomething2()");  
    }  
  
}  

现在我需要在CustomerService接口的每个方法被调用时都在方法前执行一些逻辑,所以需要配置一个拦截器:

package demo.long;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class CustomerServiceInterceptor {

    @Before("execution(* demo.long..*.*(..))")
    public void doBefore() {
        System.out.println("do some important things before..."); 
    }
}

把Bean加到Spring配置中

<aop:aspectj-autoproxy />

<bean id="customerService" class="demo.long.impl.CustomerServiceImpl" />
<bean id="customerServiceInterceptor" class="demo.long.CustomerServiceInterceptor" />

如果现在外部对象调用CustomerService的doSomething1()方法的时候,会发现只有doSomething1()方法执行前打印了“do some important things before...”,而doSomething1()内部调用doSomething2()时并没有打印上述内容;外部对象单独调用doSomething2()时会打印上述内容。

public class CustomerServiceTest {

    @Autowired
    CustomerService customerService;

    @Test
    public void testAOP() {
        customerService.doSomething1();
    }
}

原因分析

拦截器的实现原理就是动态代理,实现AOP机制。Spring 的代理实现有两种:一是基于 JDK Dynamic Proxy 技术而实现的;二是基于 CGLIB 技术而实现的。如果目标对象实现了接口,在默认情况下Spring会采用JDK的动态代理实现AOP,CustomerServerImpl正是这种情况。

JDK动态代理生成的CustomerServiceImpl的代理类大致如下:

public class CustomerServiceProxy implements CustomerService {  
  
    private CustomerService customerService;  
  
    public void setCustomerService(CustomerService customerService) {  
        this.customerService = customerService;  
    }  
  
    public void doSomething1() {  
        doBefore();  
        customerService.doSomething1();  
    }  
  
    public void doSomething2() {  
        doBefore();  
        customerService.doSomething2();  
    }  
  
    private void doBefore() {  
        // 例如,可以在此处开启事务或记录日志
        System.out.println("do some important things before...");  
    }  
  
}  

客户端程序使用代理类对象去调用业务逻辑:

public class TestProxy {  
      
    public static void main(String[] args) {  
        // 创建代理目标对象
        // 对于Spring来说,这一工作是由Spring容器完成的。  
        CustomerService serviceProxyTarget = new CustomerServiceImpl();  
  
        // 创建代理对象
        // 对于Spring来说,这一工作也是由Spring容器完成的。 
        CustomerServiceProxy serviceProxy = new CustomerServiceProxy();  
        serviceProxy.setCustomerService(serviceProxyTarget);  
        CustomerService serviceBean = (CustomerService) serviceProxy;  
  
        // 调用业务逻辑操作  
        serviceBean.doSomething1();  
    }  
}  

执行main方法,发现doSomething1()中调用doSomething2()方法的时候并未去执行CustomerServiceProxy类的doBefore()方法。其实doSomething2()等同于this.doSomething2(),在CustomerServiceImpl类中this关键字表示的是当前这个CustomerServiceImpl类的实例,所以程序会去执行CustomerServiceImpl对象中的doSomething2()方法,而不会去执行CustomerServiceProxy类对象中的 doSomething2()方法。

在使用Spring AOP的时候,我们从IOC容器中获取的Bean对象其实都是代理对象,而不是那些Bean对象本身,由于this关键字引用的并不是该Service Bean对象的代理对象,而是其本身,因此Spring AOP是不能拦截到这些被嵌套调用的方法的。

解决方案

  1. 修改类,不要出现“自调用”的情况:这是Spring文档中推荐的“最佳”方案;
  2. 若一定要使用“自调用”,那么this.doSomething2()替换为:((CustomerService) AopContext.currentProxy()).doSomething2();此时需要修改spring的aop配置:
    <aop:aspectj-autoproxy expose-proxy="true" />

  3.   如果是Springboot项目,加上注解:@EnableAspectJAutoProxy(exposeProxy = true)

备注

以上也是@Transactional注解失效的一种场景


作者:末等公民
链接:https://www.jianshu.com/p/6534945eb3b5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: @enableaspectjautoproxy(exposeproxy = true) 是一个注解,用于启用 AspectJ 自动代理功能,并暴露代理对象。在 Spring AOP 中,通过该注解可以将代理对象暴露出来,方便在 AOP 切面中获取代理对象并进行操作。exposeproxy = true 表示暴露代理对象。 ### 回答2: @enableaspectjautoproxy(exposeproxy=true)是Spring AOP框架中的一个注解,它可用于自动创建AspectJ代理并将其公开作为Spring bean,以便在需要时访问代理对象。 在默认情况下,Spring AOP使用JDK动态代理来创建代理对象。但是,如果被代理的目标对象没有实现任何接口,就无法使用JDK动态代理。在这种情况下,Spring AOP会切换到使用AspectJ实现的动态代理。 @EnableAspectJAutoProxy注解告诉Spring AOP框架在必要时启用AspectJ代理,并公开代理对象以供使用。此外,exposeproxy = true参数告诉Spring在代理创建过程中将代理对象公开为Spring bean。这大大简化了代码中访问代理对象的过程。 使用@enableaspectjautoproxyexposeproxy = true)需要注意以下几点: - 该注解需要声明在配置类中。 - 如果没有显式地指定proxy-target-class属性,Spring会自动检查目标bean类是否实现了至少一个接口。如果没有,将使用AspectJ运行时代理(CGLIB)。 - 由于代理创建是在运行时发生的,因此应该确保在运行时注入的所有依赖项都可以解析。否则可能会出现空指针异常或依赖项找不到的情况。 - 对于指定了“proxytargetclass = true”的情况,AspectJ代理将被始终使用,无论目标bean是否实现了至少一个接口。 总之,@enableaspectjautoproxy (exposeproxy = true)是一种方便的方法,在需要时访问AspectJ代理对象。它简化了代码并提供了更多的灵活性和可重用性。 ### 回答3: @enableaspectjautoproxy注解是Spring AOP框架中用于标记类的注解,它的作用是开启自动代理,同时暴露代理对象,以便在需要时可以直接访问代理对象。 当使用@EnableAspectJAutoProxy注解时,Spring框架会自动为标记了@Aspect注解的切面类生成代理对象,并将它们添加到IOC容器中。这些代理对象会拦截目标对象的方法调用,执行相关的切面逻辑,并最终将方法调用委托给目标对象。 exposeproxy = true参数告诉Spring框架,希望将代理对象暴露出来。这意味着我们可以直接使用代理对象,而无需进行类型转换或其他额外的处理。这对于在AOP切面中获取代理对象,以便在切面方法中进行一些特殊的操作非常有用。 除了exposeproxy参数外,@EnableAspectJAutoProxy注解还支持其他一些参数,比如proxytargetclass和order等。其中,proxytargetclass参数用于指定是否使用CGLib代理,而order参数用于指定多个切面的执行顺序。 总之,@EnableAspectJAutoProxy注解是Spring AOP框架中一个非常重要的注解,它可以极大地简化AOP编程,并帮助我们更轻松地实现切面逻辑。同时,通过设置不同的参数,我们还可以进一步控制代理的行为,并满足不同的业务需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值