前言:这是一篇有关代理的系列文章,记录了我对代理由浅入深的认识和理解。代理是一种编程思想,它试图做到如何在不修改或尽量少修改现有代码的情况下,改变对象的行为。而且代理绝非Java专属,它是一种使用非常广泛的编程思想,你会在很多语言和框架上看到代理的影子。既然代理无处不在,那么掌握它是非常有必要的。
初识代理
我对代理的认识是从AOP(Aspect Oriented Programming,面向切面编程)开始的。写一个切面,加一个注解,就能神不知鬼不觉在方法调用的前后增加很多行为。当时对这种技术的底层实现非常着迷,就迫不及待找了很多博客,做了很多试验,看了很多源码,终于对代理有了更深入的理解,想写一个系列文章来介绍代理。中间由于换工作等诸多原因,断掉了,现在打算重新把这个系列写完,让这个世界留下一抹我思考过的痕迹。
问题驱动
先看一段简单的代码
public class UserDao {
public boolean login(String account, String password){
return "wonking".equals(account) && "root".equals(password);
}
}
假如现在不允许修改UserDao的源码,让你在userDao.login()调用前打印一下参数,在调用后打印结果。你会怎么做?
我们很自然的会想到,新增一个类,继承UserDao,重写login方法,就像这样
public class UserDaoProxy extends UserDao {
private UserDao target;
public UserDaoProxy(){
target=new UserDao();
}
@Override
public boolean login(String account, String password) {
System.out.println(String.format("account->%s, password->%s", account, password));
boolean result=target.login(account, password);
System.out.println(String.format("result->%s", result));
return result;
}
}
然后在实例化UserDao的地方,用UserDao userDao=new UserDaoProxy()代替,这样就神不知,鬼不觉的完成了日志的打印。这就是设计模式-代理模式的手法,UserDao,我们称为目标类(官方叫委托类),UserDaoProxy,我们称为代理类。
问题又来了,现在有成百上千个类的方法,我想给他们全部都加上打印日志的功能。如果你不嫌麻烦,可以按照同样的模式复制粘贴上千个代理类。聪明人(或者说喜欢偷懒的人)一定会试图寻找一种自动化生成代理类的解决方案。所以任何问题,我们把它放大来看,它就变成了另外一个问题。
代理的特征
自动化生成代理类的方法,将在下篇介绍。正因为书写代理类是有一个固定的模式的,所以将书写代理类的工作自动化是存在解决方案的。为了了解这个模式,我们先来总结一下,动态代理类有什么特征
1.透明性
代理类与目标类,必须有一个共同的父类型,这样才能使代理类对客户端透明。
---对应到实现方案中来,要么双方实现共同的接口,要么代理类继承目标类,前者是JDK代理框架采用的方案,后者是cglib框架采用的方案。
2.动态性
动态性有两层含义。一,代理类生成的动态性,代理类是在运行期间动态生成字节码;二,代理逻辑的动态性,代理逻辑是抽象的,不确定的逻辑。
---对应到实现方案中来,首先,代理逻辑不确定,很容易想到应该抽象一个接口,表示对代理逻辑的封装,作为参数动态传递给代理类。
代理分类
1.静态代理
静态代理就是在编译前,代理类就存在了。这种方法很不灵活,实际编程中很少会用到,不多展开。
2.动态代理
动态代理实现方式非常多,下表大概是最全的一个动态代理技术方案总结了
类别 | 机制 | 原理 | 优点 | 缺点 | 技术 |
静态AOP | 静态织入 | 在编译期,切面直接以字节码的形式编译到目标字节码文件中 | 对系统无性能影响 | 灵活性不够 | AspectJ |
动态AOP | 动态代理 | 在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中 | 相对于静态AOP更加灵活 | 切入的关注点需要实现接口。对系统有一点性能影响 | JDK dynamic proxy |
动态字节码生成 | 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中 | 没有接口也可以织入 | 扩展类的实例方法为final时,则无法进行织入 | cglib | |
自定义类加载器 | 在运行期,目标加载前,将切面逻辑加到目标字节码里 | 可以对绝大部分类进行织入 | 代码中如果使用了其他类加载器,则这些类将不会被织入 | ||
字节码转换 | 在运行期,所有类加载器加载字节码前,前进行拦截 | 可以对所有类进行织入 |
后面几个严格来说已经超出了代理的范畴了,因为他们根本就没有生成代理类,而是直接修改目标类的字节码,改变其行为,目标类代理类合二为一,可以说是仙法了~~
代理应用
---添加日志切面
---添加事务特性
---加入缓存代理
---系统性能监控
---方法拦截
---权限控制
---流量控制
---负载均衡
形式千变万化,内涵只有一个。掌握真理所在,万变不离其宗^_^