Spring 代理模式知多少

2020-08-18 16:15:15 浏览数 (1)

目录

  • 1. 前言
  • 2. 静态代理
    • 2.1 简介
    • 2.2 实例
  • 3. 动态代理
    • 3.1 简介
    • 3.2 实例
    • 3.3 基于 Jdk 的动态代理
    • 3.4 cglib 代理

1. 前言

代理模式是一种设计模式,提供了对目标对象额外的访问形式,即通过代理对象来实现对目标对象的访问,能够在不修改原目标对象的前提下提供额外的功能操作,实现对目标对象的功能扩展。

简单来说,代理模式就是 设置一个中间代理来控制访问原目标对象,从而增加原对象的功能和简化访问方式。

  • AbstractSubject:抽象对象,用于接口或抽象类来实现;
  • RealSubject:真实对象,被代理的对象;
  • Proxy:代理对象,用于代理真实对象,一般会做一些附属操作;
  • Client:客户,用代理对象来进行实际操作;

代理模式可以分为 动态代理 和 静态代理,我们能在不改变原来代码的情况下实现了对原来功能的增强,是 Spring 中 AOP 的核心思想。

2. 静态代理

2.1 简介

静态代理需要 代理对象和目标对象实现一样的接口,优点在于 能够在不修改目标对象的前提下扩展目标对象的功能。当缺点也很明显:

  1. 冗余:由于代理对象要和目标对象实现一样的接口,所以会导致产生过多的代理类;
  2. 不易维护:一旦接口增加了方法,目标对象和代理对象都要进行修改;

2.2 实例

  1. 定义一个抽象对象接口
代码语言:javascript复制
/**
 * @InterfaceName : Buy
 * @Author : cunyu
 * @Date : 2020/7/18 12:23
 * @Version : 1.0
 * @Description : 抽象对象
 **/

public interface Buy {
    public void buy();
}
  1. 定义真实对象实现接口;
代码语言:javascript复制
/**
 * @author : cunyu
 * @version : 1.0
 * @className : User
 * @date : 2020/7/18 12:23
 * @description : 真实对象
 */

public class User implements Buy{
    @Override
    public void buy() {
        System.out.println("买书");
    }
}
  1. 创建代理角色;
代码语言:javascript复制
/**
 * @author : cunyu
 * @version : 1.0
 * @className : UserProxy
 * @date : 2020/7/18 12:25
 * @description : 代理对象
 */

public class UserProxy implements Buy {
    private User user;

    public UserProxy() {
    }

    public UserProxy(User user) {
        this.user = user;
    }

    @Override
    public void buy() {
       user.buy();
        read();
    }

    public void read() {
        System.out.println("看书");
    }

}

  1. 创建客户对象
代码语言:javascript复制
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Client
 * @date : 2020/7/18 12:29
 * @description : 客户对象
 */

public class Client {
    public static void main(String[] args) {
        User user = new User();
        UserProxy userProxy = new UserProxy(user);
        userProxy.buy();
    }
}

3. 动态代理

3.1 简介

动态代理利用了 JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能,动态代理又叫做 JDK 代理 或 接口代理。

动态代理具有静态的优点,能够在不修改目标对象的前提下扩展目标对象的功能,但是不需要实现接口,但 要求目标对象必须实现接口,否则不能使用动态代理。此外,一个动态代理一般代理某一类业务,而且可以代理多个类(代理的是接口)。

静态代理和动态代理的区别:

  1. 静态代理在 编译时 已经实现,编译完成后代理类是一个实际的 .class 文件;
  2. 动态代理在 运行时动态生成,即编译后没有实际的 .class 文件。而是在运行时动态生成类字节码,同时加载到 JVM 中。

动态代理可以分为两类,一类是 基于接口动态代理(JDK 动态代理),一类是 基于类的动态代理(cglib)。

3.2 实例

  1. 定义抽象对象
代码语言:javascript复制
/**
 * @InterfaceName : Buy
 * @Author : cunyu
 * @Date : 2020/7/18 12:23
 * @Version : 1.0
 * @Description : 抽象对象
 **/

public interface Buy {
    public void buy();
}
  1. 创建真实对象
