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

2022-02-22 00:00:00 proxy spring java 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可以正确地拦截第二个示例中的类内函数调用,而不是第一个示例中的类内函数调用。它是否使用不同类型的动态代理?

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

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

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

AOP

因为推荐答案代理和@Configuration类的用途不同,实现方式也明显不同(即使两者都涉及使用代理)。 基本上,AOP使用组合,而@Configuration使用继承。

AOP代理

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

(我在此添加此内容只是为了与@Configuration形成对比,因为您似乎对此部分有正确的理解。)

@配置

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

正因为如此,Spring可以对该类执行更激进的操作,而不必担心它会破坏代码中的某些内容(请记住,您知道您只为Spring提供了此类,并且您永远不会直接创建或使用它的实例)。

它实际做的是创建一个代理,它是@Configuration类的子类。这样,它可以拦截@Configuration类的每个(非finalprivate)方法的调用,甚至在同一个对象中也是如此(因为这些方法实际上都被代理重写,并且Java将所有方法都设为虚的)。代理这样做正是为了将它识别为(语义上)对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。

相关文章