代理的作用就是在你想要的操作的前后做一些你没必要关心的事.
比如你想买房,去找中介,中介会找符合你规定的房子带给你看.中介的作用就是帮你找合适的房子.中介就是代理类,找房子是你的原始方法,筛选合适的房子这个动作就是代理类织入的横切逻辑.(将原来的返回集 所有的房子变为 合规则的房子)
静态代理:
静态代理: 贼常见,只是你不知道, 比如你写了一个方法,后来这个方法不满足需求了,需要在这个方法结束后的返回参数再做操作,比如: 在原返回结果上加一
伪代码如下:
1. 由于不知道这个函数别的地方有没有用,所以不能直接改,只能写个新类,新类的方法中拿到旧类
2. 新类的新方法中调用旧类的旧函数,入参出参一致.
3. 拿到旧类旧方法的返回结果,
4. 返回结果++ ,
5. return 新返回结果
用的时候,直接 new 新类,调新方法就可以了.
OK,这就是了,静态代理,so easy ,核心就是就是包一层.并在原来的方法前后做一些修改.
总结一下静态代理: 狸猫换太子
动态代理:
动态代理就高端了,动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
那为什么要用动态代理呢?因为静态代理,每个旧类你都要写个新类.要是100个类你肯定原地螺旋爆炸心里一句妈卖批不知当讲不当讲.
动态代理用的是 JDK 中的接口 InvocationHandler , 实现它的 invoke 方法. invoke 方法可牛逼了,叉会腰. 调用所有的实现类方法最终都会调用 invoke 方法.talk is cheap 上代码.
先上代理类:
package com.resource.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* 动态代理类
*/
public class DynamicProxy implements InvocationHandler {
//被代理的对象
Object targetObject;
/**
* 获得被代理后的对象
* @param object 被代理的对象
* @return 代理后的对象
*/
public Object getProxyObject(Object object){
this.targetObject=object;
return Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(), //类加载器
targetObject.getClass().getInterfaces(), //获得被代理对象的所有接口
this); //this 是InvocationHandler对象
//loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载
//interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
//h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
}
/**
* 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口
* proxy 被代理后的对象
* method 将要被执行的方法信息(反射)
* args 执行方法时需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被织入的内容,开始时间
if(method.getName().equals("add")){//判断过来的是不是add方法
long start=System.currentTimeMillis();
//使用反射在目标对象上调用方法并传入参数
Object result=method.invoke(targetObject, args);
lazy();
//被织入的内容,结束时间
Long span= System.currentTimeMillis()-start;
System.out.println("加操作用时:"+span);
return result;
}else{
long start=System.currentTimeMillis();
//使用反射在目标对象上调用方法并传入参数
Object result=method.invoke(targetObject, args);
lazy();
//被织入的内容,结束时间
Long span= System.currentTimeMillis()-start;
System.out.println("其他用时:"+span);
return result;
}
}
//模拟延时
public void lazy()
{
try {
int n=(int)new Random().nextInt(100);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
再上抽象接口类:
package com.resource.test;
/**
* 接口
* 抽象主题
*/
public interface IMath {
//加
int add(int n1, int n2);
//减
int sub(int n1, int n2);
//乘
int mut(int n1, int n2);
//除
int div(int n1, int n2);
}
再上实现类:
package com.resource.test;
public class Math implements IMath{
public Math (){
}
//加
public int add(int n1,int n2){
int result=n1+n2;
System.out.println(n1+"+"+n2+"="+result);
return result;
}
//减
public int sub(int n1,int n2){
int result=n1-n2;
System.out.println(n1+"-"+n2+"="+result);
return result;
}
//乘
public int mut(int n1,int n2){
int result=n1*n2;
System.out.println(n1+"X"+n2+"="+result);
return result;
}
//除
public int div(int n1,int n2){
int result=n1/n2;
System.out.println(n1+"/"+n2+"="+result);
return result;
}
}
调用方法:
IMath math=(IMath)new DynamicProxy().getProxyObject(new Math());
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
这样,如果你想抓执行接口的耗时,只要在原来的类中套上这个代理类就行了,这就是 SPRING 的 AOP ,也是 retrofit 的核心黑科技.
总结一下动态代理:
- 通过实现 InvocationHandler 接口创建调用处理器
- 通过 proxy 类 及实体对象创建动态代理类
- 通过 invoke 方法,织入想要的方法
- 代理类强转型后,和正常接口一样使用
好处:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
坏处:它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
为什么 JDK 动态代理只能代理接口? 因为 Proxy 底层用字节码生成技术生成代理类时,已经将 Proxy 类设为代理类的父类了. 由于 JAVA 的单继承,所以只能代理接口.不能再代理其他类了.关系图:
想知道 Proxy 黑匣子里发生了什么,为什么就只能代理接口了,去这里
https://www.cnblogs.com/MOBIN/p/5597215.html
动态代理实现步骤:
具体步骤是:
a. 实现InvocationHandler接口创建自己的调用处理器
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
分步骤实现动态代理
代码:
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
**//此处确定了代理类继承自Proxy,由于单继承,所以只能代理接口.**
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。
简化后的动态代理实现
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class }, handler );
CGLIB 动态代理.
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib与JDK动态代理一样,采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
CGLib 创建的动态代理对象性能比 JDK 创建的动态代理对象的性能高不少,但是CGLib 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLib 合适,反之,使用 JDK 方式要更为合适一些。同时,由于 CGLib 由于是采用动态创建子类的方法,对于 final 方法,无法进行代理.
CGLIB 动态代理用起来与 JDK 动态代理差不多,实现 MethodInterceptor 接口,实现 intercept 方法织入切入逻辑.没啥说的.
一些对接口的理解
为啥要用接口,接口存在的目的是啥?不用接口也完全可以实现所有功能啊.
举个栗子:你想吃炸鸡,看到 KFC 就知道,这家店卖炸鸡,虽然别家店也可以卖炸鸡,但是不挂 KFC 这个牌子,你很可能不知道这家店里卖炸鸡. KFC 就是接口,任何挂了 KFC 牌子的店家,大家一看就知道它卖炸鸡.当然,你挂了 KFC 的牌子,也可以卖奶茶,卖别的,但是,你一定要实现所有 KFC 的功能.这就是接口存在的意义.让人家一看就知道,你这个类会干嘛.
接口就相当于动作.类就相当于对象,而你,就是上帝,赋予对象相应的动作,让整个世界动起来.