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):代理模式(静态代理)点我下载三种模式源码 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. 静态代理由 业务实现类.业务代理类 两部分组成 ...
随机推荐
- BZOJ[4127] Abs
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=4127 不算难的样子,才见过此类模型. 首先可以发现每次修改只增不减,那么这$O(n)$的负数最 ...
- 安装JDK+Eclipse+Maven+Git/Gitee(windows系统和linux系统)
1. 安装JDK 官网下载 下载java SE 下载 Java SE 7 1.1 windows配置jdk windows配置jdk 1.2 Ubuntu配置jdk Ubuntu 安装 JDK 7 / ...
- UT源码 045
(3)设计佣金问题的程序 commission方法是用来计算销售佣金的需求,手机配件的销售商,手机配件有耳机(headphone).手机壳(Mobile phone shell).手机贴膜(Cellp ...
- JAVA基础--数组的应用04
一.数组的简单应用 1. 数组平均值 例子:求下列数字的平均值:1.0,2.4,3.5,4.3,5.1,6.8,7.2,8,9,10 package cn.haile.array; /** * 练习数 ...
- 安装 statconn 使R与c#交互
很久以前完成过,但是最近重新折腾发现全忘了,所以记下来. 1.安装 R 2.安装 R studio 3.去 http://rcom.univie.ac.at/download.html 下载 stat ...
- IT兄弟连 Java Web教程 经典案例2
案例需求:写一个用户登录的html页面有账号和密码,并在登录的Servlet中获取登录的账号和密码,如果账号是abc密码是123则向浏览器输出登录成功,否则向浏览器输出登录失败. 案例实现: ● h ...
- Integer Cache(带你脱坑)
Integer Cache 废话不多说----->直接上代码: public class IntegerDemo { public static void main(String[] args) ...
- 洛谷 P1589 泥泞路
题目描述 暴雨过后,FJ的农场到镇上的公路上有一些泥泞路,他有若干块长度为L的木板可以铺在这些泥泞路上,问他至少需要多少块木板,才能把所有的泥泞路覆盖住. 输入输出格式 输入格式: 第一行为正整数n( ...
- iOS WKWebView 退出后停止播放音频/视频
带有<video>或者<audio>标签的H5网页在播放音频视频时,退出webview后不会自动停止播放,手动处理一下. 1.注入使网页停止音频.视频播放的JS代码(Swift ...
- Java | 基础归纳 | set
用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复.