Java代理模式精讲之静态代理,动态代理,CGLib代理
代理(Proxy)是一种设计模式,通俗的讲就是通过别人达到自己不可告人的目的(玩笑)。
如图:

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象
这三个代理模式,就像是更新换代,越来越先进。动态代理解决了静态代理必须同目标对象继承同一个接口或类,CGlib解决了动态代理目标对象必须继承一个接口的问题。
一.静态代理
条件:代理对象必须和目标对象继承同一个接口或者类
代码如下:
/*定义公共接口 */
public interface IUserDao {
void save();
} /*接口实现目标对象*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
} /*代理对象,静态代理*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
} /** * 测试类 */
public class App {
public static void main(String[] args) {
//创建目标对象
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系,生成代理对象
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//执行的是代理的方法
}
}
静态代理总结:
1.优点:可以做到在不修改目标对象的情况下,为目标对象添加功能。
2.缺点:
1.首先代理对象必须同目标对象继承同一接口或类;
2.如果需要增加一个需要代理的方法,代理者的代码也必须改动进而适配新的操作;
3.如果需要代理者代理另外一个操作者,同样需要对代理者进行扩展并且更加麻烦。
二.JDK动态代理
有人想到可以用策略模式和工厂模式分别解决上面静态代理所引起的两个问题,但是,有没有更加巧妙的方法呢?首先,我们了解一下 Java 代码的执行过程。
理解 Java 代码执行流程可以从根本上理解动态代理的实现原理:

生成自己的 .class 文件:
当然我们不用手动去一点一点拼装 .class 文件,目前比较常用的字节码生成工具有ASM和Javassist,根据这个思路,生成 .class 文件的过程如下:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class Test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建类 AutoGenerateClass
CtClass cc= pool.makeClass("com.guanpj.AutoGenerateClass");
//定义 show 方法
CtMethod method = CtNewMethod.make("public void show(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm just test generate .class file by javassit.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("D://temp");
}
}
生成的 .class 文件如下

