JAVA架构师之路六:设计模式之代理模式

JAVA架构师之路五:设计模式之建造者模式

决定你的人生高度的,不是你的才能,而是你的人生态度!

代理模式在生活中随处可见,比如说房产中介,快递小哥,黄牛党等等,都是替客户办事,这种形式在代码世界称之为代理模式,今天我们来深入探讨代理模式

1. 代理模式

定义

指为其他对象提供一种代理,以控制对这个对象的访问
代理对象在客户端和目标对象中间起到中介作用
属于结构型设计模式

适用场景

保护目标对象
增强目标对象

缺点

会造成系统设计中类的数目增加
在客户端和目标对象之间增加乐意个代理对象,请求处理速度变慢
增加了系统的复杂度

2. 一般代理

代码

public interface ISubject {

    void request();
}
public class Subject implements ISubject {
    public void request() {
        System.out.println("请求主题");
    }
}
public class Proxy {

    private ISubject subject;

    public Proxy(ISubject subject) {
        this.subject = subject;
    }

    public void request() {
        before();
        subject.request();
        after();
    }

    public void before() {
        System.out.println("开始");
    }

    public void after() {
        System.out.println("结束");
    }
}

public class Client {

    public static void main(String[] args) {
        Proxy proxy = new Proxy(new Subject());
        proxy.request();
    }
}

类图
在这里插入图片描述
在代理类Proxy中,before()after()方法为增强方法,增强了request() 方法。

3. 静态代理

代码


public interface IPerson {

    void findLove();
}

public class Lisi implements IPerson {
    public void findLove() {
        System.out.println("要求:肤白貌美大长腿!");
    }
}

public class LiLaoSi implements IPerson {

    private Lisi lisi;

    public LiLaoSi(Lisi lisi) {
        this.lisi = lisi;
    }

    public void findLove() {
        System.out.println("开始物色");
        lisi.findLove();
        System.out.println("找到了");
    }
}
public class Test {
    public static void main(String[] args) {
        LiLaoSi liLaoSi = new LiLaoSi(new Lisi());
        liLaoSi.findLove();
    }
}
开始物色
要求:肤白貌美大长腿!
找到了

类图
在这里插入图片描述
代理类LiLaoSi 为儿子LiSi 物色对象,体现了静态代理的特点,只能代理一个人,比如说另外一个张三也想让LiLaoSi物色对象,那么就没有办法了。动态代理可以解决这个问题。

4. 动态代理

4.1 JDK动态代理

代码

public interface IPerson {

    void findLove();
}
public class Lisi implements IPerson {
    public void findLove() {
        System.out.println("要求:肤白貌美大长腿!");
    }
}
public class Wangwu implements IPerson {

    public void findLove() {
        System.out.println("要求:有车有房!");
    }
}
public class JdkProxy implements InvocationHandler {

    private IPerson target;

    public IPerson getInstance(IPerson target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        return (IPerson)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void after() {
        System.out.println("OK的话, 开始办事儿!");
    }

    private void before() {
        System.out.println("我是媒婆,我给你找对象,已经确认你的需求");
        System.out.println("开始物色");
    }
}

public class Test {

    public static void main(String[] args) {
        JdkProxy proxy = new JdkProxy();
        IPerson lisi = proxy.getInstance(new Lisi());
        lisi.findLove();

        IPerson wangwu = proxy.getInstance(new Wangwu());
        wangwu.findLove();
    }
}
我是媒婆,我给你找对象,已经确认你的需求
开始物色
要求:肤白貌美大长腿!
OK的话, 开始办事儿!
我是媒婆,我给你找对象,已经确认你的需求
开始物色
要求:有车有房!
OK的话, 开始办事儿!

JDK 动态代理需要实现InvocationHandler接口。如果被代理对象又有了新的需求,那么只需要IPerson 增加方法,实现类实现方法,Test类直接调用这个方法,并不需要修改任何JDKProxy 代理类,就能达到目的。我们看到,JDKProxyreturn (IPerson)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); clazz.getInterfaces() 说明JDK代理只能针对接口代理,一般的class是无法代理的。

