代理模式(Proxy Pattern):控制客户端对对象的访问,访问新的代理对象
代理
一、静态代理是个啥?
它为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口。在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后打印日志。
也就是说,代理对象 = 增强代码 目标对象(原对象)。有了代理对象后,就不用原对象了。
二、动态代理是个啥?
借用网络大神的骚气讲解。
接口Class对象是大内太监,里面的方法和字段比做他的一身武艺,但是他没有小DD(构造器),所以不能new实例。一身武艺后继无人。那怎么办呢?非正常途径(动态代理):通过妙手圣医Proxy的克隆大法(Proxy.getProxyClass()),克隆一个Class,但是有小DD。所以这个克隆人Class可以创建实例,也就是代理对象。代理Class其实就是附有构造器的接口Class,一样的类结构信息,却能创建实例。
三、静态原理和动态原理的区别?
静态代理需要自己写代理类,代理类需要实现与目标对象相同的接口。
动态代理不需要自己编写代理类,代理类是动态生成的。
四、动态代理作用
1、动态代理主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法)。因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。
2、还有一个有趣的作用是可以用作远程调用。比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过Proxy.newProxyInstance代理一个该接口对应的InvocationHandler对象,然后在InvocationHandler的invoke方法内封装通讯细节就可以了。具体的应用,最经典的当然是Java标准库的RMI,其它比如hessian,各种webservice框架中的远程调用,大致都是这么实现的。
3、Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大。
4、可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用。
5、解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。
五、深聊代理
代理类的生成时期
静态代理:代理类在编译期就生成了
动态代理:代理类在Java运行时动态生成的,动态代理有JDK代理和CGLib代理两种
静态代理
将真实类作为代理类的一个属性,将对象传入处理,直接访问代理类
由聚合代替继承,而不是创建无数的子类
类图
动态类将实现类传入,作为属性值
动态代理
JDK动态代理
Java提供了动态代理类Proxy,通过创建代理对象的静态方法 newProxyInstance()方法获取
newProxyInstance()需要三个参数
ClassLoader 类加载器,用于加载代理类。可以通过目标对象获取类加载器 Class<?> [] interface 代理类实现的接口的字节码对象 InvocationHandler 代理对象的调用处理程序 重写invoke方法
代理最终会调用 InvocationHandler 类 的 invoke() 方法
invoke()有三个入参,方便做动态增强使用
Object proxy 代理对象。和代理对象是同一个对象,基本不用 Method method 对接口中的方法进行封装的method对象 Object[] args 调用方法的实际参数
执行目标对象的方法
Object object = method.invoke(目标对象,arge); 利用反射
总结:根据实现的接口,利用反射创建接口中方法,组成代理对象,根据调用实现类对象再利用反射执行方法
执行流程
- 在测试类中通过代理类调用sell()方法
- 根据多态特性,执行的是代理类($Proxy0)的sell()方法
- 代理对象($Proxy0)中sell()调用了InvacationHandler接口子实现类对象的invoke()方法
- invoke() 通过反射执行真实对象(TrainStation)中的sell方法
反射,这里先说个大概----------------------------------------------------------------
获取Method方法
代码语言:javascript复制反射原理:.class 文件经过类加载器 会产生 .class类的实例,每个类的方法属性等都包含在实例上,所有加载完后会有一个class对象集合,找到匹配的class,反射动态生成字节码,加载到JVM运行
// 1,获取Person.class 字节码文件对象
Class c = Class.forName(className);
// 2,获取构造方法
// public Person(String name, int age, String address)
Constructor con = c.getConstructor(String.class, int.class, String.class);
// 3,创建对象,运行构造方法
Object obj = con.newInstance("小明", 20, "中国");
System.out.println(obj);
// 4,获取指定的方法
// private void method5(){}
String methodName = prop.getProperty("methodName");
Method m5 = c.getDeclaredMethod(methodName, null);
// 5,开启暴力访问
m5.setAccessible(true);
// 6,执行找到的方法
m5.invoke(obj, null);
getDeclaredMethod() 方法可以获取 指定class并入参一致的方法,封装成Method对象 getDeclaredMethod() 核心方法有三个 1. privateGetDeclaredMethods() 返回class声明的方法列表 2.searchMethods() 找到匹配名称的方法对象 3.copy()方法 返回一个新对象 ----privateGetDeclaredMethods() 核心方法 -------reflectionData() 查询是否有缓存 -------ReflectionData 数据结构缓存JVM属性数据 是SoftReference类型(弱应用)GC高时会被回收 -------newReflectionData 先cas,通过unsafe.compareAndSwapObject方法重新设置reflectionData字段 --------------unsafe.compareAndSwapObject 比较的就是两个 Java Object 的地址,如果相等则将新的地址(Java Object)赋给该字段 --------------最终采用ASM 动态修改二进制,从JVM获取并创建ReflectionData对象
invoke()方法实现原理
MethodAccessor 对象是实现的关键,MethodAccessor本身是个接口 通过acquireMethodAccessor() 方法中,ReflectionFactory类的newMethodAccessor创建了实现MethodAccessor的对象 ReflectionFactory有 2 个重要的字段:
noInflation
(默认false
)和inflationThreshold
(默认15
) 当noInflation为false ,用DelegatingMethodAccessorImpl创建代理对象,将当前对象传入并invoke调用的NativeMethodAccessorImpl对象的invoke方法 NativeMethodAccessorImpl 对象中用到了inflationThreshold字段,如果大于15创建一个新的调用对象,创建MethodAccessorImpl会在ClassDefiner.defineClass
方法实现中,每被调用一次都会生成一个DelegatingClassLoader
类加载器对象 如果一直沿用一个类加载器,类加载器就无法被回收,对应类也无法回收,会占用内存
反射总结:反射可以根据类加载器中类信息创建对象,并获取对象方法,创建方法实例并执行
反射知识结束----------------------------------------------------------------------
asm 原理-------------------------------
可以操作字节码,是通过指令级别,也就是JVM指令操作的
JVM栈帧是通过指令 将 局部变量表中-- 加载到 --操作数栈-- 通过总线-- 放到CPU计算 ,返回操作数栈 - 最终将计算结果修改到 --局部变量表中 -- 最后返出栈帧
asm 采用访问者模式 ,为我们提供了ClassVisitor,MethodVisitor,FieldVisitor API接口,每当ASM扫描到类字段是会回调visitField方法,扫描到类方法是会回调MethodVisitor,
可以将程序编译成指令集执行
琥珀川:史上最通俗易懂的ASM教程zhuanlan.zhihu.com/p/94498015?utm_source=wechat_timeline
CGLIB动态代理
jdk要求必须定义接口,他是对接口代理的,CGLIB 是可以不传入接口的
CGLIB是动态生成被代理类的子类,底层使用ASM 字节码生成框架实现的 不能对final修饰的类和方法代理
指定父类,设置回调函数,创建代理对象
实现MethodInterceptor 接口,编写 intercept 回调方法
动态代理与静态代理对比
动态代理所有方法回调同一个方法,方便增强
静态代理 接口添加一个方法,所有实现都要实现这个方法
代理模式的优点
- 代理对象相当于是中介,保护目标对象
- 可以扩展目标对象
- 能将客户端和目标对象分离,降低系统的耦合度
使用场景
- 远程代理
- 防火墙代理
- 保护代理