https://www.cnblogs.com/incognitor/p/9759987.html
#### 1. 动态代理的实现方式:
涉及3个类:
- 委托类对象/被代理对象 - 实际对象
- 实现InvocationHandler对象 - 包含对实际对象方法增强的实现
- 代理对象 Proxy.newProxyInstance(zhangsan.getClass().getClassLoader(),zhangsan.getClass().getInterfaces(), stuHandler);
#### 2. 动态代理的原理
1. newProxyInstance 封装创建代理对象的步骤 Class<?> cl = getProxyClass0(loader, intfs);
2. 在运行的过程中通过ProxyGenerator创建代理类的字节码文件并加载到JVM中,这样就获取到代理类的Class对象。 3. 通过class对象 通过反射获取构造函数对象(需要传入InvocationHandler), 通过构造函数创建代理类实例对象
3. 反编译代理类的字节码文件 代理类以$Proxy0来命名 且extends Proxy implements Person 继承Proxy类实现了和委托类相同的接口
extends Proxy 集成了父类的构造函数, 父类构造函数需要传入一个InvocationHandler对象, 这个就是我们在Proxy.newProxyInstance传入的nvocationHandler对象
实现了被代理对象相同的接口, 这样就和被代理对象有相同的方法, 可以通过代理类调用相同方法。
4. 代理的构造函数会传入一个InvocationHandler的实例 而InvocationHandler有持有一个委托类的实例
5. 代理类的内部会会通过反射维护接口所有方法实例 Method m1, m2
6. 所以代理类也可以调用和委托类相同的方法 因为实现了相同的接口 当通过代理类调用方法是 实际是上调用this.h.invoke(this, m3, null); 也就是InvocationHander的invoke方法 传入当前的invocationhandler实例和 对应的方法Methond, 以及参数
7. 这样 invoke方法执行增强代码部分 然后在通过其维护的委托类实例调用m3方法也就是 委托类的方法 这样就实现了动态代理
3. #### CGLib
Java原生的动态代理 只能代理实现接口的类 使用cglib来实现对继承类的动态代理 当然cdglib是不能代理final类的
```
package com.learn.proxy.inteface;
public interface IActor {
public void basicAct(float money);
public void dangeAct(float money);
}
package com.learn.proxy.inteface;
public class Actor implements IActor{
public void basicAct(float money) {
System.out.println("拿到钱 开始基本表演 " + money);
}
public void dangeAct(float money) {
System.out.println("拿到钱 开始危险表演 " + money);
}
}
package com.learn.proxy.inteface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理:
作用:不改变源码的基础上 对已有的方法增强 (它是AOP思想的实现技术)
分类:
基于接口的动态代理
要求: 被代理对象最少实现一个接口
提供者: JDK 官方
设计的类:Proxy
创建代理类的方法: newProxyInstance(ClassLoader, class[], InvocationHandler)
参数的含义:
ClassLoader:类加载器 和被代理对象使用相同的类加载器 一般都是固定写法 比如要代理xxx 写法就是xxx.getClass().getClassLoader()
Class[]: 字节码数组 被代理类实现的接口 要求被代理对象和代理对象具有相同的行为 一般也是固定写法 xxx.getClass().getInterfaces()
InvocationHandler: 它是一个接口 就是用于我们提供增强代码的 我们一般都是些一个该接口的实现类 实现类可以是匿名内部类
它的含义就是 如何代理 此处代码只能谁用谁提供
策略模式:
使用要求: 数据已经有了 目的明确 达到目标的过程就是侧罗
在dbutils中的ResultSetHandler就是策略模式的具体应用
* @author LJ
*
*/
public class Client {
public static void main(String[] args) {
final Actor actor = new Actor();
// actor.basicAct(1000f);
// actor.dangeAct(5000f);
IActor proxyActor = (IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler(){
/**
* 执行被代理对象任何方法 都会先执行下面的方法 该方法有类似拦截的功能
* 方法参数:
* Object proxy: 代理对象的引用 不一定每次都用
* Method method: 当前执行的方法
* Object[]args: 当前执行方法所需要的参数
*
* 返回值: 当前执行方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
// 1. 取出方法中的参数
Float money = (Float) args[0];
// 2. 判断当前执行的是什么方法
if("basicAct".equals(method.getName())) {
//基本表演
if(money > 10000) {
rtValue = method.invoke(actor, money);
}
}
if("dangeAct".equals(method.getName())) {
//基本表演
if(money > 50000) {
rtValue = method.invoke(actor, money);
}
}
return rtValue;
}
});
proxyActor.basicAct(20000f);
proxyActor.dangeAct(150000f);
}
}
```
### 引入场景
动态代理在Java中是很重要的一部分,在很多框架中都会用到,如Spring中的AOP、Hadoop中的RPC等。
如果要对第三方提供的JAR包中的某个类中的某个方法的前后加上自己的逻辑,比如打log ,注意此时我们只有第三方提供的CLASS文件,因此根本不可能去修改别人的源代码,那该怎么办?
有两种方法可以实现,一种是利用继承,另一种是利用聚合。举例说明:
假设第三方中提供一个Run接口,里面只一个run方法,以及它的实现类Person
```
public interface Run {
public void run();
}
public class Person implements Run {
@Override
public void run() {
System.out.println("person running...");
}
}
```
第一种利用继承实现
```
public class SuperMan1 extends Person {
@Override
public void run() {
//方法开始前打LOG
LOG.info("super man1 before run...");
super.run();
//方法结束后打LOG
LOG.info("super man1 after run...");
}
}
```
第二种利用聚合实现
```
public class SuperMan2 implements Run {
private Run person;
public SuperMan2(Run person) {
this.person = person;
}
@Override
public void run() {
//方法开始前打LOG
LOG.info("super man2 before run...");
person.run();
//方法结束后打LOG
LOG.info("super man2 after run...");
}
}
```
这两种实现方式,哪一种更好呢?
显然是第二种利用聚合实现方法好,因为这种方式很灵活,同时又不会有多层的父子类关系。而继承最不好的地方就是不灵活,同时会很容易形成臃肿的父子类关系,不利于后期的维护
其实SuperMan1类和SuperMan2类都是Person类的代理类,对Person类中的方法进行加强,只不过这种代理是静态代理,很受限制。因为SuperMan1和SuperMan2只能代理Run类型的类,其它类型没法代理。如果有其他接口比如Fly 就需要额外在写代理类,有多少个接口就要单独实现多少个代理类。
为了解决这个问题,Java中引入动态代理。
### Java动态代理原理
**动态代理的意思就是一个类的(比如Person)的代理类(比如SuperMan2)是动态生成的,也就是说这个代理类不是提前写好的,是在程序运行时动态的生成的**
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建调用处理器
```
IvocationHandler handler = new InvocationHandlerImpl(...);
```
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
```
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
```
反编译代理类字节码
```Java
import com.xych.proxy.jdkproxy.IDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements IDao {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m0;
private static Method m3;
public $Proxy0(InvocationHandler paramInvocationHandler) throws Throwable {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch(Error | RuntimeException localError) {
throw localError;
} catch(Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void save() {
try {
this.h.invoke(this, m4, null);
return;
} catch(Error | RuntimeException localError) {
throw localError;
} catch(Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String) this.h.invoke(this, m2, null);
} catch(Error | RuntimeException localError) {
throw localError;
} catch(Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer) this.h.invoke(this, m0, null)).intValue();
} catch(Error | RuntimeException localError) {
throw localError;
} catch(Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void update() {
try
{
this.h.invoke(this, m3, null);
return;
}catch(Error | RuntimeException localError) {
throw localError;
} catch(Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);
return;
} catch(NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch(ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
```
$Proxy0继承Proxy继承,在Proxy里有一个成员变量h。并通过构造函数复制。 这个h就是在newProxyInstance传入的
```
protected InvocationHandler h;
```
jdk在程序运行过程中动态为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类字节码文件,然后编译到JVM中 这样就获取到$Proxy0的class对象
然后通过class对象 通过反射机制获取动态代理类的构造函数,其参数类型InvocationHandler接口类型
```
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
```
通过构造函数创建代理类实例,此时需传入已经实现InvocationHandler接口的对象作为参数被传入 这样就创建了代理类的实例对象
```
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
```
为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。
```
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class}, new InvocationHandlerImpl (real));
```
通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
首先来看static代码块
```
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("save", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.xych.proxy.jdkproxy.IDao").getMethod("update", new Class[0]);
return;
} catch(NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch(ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
```
IDao接口定义了两个方法save、update,加上Object类的equals、toString、hashCode这三个方法,总共5个方法,即上面代码中的m1-m5成员变量,并在’静态代码块’里初始化了这5个成员变量。
- m0-m3 反射获取Object类中的三个方法: quals、toString、hashCode
- m4-mxx 反射获取实现接口中的方法
同时$Proxy0本身也实现了IDao接口并重写了equals、toString、hashCode方法
```
public final class $Proxy0 extends Proxy implements IDao`
```
而$Proxy0本身有h成员变量,代理类调用自己这些方法时 都是通过统一调用this.h.invoke方法类执行
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中传入了对应的方法m0-mxx
```
this.h.invoke(this, m3, null);
```
而在通过反射执行方法时
```
rtValue = method.invoke(actor, money);
```
实际执行了被代理对象actor的相应方法。
通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
网友评论