代理模式

今天聊聊平时工作中很常见的Java设计模式–代理模式。

什么是代理模式?

  • 为其他对象提供一种代表,以控制对这个对象的访问。

  • 代理对象起到中介的作用,用户与代理对象打交道,不直接接触实际对象。

  • 使用代理模式创建代表对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或者需要安全控制的对象

常见的代理模式:

  1. 远程代理–为不同地理的对象,提供局域网对象(监控全国分店)

  2. 虚拟代理–将资源消耗很大的对象进行延迟加载,真正需要的时候进行创建(加载图片时,先用默认图片代替)

  3. 保护代理–控制用户的访问权限

  4. 智能引用代理–提供对目标对象额外服务(火车站代售点,不止代售车票)

静态代理

代理和被代理的对象在代理之前是确定的。他们都实现相同的接口或继承相同的抽象类。
静态代理模式类图:
这里写图片描述
从代理模式的类图可以看出,RealSubject和Proxy都实现了Subject接口,Proxy代理类持有Subject的引用,控制着对Subject的实现类RealSubject的访问。

举个栗子:

public class StaticProxyDemo {
    // 接口
    static interface Subject{
        public void request();
    }

    // 实现类
    static class RealSubject implements Subject{
        @Override
        public void request() {
            System.out.println("I am a request...");
        }
    }

    // 代理类
    static class Proxy implements Subject{
        private Subject realSubject;
        public Proxy(Subject realSubject){
            this.realSubject = realSubject;
        }
        @Override
        public void request() {
            System.out.println("enter proxy...");
            realSubject.request();
            System.out.println("out proxy...");
        }
    }
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxySubject = new Proxy(subject);
        proxySubject.request();
    }

}

程序输出:

enter proxy...
I am a request...
out proxy...

静态代理很简单,代码无需多说,下面直接进入重点动态代理。

动态代理

何为动态?动态是指运行时才把类创建出来,根据传入接口参数创建的被代理类。

利用reflect反射包,在运行时动态创建一个代理类,实现一个或多个接口,并把方法的调用转发到所指定的类。这个过程称为动态代理。

JDK动态代理

而JDK动态代理是运行时生产的class,该class需要实现一组interface,使用动态代理类时,必须实现invocationHandler接口。

JDK动态代理类图:
这里写图片描述
从类图可以看到,新增了InvocationHandler接口和实现,Proxy的任何方法调用都会被传入此类,InvocationHandler控制着对RealSubject的访问。
我们用动态代理实现之前的栗子:

public class DynamicProxyDemo {
    // 接口
    static interface Subject {
        public void request();
    }

    // 实现类
    static class RealSubject implements Subject {
        @Override
        public void request() {
            System.out.println("I am a request...");
        }
    }

    // 代理类
    static class InvocationHandlerDemo implements InvocationHandler {
        private Subject realSubject;

        public InvocationHandlerDemo(Subject realSubject) {
            this.realSubject = realSubject;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("DynamicProxy enter " + method.getName());
            Object result = method.invoke(realSubject, args);
            System.out.println("DynamicProxy out  " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxySubject = (Subject) Proxy.newProxyInstance(subject.getClass()
                .getClassLoader(), new Class<?>[] { Subject.class },
                new InvocationHandlerDemo(subject));
        proxySubject.request();
    }
}

通过代码,我们看到,Subject接口和实现都没变,但是代理对象proxySubject的创建方式变了,proxySubject是由reflect反射包中的Proxy的静态方法newProxyInstance来创建的,该方法声明如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

ClassLoader loader:表示类加载器,不同的类加载器所加载同一个类时都会产生不同的class文件,所以这里要使用与Subject一样的类加载器。
Class<?>[] interfaces:表示接口数组,即代理类要实现的接口数组,注意这里只能传接口类型,不能是其实现类。我们这里传入了Subject接口类型。
InvocationHandler h:表示InvocationHandler接口类型,这里我们传入了InvocationHandlerDemo,InvocationHandler的实现类作为参数,它只定义了一个方法invoke,对代理接口所有方法的调用都会转发给该方法。

  Proxy的静态方法newProxyInstance的返回值类型是Object类型,我们将返回值强制转换成了Subject类型,注意返回值类型不能强制转换为类类型,比如其子类RealSubject,因为它的实际代理对象类型是Subject接口类型。

基本原理
上面例子中创建proxySubject的代码可以用如下代码代替:

Class<?> proxyCls = Proxy.getProxyClass(Subject.class.getClassLoader(), 
                new Class<?>[] { Subject.class });
        Constructor<?> ctor = proxyCls.getConstructor(new Class<?>[] { InvocationHandler.class });
        InvocationHandler handler = new InvocationHandlerDemo(subject);
        Subject proxySubject = (Subject) ctor.newInstance(handler);

分为三步:

1、通过Proxy.getProxyClass创建代理类定义,类定义会被缓存

2、获取代理类的构造方法,构造方法有一个InvocationHandler类型的参数

3、创建InvocationHandler对象,创建代理类对象

CGLIB动态代理

由于JDK动态代理只能代理接口,返回的代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理不是接口中定义的方法,那么JDK动态代理将无能为力。但是CGLIB动态代理可以实现针对类的代理。
举个栗子:

public class DynamicProxyDemo3 {
    static class RealSubject {
        public void request() {
            System.out.println("I am a request...");
        }
    }

    // 代理类
    static class InterceptorDemo implements MethodInterceptor {
        @Override
        public Object intercept(Object object, Method method, Object[] objects,
                MethodProxy proxy) throws Throwable {
            System.out.println("CGLIB DynamicProxy enter " + method.getName());
            Object result = proxy.invokeSuper(object, objects);
            System.out.println("CGLIB DynamicProxy out  " + method.getName());
            return result;
        }

    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new InterceptorDemo());
        RealSubject proxySubject = (RealSubject) enhancer.create();
        proxySubject.request();
    }
}

RealSubject 表示被代理的类,它既不是接口也没有实现接口。Enhancer对象为一个类生成代理对象,这个代理对象可以安全的转换为被代理类的类型,setSuperclass设置被代理的类,setCallback设置被代理类的public非final方法被调用时的处理类,Enhancer支持多种类型,这里使用的类实现了MethodInterceptor接口,它与JDK动态代理的InvocationHandler有点类似。

基本原理
CGLIB的实现机制与JDK不同,它是通过继承实现的,它也是动态创建了一个类,但这个类的父类是被代理的类,代理类重写了父类的所有public非final方法,改为调用Callback中的相关方法。

JDK动态代理和CGLIB动态代理的比较

JDK动态代理只能代理一个接口或一组接口,它为这些接口动态创建了实现类,接口的具体逻辑由实现了InvocationHandler接口的实现类而定,可以根据情况适当的选择被代理的对象。而CGLIB代理面向的是一个具体的类,动态地创建了一个新类,继承了被代理类,重写了其方法。

总结

动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为。这些特性使得它广泛应用于各种框架中,比如Spring,MyBatis等,了解并深入理解动态代理,有助于我们更深刻的理解这些开源框架。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值