代理模式

代理模式

一、概念

代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。

使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。
客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。
客户类对目标对象的访问是通过访问代理对象来实现的。
代理类与目标类要实现同一个接口。

对于代理模式,需要注意以下几点:
(1)代理类和目标类要实现同一个接口,即业务接口。
(2)客户类对目标类的调用均是通过代理类完成的。
(3)代理类的执行既执行了对目标类的增强业务逻辑,又调用了目标类的主业务逻辑。

代理模式.png

根据代理关系建立的时间不同,可以将代理分为两类:

  • 静态代理
  • 动态代理

二、静态代理

静态代理是指,代理类在程序运行前就已经定义好,其与目标类的关系在程序运行前就已经确立。

demo:
需求:目标类中小写的abc转为大写大ABC(不改变目标类的代码)

TargetService:

package com.hcx.design.pattern.proxy.staticproxy;

/**
 * 主业务接口
 * Created by hongcaixia on 2019/5/2.
 */
public interface TargetService {
    //目标方法
    String doFirst();
    void doSecond();
}

TargetServiceImpl:

package com.hcx.design.pattern.proxy.staticproxy;

/**
 * 目标类
 * Created by hongcaixia on 2019/5/2.
 */
public class TargetServiceImpl implements TargetService{

    public String doFirst() {
        System.out.println("执行doFirst()");
        return "abc";
    }

    public void doSecond() {
        System.out.println("执行doSecond()方法");
    }
}

ProxyServiceImpl:

package com.hcx.design.pattern.proxy.staticproxy;

/**
 * 代理类
 * Created by hongcaixia on 2019/5/2.
 */
public class ProxyServiceImpl implements TargetService{

    private TargetService targetService;

    public ProxyServiceImpl(TargetService targetService) {
        this.targetService = targetService;
    }

    public String doFirst() {
        String result = targetService.doFirst();
        return result.toUpperCase();
    }

    public void doSecond() {
        targetService.doSecond();
    }
}

MyTest:

package com.hcx.design.pattern.proxy.staticproxy;

/**
 * Created by hongcaixia on 2019/5/2.
 */
public class MyTest {

    public static void main(String[] args) {
//        TargetService targetService = new TargetServiceImpl();
        TargetService targetService = new ProxyServiceImpl(new TargetServiceImpl());
        //写完上面这句话,我突然一愣:这特喵不是装饰模式吗???!!!
        String result = targetService.doFirst();
        System.out.println("result是:"+result);
        targetService.doSecond();
        /**
         *  执行doFirst()
            result是:abc
            执行doSecond()方法
         */
        /**
         * 增强之后:
         * 执行doFirst()
           result是:ABC
           执行doSecond()方法
         */
    }
}

三、动态代理

动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理生成工具(如代理工厂类)在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。

1.JDK动态代理(基于接口)

通过JDK的java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法newProxyInstance(),依据目标对象、业务接口及业务增强逻辑三者,自动生成一个动态代理对象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,
InvocationHandler handler)
loader:目标类的类加载器,通过目标对象的反射可获取
interfaces:目标类实现的接口数组,通过目标对象的反射可获取
handler:业务增强逻辑,需要再定义。

InvocationHandler 接口:
实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。程序调用主业务逻辑时,会自动调用 invoke()方法。

invoke()方法:

public Object invoke ( Object proxy, Method method, Object[] args)
proxy:代表生成的代理对象
method:代表目标方法
args:代表目标方法的参数

由于该方法是由代理对象自动调用的,所以这三个参数的值不用程序员给出。第二个参数为 Method 类对象,该类有一个方法也叫invoke(),可以调用目标类的目标方法。
这两个 invoke()方法,虽然同名,但无关:

public Object invoke ( Object obj, Object... args)
obj:表示目标对象
args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数

该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。
在代码中,一般的写法为method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。
这样,即可调用了目标类的目标方法。

TargetService:

package com.hcx.design.pattern.proxy.jdkdynamicproxy;

/**
 * 主业务接口
 * Created by hongcaixia on 2019/5/2.
 */
public interface TargetService {

    //目标方法
    String doFirst();
    void doSecond();

}

TargetServiceImpl:

package com.hcx.design.pattern.proxy.jdkdynamicproxy;

/**
 * 目标类
 * Created by hongcaixia on 2019/5/2.
 */
