设计模式:代理模式

156 阅读6分钟

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

一、静态代理

静态代理的实现方式是实现公共接口或继承抽象类

角色:

  1. 抽象⻆⾊:声明真实对象和代理对象的共同接⼝;
  2. 代理⻆⾊:代理对象⻆⾊内部含有对真实对象的引⽤,从⽽可以操作真实对象,同时代理对象提供与真实 对象相同的接⼝以便在任何时刻都能代替真实对象。同时,代理对象可以在执⾏真实对象操作时,附加其 他的操作,相当于对真实对象进⾏封装。
  3. 真实⻆⾊:代理⻆⾊所代表的真实对象,是我们最终要引⽤的对象。
/**
 * @author GodLiang
 * @version 1.0
 * @date 2021/2/14 15:38
 * @description: TODO 静态代理
 */
public class StaticProxy {
    public static void main(String[] args) {
        Subject proxySubject = new ProxySubject(new RealSubject());
        proxySubject.request();
    }
}

/**
 * 抽象接口角色  规范请求的方法
 */
interface Subject {
    /**
     * 请求方法
     */
    void request();
}

/**
 * 真实对象。被代理的对象
 */
class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("发送一个请求....");
    }
}

/**
 * 代理角色
 */
class ProxySubject implements Subject {

    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        if (realSubject == null)
            throw new NullPointerException("object not null");
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        try {
            //执行前置增强方法
            beforeRequest();

            //执行原生方法
            realSubject.request();

            //执行后置增强方法
            AfterRequest();
        }catch (Exception e) {
            //异常处理
            solveException();
        }

    }

    /**
     * 前置增强
     */
    public void beforeRequest() {
        System.out.println("前置增强");
    }

    /**
     * 后置增强
     */
    public void AfterRequest() {
        System.out.println("后置增强");
    }

    /**
     * 异常处理
     */
    public void solveException() {
        System.out.println("异常处理");
    }
}

代理类与被代理类实现同一个接口。加一些加强方法。在方法内调用增强方法进行增强

优缺点:

  1. 优点:可以做到在不修改⽬标对象的功能前提下,对⽬标功能扩展.
  2. 缺点:每⼀个代理类都必须实现⼀遍委托类(也就是realsubject)的接⼝,如果接⼝增加⽅法,则代理类 也必须跟着修改。其次,代理类每⼀个接⼝对象对应⼀个委托对象,如果委托对象⾮常多,则静态代理类 就⾮常臃肿,难以胜任。

二、JDK动态代理

动态代理解决静态代理中代理类接⼝过多的问题,通过反射来实现的,借助Java⾃带的 java.lang.reflect.Proxy,通过固定的规则⽣成。

/**
 * @author GodLiang
 * @version 1.0
 * @date 2021/2/14 16:16
 * @description: TODO 动态代理
 */
public class JdkProxy {

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        InvocationHandler dynamicProxy = new DynamicProxy(subject);
        ClassLoader classLoader = RealSubject.class.getClassLoader();
        Subject newProxySubject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy);
        newProxySubject.request();
    }

}
/**
 * 抽象接口角色  规范请求的方法
 */
interface Subject {
    /**
     * 请求方法
     */
    void request();
}

/**
 * 真实对象。被代理的对象
 */
class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("发送一个请求....");
    }
}

/**
 * 动态代理类
 */
class DynamicProxy implements InvocationHandler {

    Object object;

    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = null;
        try {
            //前置增强
            beforeRequest();
            //执行方法
            invoke = method.invoke(object, args);
            //后置增强
            AfterRequest();
        }catch (Exception e) {
            //异常处理
            solveException();
        }
        return invoke;
    }

    /**
     * 前置增强
     */
    public void beforeRequest() {
        System.out.println("前置增强");
    }

    /**
     * 后置增强
     */
    public void AfterRequest() {
        System.out.println("后置增强");
    }

    /**
     * 异常处理
     */
    public void solveException() {
        System.out.println("异常处理");
    }
}

