java - 如果 Spring 可以成功拦截 @Configuration 类中的类内函数调用,为什么它在常规 bean 中不支持呢?

标签 java spring proxy interception

我最近注意到 Spring 在 @Configuration 类中成功拦截了类内函数调用,但在常规 bean 中没有。

这样的电话

@Repository
public class CustomerDAO {  
    @Transactional(value=TxType.REQUIRED)
    public void saveCustomer() {
        // some DB stuff here...
        saveCustomer2();
    }
    @Transactional(value=TxType.REQUIRES_NEW)
    public void saveCustomer2() {
        // more DB stuff here
    }
}

无法启动新事务,因为虽然 saveCustomer() 的代码在 CustomerDAO 代理中执行,但 saveCustomer2() 的代码在展开的 CustomerDAO 类中执行,正如我在调试器中查看“this”所看到的那样,因此 Spring 没有机会拦截对 saveCustomer2 的调用。

但是,在以下示例中,当 transactionManager() 调用 createDataSource() 时,它会被正确拦截并调用代理的 createDataSource(),而不是解包类的 createDataSource(),正如在调试器中查看“this”所证明的那样。

@Configuration
public class PersistenceJPAConfig {
    @Bean
    public DriverManagerDataSource createDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        //dataSource.set ... DB stuff here
        return dataSource;
    }

   @Bean 
       public PlatformTransactionManager transactionManager(   ){
           DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(createDataSource());
           return transactionManager;
       }
}

所以我的问题是,为什么 Spring 可以正确拦截第二个示例中的类内函数调用,但不能在第一个示例中。是否使用不同类型的动态代理?

编辑: 从这里的答案和其他来源,我现在了解以下内容: @Transactional 使用 Spring AOP 实现,其中代理模式通过用户类的包装/组合来执行。 AOP 代理足够通用,因此可以将许多方面链接在一起,并且可以是 CGLib 代理或 Java 动态代理。

在@Configuration 类中,Spring 还使用CGLib 创建了一个继承自用户@Configuration 类的增强类,并用在调用用户/ super 函数之前做一些额外工作的函数覆盖用户的@Bean 函数,例如检查这是否是函数的第一次调用。这个类是代理吗?这取决于定义。你可能会说它是一个代理,它使用来自真实对象的继承而不是使用组合来包装它。

总而言之,从这里给出的答案中,我了解到这是两种完全不同的机制。为什么做出这些设计选择是另一个悬而未决的问题。

最佳答案

Is it using different types of dynamic proxies?

几乎正是

让我们弄清楚 @Configuration 类和回答以下问题的 AOP 代理有什么区别:

  1. 为什么即使 Spring 能够拦截自调用方法,自调用 @Transactional 方法也没有事务语义?
  2. @Configuration和AOP有什么关系?

为什么自调用的@Transactional方法没有事务语义?

简答:

这就是 AOP 的制作方式。

长答案:

  1. 声明式事务管理依赖于 AOP (对于 Spring AOP 上的大多数 Spring 应用程序)

The Spring Framework’s declarative transaction management is made possible with Spring aspect-oriented programming (AOP)

  1. 它是基于代理的 (§5.8.1. Understanding AOP Proxies)

Spring AOP is proxy-based.

同段SimplePojo.java:

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

还有一个代理它的片段:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy.

This means that method calls on that object reference are calls on the proxy.

As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call.

