JAVA的动态代理Jdk实现方式
首先代理模式是为了解决一组类实现同一个接口,而其中个别类在后来需要添加额外的逻辑,又不能改变原类的问题。需求如图:
原始代码如下: 接口:ControlDB
package interfaces;
public interface ControlDB {
public void add();
}
接口:ControlUI 这个接口在问题1中不考虑。
package interfaces;
public interface ControlUI {
public void change();
}
Manager类
package realclass;
import interfaces.ControlDB;
import interfaces.ControlUI;
public class Manager implements ControlDB, ControlUI {
@Override
public void change() {
// TODO Auto-generated method stub
System.out.println("我是Manager,UI改变中……");
}
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是Manager,数据添加中……");
}
}
类UserA 、UserB、 UserC
package realclass;
import interfaces.ControlDB;
public class UserA implements ControlDB {
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是UserA,数据添加中……");
}
}
package realclass;
import interfaces.ControlDB;
public class UserB implements ControlDB {
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是UserB数据添加中……");
}
}
package realclass;
import interfaces.ControlDB;
public class UserC implements ControlDB {
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是UserC数据添加中……");
}
}
不管哪种方式前提不变的是保证被代理类不变,改变逻辑。
问题1:如何给Manager方法add()执行前后增加额外的日志逻辑呢?--静态代理
有两种方式:一种是静态代理,另一种姑且叫做聚合方式吧;先来看这两种方式的实现:
1、静态代理模式:
package proxyclass;
import realclass.Manager;
import interfaces.ControlDB;
import interfaces.ControlUI;
public class ManagerLog_JT implements ControlDB {
private Manager manager ;
public ManagerLog_JT(){
manager=new Manager();
}
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是ManagerLog_JT,日志记录开始……");
manager.add();
System.out.println("我是ManagerLog_JT,日志记录结束……");
}
}
2、聚合方式:
package proxyclass;
import interfaces.ControlDB;
public class ManagerLog_JH implements ControlDB {
ControlDB controlDB;
public ManagerLog_JH(ControlDB controlDB){
this.controlDB=controlDB;
}
@Override
public void add() {
// TODO Auto-generated method stub
System.out.println("我是ManagerLog_JH,日志记录开始……");
controlDB.add();
System.out.println("我是ManagerLog_JH,日志记录结束……");
}
}
运行main方法:
import interfaces.ControlDB;
import proxyclass.ManagerLog_JH;
import proxyclass.ManagerLog_JT;
import realclass.Manager;
public class ProxyTest {
public static void main(String[] args) {
// 正常调用Manager
Manager manager= new Manager();
manager.add();
System.out.println("==============================分割线============================");
System.out.println("为Mnager的add方法执行前后添加日志记录的操作,使用静态代理和聚合方式实现:");
System.out.println("————————————————————静态方式开始————————————————————");
ControlDB managerlog_jt=new ManagerLog_JT();
managerlog_jt.add();
System.out.println("————————————————————静态方式结束————————————————————");
System.out.println("————————————————————聚合方式开始————————————————————");
ControlDB managerlog_jh=new ManagerLog_JH(new Manager());
managerlog_jh.add();
System.out.println("————————————————————聚合方式结束————————————————————");
}
}
运行结果:可以看到两种已经顺利给Manager的add()执行前后增加额外的逻辑,又不改变原类,这样付出的代价是新增一个代理类ManagerLog_JH或者ManagerLog_JT
我是Manager,数据添加中……
==============================分割线============================
为Mnager的add方法执行前后添加日志记录的操作,使用静态代理和聚合方式实现:
————————————————————静态方式开始————————————————————
我是ManagerLog_JT,日志记录开始……
我是Manager,数据添加中……
我是ManagerLog_JT,日志记录结束……
————————————————————静态方式结束————————————————————
————————————————————聚合方式开始————————————————————
我是ManagerLog_JH,日志记录开始……
我是Manager,数据添加中……
我是ManagerLog_JH,日志记录结束……
————————————————————聚合方式结束————————————————————
这样其实已经造成代码的臃肿了,虽然解决问题!看图更直观:
如果继续给类增加逻辑:权限控制逻辑、记录时间逻辑……这样Manaer需要生成多少个代理或者聚合类呢?效果图:
问题2:如何解决增加逻辑又不使代码臃肿的问题?--动态代理,所有的新类在内存中动态生成(关键点是在内存中如何重构类)。
2.1、原理:点击下载原理分析图:proxy.zip
将原类传入一个可创建代理类的“工厂”,“工厂”又将 原类的类加载器 + 原类对象的接口 + 原类同自己本身(封装的InvocationHandler实现类的对象)
传递给Proxy,Proxy得到后,在内存中利用这些参数+反射机制,开始重构类以便于抽离逻辑,怎么抽离,就是将原类方法体内的逻辑执行替换为调用外部方法的代码,这里注意了,这个行代码很讲究!要调用外部方法,就需要外部类对象(一个“工厂”类就可以充当),所以Proxy在生成新类时需要接受一个外部类对象,同时这个对象必须有一个相同方法;确保这些就需要内存类的这行代码所需的和外部类一致!( handler.invoke(this, md, null)),这里通过this传递过去的代理对象,其实并没有使用!
1、方法调用所需对象类型需要和外部类一致;确保一致就要封装外部类,封装外部类?所以Proxy要传入外部类的对象;
2、这行代码执行的方法,外部对象必须有;
以上两点正好利用接口解决,让外部类实现某接口(InvocationHandler),然后有一个必须实现的方法,然后将实现类传入,在内存类中使用InvocationHandler接受并调用这个接口的方法即可!这时候就会执行外部类中的方法,此时需要“完成原类方法体内的逻辑”(因为内存类中没有),在这个外部类的方法中,我们可以在“完成原类方法体内的逻辑”的前后增加自己的逻辑;
“完成原类方法体内的逻辑”需要在外部类("工厂"类)封装一个原类对象(Object)去调用方法才可,为什么使用Object封装呢?确保“万能”!但是引来一个问题,这样一个Object对象怎么实例化?这时候可以利用外部类的构造函数来接受最初传来的原类对象,因为根据上面分析外部类(工厂类)是必须向Proxy传递本身对象,需要对象就需要new,刚好我们将Object的实例化放在构造函数中!只要外部类向Proxy传递参数就会将接受到的原类对象,通过构造函数将本类(外部类)中的封装的Object类实例化,这就是为什么外部类向Proxy传递本身对象的时候,需要使用带参数的构造函数。
有了原类对象其实就可以调用原类的方法,原类的方法终于可以执行了吧?是的,但是此时我们并不知道对象要调用哪个方法!怎么办,可以利用Proxy生成内存类的时候根据传入的原类信息利用反射得到需要执行的方法的对象,同理方法执行所需的参数也是可以得到的,然后在调用外部类方法时传入即可!有了方法名对象、参数和原类对象,就可以执行原类方法了。但是执行的语句是这样的method.invoke(obj, args)其实等同于obj.X(args);整个东西的得到都是反射而来的,如果按照正常方法obj.X(args)执行是行不通的,因为obj是Object对象,并没有向下转型,还是不知道究竟调用哪个方法;还是要利用Method类的方法invoke,它可以去执行本身!
要理解动态代理的关键:就是知道JVM生成(JVM怎么利用反射生成代理类)的代理究竟是什么样子,下面模拟出上文中Manager类的代理类,真正的代理类名称是$Proxy0:
package proxyclass; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import interfaces.ControlDB; import interfaces.ControlUI; //ManagerLog_Proxy JDK动态生成的类名:$Proxy0 记得这个类只存在内存中,所以我称为内存类! public class ManagerLog_Proxy extends Proxy implements ControlDB,ControlUI { private InvocationHandler handler; public ManagerLog_Proxy(InvocationHandler handler){ super(handler); } @Override public void change() { try { java.lang.reflect.Method md = realclass.Manager.class.getMethod("change"); try {
//在真正的$Proxy0中是super.handler.invoke(this,md,null);其实容易理解,父类Proxy的handler才是传进来的“外部类封装的对象”;
//模拟的代理类已经在构造函数执行过super(handler)了,所以handler已经是“外部类封装的对象” handler.invoke(this, md, null);--就是“思路”中调用外部类 } catch (Throwable e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } @Override public void add() { try { java.lang.reflect.Method md = proxyclass.ManagerLog_JH.class.getMethod("add"); try { handler.invoke(this, md, null); } catch (Throwable e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } }
下面是“工厂”类:ManagerInvocationHandler 在“思路”中这工厂类的实现的接口、封装的Object以及带参数的构造函数都是为内存中的代理类所需对应的。
package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import proxyclass.ManagerLog_Proxy; import realclass.Manager; public class ManagerInvocationHandler implements InvocationHandler {
//为了封装原类对象,用在method.invoke(obj, args)中obj调用原类方法时使用;
private Object obj;
//带参数的构造函数,为了在传参数给Proxy时候,初始化obj=realObj; public ManagerInvocationHandler(Object obj) { this.obj = obj; } public static Object getProxyInstanceFactory(Object realObj){ Class<?> classType = realObj.getClass();
//前两个参数在Proxy中用来重构类时使用,最后一个参数是Proxy与本类为抽离逻辑“达成以的一致,即InvocationHandler接口实现类” 一个传入一个接受;
//并且Proxy“保证”在生成的内存类中调用该接口的方法,本类只需将本来对象传入,Proxy"承诺"使用该对象去调用接口方法,前提是本类实现该接口;至于
//使用带参数构造函数new的本类对象,参数不参与,仅仅为实例化Object属性。 return Proxy.newProxyInstance(classType.getClassLoader(),classType.getInterfaces(), new ManagerInvocationHandler(realObj)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before========日志生成"); method.invoke(obj, args); System.out.println("after=========日志结束"); return null; } }
下面是执行main方法,可以看到与上文聚合类、静态代理的调用的main方法中调用的方式类似,尤其是聚合方式的调用类似。
package test; import interfaces.ControlDB; import interfaces.ControlUI; import realclass.Manager; import realclass.UserA; public class ProxyTest { public static void main(String[] args) { Manager manager= new Manager();
//返回内存中的代理类(无逻辑的方法体)
ControlUI managerui= (ControlUI) ManagerInvocationHandler.getProxyInstanceFactory(manager);
//拿到代理类,执行内存类中的方法
managerui.change();
}
}
有必要附上Proxy的newProxyInstance方法源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
if (h == null) {
throw new NullPointerException();
}
Class cl = getProxyClass(loader, interfaces);
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
补充:CGLIB 和ASM 可以直接修改二进制码实现动态代理;不需要像JDK反射一样需要重构类。
CGLIB(Code Generation Library)是一个开源项目;是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。