Rus*_*nko 6 java proxy spring spring-aop
众所周知,bean的方法的自调用在没有AspectJ的Spring中是行不通的。
看到这个问题例如,
我认为这是因为 Spring 创建的代理使用延迟模式调用目标对象的方法。像这样:
class MyClass {
@Autowired
private MyClass self; // actually a MyProxy instance
@Transactional // or any other proxy magic
public void myMethod() {}
public void myOtherMethod() {
this.myMethod(); // or self.myMethod() to avoid self-invokation problem
}
}
class MyProxy extends MyClass { // or implements MyInterface if proxyMode is not TARGET_CLASS and MyClass also implements MyInterface
private final MyClass delegate;
@Override
public void myMethod() {
// some proxy magic: caching, transaction management etc
delegate.myMethod();
// some proxy magic: caching, transaction management etc
}
@Override
public void myOtherMethod() {
delegate.myOtherMethod();
}
}
Run Code Online (Sandbox Code Playgroud)
我对吗?
有了这个代码:
public void myOtherMethod() {
this.myMethod();
}
Run Code Online (Sandbox Code Playgroud)
this.myMethod()将绕过代理(所以全部@Transactional或@Cacheable魔法),因为它只是内部委托的调用...所以我们应该在内部注入一个MyClassbean(实际上是MyProxy实例)MyClass并调用self.myMethod()。这是可以理解的。
但为什么要这样实现代理呢?为什么它不只是扩展目标类,重写所有公共方法并调用super而不是delegate?像这样:
class MyProxy extends MyClass {
// private final MyClass delegate; // no delegate
@Override
public void myMethod() {
// some proxy magic: caching, transaction management etc
super.myMethod();
// some proxy magic: caching, transaction management etc
}
@Override
public void myOtherMethod() {
super.myOtherMethod();
}
}
Run Code Online (Sandbox Code Playgroud)
this.myMethod()它应该解决绕过代理的自调用问题,因为在这种情况下this.myMethod(),从(我们记得MyClass bean实际上是MyProxy实例)调用MyClass.myOtherMethod(),将调用覆盖子级的方法(MyProxy.myMethod())。
所以,我的主要问题是为什么不以这种方式实现?
您关于 Spring AOP 使用委托作为其代理的假设是正确的。这也有记录。
使用CGLIB,理论上你可以使用proxy.invokeSuper()来达到你想要的效果,即通过代理的方法拦截器实现的切面来注册自调用(我这里使用的是Spring的嵌入式版本的CGLIB,因此是包名称):
package spring.aop;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class SampleClass {
public void x() {
System.out.println("x");
y();
}
public void y() {
System.out.println("y");
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
if(method.getDeclaringClass() == Object.class)
return proxy.invokeSuper(obj, args);
System.out.println("Before proxy.invokeSuper " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After proxy.invokeSuper " + method.getName());
return result;
}
});
SampleClass proxy = (SampleClass) enhancer.create();
proxy.x();
}
}
Run Code Online (Sandbox Code Playgroud)
控制台日志:
Before proxy.invokeSuper x
x
Before proxy.invokeSuper y
y
After proxy.invokeSuper y
After proxy.invokeSuper x
Run Code Online (Sandbox Code Playgroud)
这正是您想要的。然而,当你有几个方面时,问题就开始了:事务、日志记录,等等。您如何确保它们都能协同工作?
选项 1:每个方面都有自己的代理。除非您根据方面优先级将代理相互嵌套,否则这显然是行不通的。但将它们嵌套在一起就意味着继承,即一个代理必须从外向内继承另一个代理。尝试代理 CGLIB 代理,它不起作用,您会遇到异常。此外,CGLIB 代理非常昂贵并且使用永久内存,请参阅此 CGLIB 入门中的描述。
选项 2:使用组合而不是继承。构图更加灵活。拥有一个可以根据需要向其注册方面的代理解决了继承问题,但也意味着委托:代理在运行时注册方面并在执行实际对象的代码之前/之后以正确的顺序调用其方法(或者不执行,如果@Around从来没有接到建议proceed())。请参阅 Spring 手册中有关手动将方面注册到代理的示例:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Run Code Online (Sandbox Code Playgroud)
至于为什么Spring 开发人员选择这种方法,以及是否可以使用单代理方法,但仍然确保自调用像上面我的小 CGLIB 示例“日志记录方面”中那样工作,我只能推测。您可以在开发人员邮件列表上询问他们或查看源代码。也许原因是 CGLIB 代理的行为应该与默认的 Java 动态代理类似,以便在两者之间无缝切换接口类型。或许还有另外一个原因。
我的评论并不是要粗鲁,只是简单明了,因为你的问题确实不适合 StackOverflow,因为它不是一个有人可以找到解决方案的技术问题。这是一个历史设计问题,本质上相当哲学,因为使用 AspectJ,实际问题下的技术问题(自调用)的解决方案已经存在。但也许您仍然想深入研究 Spring 源代码,将 Spring AOP 实现从委托更改为proxy.invokeSuper()并提交拉取请求。不过,我不确定这样的重大改变是否会被接受。
| 归档时间: |
|
| 查看次数: |
2666 次 |
| 最近记录: |