1.基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象;
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理) 和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
2.示意图
3.静态代理
3.1 基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
3.2 应用实例
3.2.1 基本要求
1) 定义一个接口:ITeacherDao
2) 目标对象 TeacherDAO 实现接口 ITeacherDAO
3) 使用静态代理方式,就需要在代理对象 TeacherDAOProxy 中也实现 ITeacherDAO
4) 调用的时候通过调用代理对象的方法来调用目标对象.
5) 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
3.2.2 类图
3.2.3 代码实现
//接口
public interface ITeacherDao {public void teach();//授课的方法
}public class TeacherDao implements ITeacherDao {@Overridepublic void teach() {System.out.println(" 老师授课中... ");}
}//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao {ITeacherDao target;//目标对象,通过接口来聚合//构造器public TeacherDaoProxy(ITeacherDao target) {this.target = target;}@Overridepublic void teach() {System.out.println("静态代理开始,完成某些操作...");//方法target.teach();System.out.println("静态代理提交...");//方法}
}public class Client {public static void main(String[] args) {//创建目标对象(被代理对象)TeacherDao teacherDao = new TeacherDao();//创建代理对象, 同时将被代理对象传递给代理对象TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);//通过代理对象,调用到被代理对象的方法//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法teacherDaoProxy.teach();}
}
3.3 优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
4.动态代理
4.1 基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
4.2 应用实例
4.2.1 类图
4.2.2 代码实现
//接口
public interface ITeacherDao {public void teach();//授课的方法public void sayHello(String name);
}// 目标类
public class TeacherDao implements ITeacherDao {@Overridepublic void teach() {System.out.println(" 老师授课中... ");}@Overridepublic void sayHello(String name) {System.out.println("Hello," + name);}
}// 代理工厂,负责生成代理对象
public class ProxyFactory {//维护一个目标对象(被代理对象),ObjectObject target;//构造器,对target进行初始化public ProxyFactory(Object target) {this.target = target;}//给目标对象(被代理对象)生成一个代理对象public Object getProxyInstance() {//说明/** public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入*/return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK代理开始~~");//反射机制调用目标对象的方法Object returnVal = method.invoke(target, args);System.out.println("JDK代理提交");return returnVal;}});}
}public class Client {public static void main(String[] args) {//创建目标对象ITeacherDao target = new TeacherDao();//给目标对象,创建代理对象, 可以转成 ITeacherDaoITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象System.out.println("proxyInstance=" + proxyInstance.getClass());//通过代理对象,调用目标对象的方法proxyInstance.teach();proxyInstance.sayHello("Jack");}
}
4.3 手动模拟实现底层原理
先定义一个Foo
接口,里面有一个foo()
方法,再定义一个Target
类来实现这个接口
第一版:定义一个类也同样地实现一下Foo
接口,然后在foo()
方法中编写增强代码,接着再new
一个Target
对象,调用它的foo()
方法,代码如下所示:
public class Main {public static void main(String[] args) {Foo proxy=new $Proxy0();proxy.foo();// 结果:// 代码增强// target foo() }
}interface Foo{void foo();
}class Target implements Foo{@Overridepublic void foo() {System.out.println(" target foo() ");}
}class $Proxy0 implements Foo{@Overridepublic void foo() {System.out.println("代码增强");new Target().foo();}
}
第二版:上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。因此可以通过定义一个InvocationHandler
接口的方式来将这部分代码解耦出来,代码如下:
public class Main {public static void main(String[] args) {Foo proxy=new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {System.out.println("代码增强");new Target().foo();}});proxy.foo();// 结果:// 代码增强// target foo() }
}interface Foo{void foo();
}class Target implements Foo{@Overridepublic void foo() {System.out.println(" target foo() ");}
}class $Proxy0 implements Foo{private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}
}interface InvocationHandler{void invoke();
}
第三版:第2个版本的代码虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但还是有问题,如果此时接口中新增了一个方法bar(),Target类和$Proxy0类中都要实现bar()方法,那么调用proxy的foo()和bar()方法都将间接调用目标对象的foo()方法,因为在InvocationHandler的invoke()方法中调用的是target.foo()方法,代码如下:
public interface InvocationHandler {void invoke();
}public interface Foo {void foo();void bar();
}@Slf4j
public final class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic void bar() {log.debug("target bar");}
}public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}@Overridepublic void bar() {h.invoke();}
}public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {// 1. 功能增强System.out.println("before...");// 2. 调用目标new Target().foo();}});proxy.foo();proxy.bar();}
}
改进方法是,代理类中调用方法的时候,通过反射把接口中对应的方法Method
对象作为参数传给InvocationHandler
,代码如下:
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println("代码增强");// 2. 调用目标method.invoke(new Target(), args);}});proxy.foo();proxy.bar();// 结果:// 代码增强// target foo() // 代码增强// target bar() }
}interface Foo {void foo();void bar();
}class Target implements Foo {@Overridepublic void foo() {System.out.println(" target foo() ");}@Overridepublic void bar() {System.out.println(" target bar() ");}
}class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {Method foo = Foo.class.getDeclaredMethod("foo");h.invoke(foo, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}@Overridepublic void bar() {try {Method bar = Foo.class.getDeclaredMethod("bar");h.invoke(bar, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}
}interface InvocationHandler {void invoke(Method method, Object[] args) throws Throwable;
}
第四版:第3个版本的代码其实已经离jdk动态代理生成的代码很相近了,为了更好地学习底层,更近一步,修改Foo
接口的中bar()
方法,使其具有int
类型的返回值,因此InvocationHandler
的invoke()
方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println("代码增强");// 2. 调用目标return method.invoke(new Target(), args);}});proxy.foo();System.out.println(proxy.bar());}
}interface Foo {void foo();int bar();
}class Target implements Foo {@Overridepublic void foo() {System.out.println(" target foo() ");}@Overridepublic int bar() {System.out.println(" target bar() ");return 100;}
}class $Proxy0 implements Foo {static Method foo;static Method bar;static {try {foo = Foo.class.getDeclaredMethod("foo");bar = Foo.class.getDeclaredMethod("bar");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {h.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {// 如果抛出RuntimeExcepton,Error和声明的异常以及其子类外的其他异常,// 都会被统一转换成 UndeclaredThrowableException 进行抛出,// 这也是实现“子类不能抛出比父类更广泛异常"的规则throw new UndeclaredThrowableException(e);}}@Overridepublic int bar() {try {return (int) h.invoke(this, bar, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}interface InvocationHandler {Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
到这里跟jdk的动态代理只有些微差距了,jdk
的动态代码会让代理类再继承一个Proxy
类,里面定义了一个InvocationHandler
接口的对象,代理类中会通过super(h)
调用父类Proxy的构造。
5.cglib代理
5.1 基本介绍
-
静态代理和 JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib 代理
-
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
-
Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
-
在AOP 编程中如何选择代理模式:
①目标对象需要实现接口,用 JDK代理
②目标对象不需要实现接口,用 Cglib 代理
- Cglib 包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
5.2 注意事项
①在内存中动态构建子类,注意代理的类不能为 final,否则报错 java.lang.IllegalArgumentException:
②目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
5.3 应用实例
5.3.1 类图
5.3.2 代码实现
public class Main {public static void main(String[] args) {//创建目标对象TeacherDao target = new TeacherDao();//获取到代理对象,并且将目标对象传递给代理对象TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();//执行代理对象的方法,触发intercept 方法,从而实现 对目标对象的调用proxyInstance.teach();// 结果://Cglib代理模式 ~~ 开始// 老师授课中, 使用cglib代理,不需要实现接口... //Cglib代理模式 ~~ 提交}
}class TeacherDao {public void teach() {System.out.println(" 老师授课中, 使用cglib代理,不需要实现接口... ");}
}class ProxyFactory implements MethodInterceptor {//维护一个目标对象(被代理对象),ObjectObject target;//构造器,对target进行初始化public ProxyFactory(Object target) {this.target = target;}//返回一个代理对象: 是 target 对象的代理对象public Object getProxyInstance() {//1. 创建一个工具类Enhancer enhancer = new Enhancer();//2. 设置父类enhancer.setSuperclass(target.getClass());//3. 设置回调函数enhancer.setCallback(this);//4. 创建子类对象,即代理对象return enhancer.create();}//重写 intercept 方法,会调用目标对象的方法@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("Cglib代理模式 ~~ 开始");// 采用反射调用的方式Object returnVal = method.invoke(target, objects);// 内部没有反射调用,但需要结合目标对象使用// Object returnVal = methodProxy.invoke(target, objects);// 内部没有反射调用,但需要结合代理对象使用// Object returnVal = methodProxy.invokeSuper(o, args);System.out.println("Cglib代理模式 ~~ 提交");return returnVal;}
}
5.4 手动模拟实现底层原理
采用反射调用的方式
public class Main {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before");// 反射调用return method.invoke(target, args);}});proxy.save();proxy.save(1);proxy.save(2L);// before// save()// before// save(int)// before// save(long)}
}// 目标类
class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long i) {System.out.println("save(long)");}
}class Proxy extends Target {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long i) {try {methodInterceptor.intercept(this, save2, new Object[]{i}, null);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}
在 CGLib 动态代理中,可以使用 intercept() 方法中 MethodProxy 类型的参数实现不经过反射来调用方法。接收的 MethodProxy 类型的参数可以像 Method 类型的参数一样,在静态代码块中被实例化。可以通过静态方法 MethodProxy.create() 来创建 MethodProxy 对象。内部没有反射调用,但需要结合目标对象或者代理对象结合使用:
public class Main {public static void main(String[] args) {Target target = new Target();Proxy proxy = new Proxy();proxy.setMethodInterceptor(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before");// 内部没有反射调用,但需要结合目标对象使用
// return methodProxy.invoke(target, args);// 内部没有反射调用,但需要结合代理对象使用return methodProxy.invokeSuper(o, args);}});proxy.save();proxy.save(1);proxy.save(2L);//before//save()//before//save(int)//before//save(long)}
}// 目标类
class Target {public void save() {System.out.println("save()");}public void save(int i) {System.out.println("save(int)");}public void save(long i) {System.out.println("save(long)");}
}class Proxy extends Target {private MethodInterceptor methodInterceptor;public void setMethodInterceptor(MethodInterceptor methodInterceptor) {this.methodInterceptor = methodInterceptor;}static Method save0;static Method save1;static Method save2;static MethodProxy save0Proxy;static MethodProxy save1Proxy;static MethodProxy save2Proxy;static {try {save0 = Target.class.getMethod("save");save1 = Target.class.getMethod("save", int.class);save2 = Target.class.getMethod("save", long.class);save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");} catch (NoSuchMethodException e) {throw new NoSuchMethodError(e.getMessage());}}// >>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法public void saveSuper() {super.save();}public void saveSuper(int i) {super.save(i);}public void saveSuper(long i) {super.save(i);}// >>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法@Overridepublic void save() {try {methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(int i) {try {methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic void save(long i) {try {methodInterceptor.intercept(this, save2, new Object[]{i}, save2Proxy);} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}
5.5 MethodProxy 原理
调用 methodProxy.invoke() 方法时,会额外使用一个代理类,该代理类配合目标对象使用。调用 methodProxy.invokeSuper() 方法时,也会额外使用一个代理类,该代理类配合代理对象使用。当调用 MethodProxy 对象的 invoke() 方法或 invokeSuper() 方法时,就会生成这两个代理类,它们都继承至 FastClass。FastClass 是一个抽象类,其内部有多个抽象方法:
public abstract class FastClass {public abstract int getIndex(String var1, Class[] var2);public abstract int getIndex(Class[] var1);public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;public abstract int getIndex(Signature signature);public abstract int getMaxIndex();
}
重点讲解 invoke() 方法与 getIndex(Signature signature) 方法。
模拟生成的与目标类相关的代理类:
public class TargetFastClass {static Signature s0 = new Signature("save", "()V");static Signature s1 = new Signature("save", "(I)V");static Signature s2 = new Signature("save", "(J)V");/*** <p>获取目标方法的编号</p>* <p>* Target 目标类中的方法:* save() 0* save(int) 1* save(long) 2* </p>** @param signature 包含方法名称、参数返回值* @return 方法编号*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;}if (s1.equals(signature)) {return 1;}if (s2.equals(signature)) {return 2;}return -1;}/*** 根据 getIndex() 方法返回的方法编号正常调用目标对象方法** @param index 方法编号* @param target 目标对象* @param args 调用目标对象方法需要的参数* @return 方法返回结果*/public Object invoke(int index, Object target, Object[] args) {if (index == 0) {((Target) target).save();return null;}if (index == 1) {((Target) target).save((int) args[0]);return null;}if (index == 2) {((Target) target).save((long) args[0]);return null;}throw new RuntimeException("无此方法");}public static void main(String[] args) {TargetFastClass fastClass = new TargetFastClass();int index = fastClass.getIndex(new Signature("save", "()V"));fastClass.invoke(index, new Target(), new Object[0]);index = fastClass.getIndex(new Signature("save", "(J)V"));fastClass.invoke(index, new Target(), new Object[]{2L});// 结果:// save()// save(long)}
}
模拟生成的与代理类相关的代理类
public class ProxyFastClass {static Signature s0 = new Signature("saveSuper", "()V");static Signature s1 = new Signature("saveSuper", "(I)V");static Signature s2 = new Signature("saveSuper", "(J)V");/*** <p>获取代理方法的编号</p>* <p>* Proxy 代理类中的方法:* saveSuper() 0* saveSuper(int) 1* saveSuper(long) 2* </p>** @param signature 包含方法名称、参数返回值* @return 方法编号*/public int getIndex(Signature signature) {if (s0.equals(signature)) {return 0;}if (s1.equals(signature)) {return 1;}if (s2.equals(signature)) {return 2;}return -1;}/*** 根据 getIndex() 方法返回的方法编号正常调用代理对象中带原始功能的方法** @param index 方法编号* @param proxy 代理对象* @param args 调用方法需要的参数* @return 方法返回结果*/public Object invoke(int index, Object proxy, Object[] args) {if (index == 0) {((Proxy) proxy).saveSuper();return null;}if (index == 1) {((Proxy) proxy).saveSuper((int) args[0]);return null;}if (index == 2) {((Proxy) proxy).saveSuper((long) args[0]);return null;}throw new RuntimeException("无此方法");}public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper", "()V"));fastClass.invoke(index, new Proxy(), new Object[0]);// 结果:// save()}
}
与 JDK 中优化反射调用方法的对比
在 JDK 中需要反射调用 16 次方法后才会生成优化反射调用的代理类,而在 CGLib 中,当调用 MethodProxy.create() 方法时就会生成由于优化反射调用的代理类;
在 JDK 中一个方法的反射调用优化就要生成一个代理类,而在 CGLib 中,一个代理类生成两个 FastClass 代理类。
6.代理模式的常见变体
- 防火墙代理: 内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理: 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据
库取,然后缓存。 - 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
同步代理:主要使用在多线程编程中,完成多线程间同步工作
).saveSuper((int) args[0]);
return null;
}
if (index == 2) {
((Proxy) proxy).saveSuper((long) args[0]);
return null;
}
throw new RuntimeException(“无此方法”);
}
public static void main(String[] args) {ProxyFastClass fastClass = new ProxyFastClass();int index = fastClass.getIndex(new Signature("saveSuper", "()V"));fastClass.invoke(index, new Proxy(), new Object[0]);// 结果:// save()
}
}
### 与 JDK 中优化反射调用方法的对比```tex
在 JDK 中需要反射调用 16 次方法后才会生成优化反射调用的代理类,而在 CGLib 中,当调用 MethodProxy.create() 方法时就会生成由于优化反射调用的代理类;
在 JDK 中一个方法的反射调用优化就要生成一个代理类,而在 CGLib 中,一个代理类生成两个 FastClass 代理类。
6.代理模式的常见变体
- 防火墙代理: 内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理: 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据
库取,然后缓存。 - 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作
同步代理:主要使用在多线程编程中,完成多线程间同步工作