转自黄勇大神的博客。http://my.oschina.net/huangyong/blog/159788

Proxy,也就是“代理”了。意思就是,你不用去做,别人代替你去处理。

它在程序开发中起到了非常重要的作用,比如传说中的 AOP(面向切面编程),就是针对代理的一种应用。此外,在设计模式中,还有一个“代理模式”。在公司里要上外网,要在浏览器里设置一个 HTTP 代理。wocao,代理无处不在啊,不搞明白,岂不是很没面子?

凡是都要由浅入深,学习也不例外。先来一个 Hello World 吧:

1
2
3
4
public interface Hello {
 
    void say(String name);
}

这是一个 Hello 接口,不用解释了,大家都懂的。赶紧上实现类吧:

1
2
3
4
5
6
7
public class HelloImpl implements Hello {
 
    @Override
    public void say(String name) {
        System.out.println("Hello! " + name);
    }
}

wocao!这简直就是 So easy 嘛!但是有个问题:如果要在 println() 方法前面和后面分别需要处理一些逻辑,怎么做呢?把这些逻辑写死在 say() 方法里面吗?肯定不够优雅啦,菜鸟一般这样干,作为一名资深的程序员,我坚决不能这样做!

我要用代理!写一个 HelloProxy 类,让它去调用 HelloImpl 的 say() 方法,在调用的前后分别进行逻辑处理不就行了吗?赶紧搞一个吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class HelloProxy implements Hello {
 
    private HelloImpl helloImpl;
 
    public HelloProxy() {
        helloImpl = new HelloImpl();
    }
 
    @Override
    public void say(String name) {
        before();
        helloImpl.say(name);
        after();
    }
 
    private void before() {
        System.out.println("Before");
    }
 
    private void after() {
        System.out.println("After");
    }
}

我将 HelloProxy 类实现了 Hello 接口(和 HelloImpl 实现相同的接口),并且在构造方法中 new 出一个 HelloImpl 类的实例。这样一来,我就可以在 HelloProxy 的 say() 方法里面去调用 HelloImpl 的 say() 方法了。更重要的是,我还可以在调用的前后分别加上 before() 与 after() 方法,在这两个方法里去实现那些前后逻辑。

用一个 main 方法来测试一下吧:

1
2
3
4
public static void main(String[] args) {
    Hello helloProxy = new HelloProxy();
    helloProxy.say("Jack");
}

运行后,打印出:

Before
Hello! Jack
After

轻而易举,我就写出了这么优雅的代码(暗自小嗨了一把)。

不久后,我在一本设计模式的书上看到,原来我写的这个 HelloProxy 就是所谓的“代理模式”啊!我只能说,自己和 GoF(四人帮)的距离有接近了一点。

于是我疯狂的使用“代理模式”,项目中到处都有 XxxProxy 的声影。直到有一天,架构师看到了我的代码,他惊呆了!他对我说:“你怎么这么喜欢用静态代理呢?你就不会用动态代理吗?给我全都重构了!”。

我表面上点了点头,说:“好的!”。其实我根本都不知道什么是“静态代理”,什么又是“动态代理”。我继续翻开我那本垫桌脚的设计模式,深入得研究了一番,最后我才明白,原来我一直用的都是“静态代理”啊,怪不得架构师说我到处都是 XxxProxy 类了。我好恨我自己!一定要将这些垃圾 Proxy 都重构为“动态代理”。

于是我就是用 JDK 给我们提供的动态代理方案,写了一个 DynamicProxy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DynamicProxy implements InvocationHandler {
 
    private Object target;
 
    public DynamicProxy(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
 
    ...
}

在 DynamicProxy 类中,我定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化(现在流行叫“注入”了,我觉得叫“射入”也不错哦!构造函数初始化叫“正着射”,所以 reflect 方式就叫“反着射”,简称“反射”)。

言归正传,DynamicProxy 实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法,参数不做解释,望文生义吧,是 JRE 给我们“射”进来的。在该方法中,直接通过反射去 invoke method,在调用前后分别处理 before 与 after,最后将 result 返回。

写一个 main() 方法看看实际怎么用吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
    Hello hello = new HelloImpl();
 
    DynamicProxy dynamicProxy = new DynamicProxy(hello);
 
    Hello helloProxy = (Hello) Proxy.newProxyInstance(
        hello.getClass().getClassLoader(),
        hello.getClass().getInterfaces(),
        dynamicProxy
    );
 
    helloProxy.say("Jack");
}

