三种代理模式之间的区别


 意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

在这里插入图片描述

如何解决:增加中间层。

静态代理

静态代理的被代理对象和代理对象都要实现接口。
写一个老师类的接口

public interface Teacher {
    public void teach();
}

写一个被代理对象体育老师

public class PE_teacher implements Teacher {

    @Override
    public void teach() {
        System.out.println("上体育课");
    }
}

接下来数学老师需要去代理体育老师,数学老师就是一个handler代理类

public class Math_teacher implements Teacher {
    private PE_teacher teacher;

    public Math_teacher(PE_teacher teacher){
        this.teacher = teacher;
    }

    @Override
    public void teach() {
        System.out.println("开始代理");
        teacher.teach();
        System.out.println("额外补课");
        System.out.println("代理结束");
    }
}

缺点

由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐

动态代理

JDK动态代理

步骤
  1. 自定义代理对象工厂类,构造参数为被代理类
  2. 通过Proxy.newProxyInstance获得动态代理类
  3. 通过被代理类实现的接口接收代理类
  4. 通过代理对象调用目标方法;
//自己封装一个代理类工厂
public class ProxyFactory {
    //目标对象
    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }
	//返回增强后的类
    public Object getProxyInstance(){
        //参数:1.目标对象的类加载器 2.目标对象实现的接口类型 3.匿名内部类,执行目标对象的方法时触发invoke方法
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理开始");
                        //1. 目标对象 2. 参数
                        Object invoke = method.invoke(target, args);
                        System.out.println("开始补课");
                        return invoke;
                    }
                });
    }
}

工厂类的代理构造器需要被代理对象,getProxyInstance返回的是代理对象。返回的是Proxy.newProxyInstance方法new出来的对象。此方法参数是固定的,第三个参数是一个类,里面重写的方法便是具体的增强代理实现。

public class Client {
    public static void main(String[] args) {
        Object proxyInstance = new ProxyFactory(new PE_Teacher()).getProxyInstance();
        //强转代理对象
        Teacher teacher = (Teacher)proxyInstance;
        teacher.teach();
    }
}

区别

JDK静态代理是通过直接编码创建的,而JDK动态代理是利用反射机制在运行时创建代理类的。

其实在动态代理中,核心是InvocationHandler。每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。对待代理实例进行调用时,将对方法的调用进行编码并指派到它的调用处理器(InvocationHandler)的invoke方法

对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。

CGLIB

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类

步骤
  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 然后在需要使用的时候,通过CGLIB动态代理获取代理对象。
代码
public class HelloService {
 
    public HelloService() {
        System.out.println("HelloService构造");
    }
 
    /**
     * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
/**
 * 自定义MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
 
    /**
     * sub:cglib生成的代理对象
     * method:被代理对象方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}
public class Client {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(HelloService.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy= (HelloService)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}
区别

JDK代理要求被代理的类必须实现接口,有很强的局限性。

而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。

  • 代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法
  • 当执行代理对象的方法时,会首先判断一下是否存在实现了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,则将调用MethodInterceptor中的intercept方法

在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过FastClass机制对Class对象进行特别的处理,比如将会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用

FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

哪个快?

在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理

Spring用JDK还是CGLIB?

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib实现。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值