public class TargetServiceImpl implements TargetService {

    public String doFirst() {
        System.out.println("执行doFirst()");
        return "abc";
    }

    public void doSecond() {
        System.out.println("执行doSecond()方法");
    }
}

MyTest:

package com.hcx.design.pattern.proxy.jdkdynamicproxy;

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

/**
 * Created by hongcaixia on 2019/5/2.
 */
public class MyTest {
    public static void main(String[] args) {
        final TargetService targetService = new TargetServiceImpl();

        /**
         * newProxyInstance:获取对应目标类的代理对象.
         * ClassLoader loader:目标类的类加载器
         * Class<?>[] interfaces:目标类所实现的所有接口
         * InvocationHandle h:InvocationHandler的实例
         */
        TargetService proxyService = (TargetService) Proxy.newProxyInstance(
                targetService.getClass().getClassLoader(),
                targetService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * invoke:调用对应的目标类的方法.
                     * @param proxy 代理对象
                     * @param method 目标方法
                     * @param args 目标方法参数列表
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //调用目标方法
                        Object result = method.invoke(targetService, args);
                        if(result!=null){
                            result = ((String)result).toUpperCase();
                        }
                        return result;
                    }
                }
        );

        String result = proxyService.doFirst();
        System.out.println("result="+result);
        proxyService.doSecond();
        /**
         *  执行doFirst()
            result=ABC
            执行doSecond()方法
         */
    }
}

Demo2:
SaleComputer:

public interface SaleComputer {

    public String sale(double money);

    public void show();
}

Target:

/**
 * 目标类
 * Created by hongcaixia on 2019/5/2.
 */
public class Target implements SaleComputer{

    public String sale(double money) {
        System.out.println("花了"+money+"买了一台mac");
        return "mac book";
    }

    public void show() {
        System.out.println("展示电脑");
    }
}

Proxy:

package com.hcx.design.pattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 代理类
 * Created by hongcaixia on 2019/5/2.
 */
public class Proxy {

    public static void main(String[] args) {
        //创建目标对象
        final Target target = new Target();

        SaleComputer proxyInstance = (SaleComputer) java.lang.reflect.Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler() {

                    //代理对象调用的所有方法都会触发该方法(即使用proxyInstance调用的方法都会触发该方法)

                    /**
                     * 增强的代码写在该方法中
                     * @param proxy  代理对象
                     * @param method  代理对象(proxyInstance)调用的方法,被封装为的对象,即通过该对象,可以获取代理对象调用的方法的信息
                     * @param args  代理对象调用方法时,传递的实际参数列表
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //使用目标对象调用方法(未增强)
//                        Object invoke = method.invoke(target, args);
                        /**
                         * 增强方法:
                         * 1.增强参数列表
                         * 2.增强返回值类型
                         * 3.增强方法体执行逻辑
                         */
                        //1.增强参数列表
                        /*if(method.getName().equals("sale")){
                            double money = (Double) args[0];
                            money = money*0.8;
                            Object invoke = method.invoke(target, money);
                            return invoke;
                        }else {
                            return method.invoke(target,args);
                        }*/

                        //2.增强返回值类型
                       /* if(method.getName().equals("sale")){
                            double money = (Double) args[0];
                            money = money*0.8;
                            String invoke = (String) method.invoke(target, money);
                            return invoke+"+送耳机";
                        }else {
                            return method.invoke(target,args);
                        }*/

                        //3.增强方法体执行逻辑
                        if(method.getName().equals("sale")){
                            double money = (Double) args[0];
                            money = money*0.8;
                            System.out.println("专车接");
                            String invoke = (String) method.invoke(target, money);
                            System.out.println("专车送");
                            return invoke+"+送耳机";
                        }else {
                            return method.invoke(target,args);
                        }
                    }
                });

        //使用目标对象来调用
//        String sale = target.sale(18000);

        //使用代理对象来调用
        String sale1 = proxyInstance.sale(18000);

    }
}

注意:使用jdk的动态代理要求目标类必须实现接口,其底层的执行原理与静态代理相同。

2.CGLIB动态代理

CGLIB(Code Generation Library)是一个开源项目,是一个强大的、高性能的、高质量的代码生成类库。它可以在运行期扩展和增强 Java 类。Hibernate 用它来实现持久对象的字节码的动态生成,Spring 用它来实现 AOP 编程。

