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函数,例如检查是否是否是该函数的首次调用。该课程是代理吗?这取决于定义。您可能会说这是一个使用真实对象继承的代理,而不是使用组合包装。
总而言之,从这里给出的答案中,我了解到这是两种完全不同的机制。为什么做出这些设计选择是另一个悬而未决的问题。
是否使用不同类型的动态代理?
几乎完全一样
让我们找出@Configuration回答以下问题的类和AOP代理之间的区别是什么:
@Transactional即使Spring能够拦截自调用方法,自调用方法也没有事务语义?@Configuration和AOP有关系吗?@Transactional方法没有事务语义?简短答案:
这就是AOP的制作方式。
长答案:
Spring面向方面的编程(AOP)使Spring框架的声明式事务管理成为可能
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()orthis.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团队目前的第一要务还相去甚远
因为AOP代理和@Configuration类服务于不同的目的,并且以明显不同的方式实现(即使两者都涉及使用代理)。
基本上,AOP使用组合,而@Configuration使用继承。
这些工作的方式基本上是,在将调用委派给原始(代理)对象之前/之后,它们创建执行相关建议逻辑的代理。容器注册该代理而不是代理对象本身,因此所有依赖项都设置为此代理,并且从一个bean到另一个bean的所有调用都通过此代理。但是,代理对象本身没有指向代理的指针(它不知道它是代理的,只有代理具有指向目标对象的指针)。因此,该对象内对其他方法的任何调用都不会通过代理。
(我只是在这里添加它以与@Configuration形成对比,因为您似乎对这部分有正确的理解。)
现在,虽然通常将AOP代理应用于的对象是应用程序的标准部分,但@Configuration类却有所不同-对于一个类,您可能永远不想自己直接创建该类的任何实例。此类实际上只是一种编写bean容器配置的方式,在Spring之外没有任何意义,并且您知道 Spring将以一种特殊的方式使用它,并且它在纯Java代码之外具有某些特殊的语义-例如,@Bean注解的方法实际上定义了Spring bean。
因此,Spring可以对该类做更多根本性的事情,而不必担心它将破坏代码中的某些内容(请记住,您知道您只为Spring提供此类,并且您将永远不会创建或使用其类实例)。
实际上,它创建的是@Configuration该类的子类的代理。通过这种方式,它可以拦截所有的(非的调用final非private的)的方法@Configuration类,即使在同一个对象(因为这些方法都是有效的全部由代理重写,和Java有所有方法虚拟)。代理正是这样做,以将其(实际上)是对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。