设计模式之代理模式
一, 概述
1, 什么是代理模式?
解释第一遍: 代理模式主要由三个元素共同构成:
1)一个接口, 接口中的方法是要真正去实现的.
2)被代理类, 实现上述接口, 这是真正去执行接口中方法的类.
3)代理类, 同样实现上述接口, 同时封装被代理类对象, 帮助被代理类去实现方法.
解释第二遍: 使用代理模式必须要让代理类和目标类实现相同的接口, 客户端通过代理类来调用目标方法, 代理类会将所有的方法调用分派到目标对象上反射执行, 还可以在分派过程中添加 "前置通知" 和后置处理
(如在调用目标方法前校验权限, 在调用完目标方法后打印日志等)等功能.
解释第三遍(如图):
这样应该是明白了, 当然代理模式又分为静态代理和动态代理, 先看静态代理.
一, 静态代理
直接举例(网上有个很不错的例子)
假如一个班的同学要向老师交班费, 但是都是通过班长把自己的钱转交给老师. 这里, 班长就是代理类(代理学生上交班费), 学生就是被代理类或者理解为目标类.
首先, 我们创建一个 Teacher 接口. 这个接口就是学生 (被代理类), 和班长(代理类) 的公共接口, 他们都有上交班费的行为. 这样, 学生上交班费就可以让班长来代理执行.
- /**
- * 创建 Teacher 接口
- */
- public interface Teacher {
- // 上交班费
- void giveMoney();
- }
Student 类实现 Teacher 接口. Student 可以具体实施上交班费的动作.
- public class Student implements Teacher {
- private String name;
- public Student(String name) {
- this.name = name;
- }
- @Override
- public void giveMoney() {
- System.out.println(name + "上交班费 100 元");
- }
- }
StudentsProxy 类, 这个类也实现了 Teacher 接口, 由于实现了 Teacher 接口, 同时持有一个学生对象, 那么他可以代理学生类对象执行上交班费 (执行 giveMoney() 方法)行为.
- /**
- * 学生代理类, 也实现了 Teacher 接口, 保存一个学生实体, 这样既可以代理学生产生行为 */
- public class StudentsProxy implements Teacher{
- // 被代理的学生
- Student stu;
- public StudentsProxy(Teacher stu) {
- // 只代理学生对象
- if(stu.getClass() == Student.class) this.stu = (Student) stu;
- }
- // 代理上交班费, 调用被代理 学生的上交班费行为
- public void giveMoney() {
- System.out.println("张三家里是土豪, 应该比其它人交更多的班费!");
- stu.giveMoney();
- System.out.println("张三班费交的最多, 你就是班长了!");
- }
- }
下面测试类 ProxyTest, 看如何使用代理模式:
- public class ProxyTest {
- public static void main(String[] args) {
- // 被代理的学生张三, 他的班费上交有代理对象 monitor(班长)完成
- Teacher zhangsan = new Student("张三");
- // 生成代理对象, 并将张三传给代理对象
- Teacher monitor = new StudentsProxy(zhangsan);
- // 班长代理上交班费
- monitor.giveMoney();
- }
- }
运行结果:
总结下:
这里并没有直接通过张三 (被代理对象) 来执行上交班费的行为, 而是通过班长 (代理对象) 来代理执行了. 这就是代理模式. 代理模式最主要的就是有一个公共接口(Teacher), 一个具体的类(Student),
一个代理类(StudentsProxy), 代理类持有具体类的实例, 代为执行具体类实例方法. 同时可以看到, 代理类里除了实现目标类的方法, 而且可以在执行前后添加其它方法来起到增强功能. 这不就是 AOP(面向切面
思想), 对的 AOP 的实现就是基于代理模式, 只不过它采用的是动态代理模式.
二, 动态代理模式
1, 案例说明
- public class MyProxy {
- // 一个接口
- public interface IHello{
- void sayHello();
- }
- // 目标类实现接口
- static class Hello implements IHello{
- public void sayHello() {
- System.out.println("sayHello......");
- }
- }
- // 自定义代理类需要实现 InvocationHandler 接口
- static class HWInvocationHandler implements InvocationHandler{
- // 目标对象
- private Object target;
- public HWInvocationHandler(Object target){
- this.target = target;
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("------ 插入前置通知代码 -------------");
- // 执行相应的目标方法
- Object rs = method.invoke(target,args);
- System.out.println("------ 插入后置处理代码 -------------");
- return rs;
- }
- }
- public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 获取动态代理类
- Class<?> proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
- // 获得代理类的构造函数, 并传入参数类型 InvocationHandler.class
- Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
- // 通过构造函数来创建动态代理对象, 将自定义的 InvocationHandler 实例传入
- IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));
- // 通过代理对象调用目标方法
- iHello.sayHello();
- }
- }
运行结果:
Proxy 类中还有个将 2~4 步骤封装好的简便方法来创建动态代理对象, 其方法签名为: newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h), 如下例:
- (方式二)
- public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- IHello ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
- new Class[]{IHello.class}, // 一组接口
- new HWInvocationHandler(new Hello())); // 自定义的 InvocationHandler
- ihello.sayHello();
- }
看到这里我们应该思考三个问题?
1)动态代理类是如何创建的?
2) 它是如何调用 MyProxy 中的 invoke 的方法的?
3) MyProxy 中 invoke 中的 invoke 方法又是啥含义?
带着这三问题我们具体分析下.
2, 动态代理类是如何创建的?
具体源码我就不分析了, 我们用上面的第一种方式, 进行断点查看(括号中的就是通过 IDEA 断点显示的信息).
- //1, 获取动态代理类(proxyClazz:class com.sun.proxy.$Proxy0 )
- Class<?> proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
- //2, 获得代理类的构造函数(constructor: public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
- Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
- //3, 通过构造函数来创建动态代理对象(iHello:cn.edu.zju.grs.alufer.albert.jz.dao.MyProxy$Hello@16b4a017)
- IHello iHello = (IHello) constructor.newInstance(new HWInvocationHandler(new Hello()));
通过断点发现:
(1)第一步已经获得了代理对象, 代理对象的名称是:$Proxy0
(2)第二步我们发现代理类的构造函数需要传入一个 java.lang.reflect.InvocationHandler 类
(3)第三步通过构造函数创建对象, 构造函数里放到就是 HWInvocationHandler 类, 而它是实现 InvocationHandler 接口的, 所以没毛病.
总的来讲这三步都是通过类的反射机制来实现创建类的.
3, 如何调用 MyProxy 中的 invoke 的方法
这里就需要看下 $Proxy0.class 进行反编译后的代码
- // 代理类名为 $Proxy0, 继承 Proxy, 实现 IHello 接口
- public final class $Proxy0 extends Proxy implements IHello {
- // 变量, 都是 private static Method XXX
- private static Method m3;
- private static Method m1;
- private static Method m0;
- private static Method m2;
- // 代理类的构造函数, 其参数正是是 InvocationHandler 实例, Proxy.newInstance 方法就是通过通过这个构造函数来创建代理实例的
- public $Proxy0(InvocationHandler var1) {
- super(var1);
- }
- // 接口代理方法
- public final void sayHello() {
- try {
- // 这里才是关键, 通过这里终于知道是如何调用 MyProxy 中的 invoke 方法的
- super.h.invoke(this, m3, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- // 以下 Object 中的三个方法
- public final boolean equals(Object var1) {
- 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 int hashCode() {
- try {
- return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- public final String toString() {
- try {
- return (String)super.h.invoke(this, m2, (Object[])null);
- } catch (RuntimeException | Error var2) {
- throw var2;
- } catch (Throwable var3) {
- throw new UndeclaredThrowableException(var3);
- }
- }
- // 对变量进行一些初始化工作
- static {
- try {
- // 我们发现 m3 就代表着 sayHello()方法
- m3 = Class.forName("com.mobin.proxy.IHello").getMethod("sayHello", new Class[0]);
- m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
- m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
- m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
- } catch (NoSuchMethodException var2) {
- throw new NoSuchMethodError(var2.getMessage());
- } catch (ClassNotFoundException var3) {
- throw new NoClassDefFoundError(var3.getMessage());
- }
- }
- }
看到这里是不是和前面断点的代码对象的获得的信息是一样的.(比如代理类名就加 $Proxy0, 构造方法也和上面分析的一样). 总算明白了.
4,MyProxy 中 invoke 中的 invoke 方法又是啥含义?
其实到这一步就已经变得简单了 Object rs = method.invoke(target,args); 执行的方法就是目标类中的 sayHello()方法, 这个也就一个反射知识.
我针对这个举例:
有一个 A 类:
- package com.jincou.study;
- public class A {
- public void foo(String name) {
- System.out.println("Hello," + name);
- }
- }
TestClassLoad 类来反射调用 A 上的方法
- public class TestClassLoad {
- public static void main(String[] args) throws Exception {
- // 获得代理类
- Class<?> clz = Class.forName("com.jincou.study.A");
- // 获得代理类对象
- Object o = clz.newInstance();
- // 获得 foo 方法
- Method m = clz.getMethod("foo", String.class);
- // 执行 foot 方法
- m.invoke(o,"张三");
- }
- }
运行结果:
那么到这里你就应该明白了, 在 MyProxy 中 invoke 中的
- // 这个 method 就是 sayHello()方法, target 是指 new Hello()对象(也就是目标类对象), 参数为 null
- Object rs = method.invoke(target,args);
是不是和上面最后一步一个意思, 总算明白了.
这里顺便看下 invoke 源码:
- // 这个源码只要知道一点就是 Object... args, 它表示的是可变参数所以他可以是空或者多个
- @CallerSensitive
- public Object invoke(Object obj, Object... args)
- throws IllegalAccessException, IllegalArgumentException,
- InvocationTargetException
- {
- if (!override) {
- if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
- Class<?> caller = Reflection.getCallerClass();
- checkAccess(caller, clazz, obj, modifiers);
- }
- }
- MethodAccessor ma = methodAccessor; // read volatile
- if (ma == null) {
- ma = acquireMethodAccessor();
- }
- return ma.invoke(obj, args);
- }
总算理解完了, 这篇文章并没有很深入的讲底层源码, 看源码自己都会被绕进去, 到头来可能还是一头雾水. 这样的分析还是蛮清晰的.
最后总结下使用动态代理的五大步骤:
1)通过实现 InvocationHandler 接口来自定义自己的 InvocationHandler;
2)通过 Proxy.getProxyClass 获得动态代理类
3)通过反射机制获得代理类的构造方法, 方法签名为 getConstructor(InvocationHandler.class)
4)通过构造函数获得代理对象并将自定义的 InvocationHandler 实例对象传为参数传入
5)通过代理对象调用目标方法
想太多, 做太少, 中间的落差就是烦恼. 想没有烦恼, 要么别想, 要么多做. 中校[5]
来源: https://www.cnblogs.com/qdhxhz/p/9241412.html