没错,意思就是,用我写的这个通用的 DynamicProxy 类去包装 HelloImpl 实例,然后再调用 JDK 给我们提供的 Proxy 类的工厂方法 newProxyInstance() 去动态地创建一个 Hello 接口的代理类,最后调用这个代理类的 say() 方法。

运行一下,结果和以前一样,动态代理成功了。其实,动态代理就是帮我们自动生成 XxxProxy 类的法宝啊!

要注意的是,Proxy.newProxyInstance() 方法的参数实在是让我“蛋碎一地”!

参数1:ClassLoader
参数2:该实现类的所有接口
参数3:动态代理对象

调用完了还要来一个强制类型转换一下。

wocao!这一坨 shi 一定要想办法封装一下,避免再次发生到处都是 Proxy.newProxyInstance(),这样架构师又要骂我了。于是我将这个 DynamicProxy 重构了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DynamicProxy implements InvocationHandler {
 
    ...
 
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
 
    ...
}

我在 DynamicProxy 里添加了一个 getProxy() 方法,无需传入任何参数,将刚才所说的那一坨 shi,放在这个方法中,并且该方法返回一个泛型类型,就不会强制类型转换了。方法头上加那个 @SuppressWarnings("unchecked") 注解表示忽略编译时的警告(因为 Proxy.newProxyInstance() 方法返回的是一个 Object,这里我强制转换为 T 了,这是向下转型,IDE 中就会有警告,编译时也会出现提示,很烦)。

好了,这下子使用 DynamicProxy 就简单了吧:

1
2
3
4
5
6
public static void main(String[] args) {
    DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
    Hello helloProxy = dynamicProxy.getProxy();
 
    helloProxy.say("Jack");
}

确实简单用 2 行代理就去掉了前面的 7 行代码(省了 5 行),架构师看到了这样的代码肯定会表扬我!

经过一番代码重构后,我提交了所有的代码,架构师看到了,没有吱声…… 可我总算学会了动态代理。

用了这个 DynamicProxy 以后,我觉得它还是非常爽的,爽的地方是,接口变了,这个动态代理类不用动。而静态代理就不一样了,接口变了,实现类还要动,代理类也要动。但我也发现动态代理并不是“万灵丹”,它也有搞不定的时候,比如说,我要代理一个没有任何接口的类,它就没有勇武之地了!这就是 JDK 给我们提供的动态代理,让我不知道该说什么了。

于是我又开始调研,能否代理没有接口的类呢?终于让我找到了这颗“银弹”!那就是 CGLib 这个类库。虽然它看起来不太起眼,但 Spring、Hibernate 这样牛逼的开源框架都用到了它。它就是一个在运行期间动态生成字节码的工具,也就是动态生成代理类了。说起来好高深,实际用起来一点都不难。我再搞一个 CGLibProxy 吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CGLibProxy implements MethodInterceptor {
 
    public <T> T getProxy(Class<T> cls) {
        return (T) Enhancer.create(cls, this);
    }
 
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }
 
    ...
}

需要实现 CGLib 给我们提供的 MethodInterceptor 实现类,并填充 intercept() 方法。方法中最后一个 MethodProxy 类型的参数 proxy,值得注意!CGLib 给我们提供的是方法级别的代理,也可以理解为对方法的拦截(这不就是传说中的“方法拦截器”吗?)。这个功能对于我们这群屌丝程序员而言,如同雪中送炭啊,此乃神器也!我们直接调用 proxy 的 invokeSuper() 方法,将被代理的对象 obj 以及方法参数 args 传入其中即可。

与 DynamicProxy 类似,我在 CGlibProxy 中也添加了一个泛型的 getProxy() 方法,便于我们可以快速地获取自动生成的代理对象。还是用一个 main() 方法来描述吧:

1
2
3
4
5
6
public static void main(String[] args) {
    CGLibProxy cgLibProxy = new CGLibProxy();
    HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class);
 
    helloProxy.say("Jack");
}

仍然通过 2 行代码就可以返回代理对象了,与 JDK 动态代理不同的是,这里不需要任何的接口信息,对谁都可以生成动态代理对象(不管它是“屌丝”还是“高富帅”)。说它是神器,过分吗?

我一向都是以追求完美而著称,2 行代码返回代理对象,我觉得还是有些多余,我不想总是去 new 这个 CGLibProxy 对象,最好 new 一次,以后随时拿随时用。于是我想到了“单例模式”:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CGLibProxy implements MethodInterceptor {
 
    private static CGLibProxy instance = new CGLibProxy();
     
    private CGLibProxy() {
    }
 
    public static CGLibProxy getInstance() {
        return instance;
    }
 
    ...
}

