@Enableaspectjautoproxy(exposeproxy = true) 注解的作用

158 阅读3分钟

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

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

package demo.long;

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

接口实现类如下:

package demo.long.impl;

import demo.long.CustomerService; 

@Service
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..."); 
    }
}

配置

@Configuration
@EnableAspectJAutoProxy()
public class TestConfiguration {

	@Bean
	public CustomerServiceInterceptor customerServiceInterceptor () {
		return new CustomerServiceInterceptor();
	}
}

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

public class CustomerServiceTest {

    @Autowired
    ICustomerService customerService;

    @Test
    public void testAOP() {
        customerService.doSomething1();
    }
}
原因分析

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

也可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 设置基于 CGLIB 技术实现
Indicate whether subclass-based (CGLIB) proxies are to be created as opposed * to standard Java interface-based proxies. The default is {@code false}.

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 配置:
@EnableAspectJAutoProxy(exposeProxy = true)