如何获取Spring代理对象?

2024-07-30 19:36:24 浏览数 (2)

作为一名面向Spring编程的开发攻城狮,Spring AOP是我们最常见、也是使用最频繁的特性之一。但是,如果使用不当就可能获取不到代理对象,从而可能会影响系统功能的使用,比如事务失效。本文将会介绍一下怎么获取Spring AOP代理对象。

示例

先看一下下面这段代码:

代码语言:javascript复制
public interface ProxyService {

    void proxy();

    void testProxy();
}
代码语言:javascript复制
@Service
public class ProxyServiceImpl implements ProxyService {

    @Override
    public void proxy() {
        System.out.println("ProxyServiceImpl.proxy 方法被调用");
    }

    @Override
    public void testProxy() {
        System.out.println("ProxyServiceImpl.testProxy 方法被调用");
        proxy();
    }
}

测试类:

代码语言:javascript复制
@SpringBootTest
public class ProxyApplicationTest {

    @Resource
    private ProxyService proxyService;

    @Test
    public void testProxy(){
        proxyService.testProxy();
    }
}

定义一个切面,用于验证获取代理对象是否生效:

代码语言:javascript复制
@Component
@Aspect
public class ProxyAspect {

    @Pointcut("execution(* site.suncodernote.proxy.ProxyService.proxy())")
    void proxyServicePointCut(){
    }

    @After("proxyServicePointCut()")
    public void jdkDivide(JoinPoint joinPoint){
        System.out.println("---------------------jdkProxyServicePointCut切面生效了----------------");
    }
}

运行测试类,你觉得结果会是什么?

结果就是这样,你没猜错。

代码语言:javascript复制
ProxyServiceImpl.testProxy 方法被调用
ProxyServiceImpl.proxy 方法被调用

为什么会这样呢?因为代理内部调用代理的其他方法时,直接调用可能会导致调用的是目标对象的方法,而不是经过增强的方法,所以也就没走切面。

1A09531E.gif

获取代理对象

把当前类注入到当前类中

代码语言:javascript复制
@Service
public class ProxyServiceImpl implements ProxyService {
    
    @Resource
    private ProxyService proxyService;

这种方式也能达到目的,但是有点不太好。

使用ObjectFactory

ObjectFactory接口是一个对象工厂,其实现类通常用于延迟注入和按需获取对象实例,通过其getObject方法来创建和返回对象实例。

如果对象已经通过ObjectFactory创建了,ObjectFactory不会再次创建新的对象实例。ObjectFactory的主要功能是按需创建对象,但它会缓存已经创建的对象实例,以便后续请求时可以直接返回这些实例,而不是重新创建。

当使用ObjectFactory获取对象时,如果之前已经通过它获取并创建了对象实例,它会返回缓存的实例。这种行为确保了单例对象的唯一性和对象的重用,同时允许按需创建新的对象实例以支持延迟初始化和解决循环依赖问题。

下面看怎么使用它:

代码语言:javascript复制
@Service  
public class ProxyServiceImpl implements ProxyService {  
  
    @Resource  
    private ObjectFactory<ProxyService> objectFactory;  

    @Override  
    public void proxy() {  
        System.out.println("JDKProxyServiceImpl.proxy");  
    }  


    @Override  
    public void testProxy() {  
        System.out.println("JDKProxyServiceImpl.testProxy");  
        // proxy();  
        ProxyService proxyService = objectFactory.getObject();  
        proxyService.proxy();  
    }  
}

打印结果:

可以看到通过ObjectFactory也能获取到代理对象。

使用 AopContext.currentProxy()

AopContext.currentProxy()方法是Spring框架中的一个重要的工具方法,用于在运行时获取当前AOP代理对象。

下面具体看一下怎么使用它。总共分2步:

  1. 在Springboot启动类或者配置类中开启暴露代理注解

默认情况下,Spring不会开启AopContext的支持,需要在配置中显式设置exposeProxy为true。

代码语言:javascript复制
@EnableAspectJAutoProxy(exposeProxy = true)

为什么默认情况下,Spring不会开启AopContext的支持呢?主要是考虑到性能问题。

下面是AopContext的源码:

代码语言:javascript复制
public final class AopContext {

   /**
    * ThreadLocal holder for AOP proxy associated with this thread.
    * Will contain {@code null} unless the "exposeProxy" property on
    * the controlling proxy configuration has been set to "true".
    * @see ProxyConfig#setExposeProxy
    */
   private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");


   private AopContext() {
   }



   /**
    * 获取当前代理对象
    */
   public static Object currentProxy() throws IllegalStateException {
      Object proxy = currentProxy.get();
      if (proxy == null) {
         throw new IllegalStateException(
               "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and "  
                     "ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.");
      }
      return proxy;
   }

  
   @Nullable
   static Object setCurrentProxy(@Nullable Object proxy) {
      Object old = currentProxy.get();
      if (proxy != null) {
         currentProxy.set(proxy);
      }
      else {
         currentProxy.remove();
      }
      return old;
   }

}

可以看到,·当开启AopContext的支持时,Spring需要在每次方法调用时维护一个当前代理对象的线程本地变量 currentProxy。这会引入额外的内存开销和执行时开销,因为需要动态地确定当前调用是否处于AOP代理的上下文中。

  1. 在方法中使用AopContext.currentProxy()

示例代码:

代码语言:javascript复制
@Override
public void testProxy() {
    System.out.println("ProxyServiceImpl.testProxy 方法被调用");
//        proxy();

    ProxyService proxyService = (ProxyService) AopContext.currentProxy();
    proxyService.proxy();
}

再次调用ProxyApplicationTest#testProxy() 单元测试方法,就可以看到控制台会打印切面中的方法了。

以上就是如何获取Spring代理对象的全部内容,谢谢观看!!!

0 人点赞