我加了以上几行代码,就搞定了!需要说明的是:这里有一个 private 的构造方法,就是为了限制外界不能再去 new 它了,换句话说,我在这里把它给“阉”了。

用一个 main() 方法来证明我的简单主义思想:

1
2
3
4
5
public static void main(String[] args) {
    HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class);
 
    helloImpl.say("Jack");
}

没错吧?只需 1 行代码就可以获取代理对象了!

java 代理学习的更多相关文章

  1. Java代理学习笔记

    代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关 ...

  2. Java 代理学习笔记

    http://blog.csdn.net/mr_seaturtle_/article/details/52686516

  3. Java反射学习总结四(动态代理使用实例和内部原理解析)

    通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加 ...

  4. JAVA 动态代理学习记录

    打算用JAVA实现一个简单的RPC框架,看完RPC参考代码之后,感觉RPC的实现主要用到了两个方面的JAVA知识:网络通信和动态代理.因此,先补补动态代理的知识.---多看看代码中写的注释 参考:Ja ...

  5. Android(java)学习笔记227:服务(service)之服务的生命周期 与 两种启动服务的区别

    1.之前我们在Android(java)学习笔记171:Service生命周期 (2015-08-18 10:56)说明过,可以回头看看: 2.Service 的两种启动方法和区别: (1)Servi ...

  6. Java代理(静态/动态 JDK,cglib)

    Java的代理模式是应用非常广泛的设计模式之一,也叫作委托模式,其目的就是为其他的对象提供一个代理以控制对某个对象的访问和使用,代理类负责为委托类预处理消息,过滤消息并转发消息,以及对消息执行后续处理 ...

  7. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  8. CgLib动态代理学习【Spring AOP基础之一】

    如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...

  9. Java工程师学习指南 完结篇

    Java工程师学习指南 完结篇 先声明一点,文章里面不会详细到每一步怎么操作,只会提供大致的思路和方向,给大家以启发,如果真的要一步一步指导操作的话,那至少需要一本书的厚度啦. 因为笔者还只是一名在校 ...

随机推荐

  1. C/C#双色球

    6个红色球号码和1个蓝色球红色球号码从1—33中选择蓝色球号码从1—16中选择 C:(粗体:随机数,红色:库排序) #include <stdio.h> #include <stdl ...

  2. sql中的in与not in,exists与not exists的区别

    1.in和exists in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询,一直以来认为exists比in效率高的说法是不准确的.如果查询的两个表 ...

  3. MySQL · 最佳实践 · 分区表基本类型

    MySQL · 最佳实践 · 分区表基本类型 MySQL分区表概述 随着MySQL越来越流行,Mysql里面的保存的数据也越来越大.在日常的工作中,我们经常遇到一张表里面保存了上亿甚至过十亿的记录.这 ...

  4. Tomcat内存优化

    一.Tomcat内存优化 T omcat内存优化主要是对 tomcat 启动参数优化,我们可以在 tomcat 的启动脚本 catalina.sh 中设置 JAVA_OPTS参数. 1.JAVA_OP ...

  5. SVG 学习<七> SVG的路径——path(1)直线命令、弧线命令

    目录 SVG 学习<一>基础图形及线段 SVG 学习<二>进阶 SVG世界,视野,视窗 stroke属性 svg分组 SVG 学习<三>渐变 SVG 学习<四 ...

  6. UiAutomator 代码记录 : 遍历桌面

    package test_one; import java.lang.*; import java.io.File; import com.android.uiautomator.core.UiDev ...

  7. Eclipse svn 项目 星号

    上图: 明明没有文件修改过就是无脑显示有提交的文件,有强迫症受不了. 而且每次提交你都点开看有没有漏掉啥文件.很麻烦.也很难受 .很容易漏提交 把本地文件跟SVN同步对比一下. 果然有文件. 添加规则 ...

  8. PHP读取txt文件到数组

    $file_path = "test.txt"; if(file_exists($file_path)){ $file_arr = file($file_path); for($i ...

  9. 12.通过微信小程序端访问企查查(采集工商信息)

    需要注意的问题: 一.1.微信端访问企查查小程序需要登录.2.访问抓包获取的url是有时效性的过一段时间就不能用了. http://xcx.qichacha.com/wxa/v1/base/getEn ...

  10. flex学习笔记 显示数字步进

    <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="ht ...