代理模式

一:代理模式概述

高中的时候遇到一个喜欢的女生,那时候我们都比较害羞,我们的交流全靠传话,有一个姑娘成了我们的中介,每天都给我们传递狗粮,就这样我们开心的过完了高中。然后大学。。。。好了继续代理模式的学习:当我们没办法访问某个对象的时候可以通过一个代理对象来间接访问。

1.1 什么是代理

1.2 代理的概念结构图

结构图:

设计模式之代理模式-LMLPHP

角色说明如下:

  • Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
  • Proxy(代理主题角色):包含了对真实主题的引用,从而可以在任何时候操作真实主题对象
  • RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

1.3 代理可以做什么

  • 方法启动和停止时记录信息
  • 对参数执行额外检查
  • 模仿原始类的行为
  • 实现对某些资源的延时访问

在实际应用中,代理类不直接实现该功能。遵循单一责任原则,代理类仅执行代理,并且实际行为修改在处理程序中实现。当调用代理对象而不是原始对象时,代理会决定是否必须调用原始方法或某个处理程序。处理程序可以执行其任务,也可以调用原始方法。

我们常常接触的代理模式主要分为两种:静态代理模式动态代理模式


二:静态代理模式

2.1 静态代理概念

2.2 静态代理实战

模拟:要求更新数据前后记录日志。

抽象接口:IPerson

public interface IPerson {
    // 更新信息
    void update();
}

目标对象Person(要被代理的对象)

public class Person implements IPerson {
    @Override
    public void update() {
        System.out.println("更新");
    }
}

代理对象

public class PersonProxy implements IPerson {

    // 接收目标对象(要被代理的对象) 针对抽象编程
    private IPerson target;
    public PersonProxy(IPerson target){
        this.target = target;
    }
    // 扩展的功能 记录日志开始
    private void handleBefore(){
        System.out.println("进入更新方法--获取参数");
    }
    // 扩展的功能 记录日志结束
    private void handleAfter(){
        System.out.println("更新成功");
    }
    // 同样的是更新方法,我们加入了记录日志的功能
    @Override
    public void update() {
       handleBefore();
       target.update();
       handleAfter();
    }
}

客户端

public class StaticProxyClient {
    public static void main(String[] args) {
        // 目标对象
        IPerson target = new Person();
        // 代理对象
        PersonProxy proxy = new PersonProxy(target);
        // 执行代理的方法
        proxy.update();
    }
}

总结:静态代理可以在不侵入目标对象的前提下扩展功能,但是代理对象需要和目标对象实现相同的接口,导致了大量的代理类,会增加维护量,那么便引入接下来的动态代理


三:动态代理模式

3.1 动态代理概念

相较于静态代理,动态代理不再需要写各个静态代理类,只需要简单地指定一组接口以及目标类对象就可以动态的获得对象,可以简单的理解这两个的区别:静态代理的代理类是程序员手动创建的,而动态代理的代理类是由程序创建的

3.2 JDK动态代理和Cglib动态代理

3.2.1 JDK动态代理

  1. 编写抽象主题接口
  2. 实现InvocationHandler接口来自定义自己的InvocationHandler。
  3. 通过Proxy.newProxyInstance获得动态代理类

--------------编码实战 ---------------

抽象主题角色

public interface IPerson {
    void update();
}

真实主题

public class Person implements IPerson {
    @Override
    public void update() {
        System.out.println("更新");
    }
}

自定义InvocationHandler

public class MyInvocationHandler implements InvocationHandler {
    //目标对象
    private Object target;
    public MyInvocationHandler(Object target){
        this.target = target;
    }
    // 横向功能扩展
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------更新前记录日志-------------");
        //执行相应的目标方法
        Object res = method.invoke(target,args);
        System.out.println("------更新后记录日志-------------");
        return res;
    }
}

客户端调用

public class client {
    public static void main(String[] args) {
        // 动态获取代理类
        IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(
                IPerson.class.getClassLoader(), // 加载接口的类加载器
                new Class[]{IPerson.class}, // 一组接口
                new MyInvocationHandler(new Person())); // 自定义的InvocationHandler
        proxyInstance.update();
    }
}

