代理模式

设计模式就是一套被反复使用、多数人知晓的、经过 分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人 理解并且保证代码可靠性。代理模式就是其中一种。

给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。

代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。

在软件设计中,使用代理模式的意图也很多,比如因为安全原因需要屏蔽客户端直接访问真实对象,或者在远程调用中需要使用代理类处理远程方法调用的技术细节 (如 RMI),也可能为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。

代理模式角色分为 3 种:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;

RealSubject(真实主题角色):真正实现业务逻辑的类;

Proxy(代理主题角色):用来代理和封装真实主题;

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象;

代理模式按照职责(使用场景)来分类,至少可以分为以下几类:1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理等等。

如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。

而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。

静态代理

编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl

/***用户业务逻辑接口*/
public interface UserService {public void select();   public void update();
}public class UserServiceImpl implements UserService {  public void select() {  System.out.println("查询 selectById");}public void update() {System.out.println("更新 update");}
}

我们此时要通过静态代理对 UserServiceImpl 进行功能增强,在调用 selectupdate 之前记录一些日志。编写一个代理类 UserServiceProxy,代理类需要实现 UserService

public class UserServiceProxy implements UserService {private UserService target; // 被代理的对象public UserServiceProxy(UserService target) {this.target = target;}public void select() {before();target.select();    // 这里才实际调用真实主题角色的方法after();}public void update() {before();target.update();    // 这里才实际调用真实主题角色的方法after();}private void before() {     // 在执行方法之前执行System.out.println(String.format("log start time [%s] ", new Date()));}private void after() {      // 在执行方法之后执行System.out.println(String.format("log end time [%s] ", new Date()));}
}

编写测试类

public class Client1 {public static void main(String[] args) {UserService userServiceImpl = new UserServiceImpl();UserService proxy = new UserServiceProxy(userServiceImpl);proxy.select();proxy.update();}
}

运行结果

log start time [Thu Dec 20 14:13:25 CST 2018]
查询 selectById
log end time [Thu Dec 20 14:13:25 CST 2018]
log start time [Thu Dec 20 14:13:25 CST 2018]
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018]

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码

分析静态代理

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

改进方法

让代理类动态的生成

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

常见的字节码操作类库

  • Apache BCEL (Byte Code Engineering Library):是Java classworking广泛使用的一种框架,它可以深入到JVM汇编语言进行类操作的细节。
  • ObjectWeb ASM:是一个Java字节码操作框架。它可以用于直接以二进制形式动态生成stub根类或其他代理类,或者在加载时动态修改类。
  • CGLIB(Code Generation Library):是一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。
  • Javassist:是Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。

为了让生成的代理类与目标对象(真实主题角色)保持一致性,我们要选择使用这两种动态代理方式:

  1. 通过实现接口的方式 -> JDK动态代理
  2. 通过继承类的方式 -> CGLIB动态代理

为什么不用别的呢?因为:使用ASM对使用者要求比较高,使用Javassist会比较麻烦

JDK动态代理

JDK动态代理主要涉及两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;public class LogHandler implements InvocationHandler {Object target;  // 被代理的对象,实际的方法执行者public LogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object result = method.invoke(target, args);  // 调用 target 的 method 方法after();return result;  // 返回方法的执行结果}// 调用invoke方法之前执行private void before() {System.out.println(String.format("log start time [%s] ", new Date()));}// 调用invoke方法之后执行private void after() {System.out.println(String.format("log end time [%s] ", new Date()));}
}

编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class Client2 {public static void main(String[] args) throws IllegalAccessException, InstantiationException {// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 1. 创建被代理的对象,UserService接口的实现类UserServiceImpl userServiceImpl = new UserServiceImpl();// 2. 获取对应的 ClassLoaderClassLoader classLoader = userServiceImpl.getClass().getClassLoader();// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,Class[] interfaces = userServiceImpl.getClass().getInterfaces();// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用//     这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImplInvocationHandler logHandler = new LogHandler(userServiceImpl);/*5.根据上面提供的信息,创建代理对象 在这个过程中,a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码b.然后根据相应的字节码转换成对应的class,c.然后调用newInstance()创建代理实例*/UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);// 调用代理的方法proxy.select();proxy.update();// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");}
}

运行结果

log start time [Thu Dec 20 16:55:19 CST 2018]
查询 selectById
log end time [Thu Dec 20 16:55:19 CST 2018]
log start time [Thu Dec 20 16:55:19 CST 2018]
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018]

