介绍
之前看过一些代理模式的实例,都被实例本身的逻辑绕晕了,今天来个彻底的总结,有些前辈已经总结的不错了,如下面这篇文章代理模式深度解析。
首先上代理模式的结构图(出自大话设计模式一书)
之前面试中被问到代理模式,首先第一反应想到的就是Spring中的AOP,其实AOP确实是基于代理实现的,但是针对代理的作用却一直没深入理解,其实代理设计模式从实质上来讲,就是对某一个目标类中的某个方法进行增强操作(大话设计模式中的实例是一个追女孩的实例,这个实例个人认为并不好理解),结合AOP思想来看,无非就是动态的将增强的业务逻辑在运行时植入到目标方法之前或之后,因此代理模式实现的底层会用到类加载器,重新实现目标类的相关接口,在调用被代理对象的目标方法之前或之后动态载入增强的业务逻辑。
静态代理
如果目标类中所有的方法,我们已经知道了,而我们需要对目标类中的某些方法进行增强,下面给出一个简单的实例。
各个类d
public class Father {
//没有办法扩展,只能帮儿子找对象了,没办法帮表妹找对象。
//为了扩展,就需要将这里的Son改成Person,而person是一个接口,里面维护一个findLove方法的声明
private Son son;
public Father(Son son){
//拿到目标对象的引用
this.son = son;
}
//代理对象完成目标对象的工作
public void findLove(){
System.out.println("根据儿子的要求物色");
son.findLove();
System.out.println("物色完了,看到结果");
}
}
public interface Person {
public void findLove();
public void buy();
public void zufangzi();
public void findJob();
}
public class Son implements Person{
//这个东西很繁杂,追妹子很费时费力。
//于是程序员就将这个任务交给了别人,交给了自己的父亲
public void findLove(){
System.out.println("找对象,肤白貌美大长腿");
}
@Override
public void buy() {
System.out.println("买东西");
}
@Override
public void zufangzi() {
System.out.println("租房子");
}
@Override
public void findJob() {
System.out.println("找工作");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
Son son = new Son();
Father father = new Father(son);
father.findLove();
}
}
静态代理最大的问题就是扩展不方便,需要知道具体的目标类,上述例子实际就是一个老爸帮儿子找对象的例子,这个例子中,老爸可以理解为儿子的代理,老爸帮着儿子找对象,会在儿子的findLove方法之前或之后增加一些业务逻辑,但是这该实例中的老爸,只能帮儿子找对象啊,不能帮表妹,表姐找对象啊,所以这就是静态代理的局限之处,无法实现动态扩展。
动态代理
动态代理其实相比静态代理而言,而且在代理之前,目标类中的方法可以为未知的。动态代理实现方式一般都有两种,一种就是JDK提供的动态代理的方式,另一种就是CGLib提供的方式,两者在实现方式上有着本质的不同,前一种是通过动态实现相关的接口来达到增强的作用,后一种是通过继承目标类的方式来实现代理。
CGLib代理
实现CGLib不需要手写大量代码,导入asm包可以完成大部分功能。这里直接给出实例代码:
目标类:
public class Person{
public void findLove(){
System.out.println("肤白,貌美,气质佳");
}
}
增强业务逻辑类:(也是获得代理类的入口)
public class PersonInterceptor implements MethodInterceptor{
public Object getInstance(Class<?> clazz) throws Exception{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//业务的增强
System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求了");
System.out.println("开始物色.......");
methodProxy.invokeSuper(o,objects);
System.out.println("物色完成");
return o;
}
}
测试类:
public class CGLibTest {
public static void main(String[] args) {
try{
Person obj = (Person)new PersonInterceptor().getInstance(Person.class);
obj.findLove();
}catch (Exception e){
e.printStackTrace();
}
}
}
JDK动态代理
JDK的动态代理,采用的是就是动态植入的方式,本文将在一些简单的实例上,对JDK动态代理的原理也进行梳理,前面提到的前辈的博客,已经对其原理进行了比较详细的总结,这里也是参照其内容加上了自己的理解。
下面先给出实例的类结构图,需要说明的是:JDK中规定,目标类都需要实现一个接口,实现接口的好处就是将目标类方法抽取出来,方便JDK的动态获取目标类中的方法信息,动态代理其实只需要目标类中的方法信息就可以了,代理类会自动去实现这个接口。
各个类的代码:
public class XiaoMing implements Person{
public void findLove(){
System.out.println("三观合,颜值过得去,能聊得来,有独立的工作能力");
}
public void buy() {
System.out.println("买东西");
}
public void zufangzi() {
System.out.println("租房子");
}
public void findJob() {
System.out.println("找工作");
}
public void buyHouse(){
System.out.println("土豪了,要买房子");
}
}
public class JDKFindJob implements InvocationHandler{
//需要被代理的对象
private Person target;
public Object getInstance(Person target){
this.target = target;
Class<?> clazz=target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
//为了JDK代理方法的第三个参数,需要实现InvocationHandler接口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是猎头,告诉我你的工作需求");
System.out.println("开始物色.......");
method.invoke(this.target,args );
System.out.println("物色完成");
return null;
}
}
测试类:
public class JDKProxyTest {
public static void main(String[] args) {
Person findJobObj = (Person)new JDKFindJob().getInstance(new XiaoMing());
findJobObj.findJob();
System.out.println(obj.getClass()); //输出:class com.sun.proxy.$Proxy0,表名生成的是一个代理类
}}
不难看出,利用JDK获取代理对象的核心就是这句代码:
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
Proxy.newProxyInstance这个方法中需要三个参数,第一个参数就是类加载器;第二个参数是目标类所实现的接口,这个不难理解,JDK需要通过知道目标类中的方法信息;第三个参数,就是增强类对象的引用,这个也不难理解,毕竟动态代理的目的就是完成业务的增强操作。
invoke方法,其实上述实例中为了简单,并没有进行方法过滤,即使在JDKProxyTest类中加入findJobObj.findLove()的方法,也会出现增强的调用效果,invoke方法调用的时候需要三个参数:第一个参数就是Proxy,这个就是JDK生成的动态代理对象,第二个参数是method,这个就是目标类中的方法信息;第三个参数是方法调用的参数信息。
如果想针对特定的方法进行增强,只需要在invoke方法中增加相应的过滤操作即可,如下所示,算是优化后的JDKFindJob类
public class JDKFindJob implements InvocationHandler{
//需要被代理的对象
private Person target;
public Object getInstance(Person target){
this.target = target;
Class<?> clazz=target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
//为了JDK代理方法的第三个参数,需要实现InvocationHandler接口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("findJob")){
System.out.println("我是猎头,告诉我你的工作需求");
System.out.println("开始物色.......");
method.invoke(this.target,args );
System.out.println("物色完成");
}else{
method.invoke(this.target,args );
}
return null;
}
}
这样在调用真正调用目标类中的其他方法的时候,就不会有增强操作了,运行结果如下所示:
动态代理深入解析
前文提到的参考博客(动态代理深入理解)中,几乎采用最原始的方式完成了动态代理的过程,根据这篇博客,画出动态代理过程的流程图,需要说明的是,动态代理中生成的代理对象在完成了相关功能后,JVM会自动将其删除。
Java中生成的代理类或者内部类,名称前都有一个$符号,以下代码是利用idea反编译看到的一个代理类代码:
import com.learn.pattern.Proxy.Person;
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 Person {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m6;
private static Method m5;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void findLove() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void zufangzi() throws {
try {
super.h.invoke(this, m6, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void buy() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void findJob() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m4 = Class.forName("com.learn.pattern.Proxy.Person").getMethod("findLove", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m6 = Class.forName("com.learn.pattern.Proxy.Person").getMethod("zufangzi", new Class[0]);
m5 = Class.forName("com.learn.pattern.Proxy.Person").getMethod("buy", new Class[0]);
m3 = Class.forName("com.learn.pattern.Proxy.Person").getMethod("findJob", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通过反编译得到的代码可以看出,代理类在构造的时候,需要传入InvocationHandler,其实这个就是一个接口,其中提供了invoke方法,个人理解的就是对增强业务逻辑的一个抽象接口而已。代理类只是通过调用invoke方法完成业务的增强。这也是为什么我们需要在invoke方法中去完成增强的业务逻辑。
其实在将代理类完成编译后,加载到JVM中之后,.class文件会被删除。下面根据动态代理的流程,给出原始动态代理的实现
SelfProxy:
public class SelfProxy{
public static final String ln = "\r\n";
/**
* 这个方法用于组装代理对象
* @param classLoader 自己写的类加载器
* @param interfaces 目标类的接口
* @param handler 增强逻辑类
* @return
*/
public static Object newProxyInstance(GPClassLoader classLoader,Class<?>[] interfaces,GPInvocationHandler handler){
try{
//1.动态生成源代码.java文件
String src = generateSrc(interfaces);
//2.Java 文件输出磁盘
String filePath = GPProxy.class.getResource("").getPath();
System.out.println(filePath);
File f = new File(filePath+"$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//3.把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task=compiler.getTask(null,manager,null,null,null,iterable);
task.call();
manager.close();
//4.编译生成的.class文件加载到jvm中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c =proxyClass.getConstructor(GPInvocationHandler.class);
//获得了构造函数后,可以删除.class文件
f.delete();
//5.返回字节码重组以后的新的代理对象——构造代理对象,并返回
return c.newInstance(handler);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 这里就是模拟动态产生$Proxy0类的源代码
* @param interfaces
* @return
*/
private static String generateSrc(Class<?>[] interfaces){
StringBuffer sb = new StringBuffer();
sb.append("package com.learn.Proxy.DynamicProxy.ProxySelfCode;"+ln);
sb.append("import com.learn.Proxy.Person;"+ln);
sb.append("import java.lang.reflect.InvocationHandler;"+ln);
sb.append("import java.lang.reflect.Method;"+ln);
sb.append("import java.lang.reflect.Proxy;"+ln);
sb.append("import java.lang.reflect.UndeclaredThrowableException;"+ln);
sb.append("public class $Proxy0 implements "+interfaces[0].getName()+ "{"+ln);
sb.append("GPInvocationHandler h;"+ln);
sb.append("public $Proxy0(GPInvocationHandler h) {"+ln);
sb.append("this.h = h;"+ln);
sb.append("}"+ln);
for(Method m:interfaces[0].getMethods()){
sb.append("public "+m.getReturnType().getName()+" "+m.getName()+"() {"+ln);
sb.append("try {"+ln);
sb.append("Method m = "+interfaces[0].getName()+" .class.getMethod(\""+m.getName()+"\",new Class[]{});"+ln);
sb.append("this.h.invoke(this,m,null);"+ln);
sb.append("}catch(Throwable e){"+ln);
sb.append("e.printStackTrace();"+ln);
sb.append("}"+ln);
sb.append("}"+ln);
}
sb.append("}"+ln);
return sb.toString();
}
}
SelfInvocationHandler,其实这个可以不用,这里只是模拟系统中的InvocationHandler接口
public interface SelfInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
SelfClassLoader,自己模拟的类加载器,其实就用到了两个功能,一个是获文件根路径,一个是找到指定的类
public class SelfClassLoader extends ClassLoader{
private File ClassPathFile;
public GPClassLoader(){
String classPath = GPClassLoader.class.getResource("").getPath();
this.ClassPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GPClassLoader.class.getPackage().getName()+"."+name;
if(ClassPathFile != null){
File classFile = new File(ClassPathFile,name.replaceAll("\\.","/")+".class");
if(classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len ;
while((len = in.read(buff))!=-1){
out.write(buff,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}finally {
if(null!=in){
try{
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
if(null!=out){
try{
out.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
return null;
}
}
SelfHandler 这个就是增强类,为了简单,这里将获取代理类的入口也放在其中的getInstance函数中
public class SelfHandler implements SelfInvocationHandler{
//需要被代理的对象
private Person target;
public Object getInstance(Person target){
this.target = target;
Class<?> clazz=target.getClass();
return GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
}
//自己扩展的增强类
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求了");
System.out.println("开始物色.......");
method.invoke(this.target,args );
System.out.println("物色完成");
return null;
}
}
测试类:
public class SelfProxyTest {
public static void main(String[] args) {
Person obj = (Person)new CustomerHandler().getInstance(new XiaoMing());
System.out.println(obj.getClass());
obj.findJob();
}
}
最后运行结果:
总结:
动态代理一直没有透彻理解,根据前辈的经验从原理上进行了梳理,之前在参看大话设计模式中的代理模式时,由于其本身实例有点绕,依旧没有完全理解,该文中总体来看写的依旧比较凌乱。总体来说只要明确InvocationHandler只是一个标签的左右,基本上也能理解个七七八八。从设计模式的几大原则出发,似乎隐约感觉到动态代理比静态代理的耦合度要低,但是由于小生经验文笔欠佳,似乎很难悟到那一层,所以难以表述详细,这里也望各位有才的网友提出批评指正。