使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。
若目标类不存在接口,则无法使用该方式实现。
但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。
所以,使用CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的类。

CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架),来转换字节码并生成新的类。
CGLIB 是通过对字节码进行增强来生成代理的。

  • 涉及的类:Ehancer,提供者:第三方cglib库
  • 创建代理对象:使用Ehancer类的create方法
  • 对代理对象的要求:被代理类不能是final类

create方法:
Class:目标类的字节码
Callback:用于提供增强的代码(一般都是写该接口的子接口实现类MethodInterceptor)

demo1:

Producer:

package com.hcx.cglib;

/**
 * 生产者
 */
public class Producer {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}

Consumer:

package com.hcx.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 Consumer {

    public static void main(String[] args) {
        
        final Producer producer = new Producer();

        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理的对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(8000f);
    }
}

demo2:
TargetService:

package com.hcx.design.pattern.proxy.cglibdynamicproxy;

/**
 * 目标类
 * Created by hongcaixia on 2019/5/2.
 */
public class TargetService {

    public String doFirst(){
        System.out.println("执行doFist()方法");
        return "abc";
    }

    public void doSecond(){
        System.out.println("执行doSecond()方法");
    }
}

MyCglibFactory:

package com.hcx.design.pattern.proxy.cglibdynamicproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Created by hongcaixia on 2019/5/2.
 */
public class MyCglibFactory implements MethodInterceptor {

    private TargetService targetService;

    public MyCglibFactory(TargetService targetService) {
        this.targetService = targetService;
    }

    public TargetService myCglibFactory(){

        //创建增强器对象
        Enhancer enhancer = new Enhancer();
        //指定目标类,即父类
        enhancer.setSuperclass(TargetService.class);
        //设置回调接口对象(一个类实现了Callback接口,那么该类的对象就是回调对象)
        /**
         * 当前类实现了MethodInterceptor接口,而MethodInterceptor又继承了CallBack,
         * 所以当前类实现了Callback接口,所以当前类就是一个回调对象
         */
        enhancer.setCallback(this);
        //返回代理对象
        return (TargetService) enhancer.create();
    }

    /**
     * 回调方法
     * @param o 代理对象
     * @param method 代理对象的方法
     * @param objects 方法参数
     * @param methodProxy 代理对象方法的代理对象
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //调用目标方法
        Object result = method.invoke(targetService, objects);
        if(result!=null){
            result = ((String) result).toUpperCase();
        }
        return result;
    }
}

MyTest:

package com.hcx.design.pattern.proxy.cglibdynamicproxy;

/**
 * Created by hongcaixia on 2019/5/2.
 */
public class MyTest {
    public static void main(String[] args) {
        TargetService proxyService = new MyCglibFactory(new TargetService()).myCglibFactory();
        String result = proxyService.doFirst();
        System.out.println("result="+result);
        proxyService.doSecond();
        /**
         * 执行doFist()方法
           result=ABC
           执行doSecond()方法
         */
    }
}

注意,使用MethodInterceptor需要手动添加两个jar:
在idea中的步骤:
File-->Project Structure-->Modules:

手动引入第三方依赖.png

cglib动态代理也可以使用接口的方式,在此不再赘述,代码详见文末的链接。

方法回调设计:
在 Java 中,就是类 A 调用类 B 中的某个方法 b,然后类 B 又在某个时候反过来调用类 A中的某个方法 a,对于 A 来说,这个 a 方法便叫做回调方法。
Java 的接口提供了一种很好的方式来实现方法回调。
这个方式就是定义一个简单的接口,在接口之中定义一个我们希望回调的方法。这个接口称为回调接口。

在上面的例子中,
MyCglibFactory类就相当于前面所说的 A类,
而 Enhancer 类则是 B 类。
A 类中调用了 Enhancer 类的setCallback(this)方法,并将回调
对象 this 作为实参传递给了 Enhancer 类。Enhancer 类在后续执行过程中,会调用 A 类中的intercept()方法,这个 intercept()方法就是回调方法。

代码链接:https://github.com/GitHongcx/design_pattern/tree/master/src/main/java/com/hcx/design/pattern/proxy

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

推荐阅读更多精彩内容