![](/img/trans.png)
[英]Why do I not need @Autowired on @Bean methods in a Spring configuration class?
[英]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类中的类内函数调用,但没有在常规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(),而不是解包类的调用,如在调试器中查看“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类的增强类,并在调用用户/超级函数之前用一些额外的工作覆盖用户的@Bean函数,例如检查是否这样是否是第一次调用函数。 这个班级是代理吗? 这取决于定义。 您可以说它是一个代理,它使用来自真实对象的继承而不是使用组合包装它。
总而言之,从这里给出的答案我理解这些是两种完全不同的机制。 为什么做出这些设计选择是另一个开放的问题。
它是否使用不同类型的动态代理?
几乎完全一样
让我们弄清楚@Configuration
类和AOP代理之间的区别是回答以下问题:
@Transactional
方法没有事务语义,即使Spring能够拦截自调用方法? @Configuration
和AOP如何相关? @Transactional
方法没有事务语义? 简短回答:
这就是AOP的制作方式。
答案很长:
使用Spring面向方面编程(AOP)实现Spring Framework的声明式事务管理
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...
}
}
并代表它的代码片段:
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
参考,而不是代理 。这具有重要意义。 这意味着自我调用不会导致与方法调用相关的建议有机会执行。
( 强调关键部分。 )
您可能认为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:
在相同类型的不同对象上调用原始方法。
正如我之前提到的文档:
一旦调用最终到达
target
对象,任何方法调用它可能会this
引用进行调用,而不是代理调用
我希望现在,或多或少,很明显为什么。
@Configuration
和AOP如何相关? 答案是他们没有关系 。
所以Spring在这里可以自由地做任何想做的事。 这里它与代理AOP语义无关。
它使用ConfigurationClassEnhancer
增强了这些类。
看一眼:
如果Spring能够在@Configuration类中成功拦截内部类函数调用,为什么它不能在常规bean中支持它?
我希望从技术角度来看很清楚为什么。
现在我的想法来自非技术方面:
我认为它没有完成,因为Spring AOP已经足够长了 ......
自Spring Framework 5以来,引入了Spring WebFlux框架。
目前, Spring Team正在努力提升反应式编程模型
查看一些值得注意的近期 博文 :
介绍了构建Spring应用程序的代理方法越来越少的特性。 (例如,参见此提交 )
所以我认为尽管可能会做你所描述的,但它远远不是Spring Team现在的首要任务
因为AOP代理和@Configuration
类服务于不同的目的,并且以显着不同的方式实现(即使两者都涉及使用代理)。 基本上,AOP使用组合,而@Configuration使用继承 。
这些工作的方式基本上是他们创建代理,在委托调用原始(代理)对象之前/之后执行相关的建议逻辑。 容器注册此代理而不是代理对象本身,因此所有依赖项都设置为此代理,并且从一个bean到另一个bean的所有调用都通过此代理。 但是,代理对象本身没有指向代理的指针(它不知道代理,只有代理有指向目标对象的指针)。 因此,该对象中对其他方法的任何调用都不会通过代理。
(我只是在这里添加它与@Configuration形成对比,因为你似乎对这部分有了正确的理解。)
现在虽然您通常应用AOP代理的对象是应用程序的标准部分,但@Configuration
类是不同的 - 对于一个,您可能永远不打算直接自己创建该类的任何实例。 这个类真的只是编写bean容器配置的一种方式,在Spring之外没有任何意义,你知道它将以一种特殊的方式被Spring使用,并且它只有普通的Java代码之外还有一些特殊的语义 - 例如@Bean
-annotated方法实际上定义了Spring bean。
因此,Spring可以为这个类做更激进的事情,而不用担心它会破坏你的代码中的某些东西(记住,你知道你只为Spring提供这个类,你不会创建或使用它实例直接)。
它实际上做的是创建一个代理,它是@Configuration
类的子类 。 通过这种方式,它可以拦截@Configuration
类的每个(非final
非private
)方法的调用,即使在同一个对象中也是如此(因为这些方法实际上都被代理覆盖,并且Java将所有方法都设置为虚拟)。 代理正是这样做,将它识别为(语义上)对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。
读一点弹簧源代码。 我试着回答它。
关键是spring如何处理@Configuration
和@bean
。 在作为BeanFactoryPostProcessor的ConfigurationClassPostProcessor中,它将增强所有ConfigurationClasses并创建一个Enhancer
作为Enhancer
。 这个Enhancer
注册了两个CALLBACKS(BeanMethodInterceptor,BeanFactoryAwareMethodInterceptor)。 你调用PersistenceJPAConfig
方法将通过CALLBACKS。 在BeanMethodInterceptor中,它将从spring容器中获取bean。
可能不清楚。 您可以在ConfigurationClassEnhancer.java BeanMethodInterceptor
看到源代码。 ConfigurationClassPostProcessor.java enhanceConfigurationClasses
这是Spring AOP(动态对象和cglib)的限制。
如果您将Spring配置为使用AspectJ来处理事务,那么您的代码将起作用。
简单且可能最好的替代方法是重构代码。 例如,一个处理用户的类和一个处理每个用户的类。 然后使用Spring AOP进行默认事务处理将起作用。
@Transactional也应位于服务层而不是@Repository
事务属于服务层。 这是了解工作单位和用例的人。 如果您将几个DAO注入到需要在单个事务中协同工作的服务中,那么这是正确的答案。
因此,您需要重新考虑您的事务方法,因此您的方法可以在流中重用,包括其他几个可滚动的 DAO操作
Spring使用代理进行方法调用,当你使用它时...它会绕过该代理。 对于@Bean注释,Spring使用反射来查找它们。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.