InvocationHandler 和 Proxy 的主要方法介绍

java.lang.reflect.InvocationHandler

Object invoke(Object proxy, Method method, Object[] args) 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用

java.lang.reflect.Proxy

static InvocationHandler getInvocationHandler(Object proxy) 用于获取指定代理对象所关联的调用处理器

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回指定接口的代理类

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法

static boolean isProxyClass(Class<?> cl) 返回 cl 是否为一个代理类

分析JDK动态代理

  • UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
  • 而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法

CGLIB动态代理

编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

public class UserDao {public void select() {System.out.println("UserDao 查询 selectById");}public void update() {System.out.println("UserDao 更新 update");}
}

编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调

import java.lang.reflect.Method;
import java.util.Date;public class LogInterceptor implements MethodInterceptor {/*** @param object 表示要进行增强的对象* @param method 表示拦截的方法* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用* @return 执行结果* @throws Throwable*/@Overridepublic Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {before();Object result = methodProxy.invokeSuper(object, objects);   // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法after();return result;}private void before() {System.out.println(String.format("log start time [%s] ", new Date()));}private void after() {System.out.println(String.format("log end time [%s] ", new Date()));}
}

测试

import net.sf.cglib.proxy.Enhancer;public class CglibTest {public static void main(String[] args) {DaoProxy daoProxy = new DaoProxy(); Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Dao.class);  // 设置超类,cglib是通过继承来实现的enhancer.setCallback(daoProxy);Dao dao = (Dao)enhancer.create();   // 创建代理类dao.update();dao.select();}
}

运行结果

log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:06:40 CST 2018]
log start time [Fri Dec 21 00:06:40 CST 2018]
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018]

还可以进一步多个 MethodInterceptor 进行过滤筛选

public class LogInterceptor2 implements MethodInterceptor {@Overridepublic Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {before();Object result = methodProxy.invokeSuper(object, objects);after();return result;}private void before() {System.out.println(String.format("log2 start time [%s] ", new Date()));}private void after() {System.out.println(String.format("log2 end time [%s] ", new Date()));}
}// 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
public class DaoFilter implements CallbackFilter {@Overridepublic int accept(Method method) {if ("select".equals(method.getName())) {return 0;   // Callback 列表第1个拦截器}return 1;   // Callback 列表第2个拦截器,return 2 则为第3个,以此类推}
}
再次测试public class CglibTest2 {public static void main(String[] args) {LogInterceptor logInterceptor = new LogInterceptor();LogInterceptor2 logInterceptor2 = new LogInterceptor2();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserDao.class);   // 设置超类,cglib是通过继承来实现的enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE});   // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理enhancer.setCallbackFilter(new DaoFilter());UserDao proxy = (UserDao) enhancer.create();   // 创建代理类proxy.select();proxy.update();}
}

运行结果

log start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 查询 selectById
log end time [Fri Dec 21 00:22:39 CST 2018]
log2 start time [Fri Dec 21 00:22:39 CST 2018]
UserDao 更新 update
log2 end time [Fri Dec 21 00:22:39 CST 2018]

CGlib 对接口实现代理

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;/*** 创建代理类的工厂 该类要实现 MethodInterceptor 接口。* 该类中完成三样工作:* (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象* (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类* (3)定义回调接口方法。对目标类的增强这在这里完成*/
public class CGLibFactory implements MethodInterceptor {// 声明目标类的成员变量private UserService target;public CGLibFactory(UserService target) {this.target = target;}// 定义代理的生成方法,用于创建代理对象public UserService myCGLibCreator() {Enhancer enhancer = new Enhancer();// 为代理对象设置父类,即指定目标类enhancer.setSuperclass(UserService.class);/*** 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,* 是因为MethodIntecepter接口继承自Callback,是其子接口*/enhancer.setCallback(this);return (UserService) enhancer.create();// create用以生成CGLib代理对象}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("start invoke " + method.getName());Object result = method.invoke(target, args);System.out.println("end invoke " + method.getName());return result;}
}

分析CGLIB 动态代理类

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

三种代理优缺点总结

静态代理

​ 代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理 对象而真正调用的是 Real Object

  • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点:不同的接口要有不同的代理类实现,会很冗余

JDK 动态代理