上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)⽅法,该⽅法会根据指定的参数动态创建代理对象。三个参数的意义如下:

  1. loader,指定被代理对象实现类(Subject)的类加载器;
  2. interfaces,代理对象需要实现的接⼝,可以同时指定多个接⼝;
  3. handler,⽅法调⽤的实际处理者,代理对象的⽅法调⽤都会转发到这⾥(*注意1)。

三、cglib动态代理

注:引入cglib.jar包和相关依赖asm

/**
 * @author GodLiang
 * @version 1.0
 * @date 2021/2/14 17:07
 * @description: TODO cglib动态代理
 */
public class CglibProxy {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloController.class);
        enhancer.setCallback(new MyMethodInterceptor());
        HelloController helloController = (HelloController) enhancer.create();
        helloController.hello();
    }

}

/**
 * 被代理的类   无接口
 */
class HelloController {
    public void hello() {
        System.out.println("hello world");
    }
}

/**
 * cglib动态代理实现类
 * ⾸先实现⼀个MethodInterceptor,⽅法调⽤会被转发到该类的intercept() ⽅法。
 */
class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //todo 前置加强...
        //方法代理  注意这里是methodProxy  不是用method进行反射
        Object invoke = methodProxy.invokeSuper(o, objects);
        //todo 后置加强...
        return invoke;
    }
    
}

通过CGLIB的Enhancer来指定要代理的⽬标对象、实际处理代理逻辑的对象,最终通过调⽤create()⽅法 得到代理对象,对这个对象所有⾮final⽅法(非final是因为final的方法无法被继承)的调⽤都会转发给MethodInterceptor.intercept()⽅法,在 intercept()⽅法⾥我们可以加⼊任何逻辑,⽐如修改⽅法参数,加⼊⽇志功能、安全检查功能等;通过调 ⽤MethodProxy.invokeSuper()⽅法,我们将调⽤转发给原始对象,具体到本例,就是HelloController的 具体⽅法。 对于从Object中继承的⽅法,CGLIB代理也会进⾏代理,如hashCode()、equals()、toString()等,但是 getClass()、wait()等⽅法不会,因为它是final⽅法,CGLIB⽆法代理。

原理:

CGLIB是⼀个强⼤的⾼性能的代码⽣成包,底层是通过使⽤⼀个⼩⽽快的字节码处理框架ASM,它可以在 运⾏期扩展Java类与实现Java接⼝ Enhancer是CGLIB的字节码增强器,可以很⽅便的对类进⾏拓展 创建代理对象的⼏个步骤:

  1. ⽣成代理类的⼆进制字节码⽂件
  2. 加载⼆进制字节码,⽣成Class对象( 例如使⽤Class.forName()⽅法 )
  3. 通过反射机制获得实例构造,并创建代理类对象

四、关系总结

  1. jdk动态代理:利⽤拦截器(拦截器必须实现InvocationHanlder)加上反射机制⽣成⼀个实现代理接⼝的 匿名类,在调⽤具体⽅法前调⽤InvokeHandler来处理。只能对实现了接⼝的类⽣成代理只能对实现了接⼝的类⽣成代理

  2. cglib:利⽤ASM开源包,对代理对象类的class⽂件加载进来,通过修改其字节码⽣成⼦类来处理。主 要是对指定的类⽣成⼀个⼦类,覆盖其中的⽅法,并覆盖其中⽅法实现增强,但是因为采⽤的是继承, 对于final类或⽅法,是⽆法继承的。

  3. 选择

    a、如果⽬标对象实现了接⼝,默认情况下会采⽤JDK的动态代理实现AOP。

    b、如果⽬标对象实现了接⼝,可以强制使⽤CGLIB实现AOP。

    c、如果⽬标对象没有实现了接⼝,必须采⽤CGLIB库,Spring会⾃动在JDK动态代理和CGLIB之间转 换。