Java动态代理的一个简单例子

最早看到动态代理是java core第一卷上,写的很简略,例子用的是二分查找打日志,感觉不是很好懂。
最尽学Mybatis底层也用到了动态代理,重新学习了一遍动态代理。

什么是代理: 简单说,代理类(对象)就是一个包裹类(对象),可以通过代理来传递函数调用给被代理的对象,通过代理可能增加一些功能或者屏蔽掉带代理对象的一些接口(函数调用)。

什么是动态代理:动态代理允许一个类其有一个方法,该方法可以服务多个方法调用,这些方法调用可以是对多个类,这些类可以有多个方法。动态代理可以被认为是种伪装,可以被当作是任何接口的实现。在这个封装之下,它将所有方法调用,路由到/传递到一个调用处理器(invocation handler)的方法invoke()。调用处理器是实现了InvocationHandler接口的类对象。该接口只有一个方法:
Object invoke(Object proxy, Method method, Object[] args)。
所以,无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并(自动)向其传递Method对象和原始的参数。

举个简单易懂的例子,给一个数学算术处理类的接口和实现:

package com.proxy.example;

/**
 * 四则数学运算的接口,需要一个实现类
 */
public interface ArithmeticCalculator {

    int add(int i, int j);
    int sub(int i, int j);
    
    int mul(int i, int j);
    int div(int i, int j);
    
}
package com.proxy.example;


/**
 * 四则数学运算的接口的实现类
 */
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }

}

现在的需求是对其运行时的方法和结果打个日志,这里就将其输出到控制台,最直接也是最繁琐和不可扩展不易改动的实现也就是直接在每个方法调用前后打印日志,实际中都应该避免。

如果用动态代理的话:需要调用处理器,一个代理对象,和一组代理的接口,还有调用处理器。
不按需要的顺序看的话:

//  通过反射创建一个代理对象,classLoader是jvm里的类加载器,handler是调用处理器,interface是一个Class [],其每个元素都是需要实现的接口
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler); 

然后主要的是写一个调用处理器:其内包含一个代理对象target的引用, 一个invoke方法, 使用匿名内部类/或者lambda表达式的话可以将target 放在外面的包裹类属性中。

package com.proxy.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ArithmeticCalculatorLoggingProxy {
    
    //要代理的对象
    private ArithmeticCalculator target;
    
    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        super();
        this.target = target;
    }

    //返回代理对象
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        
        ClassLoader classLoader = target.getClass().getClassLoader();   // 获取类加载器,一般用户定义的类的类加载器都是同一个
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        InvocationHandler handler = new InvocationHandler() {
            /**
             * proxy: 代理对象。 一般不使用该对象
             * method: 正在被调用的方法
             * args: 调用方法传入的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                String methodName = method.getName();
                //打印日志
                System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
                
                //调用目标方法返回的结果
                Object result = null;
                
                try {
                    result = method.invoke(target, args);
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
                
                //打印日志
                System.out.println("[after] The method ends with " + result);
                
                return result;
            }
        };
        
        /**
         * loader: 代理对象使用的类加载器。 
         * interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法. 
         * h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
         */
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(classLoader, interfaces, handler); 
        
        return proxy;
    }
}

via lambda:

 Proxy.newProxyInstance(classLoader, interfaces, (proxyObject, method,args)-> {
                String methodName = method.getName();
                System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
                Object result = null;
                try {
                    result = method.invoke(target, args);
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
                System.out.println("[after] The method ends with " + result);
                return result;
        });

现在来看《java 核心技术 1》里的例子更容易看懂了。
代理对象(target)是每个Integer, 代理的Classs数组内只有Comparable.class, 实际上代理类还包括Object中的全部方法,如toString, equals。故在这里compareTo方法和toString方法都会通过调用处理器的invoke方法:

package proxy;

import java.lang.reflect.*;
import java.util.*;

/**
 * This program demonstrates the use of proxies
 */
public class ProxyTest
{
    public static void main(String[] args)
    {
        Object[] elements = new Object[1000];

        // fill elements with proxies for the intergers 1...100
        for (int i = 0; i < elements.length; i++)
        {
            Integer value = i + 1;
            InvocationHandler handler = new TraceHandler(value);
            Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
            elements [i] = proxy;
        }

        // construct a random interger 
        Integer key = new Random().nextInt(elements.length) + 1;

        // saarch for the key 
        int result = Arrays.binarySearch(elements, key);

        // print match if found
        if (result >= 0) System.out.println(elements[result]);
    }
}

/**
 * An invocation handler that prints out the method name and parameters, then
 * invokes the original method
 */
class TraceHandler implements InvocationHandler
{
    private Object target;

    /**
     * COnstructs a TraceHandler
     */
    public TraceHandler (Object t)
    {
        target = t;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
    {
        // print implicit argument
        System.out.print(target);
        // print method name
        System.out.print("." + m.getName() + "(");
        // porint explicit arguments
        if (args != null)
        {
            for (int i = 0; i < args.length; i++)
            {
                System.out.print(args[i]);
                if (i < args.length - 1) System.out.print(", ");
            }
        } 
        System.out.println(")");

        // invoke actual method
        return m.invoke(target, args);
    }
} 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容