However, once the call has finally reached the target object (the SimplePojo, reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy.

This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

(重点部分被强调了。)

你可能认为aop的工作原理如下:

假设我们有一个想要代理的 Foo 类:

Foo.java:

public class Foo {
  public int getInt() {
    return 42;
  }
}

没有什么特别的。只是 getInt 方法返回 42

拦截器:

Interceptor.java:

public interface Interceptor {
  Object invoke(InterceptingFoo interceptingFoo);
}

LogInterceptor.java(用于演示):

public class LogInterceptor implements Interceptor {
  @Override
  public Object invoke(InterceptingFoo interceptingFoo) {
    System.out.println("log. before");
    try {
      return interceptingFoo.getInt();
    } finally {
      System.out.println("log. after");
    }
  }
}

InvokeTargetInterceptor.java:

public class InvokeTargetInterceptor implements Interceptor {
  @Override
  public Object invoke(InterceptingFoo interceptingFoo) {
    try {
      System.out.println("Invoking target");
      Object targetRetVal = interceptingFoo.method.invoke(interceptingFoo.target);
      System.out.println("Target returned " + targetRetVal);
      return targetRetVal;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    } finally {
      System.out.println("Invoked target");
    }
  }
}

最后InterceptingFoo.java:

public class InterceptingFoo extends Foo {
  public Foo target;
  public List<Interceptor> interceptors = new ArrayList<>();
  public int index = 0;
  public Method method;

  @Override
  public int getInt() {
    try {
      Interceptor interceptor = interceptors.get(index++);
      return (Integer) interceptor.invoke(this);
    } finally {
      index--;
    }
  }
}

将所有东西连接在一起:

public static void main(String[] args) throws Throwable {
  Foo target = new Foo();
  InterceptingFoo interceptingFoo = new InterceptingFoo();
  interceptingFoo.method = Foo.class.getDeclaredMethod("getInt");
  interceptingFoo.target = target;
  interceptingFoo.interceptors.add(new LogInterceptor());
  interceptingFoo.interceptors.add(new InvokeTargetInterceptor());

  interceptingFoo.getInt();
  interceptingFoo.getInt();
}

将打印:

log. before
Invoking target
Target returned 42
Invoked target
log. after
log. before
Invoking target
Target returned 42
Invoked target
log. after

现在我们来看看ReflectiveMethodInvocation

这是它的proceed方法的一部分:

Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

++this.currentInterceptorIndex 现在看起来应该很熟悉了

您可以尝试在应用程序中引入几个方面,并在调用建议方法时看到堆栈在 proceed 方法处增长

最后一切都在 MethodProxy 结束。 .

来自 invoke方法javadoc:

Invoke the original method, on a different object of the same type.

正如我之前提到的文档:

once the call has finally reached the target object any method calls that it may make on itself are going to be invoked against the this reference, and not the proxy

我希望现在,或多或少,很清楚为什么。

@Configuration和AOP有什么关系?

答案是他们不相关

所以这里的 Spring 可以自由地为所欲为。这里它与 proxy AOP 语义无关。

它使用 ConfigurationClassEnhancer 增强了这些类.

看看:

回到问题

If Spring can successfully intercept intra class function calls in a @Configuration class, why does it not support it in a regular bean?

希望从技术角度很清楚为什么。

现在我的想法来自非技术方面:

我认为它没有完成,因为 Spring AOP 在这里足够长...

自 Spring Framework 5 起 Spring WebFlux框架已经引入。

目前Spring团队正在努力增强reactive编程模型

查看一些值得注意的近期博文:

引入了越来越多的 less-proxying 方法来构建 Spring 应用程序。 (例如,参见 this commit)

所以我认为,即使有可能做你所描述的事情,它也远非 Spring Team 目前的第一优先事项

关于java - 如果 Spring 可以成功拦截 @Configuration 类中的类内函数调用,为什么它在常规 bean 中不支持呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56193642/

相关文章:

express - axios 请求获取连接 EHOSTUNREACH 错误。响应头得到默认源代码 'self'

java - 公历法

javax.validation.Validation 多重实例化与重用单个 Validator 实例

xml - Spring 加载所有 JNDI 属性

Spring KafkaListener : How to know when it's ready

SVG 图像被 Gmail 代理阻止

java - 以编程方式显示来自 Servlet 类的网页

java - Selenium Java - 我如何只运行一个测试?

java - 所有数据库查询的全局 hibernate 过滤器

java - Java 中的代理检查