代码语言:javascript复制
/**
 * @author : cunyu
 * @version : 1.0
 * @className : User
 * @date : 2020/7/18 12:23
 * @description : 真实对象
 */

public class User implements Buy{
    @Override
    public void buy() {
        System.out.println("买书");
    }
}
  1. 创建代理对象
代码语言:javascript复制
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : ProxyInvocationHandler
 * @date : 2020/7/18 14:08
 * @description : 动态代理对象
 */

public class ProxyInvocationHandler implements InvocationHandler {
    private Buy buy;

    public void setBuy(Buy buy) {
        this.buy = buy;
    }

    /**
     * @param
     * @return
     * @description 生成代理对象
     * @date 2020/7/18 14:10
     * @author cunyu1943
     * @version 1.0
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), buy.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(buy, args);
        read();
        return result;
    }

    public void read() {
        System.out.println("读书");
    }
}
  1. 创建客户对象
代码语言:javascript复制
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Client
 * @date : 2020/7/18 12:29
 * @description : 客户对象
 */

public class Client {
    public static void main(String[] args) {
        User user = new User();

        // 代理实例的调用处理程序
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        // 将真实对象放置进去
        proxyInvocationHandler.setBuy(user);
        // 动态生成对应的代理类
        Buy proxy = (Buy) proxyInvocationHandler.getProxy();
        proxy.buy();
    }
}

3.3 基于 Jdk 的动态代理

  1. 先定义一个接口;
代码语言:javascript复制
public interface MyCalculator{
    int mul(int num1, int num2);
}
  1. 实现接口;
代码语言:javascript复制
public class MyCalculatorImpl implements MyCalculator{
    @Override
    public int mul(int num1, int num2){
        return num1 * num2;
    }
}
  1. 实现代理类
代码语言:javascript复制
public class CalculatorProxy{
    public static Object getInstance(final MyCalculatorImpl myCalculator){
        return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler(){
            /**
             * @param proxy 代理对象
             * @param method 代理的方法
             * @param args 方法的参数
             * @return
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                System.out.println(method.getName() "方法开始执行...");
                Object invoke = method.invoke(myCalculator, args);
                System.out.println(method.getName() "方法执行结束...");
            }
        });
    }
}

其中 Proxy.newProxyInstance 方法一共接收了三个参数,分别代表:

  • 一个 classloader
  • 代理多项实现的接口
  • 代理对象方法的处理器

3.4 cglib 代理

由于静态代理需要实现目标对象的相同接口,就可能导致代理类增多,难以维护,这个时候可以用动态代理。而动态代理也有个限制:目标对象一定要有接口,否则就不能实现动态代理,为了突破这个限制,于是才出现了 cglib 代理。

cglib 代理也叫子代理,能够从内存中构建出一个子类来扩展目标对象。是一个强大的高性能代码生成包,能在运行期间扩展 Java 类和实现 Java 接口,被许多 AOP 的框架广泛使用,为其提供方法的 interception(拦截)。

3.4.1 编写 cglib 代理

要实现 cglib 代理,通常要进行如下步骤:

  1. 首先引入 cglib-jar 文件,但 Spring 的核心包中已经包括该功能,所以直接引入 spring-core 即可;
  2. 然后就可以在内存中动态构建子类,需要注意的一点:代理的类不能是 final,否则将会报错。此外,如果目标对象的方法为 final/static,那么则不会被拦截(即不会执行目标对象额外的业务方法)。
代码语言:javascript复制
// 实现 MethodInterceptor 接口
public class ProxyFactory implements MethodInterceptor{

    // 维护目标对象
    private Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }

    // 给目标对象创建代理对象
    public Object getProxyInstance(){
        //1. 工具类
        Enhancer en = new Enhancer();
        //2. 设置父类
        en.setSuperclass(target.getClass());
        //3. 设置回调函数
        en.setCallback(this);
        //4. 创建子类(代理对象)
        return en.create();
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        System.out.println("开始事务.....");

        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("提交事务.....");

        return returnValue;
    }
}
  1. 测试
代码语言:javascript复制
public class App {

    public static void main(String[] args) {
        UserDao userDao = new UserDao();
        UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance();

        factory.save();
    }
}

0 人点赞