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中的Cloneable接口与深拷贝、浅拷贝
Cloneable接口是一个标记接口,也就是没有任何内容,定义如下: 这里分析一下这个接口的用法,clone方法是在Object种定义的,而且是protected型的,只有实现了这个接口,才可以在该类 ...
- XMPP即时通讯基础知识
XMPP参考 一.定义 XMPP 是一种很类似于http协议的一种数据传输协议,它的过程就如同“解包装--〉包装”的过程,用户只需要明白它接受的类型,并理解它返回的类型,就可以很好的利用xmpp来进行 ...
- A. Mishka and Game
time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...
- ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 06. Controller 返回View
Controller父类会提供很多上下文的相关信息,还提供了很多封装的方法 返回的对象要求实现了IActionResult接口 继承父类,并引入命名空间 写this点就出现很多东西,这些就是上下文的信 ...
- lightoj 1033【区间DP/LCS】
题意: 给你一个长度<=100的字符串. 然后你可以在任何位置插入字符,问最少插入几个构成回文. 思路: 1.长度-LCS: 2.区间DP; 我保证小的区间是一个回文,然后枚举区间,构成大区间, ...
- bzoj 1058: [ZJOI2007]报表统计【set】
我想写FHQtreap的!是set自己跑进代码的!因为太好写了 是有点慢--洛谷上不吸氧会T一个点 就是,用一个set p维护所有点值,ans维护MIN_SORT_GAP的答案,每次insert一个点 ...
- P5166 xtq的口令
传送门 这题要是搞懂在干什么其实不难(虽然某个花了几个小时才搞明白的家伙似乎没资格这么说--) 假设所有人都没有听到老师的命令,我们从左到右考虑,对于当前的人,如果它没有观察者,那么肯定要让它听到老师 ...
- Nginx(四) nginx+consul+upasync 在ubnutu18带桌面系统 实现动态负载均衡
1.1 什么是动态负载均衡 传统的负载均衡,如果Upstream参数发生变化,每次都需要重新加载nginx.conf文件,因此扩展性不是很高,所以我们可以采用动态负载均衡,实现Upstream可配置化 ...
- 使用ansible对远程主机上的ssh公钥进行批量分发
使用ansible对远程主机上的ssh公钥进行批量分发或者是删除修改操作 ansible内置了一个authorized_key模块,这个模块很好用,我们使用这个模块可以对远程 主机上的ssh公钥进行批 ...
- 线段树/树状数组 POJ 2182 Lost Cows
题目传送门 题意:n头牛,1~n的id给它们乱序编号,已知每头牛前面有多少头牛的编号是比它小的,求原来乱序的编号 分析:从后往前考虑,最后一头牛a[i] = 0,那么它的编号为第a[i] + 1编号: ...