十、动态代理的本质与边界(this 失效?)
动态代理是静态代理的自动化演进。一般,动态代理类都是自动并动态创建的(所以叫动态),可以省去静态代理类的手动构建的过程。Solon 官网的有些地方叫“代理”,也有些地方叫“动态代理”。都是一个意思。
动态代理,还会把代理行为抽象成通用的 InvocationHandler,进而行成扩展体系。Java 的动态代理方案有:
方案 | 实现方式 | 备注 |
---|---|---|
接口动态代理 | 动态构建类,并实现接口。然后转发给 InvocationHandler | jdk 直接支持 |
类动态代理 | 动态构建扩展类,并重写方法。然后转发给 InvocationHandler | 需要借助外力,比如 asm |
1、接口动态代理
这是 jdk 直接支持的能力。内在的原理是:框架会动态生成目标接口的一个代理类(即接口的实现类)并返回,使用者在调用接口的函数时,实际上调用的是这个代理类的函数,而代理类又把数据转给了调用处理器接口。
而整个过程的感受是调用目标接口,最终到了 InvocationHandler 的实现类上:
//1. 定义目标接口
public interface UserService{
void addUser(int userId, String userName);
}
//=>
//2. 通过JDK接口,获得一个代理实例
UserService userService = Proxy.getProxy(UserService.class, new InvocationHandlerImpl());
//生成的 UserService 代理类,差不多是这个样子:
public class UserService$Proxy implements UserService{
final InvocationHandler handler;
final Method addUser2; //示意一下,别太计较它哪来的
public UserService$Proxy(InvocationHandler handler){
this.handler = handler;
}
@Override
public void void addUser(int userId, String userName){
handler.invoke(this, addUser2, new Object[](userId, userName));
}
}
//在调用 userService 时,本质是调用 UserService$Proxy 的函数,最终又是转发到 InvocationHandler 的实现类上。
//=>
//3. 实现调用处理器接口
public class InvocationHandlerImpl implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//...
}
}
一般,接口动态代理是为了:转发处理。
2、类动态代理
类的动态代理,略麻烦些,一般是要借助字符码工具框架(Solon 用的是 ASM)。内在的原理倒是相差不大:框架会动态生成目标类的一个代理类(一个重写了所有函数的子类)并返回,使用者在调用目标类的函数时,实际上调用的是这个代理类的函数,而代理类又把数据转给了调用处理器接口。调用处理器在处理时,会附加上别的处理。
而整个过程的感受是调用目标类,可以附加上很多拦截处理:
//1. 定义目标类
public class UserService{
public void addUser(int userId, String userName){
//..
}
}
//=>
//2. 通过框架接口,获得一个代理实例(::注意这里的区别!)
UserService userService = new UserService();
userService = AsmProxy.getProxy(UserService.class, new AsmInvocationHandlerImpl(userService));
//生成的 UserService 代理类,差不多是这个样子:
public class UserService$AsmProxy extends UserService{
final AsmInvocationHandler handler;
final Method addUser2; //示意一下,别太计较它哪来的
public UserService$Proxy(AsmInvocationHandler handler){
this.handler = handler;
}
@Override
public void void addUser(int userId, String userName){
handler.invoke(this, addUser2, new Object[](userId, userName));
}
}
//本质还是调用 UserService$AsmProxy 的函数,最终也是转发到 InvocationHandler 的实现类上。
//=>
//3. 实现调用处理器接口
public class AsmInvocationHandlerImpl implements InvocationHandler{
//::注意这里的区别
final Object target;
public AsmInvocationHandlerImpl(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//::注意这里的区别
MethodWrap methodWrap = MethodWrap.get(method);
//MethodWrap 内部对各种拦截器做了封装处理
methodWrap.invoke(target, args);
}
}
一般,类动态代理是为了:拦截并附加处理。
3、关于 Solon 的类代理情况与“函数环绕拦截”
对 Solon 来讲,只要一个函数反射后再经 MethodWrap 包装后执行的,就是被代理了。所有的“函数环绕拦截”处理就封装在 MethodWrap 里面。
- @Controller、@Remoting 注解的类
这两个注解类,没有 ASM 的类代码,但是它们的 Method 会转为 MethodWrap ,并包装成 Action 注册到路由器。即它们是经 MethodWrap 再调用的。所以它们有代理能力,支持“函数环绕拦截”。
- @Component 注解的类,启动代理时(自动的)
它注解的类,都会被自动动态代理(只对 public 函数做了代理)。当启用代理时,跟上面原理分析的一样,也支持“函数环绕拦截”。
- 有克制的拦截
Solon 不支持表达式的随意拦截,必须以注解为“切点”进行显示拦截。
4、Solon 类代理的边界(this 失效!)
- 不能用 this 调用另一个代理函数(以及,对应办法)
//反例:
@Component
public class DemoCom1{
@Tran
public void test1(){
this.test2(); //test2 的 @Cache 会失效
}
@Cache
public String test2(){ }
}
//正例:
@Component
public class DemoCom1{
@Inject
DemoCom1 self; //注解的是代理类
@Tran
public void test1(){
self.test2(); //代理类的 test2 的 @Cache 有效。
}
@Cache
public String test2(){ }
}
- 代理类的非公有函数,无法使用注入字段
@Component
public class DemoCom1{
@Inject
HelloService helloService;
@Inject
DemoCom1 self; //注解的是代理类
@Tran
public void test1(){
this.test2(); //正常
this.test3(); //正常
self.test2(); //会抛 null 异常
self.test3(); //会抛 null 异常
}
private void test2(){ helloService.hello(); }
protected void test3(){ helloService.hello(); }
}