关于代理模式的介绍,以及动态代理(jdk、cglib)的介绍

代理模式

简要介绍

代理,就是代理人

当A想找B做一件事,但是可能B在十万八千里之外,联系不上,但是C又能联系上B,而且A也能找到C,所以A找C办这件事,但真正做事的人是B,C只是个代理人

一句话:代理人可以控制客户端对其他对象的访问

代理种类:

  • 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

哪里用到这个模式了?

  1. 使用过的一些中间件例如;RPC框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。
  2. 另外像我们常用的MyBatis,基本是定义接口但是不需要写实现类,就可以对xml或者自定义注解里的sql语句进行

类图

image-20220219144804350

在上图中,Client就是A,Proxy就是C,RealSubject就是B,也就是真正做事的人

例子

定义一个接口叫HelloService,然后定义一个sayHello方法,我们通过代理来调用这个方法,来实现在目标对象执行sayHello方法的前后记录日志

HelloService

public interface HelloService {

    /**
     * say hello
     */
    void say();
}

实现类(具体做事的人):HelloServiceImpl

public class HelloServiceImpl implements HelloService{
    @Override
    public void say() {
        System.out.println("hello!");
    }
}

代理:HelloServiceProxy

可以看到我们就是在这里直接调用真正做事的人执行方法,并且前后还可以加额外的东西

public class HelloServiceProxy implements HelloService{

    private final HelloService target;

    public HelloServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void say() {
        System.out.println("记录日志");
        target.say();
        System.out.println("清理数据");
    }
}

客户端:Main

public class Main {
    public static void main(String[] args) {
        // 目标对象
        HelloService target = new HelloServiceImpl();
        // 代理对象
        HelloServiceProxy helloServiceProxy = new HelloServiceProxy(target);
        helloServiceProxy.say();
    }
}

执行结果:

记录日志
hello!
清理数据

缺点

经过上面的介绍,我们可以发现,一个代理类只能为一个接口服务,平时开发是有N多个接口的,肯定会产生很多的代理类的,所以我们就会想,有没有可能一个代理类,可以完成所有代理的功能,所以就有了动态代理

动态代理

JDK

JDK为我们提供了动态代理的支持,我们的代理类需要实现java.lang.reflect.InvocationHandler接口并且调用java.lang.reflect.Proxy类的newProxyInstance方法创建一个代理实例,然后调用具体的方法,其实就是通过反射生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

在上述代码基础上加入MyInvocationHandler:

public class MyInvocationHandler implements InvocationHandler {

    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 执行具体方法
     * @param proxy 代理类
     * @param method 目标具体的方法
     * @param args 方法参数
     * @return 方法返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印方法执行信息
        System.out.println(target.getClass().getName() + "." + method.getName());
        // 前置通知
        System.out.println("记录日志");
        Object result = method.invoke(target, args);
        System.out.println("清理数据");
        return result;
    }


    public Object getProxy() {
        // target.getClass().getInterfaces() 这个是获取目标对象实现的接口,也就是jdk的动态代理的对象必须得实现至少一个接口才可以被代理
        // 第三个参数是一个InvocationHandler,可以写到方法里,也可以以这种形式写下来,实现接口然后this
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}

关于Proxy.newProxyInstance方法参数的说明:

  1. 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
  2. 第一个参数指定产生代理对象的类加载器(就是要为谁加代理,就传谁的class),需要将其指定为和目标对象同一个类加载器
  3. 第二个参数要实现和目标对象一样的接口(可以看出jdk的动态代理,其实就是把我们手动创建和代理对象相同接口的代理类自动化了),所以只需要拿到目标对象的实现接口
  4. 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法

下面使用一下这个动态代理,看看好使不:

public class Main {
    public static void main(String[] args) {
        // 目标对象
        HelloService target = new HelloServiceImpl();
        MyInvocationHandler invocationHandler = new MyInvocationHandler(target);
        HelloService proxy = (HelloService) invocationHandler.getProxy();
        proxy.say();
    }
}

结果:

com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据

cglib

jdk动态代理的局限性:只能对实现了接口的类进行代理,对于没有实现接口的类无法代理

cglib对于没有实现接口的类也可以进行代理

这个是咋动态生成代理的呢?他利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

cglib需要引入依赖:

<!-- cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

在上述基础,再加一个CglibProxyFactory类:

public class CglibProxyFactory implements MethodInterceptor {

    /**
     * 工具类
     */
    private final Enhancer enhancer = new Enhancer();

    private final Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        // 设置要创建子类的父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调
        enhancer.setCallback(this);
        // 通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 打印方法执行信息
        System.out.println(target.getClass().getName() + "." + method.getName());
        // 前置通知
        System.out.println("记录日志");
        Object result = method.invoke(target, args);
        System.out.println("清理数据");
        return result;
    }
}

测试:

public class Main {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        CglibProxyFactory helloServiceProxyFactory = new CglibProxyFactory(helloService);
        HelloService helloServiceProxy = (HelloService) helloServiceProxyFactory.getProxy();
        helloServiceProxy.say();
        // 找一个没有实现接口的类进行代理,上面的helloService实现了HelloService接口
        Student student = new Student();
        CglibProxyFactory studentProxyFactory = new CglibProxyFactory(student);
        Student studentProxy = (Student) studentProxyFactory.getProxy();
        studentProxy.study();
    }
}
com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据
com.hc.basics.dynamicproxy.cglib.Student.study
记录日志
student study!
清理数据
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值