Java 代理模式

代理模式是一种设计模式,设计模式中将它的含义定义为,为其他对象提供一种代理以控制对这个对象的访问。
从它的设计意图上来说就是可以理解为在不改动目标对象的基础上,增加其他额外的功能(扩展功能)。通俗来说即是在不改变源码的情况下,实现对源码对象的功能扩展。

1. 静态代理

Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一单需要修改接口,代理类和委托类都需要修改。

我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

public interface Movie {
     void play();
}

然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。

public class RealMovie implements Movie {

    @Override
    public void play() {
        // TODO Auto-generated method stub
        System.out.println("您正在观看电影 《肖申克的救赎》");
    }
}

这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。
Proxy代理

public class Cinema implements Movie {

    RealMovie movie;

    public Cinema(RealMovie movie) {
        super();
        this.movie = movie;
    }


    @Override
    public void play() {

        guanggao(true);

        movie.play();

        guanggao(false);
    }

    public void guanggao(boolean isStart){
        if ( isStart ) {
            System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
        } else {
            System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
        }
    }
}

Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。

public class ProxyTest {

    public static void main(String[] args) {

        RealMovie realmovie = new RealMovie();

        Movie movie = new Cinema(realmovie);

        movie.play();

    }
}

结果:

电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。

2. 动态代理

Java中的动态代理依靠反射来实现,代理类和委托类需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理。代理类在JVM运行时动态生成,而不是编译期就能确定。

动态代理中,常用的有两种:

  • jdk动态代理
  • cglib动态代理

jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效。

jdk动态代理

jdk动态代理是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。

实现方式:

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

public class JDKDynamicProxyImpl<T> implements DynamicProxy<T>, InvocationHandler {

    private T target;
    public JDKDynamicProxyImpl(T target) {
        this.target = target;
    }

    public T bind() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method " + method.getName() + " begin");
        Object invoke = method.invoke(target, args);
        System.out.println("method " + method.getName() + " end");
        return invoke;
    }
}

使用:

public static void jdkProxyImplTest() {

        UserMapper userMapper = new UserMapperImpl();
        JDKDynamicProxyImpl<UserMapper> jpx = new JDKDynamicProxyImpl<UserMapper>(userMapper);
        UserMapper proxy = jpx.bind();
        proxy.getUserList();
}

cglib动态代理

JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

实现方式:

import cn.sivan.proxy.DynamicProxy;
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 CGLibDynamicProxyImpl<T> implements DynamicProxy<T>, MethodInterceptor {

    private T target;

    public CGLibDynamicProxyImpl(T target) {
        this.target = target;
    }

    public T bind() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("method " + method.getName() + " begin");
        //methodProxy.invokeSuper(o, objects);
        method.invoke(target,objects);
        System.out.println("method " + method.getName() + " end");
        return null;
    }
}

使用:

public static void cglibProxyImplTest() {

        UserMapper userMapper = new UserMapperImpl();
        CGLibDynamicProxyImpl<UserMapper> cpx = new CGLibDynamicProxyImpl<UserMapper>(userMapper);
        UserMapper proxy = cpx.bind();
        proxy.getUserList();
}

jdk动态代理和cglib动态代理:

  • JDK动态代理是面向接口的。
  • CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。

所以:

  • 静态代理实现较简单,代理类在编译期生成,效率高。缺点是会生成大量的代理类。
  • 动态代理通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
posted @ 2020-08-24 22:36  宁川  阅读(139)  评论(0)    收藏  举报