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

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

我最近注意到 Spring 在 @Configuration class 中成功拦截了内部 class 函数调用,但在常规 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 class 中执行,正如我通过查看所见'this' 在调试器中,因此 Spring 没有机会拦截对 saveCustomer2 的调用。

但是,在以下示例中,当 transactionManager() 调用 createDataSource() 时,它被正确拦截并调用代理的 createDataSource(),而不是未包装的 class,如查看 '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可以正确拦截内部class函数调用,而在第一个例子中却不能。是否使用了不同类型的动态代理?

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

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

综上所述,根据此处给出的答案,我了解到这是两种完全不同的机制。为什么做出这些设计选择是另一个悬而未决的问题。

Spring 使用代理进行方法调用,当您使用它时...它会绕过该代理。对于@Bean 注释 Spring 使用反射来查找它们。

读一点 spring 源代码。我试着回答一下。

重点是 spring 如何处理 @Configuration@bean。 在作为 BeanFactoryPostProcessor 的 ConfigurationClassPostProcessor 中,它将增强所有 ConfigurationClasses 并创建一个 Enhancer 作为子类。 这个 Enhancer 注册了两个 CALLBACKS(BeanMethodInterceptor,BeanFactoryAwareMethodInterceptor)。 您调用 PersistenceJPAConfig 方法将通过回调。在 BeanMethodInterceptor 中,它将从 spring 容器中获取 bean。

可能不是很清楚。你可以在ConfigurationClassEnhancer.java BeanMethodInterceptor.ConfigurationClassPostProcessor.java enhanceConfigurationClasses

中查看源代码

can't call @Transactional method in same class

It's a limitation of Spring AOP (dynamic objects and cglib).

If you configure Spring to use AspectJ to handle the transactions, your code will work.

The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.

@Transactional 也应该在 Service layer 而不是 @Repository

transactions belong on the Service layer. It's the one that knows about units of work and use cases. It's the right answer if you have several DAOs injected into a Service that need to work together in a single transaction.

因此您需要重新考虑您的交易方法,以便您的方法可以在流程中重复使用,包括 roll-able

的其他几个 DAO 操作

Is it using different types of dynamic proxies?

差不多正好

让我们找出 @Configuration classes 和 AOP 代理之间的区别,回答以下问题:

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

为什么 self-invoked @Transactional 方法没有事务语义?

简答:

AOP是这样制作的

长答案:

  1. 声明式事务管理依赖于AOP (for the majority of Spring applications on Spring AOP)

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

  1. 是proxy-based (§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 class 我们要代理:

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.

增强了这样的 classes

看看:

回到问题

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

希望从技术角度清楚原因。

现在我的想法non-technical方面:

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

自 Spring Framework 5 以来,引入了 Spring WebFlux 框架。

目前 Spring 团队正在努力增强反应式 编程模型

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

越来越多的 less-proxying 构建 Spring 应用程序的功能被引入。 (例如参见 [​​=78=])

所以我认为即使有可能完成您所描述的事情,它也远非 Spring 团队目前的第一要务

因为 AOP 代理和 @Configuration class 服务于不同的目的,并且以截然不同的方式实现(即使两者都涉及使用代理)。 基本上,AOP使用组合而@Configuration使用继承.

AOP 代理

这些工作的方式基本上是他们创建执行相关建议逻辑的代理 before/after 委托 对原始(代理)对象的调用。容器注册此代理而不是代理对象本身,因此所有依赖项都设置到此代理,并且所有从一个 bean 到另一个 的调用 都通过此代理。但是,被代理对象本身没有指向代理的指针(它不知道自己被代理了,只有代理有指向目标对象的指针)。因此,该对象内对其他方法的任何调用都不会通过代理。

(我加这个只是为了和@Configuration对比,看来你对这部分的理解是正确的。)

@配置

现在,虽然您通常应用 AOP 代理的对象是您应用程序的标准部分,但 @Configuration class 是不同的 - 一方面,您可能从未打算创建任何实例其中 class 直接由您自己完成。这个 class 确实只是一种编写 bean 容器配置的方法,在 Spring 之外没有任何意义,你 知道 它会被 [=58 使用=] 以一种特殊的方式并且它在纯 Java 代码之外具有一些特殊的语义 - 例如@Bean 注释方法实际上定义了 Spring 个 bean。

正因为如此,Spring 可以对这个 class 做更激进的事情,而不用担心它会破坏你的代码(记住,你知道你只提供这个 class for Spring,你永远不会直接创建或使用它的实例)。

它实际上做的是创建一个代理,该代理是 @Configuration class 的 subclass。这样,它可以拦截 @Configuration class 的每个(非 finalprivate)方法的调用,即使是在同一个对象中(因为这些方法有效地全部被代理覆盖,并且 Java 具有所有虚拟方法)。代理正是这样做的,以将它识别为(语义上)对 Spring bean 的引用的任何方法调用重定向到实际的 bean 实例,而不是调用 superclass 方法。