代理(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 代码执行流程可以从根本上理解动态代理的实现原理:

               

JVM 在运行时通过编译器将.java文件编译为.class 文件,首先通过 ClassLoader 将 .class 文件以二进制的形式解析并生成实例以供调用,我们的代码执行逻辑是在 JVM 的运行期系统中进行工作的,那么,我们可不可以在自己的代码里面按照 .class 的格式生成自己的 .class 文件,进而调用自定义的 ClassLoader 将其加载出来呢?答案是肯定的,这样我们就可以动态地创建一个类了。
                 

生成自己的 .class 文件

当然我们不用手动去一点一点拼装 .class 文件,目前比较常用的字节码生成工具有ASMJavassist,根据这个思路,生成 .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代理的更多相关文章

  1. 代理模式精讲(手写JDK动态代理)

    代理模式是一种架构型模式,表现出来就是一个类代表另一个类的功能,一般用在想对访问一个类的时候做一些控制,同时又不想影响正常的业务,这种代理模式在现实的生活中应用的也非常的广泛,我用穷举法给举几个好理解 ...

  2. 代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  3. 第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP

    第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP 设置代理ip只需要,自定义一个中间件,重写process_request方法, request ...

  4. 总结:Java 集合进阶精讲2-ArrayList

    知识点:Java 集合框架图 总结:Java 集合进阶精讲1 总结:Java 集合进阶精讲2-ArrayList 初探: ArrayList底层结构是数组,是List接口的 可变数组的实现,所以会占用 ...

  5. 总结:Java 集合进阶精讲1

    知识点:Java 集合框架图 总结:Java 集合进阶精讲1 总结:Java 集合进阶精讲2-ArrayList 集合进阶1---为集合指定初始容量 集合在Java编程中使用非常广泛,当容器的量变得非 ...

  6. Java 代理模式(一) 静态代理

    转自: http://www.cnblogs.com/mengdd/archive/2013/01/30/2883468.html 代理模式 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的 ...

  7. 代理模式(Proxy)--静态代理

    1,代理模式的概念 代理模式:为其他对象提供一种代理,以控制对这个对象的访问(代理对对象起到中介的作用,可去掉功能服务或者添加额外的服务) 2,代理模式的分类 (1)远程代理:类似于客户机服务器模式 ...

  8. spring AOP 代理(静态与动态+使用cglib实现)

    一.没有代理模式 缺点: 1.工作量特别大,如果项目中有多个类,多个方法,则要修改多次. 2.违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护 ...

  9. 分享知识-快乐自己:三种代理(静态、JDK、CGlib 代理)

    1):代理模式(静态代理)点我下载三种模式源码 代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. 静态代理由 业务实现类.业务代理类 两部分组成 ...

随机推荐

  1. 国产免费的visio替代品edraw mind map,用来话流程图够用了

    最新版Edraw Mind Map可以创建基本的思维导图.气泡图和基本流程图,提供了强大的设计功能,包括丰富设计素材.全面的页面布局定义.预置的符号库与绘图工具等.创建的图形,可以导出为常用图像格式. ...

  2. curl测试dns解析时间及tcp连接时间

    1.用Linux下的curl命令测量网络请求(分号是分隔符,可以是其他符号): curl -o /dev/null -s -w %{time_connect}:%{time_starttransfer ...

  3. linux下sprintf_s函数的替代(转载)

    转自:http://www.cnblogs.com/yeahgis/archive/2013/01/22/2872179.html windows平台下线程安全的格式化字符串函数sprint_s并非标 ...

  4. 51nod 1247 可能的路径(gcd)

    传送门 题意 略 分析 有以下结论 \(1.(x,y)->(y,x)\) \(2.(x,y)->(a,b)==>(a,b)->(x,y)\) 证明 做如下变换 \((a,b)- ...

  5. AssetDatabase文档翻译

    AssetDatabase是一个能获取工程资源的API,它提供一些方法比如:查找.加载.创建.删除和修改.Unity需要了解工程文件夹里的所有改变,假如想要获取或修改资源文件,就使用 AssetDat ...

  6. bzoj 2251: [2010Beijing Wc]外星联络【SA】

    先求SA,然后按字典序从小到大枚举子串,每到一个后缀从长到短枚举子串(跳过长为he[i]的和前一段重复的子串),然后维护一个点p,保证i~p之间最小的he>=当前枚举长度,p是单调向右移的 然后 ...

  7. Mac上搭建直播服务器Nginx+rtmp,实现手机推流、拉流

    转载自http://www.cnblogs.com/jys509/p/5649066.html 简介 nginx是非常优秀的开源服务器,用它来做hls或者rtmp流媒体服务器是非常不错的选择,本人在网 ...

  8. 51Nod 1116 K进制下的大数(暴力枚举)

    #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> us ...

  9. hdu1829&&poj2492 A Bug's Life 基础种类并查集

    把性别相同的虫子放在同一个集合,然后每读入一对虫子号,判断它们在不在同一集合,在则同性别,不在则继续 #include <cstdio> #include <cstring> ...

  10. SpringBoot整合Spring Data Solr

    此文不讲solr相关,只讲整合,内容清单如下 1. maven依赖坐标 2. application.properties配置 3. Java Config配置 1. maven坐标 <depe ...