Proxy
场景引入
追女生:当你因为在你的女神面前过于害羞,这时候你不敢交流,那么该怎么办呢?于是你委托隔壁班的校草男神来帮你天天给你的女神送早餐,送小零食。
而这就是一种代理模式,校草男神代理你将你的礼物送给女神,而女神不知道是你送的
基本概念介绍
代理模式是一种结构型设计模式,能够为目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,代理对象则起到中介作用,连接客户端和目标对象,并在将请求交给对象前后做一些处理
举例:像大家常说的“挂梯子”就是由一个在外面的服务器通过伪装协议将服务器在外面浏览的内容传回到终端上,这也是一种代理
静态代理
介绍
代理对象拥有对目标对象的引用,客户端通过代理对象间接的访问目标对象。这样做能够解决的问题就是防止直接访问目标对象给系统带来的不必要的复杂性
静态代理简单实现
拿引入的例子举例,男神代替你送给女神礼物就是一种代理
男神(代理对象)代替我(真实对象)去给女神送东西(间接访问的操作)
uml类图
实例
- 创建抽象的对象接口:声明你需要男神帮你做的事情
public interface subject
{
void giveBreakfast();
void giveCake();
}
- 创建真实对象类,即自己
public class realSubject implements subject{
private String girlName;
public realSubject(String girlName) {
this.girlName = girlName;
}
@Override
public void giveBreakfast() {
System.out.println("送给 " + girlName + " 早餐");
}
@Override
public void giveCake() {
System.out.println("送给 " + girlName + " 蛋糕");
}
}
- 创建代理对象类(男神):代理对象通过目标对象的引用从而实现具体操作
public class proxy implements subject{
private realSubject realsubject;
public proxy(realSubject realsubject) {
this.realsubject = realsubject;
}
@Override
public void giveBreakfast() {
realsubject.giveBreakfast();
}
@Override
public void giveCake() {
realsubject.giveCake();
}
}
- 实现客户端的调用:最终男神帮你送了东西
public class client {
public static void main(String[] args) {
subject proxy_=new proxy(new realSubject("女神"));
proxy_.giveBreakfast();
proxy_.giveCake();
//你通过男神代理送给女神东西成功了
}
}
- 最终结果输出
送给 女神 早餐
送给 女神 蛋糕
优点
- 协调了调用者和被调用者,降低系统耦合
- 代理对象作为中介,起到了保护目标对象的作用
缺点
-
在系统中增加了代理对象,导致客户端的请求处理速度变慢
-
有些代理的实现很复杂,增加了系统实现的复杂度
-
只能代理单一的目标对象,要服务多种类型的目标对象(目标对象较多的情况下)就需要为每种目标对象都实现一个静态代理对象----->引入动态代理
应用场景
- 虚拟代理:用一个较为小的对象代理一个开销大的对象从而减小系统的开销(举例:代理可以将自己伪装成数据库对象,可在客户端或者实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作)
- 防火墙代理:控制对目标对象的访问,给不同的用户以不同的访问权限
- 远程代理:为一个对象在不同的地址空间提供局部的代表(隐藏真实对象的地址空间),远程机器可以做到真实对象做不到的东西(举例:像之前提到的挂梯子)
动态代理
场景引入
通过之前要男神帮你代理送东西,你发现效果非常好,最近碰见女神你就感觉她在对你笑一样。于是你要求男神代理加大力度,同时你要求男神增加了大量的代理需求,像是要求男神代理你跟她打球等等,并且这时你要求对代理的方法做增强处理,即男神每次帮你代理之前都要给你发消息通知你,这时候就发现,像之前的方法,每增加方法或者是修改需求时都需要跟男神声明,这就很累很繁琐。那么有没有一种方法能够做统一处理,就跟男神声明一次,那么男神代理我的每个方法都能够作出增强处理呢?这就需要用到我们的动态代理了
介绍
动态代理指的是代理类在程序运行时,通过反射机制动态生成的代理类,相比较于静态代理在运行前预先编译好的代理类,动态代理在运行的时候才能够确定代理类
相比较于静态代理,动态代理的优势就是可以很方便的对代理类的函数进行统一化的处理,并且不需要修改每个代理类中的方法
动态代理简单实现
uml类图
实例
像引入的例子一样,我们要求男神的每个方法都做增强处理
- 首先引入拦截处理器类,这个类对代理的对象内的方法做出统一的处理,该类实现了InvocationHandler接口,该类维护了一个目标对象。InvocationHandler拥有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。在invoke方法中除了执行目标对象的方法之外,还可以加入自己的增强处理
public class proxy implements InvocationHandler {
private subject subject_;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
proxy:代表动态代理对象(即哪个动态代理对象调用了method)
method:代表正在执行的方法
args:表明调用目标方法时传入的实参
*/
System.out.println("本人收到通知");
//反射调用
Object result = method.invoke(subject_, args);
return result;
}
//得到动态代理类实例
public Object getInstance(subject subject_)
{
this.subject_=subject_;
return Proxy.newProxyInstance(subject_.getClass().getClassLoader(),subject_.getClass().getInterfaces(),this);
}
}
- 创建目标对象的抽象接口
public interface subject
{
void giveBreakfast();
void giveCake();
}
- 创建实现接口的目标类
public class realSubject implements subject {
private String girlName;
public realSubject(String girlName) {
this.girlName = girlName;
}
public void notify_()
{
System.out.println("已收到通知");
}
@Override
public void giveBreakfast() {
System.out.println("送给 " + girlName + " 早餐");
}
@Override
public void giveCake() {
System.out.println("送给 " + girlName + " 蛋糕");
}
}
- 在客户端中动态创建代理类,调用目标对象的方法
public class client {
public static void main(String[] args) {
subject readsubject=new realSubject("女神");
proxy proxy = new proxy();
subject proxyInstance = (subject)proxy.getInstance(readsubject);
proxyInstance.giveBreakfast();
proxyInstance.giveCake();
}
}
- 最终结果输出
动态代理的简单总结
从这个简单的实现中我们可以看出,动态代理的最大优势就是可以对代理类的函数做出统一化的处理而不需要修改每个代理类中的方法。这是因为所有代理类的方法都是在处理器类中的invoke方法执行的,在invoke方法中做统一处理,就可以对所有的被代理方法进行相同的操作了
动态代理的分类
jdk动态代理
上述的动态代理的简单实现就是运用了jdk动态代理,一种基于jdk下的反射包下的一种原生动态代理方式,通过reflect包下的Proxy类和InvocaionHandler接口可以生产jdk动态代理类和对象
动态代理类和实例是怎样生成的
我们发现,动态代理类的生成是通过Proxy类下的new instance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数说明:
- loader:指定产生代理对象的类加载器,需要将其指定为和目标对象为同一个类加载器
- interfaces:指目标对象的实现接口
- InvocationHandler:指定处理器类对象,即说明代理对象在调用方法时会关联到哪个InvocationHandler处理类对象
总结
- 通过为Proxy类指定类加载器对象和一组接口,从而可以创建动态代理类的字节码,再根据字节码创建动态代理类
- 通过反射机制获取动态代理类的构造函数(参数类型等于调用处理器的接口类型)
- 通过动态代理类的构造函数创建 代理类实例 (传入调用处理器对象)
如何调用动态代理对象方法,从而调用目标对象方法
总结
- 动态代理类实现了和目标类一样的接口,并实现了需要目标类对象调用的方法
- 这种调用的实现逻辑:调用父类Proxy中的h.invoke (h指的是处理器类对象的参数)
- 通过反射机制,实现对目标类对象的调用
总结
jdk为我们生成了一个叫做**$Proxy0**的代理类,这个类文件是放在内存中的,在创建动态代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例
我们可以把InvocationHandler看作为一个中介类,中介类持有一个被代理的对象也就是目标对象,在invoke方法中调用了目标对象的相应方法。通过聚合的方式持有目标对象的引用,把外部对invoke的调用最终转化为对目标对象方法的调用
动态代理类调用自己方法时,通过自身持有的处理器类对象来调用其invoke方法,从而达到代理执行目标对象的方法,也就是说动态代理通过处理器类实现了具体的代理功能
CGlib字节码动态代理
和jdk动态代理不同,cglib代理依赖于cglib类库,其通过asm字节码框架实现,具体就是产生目标类的子类并重写其中的方法
JDK和CGlib动态代理的对比与总结
- jdk动态代理只能对实现了接口的类产生代理而不能针对普通的类,并且采用的是反射技术实现创建动态代理类,生成类的过程比较搞笑
- cglib动态代理是针对类进行代理,主要就是对指定的类(不是final修饰的)产生一个子类,覆盖其中的方法(重写),使用asm字节码框架实现,相关执行的过程比较的高效
- jdk代理基于本身的jdk自带的反射包实现
- cglib需要依赖于cglib的类库
优点
- 更强的灵活性
- 一个动态代理类可解决创建多个代理类
- 动态创建代理类不需要预先编译,只有需要用到时即调用目标对象方法时才创建,将编译推迟到程序运行时由JVM来实现
缺点
- 采用反射技术实现,产生代理类的字节码文件,效率比较低
应用场景
最常见的就属于是spring中的aop
spring aop可在多种应用场景中使用:日志记录,性能统计,安全控制等
参考
https://www.jianshu.com/p/5dc416ea58a2
https://blog.csdn.net/xiaofeng10330111/article/details/105633821