  • 为了解决静态代理中,生成大量的代理类造成的冗余;
  • JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
  • jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
  • jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
  • 优点:解决了静态代理中冗余的代理实现类问题。
  • 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理

  • 由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
  • CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
  • 实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
  • 但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
  • 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
  • 优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
  • 缺点:技术实现相对难理解些。

Java~Java代理模式相关推荐

  1. java设计模式代理模式_Java中的代理设计模式

    java设计模式代理模式 代理对象或代理对象为另一个对象提供占位符,以控制对该对象的访问. 代理充当原始对象的轻量级版本或简化版本. 它支持与原始对象相同的操作,但可以将那些请求委托给原始对象以实现它 ...

  2. 黑马程序员——Java的代理模式

    ------- android培训.java培训.期待与您交流! ---------- Java中代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接 ...

  3. Java设计模式(代理模式-模板方法模式-命令模式)

    Java设计模式Ⅴ 1.代理模式 1.1 代理模式概述 1.2 静态代理 1.2.1 静态代理概述 1.2.2 代码理解 1.3 动态代理之JDK代理 1.3.1 动态代理之JDK代理概述 1.3.2 ...

  4. Java设计模式—代理模式

    1. 什么是代理模式? 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问. 2. 代理模式有什么好处? 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标 ...

  5. java设计模式-代理模式初探

    根据圣思园的视频,整理的java代理模式. 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问. 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间 ...

  6. 通俗易懂的讲解一下Java的代理模式

    一.基本概念 代理模式是对象的结构模式. 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(接口的引用) 二.静态代理 静态代理是指,代理类在程序运行前就已经定义好,其与**目标类 ...

  7. Java的代理模式之静态代理和动态代理

    文章目录 静态代理 动态代理 jdk生成代理对象 cglib代理 代理模式简介: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目 ...

  8. JAVA设计模式 - 代理模式

    在对象的一个业务方法完成之后, 有时候我们可能需要去添加一些新的功能(前置校验等). 但我们又不想更改原来的代码 , 代理模式就为我们提供了一种解决方案 . 1 . 代理模式的定义 代理模式就是在不改 ...

  9. java动态代理模式初解

    第一部分 1.代理模式的作用和定义: 为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用.  2.代 ...

  10. Java业务代理模式~

    业务代理模式用于解耦表示层和业务层. 它基本上用于减少表示层代码中的业务层代码的通信或远程查找功能.在业务层有以下实体. 客户端(Client) - 表示层代码可以是JSP,servlet或UI ja ...

最新文章

  1. QT设置坐标轴XY轴显示范围方法
  2. ACM入门之【组合数】
  3. Swoole Interview
  4. [图解]ARP协议(一)
  5. 垃圾优先型垃圾回收器调优
  6. python调用robotframework_robotframework+python接口自动化的点滴记录(2)
  7. ConcurrentHashMap 源码分析
  8. ORA-12518: TNS: 监听程序无法分发客户机连接
  9. 笔记本电脑桌面的计算机不见了,小编为你分析win7系统笔记本电脑桌面计算机图标不见了的设置方案....
  10. setInterval()与setTimeout()计时器
  11. 家庭理财项目 分析
  12. 运放系列:电压跟随器
  13. JVM-GC日志分析
  14. CircleImageView用法及源码解析(雷惊风)
  15. 基于在线学习行为的评价模型的设计与实现
  16. win10服务器权限修改时间,Win10没有权限修改系统时间如何解决?
  17. 树和二叉树(TreeBinary Tree)
  18. STM32:USART串口外设(内含:1.USART简介+2.USART基本结构+3.数据帧+4.起始位侦测+5.数据采样+6.波特率发生器)
  19. BI Publisher rtf模板页眉页脚
  20. Python - 文本处理模块

热门文章

  1. SLIC——代码、改进
  2. 效率倍增!12 个必知必会的 Python 数据处理技巧!
  3. java并发编程艺术——基础篇
  4. BJ模拟:医院(支配树)
  5. 四旋翼自主飞行器探测跟踪系统项目的随笔
  6. 等值连接mysql_数据库等值连接,左连接,右连接的区别
  7. AT8637S(PHS/EN输入接口单通道0.8A低压H桥IC)
  8. MYSQL环境搭建(windows)
  9. 关于电阻屏与电容屏的区别
  10. c语言用sort函数选择排序,简单选择排序(Simple Selection Sort)的C语言实现