设计模式(4)之代理模式

996.icu
撇开模式,只看代理。日常生活中我们经常听说代理,特别是搭梯子的时候,代理一词经常被提及。

字面义就是你想要做某事,但由于有这样那样的原因,你无法完成,而别人可以完成,你只需要提供一些信息给代理商就可以享受到代理的服务。

一、静态代理
举个例子,小徐是一个三十多岁的程序员,因为一直都处在996的工作制中,所以三十多了还找不到女朋友。家里人很着急,于是小徐的母亲托熟人帮忙介绍。在这里,小徐的母亲就是一个代理,小徐是原始对象。既然要代理,那么首先得有一个约束,这里我们使用一个叫Subject的借口来约束代理类和被代理类。

public interface Subject {
    void findGirlfriend();
}

然后让小徐和他母亲一起实现这个接口,同时小徐的母亲要持有小徐对象,这样才能知道小徐的需求:

//小徐
public class RealSubject implements Subject {
    @Override
    public void findGirlfriend() {
        System.out.println("找一个能接受996的媳妇");
    }
}
//母亲
public class Proxy implements Subject {

    private RealSubject rs;

    public Proxy(RealSubject rs){
        this.rs = rs;
    }

    @Override
    public void findGirlfriend() {
        System.out.println("给儿子找对象");
        System.out.println("儿子的要求是:");
        rs.findGirlfriend();
        System.out.println("这要求有点难啊,只能尽力了!");
    }
}

测试一下:

public class ProxyTest {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new RealSubject());
        proxy.findGirlfriend();
    }
}

输出结果:

给儿子找对象
儿子的要求是:
找一个能接受996的媳妇
这要求有点难啊,只能尽力了!

可以看到,在代理对象中,不仅执行了实际对象的方法,还额外补充了一些功能。如果仅看上面的代码可能会觉得,代理模式虽然有其优势,但是引入了额外的接口和代理类,优点还不够明显。不过,在这里真实对象RealSubject类是在同一个jvm中的,要引用只需要注入到构造器中或者通过其他方法就可以获得。那么如果真实对象在另一个jvm中呢?这里就需要用到jdk为我们提供的rmi技术类。该技术我会在分布式的地方详细讲解。这里重点还是放在设计模式的思想上。

二、动态代理
静态代理每次她母亲都得拿到小徐对象才能执行找女朋友方法,那小徐的公司公然违反了中国劳动法,让小徐根本没有时间每次都被母亲带着去找女朋友怎么办呢?动态代理就登场了。
首先创建一个媒婆接口:

public interface Matchmaker {
    void findGirlFriend();
}

接口很简单,只定义了一个找女朋友的接口。小徐要实现该接口,并提出自己的要求:

public class Xiaoxu implements Matchmaker {
    @Override
    public void findGirlFriend() {
        System.out.println("找一个能接受996的媳妇");
    }
}

然后我们来写一个动态代理类:

public class DynamicProxy implements InvocationHandler {
    //被代理的对象
    private Object target;
    //获得被代理对象实例
    public Object getInstance(Object target) {
        this.target = target;

        Class clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("先拿到小徐的要求:");
        Object obj = method.invoke(this.target, args);
        System.out.println("难度较大,需要加钱!");
        return obj;
    }
}

首先我们需要实现jdk为我们提供好的InvocationHandler接口,并重写invoke方法。同时写一个getInstance方法对外提供被代理对象的实现。
测试代码:

public class DynamicProxyTest {
    public static void main(String[] args) {
        try{
            Matchmaker xiaoxu = (Matchmaker) new DynamicProxy().getInstance(new Xiaoxu());
            xiaoxu.findGirlFriend();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在第5行打断点,debug的时候会发现并没有直接调用Xiaoxu的findGirlFriend方法,而是调用类动态代理类的invoke方法。而在该方法里的method.invoke方法确调用了Xiaoxu的findGirlFriend方法。这是为什么呢?这是jdk利用字节码重组技术为我们提供的便捷服务。

字节码重组:

  1. 拿到被代理对象的引用,利用反射获得该代理对象的所有接口;
  2. Proxy重新生成了一个实现了1中拿到的所有接口的类;
  3. 动态生成java代码,把新加的业务逻辑方法由一定的逻辑代码去调用;
  4. 编译新生成的类;
  5. 重新加载到jvm中运行

从字节码重组我们看到,动态代理有一个限制,那就是被代理对象必须实现接口,否则动态代理将无法使用,那么这么解决这个问题呢?

三、cglib代理
cglib代理就解决类必须实现接口的问题,首先是Xiaoxu类,可以看到这里没有实现任何接口:

public class Xiaoxu {
    public void findGirlFriend(){
        System.out.println("找一个能接受996的媳妇");
    }
}

再来看看cglib代理类:

public class CglibProxy implements MethodInterceptor {
    public Object getInstance(Class<?> clazz) throws Exception{
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("小徐的要求是:");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("难度有点大,要加钱!");
        return obj;
    }
}

然后是测试类:

public class CglibProxyTest {
    public static void main(String[] args) {
        try {
            Xiaoxu xiaoxu = (Xiaoxu) new CglibProxy().getInstance(Xiaoxu.class);
            xiaoxu.findGirlFriend();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以看到,cglib的原理其实就是拿到被代理对象,并且创建一个被代理对象的子类的方式来实现动态代理的。这样就可以避免被代理对象必须实现接口的问题。不过如果被代理类被私有化,也就是不给创建其子类,cglib就没办法实现动态代理的功能了。

总结
静态代理和动态代理的区别:

  • 静态代理只能通过手写完成代理操作,如果被代理类增加新的方法,代理类需要同步新增;
  • 动态代理采用在运行时动态生成代码的方式,取消类对被代理类的扩展限制;
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码

代理模式的优缺点:
优点:

  1. 代理模式能将代理对象与真实被调用的目标对象分离;
  2. 一定程序上降低类系统的耦合度,扩展性好;
  3. 可以起到保护目标对象的作用;
  4. 可以对目标对象的功能增强;

缺点:

  1. 代理模式会造成系统设计中类的数量增加;
  2. 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;
  3. 增加了系统的复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值