4.2 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 {

    public Object getInstance(Class<?> calzz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(calzz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void after() {
        System.out.println("结束");
    }

    private void before() {
        System.out.println("开始");
    }
}

public class Test {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Lisi instance = (Lisi)cglibProxy.getInstance(Lisi.class);
        instance.findLove();
    }
}
开始
要求:肤白貌美大长腿!
结束

Cglib的主要用法就是这样,一般开发用不到。Spring中用的是Cglib代理。我们可以看到,Cglib是创造了一个类,然后这个类继承了被代理对象,说明代理类不仅仅可以代理接口,还能代理一般的class

4.3 JDK与Cglib动态代理对比

Cglib是继承的方式,覆盖父类的方法

JDK采用实现的方式,必须要求代理的目标对象啊你给一定要实现一个接口

两者的思想:都是通过生成字节码,重组成一个新的类

JDK Proxy对于用户而言,以来更强,调用页更复杂

Cglib对目标类没有任何要求

Cglib 效率更高,性能也更高,底层没有用到反射

JDK Proxy生成逻辑较为简单,执行效率要低,每次都要用反射

Cglib 有个坑,目标对象不能有final的方法,因为final方法无法被重写,所以会自动忽略final方法

5. JDK动态代理分析

思考一个问题:IPerson lisi = proxy.getInstance(new Lisi()); 生成的lisiLisi生成的对象还是代理生成的对象呢?
证明方法:程序打断点。
在这里插入图片描述
lisi在内存中是$Proxy0的对象,并不是Lisi 对象,说明这个对象是通过代理生成的。那我们可以把这个内存中的对象序列化到本地磁盘,看看这个类是怎么样的。

public static void main(String[] args){
        JdkProxy proxy = new JdkProxy();
        IPerson lisi = proxy.getInstance(new Lisi());
        lisi.findLove();

        byte[] ss = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{IPerson.class});
        try {
            FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
            os.write(ss);
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

我们把$Proxy0 写到了磁盘$Proxy0.class 文件。如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.test.proxy.dynamicproxy.IPerson;

public final class $Proxy0 extends Proxy implements IPerson {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void findLove() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void baby() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("org.test.proxy.dynamicproxy.IPerson").getMethod("findLove");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("org.test.proxy.dynamicproxy.IPerson").getMethod("baby");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

很明显,代理生成的$Proxy0 继承了Proxy,实现了IPerson接口,说明JDK代理为我们生成了一个被代理对象的子类。

如果自己手动实现动态代理这个过程,要怎么写呢?需要如下步骤:

1.动态生成.java文件,可以通过用StringBuilder的方式重组一个字符串java文件
2.java文件输出到磁盘,保存为$Proxy0.java文件
3.把$Proxy0.java文件翻译成$Proxy0.class文件
4.把生成的class文件加载到JVM中
5.返回这个代理对象

6. Cglib动态代理分析

同样的方法,我们debug看Cglib生成的代理对象是什么样的。
在这里插入图片描述

7. Spring中的代理选择原则

当Bean有实现接口的时候,Spring就会使用JDK的动态代理。

当Bean没有实现接口时,Spring选择Cglib代理

Spring可以通过配置强制使用CGlib,只需要在Spring的配置文件中加入以下代码:
<aop:aspectj-autoproxy proxy-target-class=“true” />

8. 代理模式总结

在工作中,基本就这两种代理模式,作为一个架构师,必须要掌握这两中代理模式的实现方式。即使字节码重组过程不清楚,但整体步骤需要了解。实际开发中,用到代理模式,也不需要我们自己去实现重组过程,我们也只是站在巨人的肩膀上,选择JDK或者是Cglib模式,实现我们自己的Proxy类就行了,增强调用方法的前后处理过程。

感谢您阅读本文,如果您觉得文章写的对您有用的话,请您点击上面的“关注”,点个赞,这样您就可以持续收到《JAVA架构师之路》的最新文章了。文章内容属于自己的一点点心得,难免有不对的地方,欢迎在下方评论区探讨,你们的关注是我创作优质文章的动力。

JAVA架构师之路七:设计模式之门面模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值