设计模式之代理模式

一、概述

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】

【java设计模式】(3)---代理模式(案例解析)的更多相关文章

  1. Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景

    我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...

  2. java设计模式6——代理模式

    java设计模式6--代理模式 1.代理模式介绍: 1.1.为什么要学习代理模式?因为这就是Spring Aop的底层!(SpringAop 和 SpringMvc) 1.2.代理模式的分类: 静态代 ...

  3. Java设计模式:代理模式(转)

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一 ...

  4. 夜话JAVA设计模式之代理模式(Proxy)

    代理模式定义:为另一个对象提供一个替身或者占位符以控制对这个对象的访问.---<Head First 设计模式> 代理模式换句话说就是给某一个对象创建一个代理对象,由这个代理对象控制对原对 ...

  5. Java设计模式之代理模式(Proxy)

    前言: 最近在研究Retrofit开源框架的时候,其主要核心代码是通过注解标示参数,动态代理模式实现具体接口,反射机制进行参数解析,最终实现发送请求.其实之前在学习Xutils源码的时候,Xutils ...

  6. Java 设计模式_代理模式(2016-08-19)

    概念: 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 就是一个人或者机构代表另一个人或者机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一 ...

  7. JAVA设计模式:代理模式&& 装饰模式区别

    在前面学习了代理模式和装饰模式后,发现对两者之间有时候会混淆,因此对两者进行了区别和理解: 装饰模式你可以这样理解,就像糖一样,卖的时候商家大多要在外面包一层糖纸,其实原本还是糖. public in ...

  8. Java设计模式:代理模式(二)

    承接上文 三.计数代理 计数代理的应用场景是:当客户程序需要在调用服务提供者对象的方法之前或之后执行日志或者计数等额外功能时,就可以用到技术代理模式.计数代理模式并不是把额外操作的代码直接添加到原服务 ...

  9. java设计模式之代理模式 ,以及和java 回调机制的区别

    java 代理模式就是: 将自己要做的事交给别人去做(这个别人就是代理者,自己就是被代理者),为什么自己能做的要交给别人去做了?假如一个小学生小明,现在要写作业,但是又想玩游戏,他更想玩游戏,并且不想 ...

  10. Java设计模式 之 代理模式

    所谓的代理模式就是为其它类或对象提供一个代理以控制对这个对象的访问.那么常见的代理有远程代理,虚拟代理,保护代理,智能代理. 1. 远程代理:为一个不同地址空间的对象提供一个本地代理对象. 2. 虚拟 ...

随机推荐

  1. Echarts line折线图使用(vue)

    实现 首先引入echarts工具 // vue文件中引入echarts工具 let echarts = require('echarts/lib/echarts') require('echarts/ ...

  2. Linux磁盘和文件系统管理

    1.检测并确认新硬盘 挂载好新的硬盘设备并启动主机后,Linux系统会自动检测并加载该硬盘,无须额外安装驱动.执行“fdisk -l”命令即可查看,确认新增硬盘的设备名称和位置.作用:列出当前系统中所 ...

  3. ImCash:论拥有靠谱数字钱包的重要性!

    数字货币被盗已经不是什么新鲜事,前有交易所币安被黑客攻击,Youbit破产,后有“钓鱼邮件“盗号木马,安全对于数字货币用户来讲至关重要. 现行市场痛点:   2017年9月以太坊Parity钱包的漏洞 ...

  4. POJ 3751 JAVA

    题意: 对于给定的采用”yyyy/mm/dd”加24小时制(用短横线”-”连接)来表示日期和时间的字符串, 请编程实现将其转换成”mm/dd/yyyy”加12小时制格式的字符串,末尾加上pm或者am. ...

  5. C语言中数组使用负数值的标记

    ·引 对数组的认知 在c语言中,我们经常使用的一个结构便是数组,在最开始学习数组的时候,它被描述成这样(以一维二维数组为例):一维数组是若干个数连续排列在一起的集合,我们可以通过0-N的标记(N为数组 ...

  6. Codeforces.765F.Souvenirs(主席树)

    题目链接 看题解觉得非常眼熟,总感觉做过非常非常类似的题啊,就是想不起来=v=. 似乎是这道...也好像不是. \(Description\) 给定长为\(n\)的序列\(A_i\).\(m\)次询问 ...

  7. 操作系统PV编程题目总结一

    1.今有一个文件F供进程共享,现把这些进程分为A.B两组,规定同组的进程可以同时读文件F:但当有A组(或B组)的进程在读文件F时就不允许B组(或A组)的进程读文件F.试用P.V操作(记录型信号量)来进 ...

  8. VMware14 安装CentOS7 实现宿主机ping通虚拟机、虚拟机ping通宿主机、虚拟机能上网且能ping通百度

    本文旨在通过通过虚拟机VMware14来安装CentOS7 系统,并配置固定IP来实现在Windows系统中使用Linux环境. 本文目录: 0.本机环境 1.VMware14 初始化 1.1.安装V ...

  9. 用ttBulkCp把excel中的数据导入到timesten数据库中

    最近要做数据预处理,需要用到数据库.而且是以前从来没听说过的TimesTen. 首要目标是要把Excel里的数据,导入到TimesTen数据库中.而TimesTen在win10里用不了,于是我就在虚拟 ...

  10. 小白学习随笔the first week

    The First Week 一.计算机基础 1.软件(应用程序) 2.解释器/编译器 - 解释型语言:将代码每一行传递给计算机一行,常用编程语言python,PHP,Ruby. - 编译型语言:将代 ...