Java动态代理与静态代理

我们先看一个简单的例子,当我们需要程序中加入方法执行的日志信息的时候,很显然我们最容易想到的实现方法,就是在方法前后插入日志记录信息。

import java.util.logging.*;

public class HelloSpeaker {
    private Logger logger = 
            Logger.getLogger(this.getClass().getName()); 

    public void hello(String name) { 
        // 方法執行開始時留下日誌
        logger.log(Level.INFO, "hello method starts...."); 
        // 程式主要功能
        System.out.println("Hello, " + name); 
        // 方法執行完畢前留下日誌
        logger.log(Level.INFO, "hello method ends...."); 
    }
}

然后这种实现方式有明显的不足,这种切入式的代码(Cross-cutting),会使得HelloSpeaker拥有了本该不属于他的职责,要在hello的同时记录日志。

试想一下,如果程序中的代码到处都是这种日志需求,那么我们的就必须在到处都加上这些日志代码,想必那是很大的工作量,而且当我们需要修改密码的时候,将会变得更加复杂,维护起来变得困难,所以我们自然想到封装,由于很多对象都需要日志记录这种需求,我们何不把日志行为分离出来。

这时候就可以代理模式解决这个问题,代理又分为静态代理(Static proxy)动态代理(Dynamic proxy)

静态代理

在静态代理模式中,代理与被代理对象必须实现同一个接口,代理专注于实现日志记录需求,并在合适的时候,调用被代理对象,这样被代理对象就可以专注于执行业务逻辑。

改进上面那个例子
首先定义一个接口

  • IHello.java
package Reflection;

public interface IHello {
    public void hello(String name);

}

然后专注于业务逻辑实现HelloSpeaker实现上面这个接口:

  • HelloSpeaker.java
package Reflection;

public class HelloSpeaker implements IHello {

    @Override
    public void hello(String name) {
        System.out.println("Hello, " + name);
        
    }
    
}

可以看到,在这个类中没有日志记录的代码,其只需要专注于实现业务功能,而记录日志的工作则可以交给代理对象来实现,代理对象也要实现Ihello接口:

  • HelloProxy.java
package Reflection;

import java.util.logging.*; 

public class HelloProxy implements IHello { 
    private Logger logger = 
            Logger.getLogger(this.getClass().getName());
    
    private IHello helloObject; 

    public HelloProxy(IHello helloObject) { 
        this.helloObject = helloObject; 
    } 

    public void hello(String name) { 
        // 日誌服務
        log("hello method starts....");      

        // 執行商務邏輯
        helloObject.hello(name);
        
        // 日誌服務
        log("hello method ends...."); 
    } 
    
    private void log(String msg) {
        logger.log(Level.INFO, msg);
    }
}

我们可以看到在hello方法的实现中,前后插入了日志记录的方法。
下面我们就测试一下

public class ProxyDemo {
    public static void main(String[] args) {
        IHello proxy = 
            new HelloProxy(new HelloSpeaker());
        proxy.hello("Justin");
    }
}
Paste_Image.png

程序中执行hello方法的是代理对象,实例化代理对象的时候,必须传入被代理对象,而且声明代理对象的时候,必须使用代理对象和被代理对象共同实现的接口,以便实现多态。

代理对象将代理真正执行hello方法的被代理对象来执行hello,并在执行的前后加入日志记录的操作这样就可以使业务代码专注于业务实现。

这就是静态代理

动态代理

jdk1.3加入了动态代理相关的API,从上面静态代理的例子我们知道,静态代理,需要为被代理对象和方法实现撰写特定的代理对象,显然这样做并不灵活,我们希望可以有一个公用的代理,可以动态的实现对不同对象的代理,这就需要利用到反射机制和动态代理机制。
在动态代理中,一个handler可以代理服务各种对象,首先,每一个handler都必须继承实现java.lang.reflect.InvocationHandler接口,下面具体实例说明,依然是上面那个记录日志的例子

  • LogHandler.java
package Reflection;

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

public class LogHandler implements InvocationHandler { 
    private Logger logger = 
            Logger.getLogger(this.getClass().getName()); 
    
    private Object delegate;

    public Object bind(Object delegate) { 
        this.delegate = delegate; 
        return Proxy.newProxyInstance( 
                           delegate.getClass().getClassLoader(), 
                           delegate.getClass().getInterfaces(), 
                           this); 
    } 

    public Object invoke(Object proxy, Method method, 
                         Object[] args) throws Throwable { 
        Object result = null; 
        
        try { 
            log("method starts..." + method);
            
            result = method.invoke(delegate, args);
            
            logger.log(Level.INFO, "method ends..." + method); 
        } catch (Exception e){ 
            log(e.toString()); 
        }
        
        return result; 
    } 
    
    private void log(String message) {
        logger.log(Level.INFO, message);
    }
}

具体来说就是使用Proxy.newProxyInstance()静态方法new一个代理对象出来,底层会使用反射机制,建立代理对象的时候,需要传入被代理对象的class,以及被代理对象的所实现的接口,以及代理方法调用的调用程序 InvocationHandler,即实现 InvocationHandler接口的对象。这个对象会返回一个指定类指定接口,指定 InvocationHandler的代理类实例,这个实例执行方法时,每次都会调用 InvocationHandler的invoke方法,invoke方法会传入被代理对象的方法与方法参数,实际方法的执行会交给method.invoke().所以我们就可以在其前后加上日志记录的工作。

接下来我们就来测试一下,使用logHandler的bind方法来绑定代理对象:

package Reflection;

import java.lang.reflect.Proxy;

public class ProxyDemo {

    public static void main(String[] args) {
        
            
            LogHandler logHandler  = new LogHandler(); 
            
            IHello helloProxy = 
                    (IHello) logHandler.bind(new HelloSpeaker()); 
            helloProxy.hello("baba");
                    
    }

}

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

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,497评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 原文: Dyanmic Proxy Classes 介绍 一个动态代理类是实现了多个接口存在于运行时的类,这样,一...
    半黑月缺阅读 920评论 0 0
  • 秋风多情,催开 紫薇,菊花,雁鸣,以及 一些薄如蝉翼的希望 填满空谷。也 催开一朵毛月亮 清冷的光,覆盖 夜的黑,...
    蔚霐_d38f阅读 239评论 1 9