代理模式:静态代理和动态代理以及JDK动态代理原理

167 阅读5分钟

为什么要使用代理模式?

当一个类不想让外部直接访问或者需要外部进行增强时,就可以使用代理模式生成一个代理对象,通过代理对象来访问目标对象或者对目标对象的一些功能进行增强。比如有一个大明星,他只想唱歌或者跳舞,但是又不想管理一些琐事,比如安排唱歌跳舞的场地还有收钱的一些事,那么这个明星就可以找一个经纪公司,让这个公司来做这些琐事,如果有粉丝想要点歌,这个公司收到后会安排场地,然后让这个明星来唱歌,这个公司完成一些其他事。在以上的案例中,明星就是一个被代理类,而代理公司是一个代理类,明星的一些行为只能由公司来调用,而公司可以对明星的行为进行增强。而代理模式又分为静态代理和动态代理我们展开讲讲。


静态代理

静态代理就是我们手动进行编译,写一个代理类并对被代理类的方法进行增强,静态代理是在编译时就生成代理类。

代码实现

被代理类:

public class BigStar {
    private String name;

    public BigStar(String name) {
        this.name = name;
    }
    public String sing(){
        System.out.println(name+"开始唱歌");
        return "谢谢大家";
    }
    public void dance(){
        System.out.println(name+"开始跳舞");
    }
}

手动编译的代理类:

public class StarProxy {
    private BigStar bigStar;
    public StarProxy(BigStar bigStar){
        this.bigStar=bigStar;
    }
    public String sing(){
        System.out.println("安排唱歌座位场地,收钱10w");
        return bigStar.sing();
    }
    public void dance(){
        System.out.println("安排唱歌座位场地,收钱50w");
        bigStar.dance();
    }
}

通过代理类调用被代理类的方法:

public class Client {
    public static void main(String[] args) {
        BigStar bigStar=new BigStar("坤坤");
        StarProxy starProxy=new StarProxy(bigStar);
        System.out.println(starProxy.sing());
        starProxy.dance();
    }
}

运行结果: 在这里插入图片描述

优缺点

优点:实现简单 缺点: 1、当要被代理的方法和类多时,手动编译麻烦 2、耦合度高。当被代理类修改方法时需要手动修改代理类的方法

动态代理

动态代理即运行时动态的生成代理对象,和静态代理在编译时就确定代理对象不同,其灵活度更高,并且耦合度更低,动态代理又分为JDK动态代理和CGLIB动态代理。

JDK动态代理

JDK动态代理是基于反射实现的

定义接口:

public interface Star {
    String sing();
    void dance();
}

生成代理对象并且对方法进行增强:


public class ProxyFactory {
    public static Star getProxy(BigStar bigStar){
        Star starProxy = (Star) Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("sing".equals(method.getName())||"dance".equals(method.getName())){
                    System.out.println("安排场地座位,收钱");
                }
                return method.invoke(bigStar,args);
            }
        });
        return starProxy;
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        BigStar bigStar=new BigStar("坤坤");
        Star proxy = ProxyFactory.getProxy(bigStar);
        System.out.println(proxy.sing());
        System.out.println("------");
        proxy.dance();

    }
}

JDK动态代理原理

ProxyFactory是一个生成代理类的工厂,而不是代理类,通过调用方法在运行时动态生成代理类,我们来查看这个在运行过程中生成的代理类,我对其进行了简化如下图:


public final class $Proxy0 extends Proxy implements Star {
    private static Method m4;
    private static Method m3;

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

    public final void dance() throws  {
            super.h.invoke(this, m4, (Object[])null);
        }

    public final String sing() throws  {
            return (String)super.h.invoke(this, m3, (Object[])null);
    }

    static {
            m4 = Class.forName("proxy.Star").getMethod("dance");
            m3 = Class.forName("proxy.Star").getMethod("sing");
    }
}

我们在测试时是通过生成的代理类也就是上图的类来调用方法的,因此我们需要搞懂这个生成代理类。 我们发现代理类在进行初始化时调用了父类的构造器,我们来查看父类Proxy的构造器

   protected InvocationHandler h;
    private Proxy() {
    }
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

易见,父类将子类传入的InvocationHandler 赋给了h,我们也因此知道了这个h是什么,就是生成了代理类的初始化时传入的InvocationHandler 参数,那这个代理类是怎么创建的呢?

在这里插入图片描述

我们查看这个Proxy的创建代理方法 在这里插入图片描述 我们发现,在这个方法中,首先通过传入的类加载器和接口获得class类,然后通过class类获得构造器,然后使用构造器传入我们通过匿名内部类创建的InvocationHandler对象就可以创建出一个代理对象 我们通过代理对象调用方法时就是以下的步骤: 在这里插入图片描述

JDK动态代理为什么需要被代理的对象实现接口?

因为我们在运行时生成代理类需要继承父类Proxy完成初始化并且使用反射来增强方法,而java是单继承多实现,又为了实现代理类和接口的关联也就是获取类的类信息来得到方法信息,需要实现接口。

优缺点

优点: 1、将类的所有方法都集中到一个处理器中处理 2、在运行时动态生成代理对象,比较灵活 缺点:需要被代理类实现接口

CGLIB动态代理

CGLIB动态代理是基于CGLIB工具类实现的,可以在运行时继承代理类根据代理类的类信息方法等生成代理类,因此被代理类不能被final修饰

public class ProxyFactory implements MethodInterceptor {
    private BigStar bigStar;
    public ProxyFactory (BigStar bigStar){
        this.bigStar=bigStar;
    }
    public  BigStar getProxy(){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(BigStar.class);
        enhancer.setCallback(this);
        return (BigStar) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("收钱收钱收钱");
        return method.invoke(bigStar,objects);
    }
}
public class test {
    public static void main(String[] args) {
        BigStar bigStar=new BigStar("坤坤");
        ProxyFactory proxyFactory=new ProxyFactory(bigStar);
        BigStar proxy = proxyFactory.getProxy();
        System.out.println(proxy.sing());

    }
}

在这里插入图片描述

优缺点

优点: 1、和JDK动态代理一样,可以集中处理需要被扩展的方法 2、弥补了没有实现接口的类也可以实现动态代理 缺点: 被代理的类不能被final修饰

代理模式的应用

经给以上的实现,代理模式的作用以及很明显了,防止直接访问目标对象,通过生成代理对象使用反射增强被代理对象的方法。实际上我们已经接触了不少的代理对象的实现了,例如Spring Aop就是基于动态代理实现的,我们下节再来讲讲SpringAop