结果打印

---------------使用静态工厂方法来重构代码----------------

抽象主题角色和真实主题角色的代理不需要变动,我们新建一个proxyFactory,将MyInvocationHandler的代码和Proxy.newProxyInstance结合起来(其实这两部分就是整个jdk代理的核心要实现的内容)。

ProxyFactory

public class ProxyFactory {
    public static<T> Object  getProxyInstance(final T target){
        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("------更新前记录日志-------------");
                        Object proxyInstance = method.invoke(target, args);
                        System.out.println("-----更新后记录日志-------------");
                        return proxyInstance;
                    }
                });
    }
}

那么客户端的调用

public class JDKProxyClient {
    public static void main(String[] args) {
        IPerson target = new Person();
        IPerson proxy = (IPerson) ProxyFactory.getProxyInstance(target);
        proxy.update();
    }
}

看完了小例子,再回顾一下InvocationHandler是干什么的:

InvocationHandler:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

3.2.2 JDK动态代理总结

  • jdk动态代理不再需要编写许多静态的代理类,但是目标类对象还是必须实现接口,那么当目标类不实现接口的话,jdk就无法再提供动态代理了。

3.2.3 Cglib动态代理

jdk提供的代理有个明显的缺点:需要目标对象实现一个或者多个接口。而假如你需要代理没有接口的类,可以使用Cglib库。

CGLIB是一个强大、高性能的代码生成库,被广泛的运用于AOP框架提供方法拦截,在实现内部,CGLIB库使用了ASM这轻量性能高的字节码操作框架来转化字节码,产生新类。spring的aop默认使用JDK动态代理,除非强制使用CGLIB。但是有个主意点:CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类

实现的基本步骤如下:

  1. 编写目标对象
  2. 编写MethodInterceptor,当代理对象调用方法时候,会调用该类的intercept方法
  3. 编写Cglib动态代理类

3.2.4 Cglib实例演示

目标对象

public class Hello {
    public String sayHello(String msg){
        return "hello" + msg;
    }
}

拦截器:类似于jdk代理的InvocationHandler

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(" --- 记录日志开始--- ");
        return methodProxy.invokeSuper(o, objects);
    }
}
public class client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 设置父类,cglib代理将会产生hello的子类
        enhancer.setSuperclass(Hello.class);
        // 设置回调方法,将会执行MyMethodInterceptor的interapt()方法
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        Hello proxy = (Hello) enhancer.create();
        System.out.println(proxy.sayHello("codecarver"));
    }
}

结果打印

上述代码通过CGLIB的的Enhancer来生成代理对象,通过将目标对象设置为父类,以及设置回调方法,生成继承自父类的代理类,所以我们的目标对象绝对不能是final类型,我们可以在intercept方法中加入我们要增强的逻辑,通过methodProxy.invokeSuper(o, objects)来将调用转发给目标对象(原始对象)。可以再看看jdk的动态代理,其实很类似,只不过一个是目标对象必须实现接口,一个是目标对象必须可以被继承


四:总结

  1. 代理可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
  2. 静态代理模式要求代理对象实现和目标对象一样的接口,且由程序员提前写好。会产生大量的代理类。
  3. 动态代理可以动态生成代理类,不会产生大量的静态class文件。
  4. 动态代理也是需要代理对象和目标对象实现一样的接口,但要求是目标对象必须实现接口。
  5. glib代理不要求目标对象实现接口,它是根据目标对象生成子类,让子类作为代理对象去工作的

号外号外:

  • 如果有小伙伴觉得我写的不错的话可以关注一下我的博客哦
  • 可以关注下方我的公众号java架构师小密圈,回复1获取2Tjava架构师必备干货另外:小伙伴可以回复任意想学的技术,可以免费帮你搜寻其实我们还需要学很多!!!!!!

设计模式之代理模式-LMLPHP

  • 还会分享一些赚钱理财的小套路哦,欢迎大家来支持,一起学习成长,程序员不仅仅是搬瓦工!

公众号:分享系列好文章
设计模式之代理模式-LMLPHP

交流群:一起奔着java架构师努力
设计模式之代理模式-LMLPHP

12-22 15:55