设计模式之代理模式

一、简介

代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,其中的代理对象就类似于中介。

代理模式可以分为静态代理和动态代理,静态代理就是在程序运行之前就为每一个委托类分别创建一个代理类,工作量大,不易管理,同时接口一旦发生变化,代理类也得相应修改。而动态代理,是在程序运行期间,通过反射机制动态的创建代理类,会方便很多。

目前实现动态代理主要有两种方法,一是JDK自带的动态代理,但要求代理类和委托类实现共同接口,在接口中定义业务方法。这样当代理对象调用某业务方法时,才能在委托类中找到对应的方法。

对于没有实现任何接口的委托类,可以使用CGLib,它的原理是通过字节码技术为一个类创建子类并在子类中通过方法拦截的技术拦截所有父类方法的调用,并织入横切逻辑,但因为采用的是继承,所以不能对final修饰的类进行代理。

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

二、使用场景

之所以使用代理对象,一是有时出于安全原因需要屏蔽用户对真实对象的访问; 二是还可以在满足开闭原则的基础上,通过给代理类增加额外的功能来扩展委托类的功能,其中核心的业务功能还是由委托类来实现,而代理类只是在业务功能执行的前后加入一些公共的服务,如权限控制、缓存、日志功能。

三、举例

(1)静态代理

/*
 * 静态代理:聚合复用
 */
//第一步:创建服务类接口
interface HouseService{
    void buyHouse();
    void rentHouse();
}
//第二步:创建委托类,实现服务接口中的业务功能
class HouseServiceImpl implements HouseService{ 
    @Override
    public void buyHouse() {
        System.out.println("执行买房业务");
    }
    @Override
    public void rentHouse() {
        System.out.println("执行租房业务");
    }
}
//第三步:创建代理类,同样让它实现相同的服务接口,只不过内部的业务功能由被代理对象实现
class HouseServiceProxy implements HouseService{    
    private HouseService service;//被代理对象
    public HouseServiceProxy(HouseService service){
        this.service=service;
    }
    @Override
    public void buyHouse() {
        System.out.println("买房前准备");
        service.buyHouse();
        System.out.println("买房后装饰");
    }
    @Override
    public void rentHouse() {
        System.out.println("租房前准备");
        service.rentHouse();
        System.out.println("租房后入住");
    }
}
//测试
public class 代理模式 {
    public static void main(String[] args) {
        //静态代理测试
        System.out.println("---------------JDK实现静态代理-------------------");
        HouseServiceProxy proxy=new HouseServiceProxy(new HouseServiceImpl());
        proxy.buyHouse();
        proxy.rentHouse();
    }

}

(2)JDK实现的动态代理

/*
 * JDK实现动态代理
 */
//第一步:创建服务类接口
interface HouseService_D{
    void buyHouse();
    void rentHouse();
}
//第二步:创建委托类,实现服务接口中的业务功能
class HouseServiceImpl_D implements HouseService_D{ 
    @Override
    public void buyHouse() {
        System.out.println("执行买房业务");
    }
    @Override
    public void rentHouse() {
        System.out.println("执行租房业务");
    }
}
//第三步:实现调用处理器
class MyInvocationHandler implements InvocationHandler{
    private Object object;//被代理对象
    public MyInvocationHandler(Object obj){
        this.object=obj;
    }
    //代理对象调用的所有方法都将被派遣给该方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object res=null;
        if("buyHouse".equals(method.getName())){
            System.out.println("买房前准备");
            res=method.invoke(object, args);
            System.out.println("买房后装饰");
        }else if("rentHouse".equals(method.getName())){
            System.out.println("租房前准备");
            res=method.invoke(object, args);
            System.out.println("租房后入住");
        }
        return res;
    }
}
//测试
public class 代理模式 {
    public static void main(String[] args) {
        //JDK动态代理测试
        System.out.println("---------------JDK实现动态代理-------------------");
        //创建被代理对象
        HouseServiceImpl_D houseServiceImpl=new HouseServiceImpl_D();
        //动态创建代理类及代理对象
        HouseService_D proxyD=(HouseService_D)Proxy.newProxyInstance(houseServiceImpl.getClass().getClassLoader(), 
                houseServiceImpl.getClass().getInterfaces(), new MyInvocationHandler(houseServiceImpl));
        proxyD.buyHouse();
        proxyD.rentHouse();
    }

}

(3)CGLIB实现的动态代理

 import net.sf.cglib.proxy.Enhancer;
 import net.sf.cglib.proxy.MethodInterceptor;
 import net.sf.cglib.proxy.MethodProxy;
 import java.lang.reflect.Method;
 //实现方法拦截器
 public class CglibProxy implements MethodInterceptor {
     private Object target;//被代理对象
     //获得代理对象
     public Object getInstance(Object target) {
         this.target = target;
         //创建Enhancer对象,类似于JDK动态代理的Proxy类
         Enhancer enhancer = new Enhancer();
         // 设置目标类的字节码文件
         enhancer.setSuperclass(this.target.getClass());
         // 设置回调方法
         enhancer.setCallback(this);
         // 创建代理对象
         return enhancer.create();
     }
   /**
     *回调方法
     * @param obj 表示代理对象
     * @param method 表示拦截的方法
     * @param args 数组表示参数列表
     * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     * @return 执行结果
     */
     @Override
     public Object intercept(Object object, Method method, Object[] args, 
                             MethodProxy methodProxy) throws Throwable {
        Object res=null;
        if("buyHouse".equals(method.getName())){
            // 注意这里是调用invokeSuper而不是invoke,否则死循环;
            // methodProxy.invokeSuper执行的是原始类的方法;
            // method.invoke执行的是子类的方法;
            System.out.println("买房前准备");
            res=methodProxy.invokeSuper(object, args);
            System.out.println("买房后装饰");
        }else if("rentHouse".equals(method.getName())){
            System.out.println("租房前准备");
            res=methodProxy.invokeSuper(object, args);
            System.out.println("租房后入住");
        }
        return res;
     }
 }
//测试
public class 代理模式 {
    public static void main(String[] args) {
        //JDK动态代理测试
        System.out.println("-----------CGLib实现动态代理----------");
        HouseService houseService = new HouseServiceImpl();
        CglibProxy cglibProxy = new CglibProxy();
        HouseServiceImpl proxy = (HouseServiceImpl)cglibProxy.getInstance(houseService);
        proxy.buyHosue();
}

四、参考

Java动态代理的两种实现方法
代理模式原理及实例讲解
设计模式---代理模式
Java动态代理之一CGLIB详解

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