Java 如果Spring能够在@Configuration类中成功拦截类内函数调用,为什么它在常规bean中不支持它?
我最近注意到Spring在@Configuration类中成功地拦截了类内函数调用,但在普通bean中没有 这样的电话Java 如果Spring能够在@Configuration类中成功拦截类内函数调用,为什么它在常规bean中不支持它?,java,spring,proxy,interception,Java,Spring,Proxy,Interception,我最近注意到Spring在@Configuration类中成功地拦截了类内函数调用,但在普通bean中没有 这样的电话 @Repository public class CustomerDAO { @Transactional(value=TxType.REQUIRED) public void saveCustomer() { // some DB stuff here... saveCustomer2(); } @Trans
@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(),而不是unwrapped类的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可以在第二个示例中正确地拦截类内函数调用,而在第一个示例中却不能。它是否使用不同类型的动态代理
编辑:
从这里的答案和其他来源,我现在了解到:
@事务模式使用SpringAOP实现,其中代理模式通过包装/组合用户类来执行。AOP代理足够通用,因此许多方面可以链接在一起,可以是CGLib代理或Java动态代理
在@Configuration类中,Spring还使用CGLib创建一个从user@Configuration类继承的增强类,并使用在调用user/super函数之前做一些额外工作的函数覆盖用户的@Bean函数,例如检查这是否是函数的第一次调用。这个类是代理吗?这取决于定义。您可以说它是一个代理,它使用来自真实对象的继承,而不是使用组合来包装它
总而言之,从这里给出的答案中,我了解到这是两种完全不同的机制。为什么做出这些设计选择是另一个悬而未决的问题。Spring使用代理进行方法调用,当您使用此选项时。。。它绕过了代理。对于@Bean注释,Spring使用反射来查找它们。阅读一些Spring源代码。我试着回答 关键是spring如何处理
@配置
和@bean
。
在作为BeanFactory后处理器的ConfigurationClassPostProcessor中,它将增强所有ConfigurationClass,并创建一个增强器作为子类。
此增强程序注册两个回调(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor)。
您调用PersistenceJPAConfig
方法将通过回调。在BeanMethodInterceptor中,它将从spring容器中获取bean
可能不太清楚。您可以在ConfigurationClassEnhancer.java BeanMethodInterceptor
ConfigurationClassPostProcessor.java enhanceConfigurationClasses
这是SpringAOP(动态对象和cglib)的一个限制
如果您将Spring配置为使用AspectJ来处理事务,那么您的代码将正常工作
最简单也是最好的选择是重构代码。例如,一个处理用户的类和一个处理每个用户的类。然后,SpringAOP的默认事务处理将起作用
此外,@Transactional应该在@Repository上,而不是在@Repository上
事务属于服务层。它了解工作单元和用例。如果将多个DAO注入到需要在单个事务中协同工作的服务中,那么这是正确的答案
因此,您需要重新考虑您的事务方法,以便您的方法可以在一个流中重用,该流包括其他几个可滚动的DAO操作
它是否使用不同类型的动态代理
几乎完全正确
让我们找出@Configuration
类和AOP代理之间的区别,回答以下问题:
为什么自调用的@Transactional
方法没有事务语义,即使Spring能够拦截自调用的方法
@Configuration
与AOP有何关联
为什么自调用的@Transactional
方法没有事务语义?
简短回答:
这就是AOP的制作方法
长答案:
声明性事务管理依赖于(对于上的大多数Spring应用程序)
Spring框架的声明性事务管理是通过Spring面向方面编程(AOP)实现的
它是基于代理的()
SpringAOP是基于代理的
从同一段落SimplePojo.java
:
公共类SimplePojo实现Pojo{
公共图书馆{
//下一个方法调用是对“this”引用的直接调用
这个.bar();
}
公共空白栏(){
//一些逻辑。。。
}
}
和一个代理它的片段:
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();
}
}
这里要理解的关键是main
类的main(..)
方法中的客户机代码引用了代理
这意味着对该对象引用的方法调用是对代理的调用
因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)
但是,一旦调用最终到达目标对象(本例中的SimplePojo
,引用),它可能对自身进行的任何方法调用,例如this.bar()
或this.foo()
,都将针对this
引用而不是代理进行调用
这具有重要意义。这意味着自调用不会产生与方法inv关联的通知
public interface Interceptor {
Object invoke(InterceptingFoo interceptingFoo);
}
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