反编译后查看内容():
//Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
package com.guanpj;
public class AutoGenerateClass {
public voidshow() {
System.out.println("I'm just test generate .class file by javassit.....");
}
publicAutoGenerateClass() {
}
}
可以看到,javassit 生成的类中,除了 show() 方法之外还默认生成了一个无参的构造方法。
自定义类加载器加载:
为了能够让自定的类被加载出来,我们自定义了一个类加载器来加载指定的 .class 文件:
public class CustomClassLoader extends ClassLoader {
publicCustomClassLoader() {}
protected Class findClass(String className) {
String path ="D://temp//"+ className.replace(".","//") +".class";
byte[] classData = getClassData(path);
return defineClass(className, classData, 0, classData.length);
}
private byte[] getClassData(String path) {
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
接着,用 ClassLoader 加载刚才生成的 .class 文件,然后动态执行show()方法:
public class TestLoadClass {
public static void main(String[] args) throws Exception {
CustomClassLoader classLoader = new CustomClassLoader();
Class clazz = classLoader.findClass("com.guanpj.AutoGenerateClass");
Object object = clazz.newInstance();
Method showMethod = clazz.getMethod("show", null);
showMethod.invoke(object, null);
}
}
成功执行了 show 方法!

利用 JDK 中的 Proxy 类进行动态代理:
使用动态代理的初衷是简化代码,不管是 ASM 还是 Javassist,在进行动态代理的时候操作还是不够简便,这也违背了我们的初衷。我们来看一下怎么 InvocationHandler 怎么做:
代理工厂类:
/** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
} //给目标对象生成代理对象并返回
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
测试代码为:
/** * 测试类 */
public class App {
public static void main(String[] args) {
// 目标对象
IUserDao target = new UserDao();
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// 执行方法
proxy.save();
}
}
JDK动态代理总结:
优点:不需要实现接口,代理对象的生成是利用JDK的API,在内存中生存代理对象。
缺点:目标对象必须继承一个接口,因为在创建的时候需要指定目标对象的接口
代理对象的包:java.lang.reflect.Proxy
JDK实现代理只需要使用Proxy.newProxyInstance()方法,但是该方法需要接收三个参数,完整的写法是:
ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
三.CGLIB动态代理
Cglib代理,目标对象不需要继承一个接口,它是已目标子类的方式实现代理的,所以Cglib代理也叫子类代理
代码示例(目标类):
/**
* 目标对象,没有实现任何接口
*/
public class UserDao { public void save() {
System.out.println("----已经保存数据!----");
}
}
cglib代理类:
/** * Cglib子类代理工厂 * 对UserDao在内存中动态构建一个子类对象 */
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
测试类:
/** * 测试类 */
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
使用 CGLIB 进行动态代理的过程分为四个步骤:
1.使用 MethodInterceptorImpl 实现 MethodInterceptor 接口,并在 intercept 方法中进行额外的操作
2.创建增强器 Enhance 并设置被代理的操作类
3.生成代理类
4.调用代理对象的操作方法
总结
无论是静态代理还是动态代理,都能一定程度地解决我们的问题,在开发过程中可以根据实际情况选择合适的方案。总之,没有好不好的方案,只有适不适合自己项目的方案,我们应该深入研究和理解方案背后的原理,以便能够应对开发过程中产生的变数。
在Spring的AOP编程中,如果加入容器的目标对象有实现的接口,用JDK代理,如果没有实现接口用Cglib代理,后面会介绍Spring内容,掌握动态代理会更容易理解Spring的AOP编程
Java代理模式精讲之静态代理,动态代理,CGLib代理的更多相关文章
- 代理模式精讲(手写JDK动态代理)
代理模式是一种架构型模式,表现出来就是一个类代表另一个类的功能,一般用在想对访问一个类的时候做一些控制,同时又不想影响正常的业务,这种代理模式在现实的生活中应用的也非常的广泛,我用穷举法给举几个好理解 ...
- 代理模式详解:静态代理+JDK/CGLIB 动态代理实战
1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...
- 第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP
第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP 设置代理ip只需要,自定义一个中间件,重写process_request方法, request ...
- 总结:Java 集合进阶精讲2-ArrayList
知识点:Java 集合框架图 总结:Java 集合进阶精讲1 总结:Java 集合进阶精讲2-ArrayList 初探: ArrayList底层结构是数组,是List接口的 可变数组的实现,所以会占用 ...
- 总结:Java 集合进阶精讲1
知识点:Java 集合框架图 总结:Java 集合进阶精讲1 总结:Java 集合进阶精讲2-ArrayList 集合进阶1---为集合指定初始容量 集合在Java编程中使用非常广泛,当容器的量变得非 ...
- Java 代理模式(一) 静态代理
转自: http://www.cnblogs.com/mengdd/archive/2013/01/30/2883468.html 代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的 ...
- 代理模式(Proxy)--静态代理
1,代理模式的概念 代理模式:为其他对象提供一种代理,以控制对这个对象的访问(代理对对象起到中介的作用,可去掉功能服务或者添加额外的服务) 2,代理模式的分类 (1)远程代理:类似于客户机服务器模式 ...
- spring AOP 代理(静态与动态+使用cglib实现)
一.没有代理模式 缺点: 1.工作量特别大,如果项目中有多个类,多个方法,则要修改多次. 2.违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护 ...
- 分享知识-快乐自己:三种代理(静态、JDK、CGlib 代理)
1):代理模式(静态代理)点我下载三种模式源码 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. 静态代理由 业务实现类.业务代理类 两部分组成 ...
随机推荐
- JAVA 布局控制
在Java里该方法是安一个组件到一个窗体中去,它不同我们使用过的其它GUI系统.首先,它是全代码的:没有控制安放组件的“资源”.其次,该方法的组件被安放到一个被“布局管理器”控制的窗体中,由“布局管理 ...
- 重新安装VMware10提示"The Msi '' Failed"问题解决方案
想把虚拟机软件升级以下,没想到卸载的时候不干净,再安装的时候总提示让我先卸载旧版本但实际上旧版本已经卸载过了,这里又没法再卸载一次,所以就提示”The MSI '' failed“ 显然,安装程序还是 ...
- InstallShield 12 豪华版+破解版 下载
InstallShield 12 豪华版+破解版 下载 2009-07-09 19:18:30| 分类: 默认分类|字号 订阅 InstallShield 12 豪华版+破解版 下载 下载方 ...
- A. Transformation: from A to B
time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...
- CodeForces 718A Efim and Strange Grade (贪心)
题意:给定一个浮点数,让你在时间 t 内,变成一个最大的数,操作只有把某个小数位进行四舍五入,每秒可进行一次. 析:贪心策略就是从小数点开始找第一个大于等于5的,然后进行四舍五入,完成后再看看是不是还 ...
- 关于 android 中 postDelayed方法的讲解 (转载)
转自:http://blog.csdn.net/xiabo851205/article/details/7991529 这是一种可以创建多线程消息的函数 使用方法: 1,首先创建一个Handler对象 ...
- (水题)洛谷 - P1308 - 统计单词数
https://www.luogu.org/problemnew/show/P1308 简单哈希一下判断,练练手. 注意fgets()的用法,第一个参数传存储位置,第二个参数传内存上限,第三个传std ...
- bzoj 2064: 分裂【状压dp】
参考:https://www.cnblogs.com/liu-runda/p/6019426.html 有点神奇 大概就是显然最直观的转移是全部合起来再一个一个拆,是n+m次,然后设f[i][j]为分 ...
- rabbitMQ的使用
介绍 一款消息队列数据库,类似redis发布订阅,但是rq 做了功能完善和数据持久化.在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应 ...
- JS判断字符串长度(英文占1个字符,中文汉字占2个字符)
//计算字符串长度(英文占1个字符,中文汉字占2个字符) 方法一: String.prototype.gblen = function() { var len = 0; for (var i=0; i ...