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

Gon*_*n I 21 java proxy spring 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
    }
}
Run Code Online (Sandbox Code Playgroud)

未能启动新事务,因为尽管在CustomerDAO代理中执行了saveCustomer()的代码,但在已解包的CustomerDAO类中执行了saveCustomer2()的代码,正如我在调试器中查看“ this”所看到的,等等Spring没有机会拦截对saveCustomer2的调用。

但是,在以下示例中,当transactionManager()调用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;
       }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

cac*_*co3 9

是否使用不同类型的动态代理?

几乎完全一样

让我们找出@Configuration回答以下问题的类和AOP代理之间的区别是什么:

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

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

简短答案:

这就是AOP的制作方式。

长答案:

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

Spring面向方面的编程(AOP)使Spring框架的声明式事务管理成为可能

  1. 它基于代理(第5.8.1节,了解AOP代理

Spring AOP是基于代理的。

在同一段中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...
    }
}
Run Code Online (Sandbox Code Playgroud)

以及一段代理它的代码:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这里要了解的关键是,类main(..)方法中的客户端代码Main具有对代理的引用。

这意味着该对象引用上的方法调用是代理上的调用

结果,代理可以委派给与该特定方法调用相关的所有拦截器(建议)。

但是,一旦调用最终到达目标对象(SimplePojo在这种情况下为reference),它可能会对自身进行的任何方法调用(例如this.bar()or this.foo())都将针对该this引用而不是proxy 进行调用

这具有重要的意义。这意味着自调用不会导致与方法调用相关的建议得到执行的机会。

重点部分。

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

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

Foo.java

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

拦截器:

Interceptor.java

public interface Interceptor {
  Object invoke(InterceptingFoo interceptingFoo);
}
Run Code Online (Sandbox Code Playgroud)

LogInterceptor.java (用于演示):

public class Foo {
  public int getInt() {
    return 42;
  }
}
Run Code Online (Sandbox Code Playgroud)

InvokeTargetInterceptor.java

public interface Interceptor {
  Object invoke(InterceptingFoo interceptingFoo);
}
Run Code Online (Sandbox Code Playgroud)

最后InterceptingFoo.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");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

一起布线:

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();
}
Run Code Online (Sandbox Code Playgroud)

将打印:

log. before
Invoking target
Target returned 42
Invoked target
log. after
log. before
Invoking target
Target returned 42
Invoked target
log. after
Run Code Online (Sandbox Code Playgroud)

现在让我们来看看ReflectiveMethodInvocation

这是其proceed方法的一部分:

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");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

最后,一切都以MethodProxy结尾

从其invoke方法javadoc:

在相同类型的不同对象上调用原始方法。

正如我之前提到的文档:

一旦调用最终到达target对象,它可能对自身进行的任何方法调用都将针对this引用而不是代理进行调用

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

如何@Configuration和AOP有关系吗?

答案是它们不相关

所以Spring在这里可以自由地做任何想做的事情。在这里,它与代理AOP语义无关。

它使用增强了此类ConfigurationClassEnhancer

看一眼:

回到问题

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

希望从技术角度明确原因。

现在从非技术方面的想法

我认为这还没有完成,因为Spring AOP已经足够长了 ...

从Spring Framework 5 开始,引入了Spring WebFlux框架。

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

查看最近 一些著名的博客文章

引入了越来越多的功能,可减少构建Spring应用程序的代理。(例如,参见此提交

因此,我认为,即使有可能按照您的描述进行操作,但与Spring团队目前的第一要务还相去甚远


Jir*_*sek 6

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

AOP代理

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

(我只是在这里添加它以与@Configuration形成对比,因为您似乎对这部分有正确的理解。)

@组态

现在,虽然通常将AOP代理应用于的对象是应用程序的标准部分,但@Configuration类却有所不同-对于一个类,您可能永远不想自己直接创建该类的任何实例。此类实际上只是一种编写bean容器配置的方式,在Spring之外没有任何意义,并且您知道 Spring将以一种特殊的方式使用它,并且它在纯Java代码之外具有某些特殊的语义-例如,@Bean注解的方法实际上定义了Spring bean。

因此,Spring可以对该类做更多根本性的事情,而不必担心它将破坏代码中的某些内容(请记住,您知道您只为Spring提供此类,并且您将永远不会创建或使用其类实例)。

实际上,它创建的是@Configuration该类子类的代理。通过这种方式,它可以拦截所有的(非的调用finalprivate的)的方法@Configuration类,即使在同一个对象(因为这些方法都是有效的全部由代理重写,和Java有所有方法虚拟)。代理正是这样做,以将其(实际上)是对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。