代理模式(CGLIB和JDK)

一. 简介

1. 生活中的代理案例

  • 房屋中介代理:客户手里没有房源信息,所以我们需要找一个中介来获得房源
  • 商品代购:我们手里没有低成本的商品,所以我们需要找代购者获得低成本商品

对于消费者而言,通过代购只需要关心自己的商品,而不需要关注如何获得商品,这样就对消费者进行了增强

2. java中代理模式的应用

  • 统一异常处理
  • Mybatis
  • Spring的AOP使用原理
  • 日志框架等

3. 什么是代理模式

代理模式是23种设计模式中的一种,属于结构型的模式。指一个对象本身不做实际的操作,而是通过其它对象得到自己想得到的结果,所以目标对象只需要关心自己实现的细节,而通过代理对象来实现功能的增强,可以扩展目标对象的功能。这体现了一种非常重要的编程思想,不能随便修改源码,如果需要修改源码,只需要通过代理的方式来实现功能的扩展。
在这里插入图片描述

二. 如何实现代理

1. 简介

正常情况下我们定义一个接口,即定义了一个规范,然后用一个类实现该接口,然后client调用实现类就可以使用基本功能。现在的情况下,我们不直接调用实现类,而是通过一个代理类实现接口,并加上前置通知和后置通知来增强该接口方法,然后client通过代理类来使用接口定义的功能
在这里插入图片描述

元素组成:

  • 接口:定义行为和规范
  • 被代理类:是目标对象
  • 代理类:功能增强

2. 静态代理

静态代理在 Java 中是一种非常常见的设计模式。在这种模式中,一个类(代理类)提供与另一个类(目标类)相同的接口,然后将调用转发到目标类。代理类和目标类都实现同一个接口。这种模式的一个常见用途是添加跨切面的行为,如日志记录、性能度量或者安全检查等,而不修改目标类的代码。

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        // 实际业务逻辑
    }
}

public class ProxyService implements IService {
    
    
    private IService realService;

    public ProxyService(IService realService) {
    
    
        this.realService = realService;
    }

    public void doSomething() {
    
    
        // 在调用实际方法前可以执行一些操作
        System.out.println("Before doSomething...");
        realService.doSomething();
        // 在调用实际方法后也可以执行一些操作
        System.out.println("After doSomething...");
    }
}

在这个例子中,RealService 是我们的目标类,ProxyService 是我们的代理类。ProxyService 在调用 RealService 的 doSomething 方法之前和之后执行了一些额外的操作。静态代理有以下特点:

  • 代理类和目标类在编译时就确定下来,是静态的;
  • 代理类和目标类都实现同一个接口,代理类通过接口中定义的方法来调用目标类的实现;
  • 用户通过代理类来调用目标类的方法,代理类可以在调用目标类的方法之前或之后添加一些额外的处理。

静态代理的主要问题是如果接口定义了很多方法,那么代理类需要为每一个方法都写一次转发代码,这显然是非常繁琐的。此外,如果接口发生变化,那么代理类也需要跟着修改,这增加了维护的复杂性。这也是为什么在Java中,动态代理(如使用Java的 java.lang.reflect.Proxy 或者第三方库如CGLIB)常常被用于替代静态代理。

3. 动态代理

Java 中的动态代理是指在运行时动态地生成代理类并创建代理对象的技术。Java 的核心库提供了动态代理的支持,主要在 java.lang.reflect 包中。动态代理是一种方便和强大的工具,它的主要应用包括拦截器、模拟对象(mock objects)、数据库连接以及事务管理等。使用 Java 的动态代理需要定义一个调用处理器(Invocation Handler)。调用处理器是一个实现了 java.lang.reflect.InvocationHandler 接口的类,该接口只定义了一个方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

这个 invoke 方法在代理实例上调用方法时会被调用。它接收三个参数:一个是代理对象本身,一个是在代理对象上调用的方法对象,一个是调用该方法时传递的参数。这个方法可以在调用目标方法前后添加额外的处理逻辑。下面是一个简单的动态代理的例子:

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        System.out.println("Do something in RealService");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    
    
    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("Before invoke method...");
        Object result = method.invoke(target, args);
        System.out.println("After invoke method...");
        return result;
    }
}

然后,你可以使用 Proxy 类的 newProxyInstance 方法来创建代理对象:

//一个是在代理对象上调用的方法对象
IService realService = new RealService();
//调用处理器
InvocationHandler handler = new MyInvocationHandler(realService);
//创建代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
    IService.class.getClassLoader(),
    new Class<?>[]{
    
    IService.class},
    handler
);
proxyService.doSomething();  // 调用这个方法时,会触发 handler 的 invoke 方法

在这个例子中,我们为 RealService 创建了一个代理,当我们通过代理调用 doSomething 方法时,会先调用 MyInvocationHandler 的 invoke 方法。需要注意的是,Java 的动态代理只能为接口创建代理,不能为类创建代理。如果需要为类创建代理,可以使用如 CGLIB 这样的第三方库。

静态代理 动态代理
编译时期 代理类在编译时就已经存在 代理类是在运行时动态生成
代码复杂度 需要为每一个方法都写一次转发代码,如果接口有很多方法,代码量较大 只需要写一个调用处理器(Invocation Handler),代码量相对较小
代码灵活性 如果接口发生变化,代理类需要跟着修改,维护复杂 调用处理器中通常只依赖接口而不依赖具体实现,如果接口发生变化,一般不需要修改调用处理器的代码
实现方式 代理类和目标类都实现同一个接口,代理类直接调用目标类的方法 使用 Java 的 Proxy 类和 InvocationHandler 接口,代理对象由 JVM 在运行时动态生成
应用场景 当需要为一个或几个特定的类或接口创建代理时,静态代理更为简单直接 当需要为多个或者不确定的类或接口创建代理,或者需要更灵活、更强大的代理功能(如拦截器、AOP)时,动态代理是更好的选择

三. JDK中的动态代理

1. 介绍

package java.lang.reflect;

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

InvocationHandler 接口(用来做方法拦截的)定义了一个方法:invoke。该方法在代理实例上调用方法时被调用。invoke 方法接受三个参数:

  1. proxy:代理实例。这是调用 invoke 方法的代理类的实例。(通过newProxyInstance创建代理实例)
  2. method:表示在代理实例上调用的接口方法的 Method 实例。这可以用来获取被调用方法的信息,如方法名、参数类型等。(执行目标方法的)
  3. args:包含在代理实例的方法调用传递的参数的对象数组。如果接口方法不带有参数,则为 null。(目标方法的参数)

invoke 方法的返回值是代理实例方法调用的返回值。也就是说,这个方法实际上决定了代理方法的行为。如果在代理实例的方法调用中抛出了异常,那么这个异常会被 invoke 方法抛出。在实际使用中,你通常会创建一个实现 InvocationHandler 接口的类,并在 invoke 方法中编写你的自定义逻辑,如在方法调用前后添加额外的处理等。
在 Java 中,InvocationHandler 是一个接口,用于处理代理实例上的方法调用。它是 Java 动态代理机制的核心接口

Java 的 java.lang.reflect.Proxy 类是实现动态代理的核心类。在动态代理的工作过程中,Proxy 类为指定的接口生成代理类及其对象,并通过调用 InvocationHandler 接口实现方法的动态调用。

public class Proxy implements java.io.Serializable {
    
    
    private static final long serialVersionUID = -2222568056686623797L;
    
    private InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
    
    
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
    
    
        // ...
    }
}

Proxy 类提供了以下主要方法:

  1. getProxyClass(ClassLoader loader, Class<?>… interfaces): 该方法用于生成指定接口的代理类的 Class 对象。参数 loader 是类加载器,用于定义代理类;interfaces 是一组接口,代理类会实现这些接口。
  2. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h): 该方法用于生成指定接口的代理对象。这个方法不仅生成代理类的 Class 对象,还创建了这个类的实例。参数 h 是 InvocationHandler 接口的实现,它定义了在代理对象上的方法调用的行为。
  3. getInvocationHandler(Object proxy): 该方法返回与指定代理对象关联的调用处理器。参数 proxy 是一个代理对象。

在使用 Proxy 类生成代理对象时,首先需要提供一个实现了 InvocationHandler 接口的类,这个类的 invoke 方法定义了在代理对象上的方法调用的行为。然后,可以调用 Proxy.newProxyInstance 方法生成代理对象。当在代理对象上调用方法时,调用将被转发到调用处理器的 invoke 方法。

2. 测试

创建student类

public class Student {
    
    
    private String name;

    public Student(String name) {
    
    
        this.name = name;
    }
    public Student() {
    
    
    }

    public String getName() {
    
    
        return name;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

创建service接口

public interface StudentService {
    
    
    public void save();
    public Student query();
}

实现service接口(需要代理的一个类——需要增强的类)

public class IStudentServiceImpl implements StudentService {
    
    
    public void save() {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;
        
    }
}

实现增强类

public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}

实现InvocationHandler接口

public class TransactionHandler implements InvocationHandler {
    
    
    //增强类对象
    private DaoTrascation daoTrascation;
    //需要代理的目标对象
    private Object object;
    public TransactionHandler(DaoTrascation daoTrascation,Object object){
    
    
        this.daoTrascation=daoTrascation;
        this.object=object;
    }
    
    //代理对象要执行的方法,实现目标方法执行,和功能增强
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object ret=null;
        //判断当前方法是否是save方法,是才做事务操作
        if("save".equals(method.getName())){
    
    
            daoTrascation.before();
            ret=method.invoke(object,args);
            daoTrascation.after();
        }else {
    
    
            method.invoke(object,args);
        }
        return ret;
    }
}

测试类代码

public class MybeanTest {
    
    
    @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),  //类加载器
                IStudentServiceImpl.class.getInterfaces(),   //目标类所实现的所有接口
                transactionHandler //方法拦截处理器
        );
        studentService1.save();
        studentService1.query();
    }
}

在这里插入图片描述

3. 原理分析

结合上面代码,通过反编译查看底层

修改测试类代码

 @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),
                IStudentServiceImpl.class.getInterfaces(),
                transactionHandler
        );
        studentService1.save(new Student());
        Student query = studentService1.query();
        saveProxyClass("/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest/src");
    }
    //使用生成字节码学习使用
    private void  saveProxyClass(String path) {
    
    
        //生成的代理类的字节码文件
        byte[] $proxy1 = ProxyGenerator.generateProxyClass("$Proxy1", IStudentServiceImpl.class.getInterfaces());
        try(FileOutputStream fileOutputStream = new FileOutputStream(new File(path + "$Proxy1.class"));) {
    
    
            fileOutputStream.write($proxy1);
        }  catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }

    }

允许代码生成字节吗文件

//代理对象首先实现了StudentService接口
ublic final class $Proxy1 extends Proxy implements StudentService {
    
    
//生成5个代理方法对象(因为Object对象中有一些方法)
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    //构造函数
    public $Proxy1(InvocationHandler var1) throws  {
    
    
        super(var1);
    }
    //equals方法(Object方法)
    public final boolean equals(Object var1) throws  {
    
    
        try {
    
    
            return (Boolean)super.h.invoke(this, m1, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }
    //StudentService的query方法
    public final Student query() throws  {
    
    
        try {
    
    
            return (Student)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }
    //object的tostring方法
    public final String toString() throws  {
    
    
        try {
    
    
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }
    StudentService的save方法
    public final void save(Student var1) throws  {
    
    
        try {
    
    
            super.h.invoke(this, m3, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }
    //object的hashcode方法
    public final int hashCode() throws  {
    
    
        try {
    
    
        //invoke方法,代理对象为当前对象,代理方法为m0,参数为null
        //h是Proxy类的InvocationHandeler类的实例
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
    
    
        try {
    
    
        //使用的反射
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("Service.StudentService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Service.StudentService").getMethod("save", Class.forName("pojo.Student"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
    
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4. 原理图

在这里插入图片描述

四. CGLIB中的动态代理

1. 简介

CGLIB是一个强大、高性能和高质量的第三方代码生成库。该库被Spring、Mybatis、Hibernate等第三方框架广泛应用,用以提供方法拦截操作。CGLIB属于开源项目,其CGLIB源码地址。CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。CGLIB向比与基于Java反射的JDK动态代理来说,CGLIB功能更加强大。JDK动态代理有个缺陷就是只能对接口进行代理,无法对单独普通类进行代理,而CGLIB则可以解决这一问题。(其实就是对JDK动态代理的一个补充)

2. 案例

  • 导入相关包
 <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
  • 创建目标类
public class IStudentServiceImpl implements StudentService {
    
    
    public void save(Student student) {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;

    }
}
  • 创建事务操作
public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}
  • 进行事务拦截
public class CglibInterceptor implements MethodInterceptor {
    
    
    DaoTrascation daoTrascation;
    StudentService studentService;

    public CglibInterceptor(DaoTrascation daoTrascation, StudentService studentService) {
    
    
        this.daoTrascation = daoTrascation;
        this.studentService = studentService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        daoTrascation.before();
        Object ret=methodProxy.invokeSuper(o,objects);
        daoTrascation.after();
        return ret;
    }
}
  • 测试
  @Test
    public void testMyBean(){
    
    
        DaoTrascation trascation = new DaoTrascation();

        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

在这里插入图片描述

3. 底层原理分析

  • 修改测试类
public void testMyBean(){
    
    
        //当Spring使用CGLIB创建代理对象时,CGLIB会生成字节码来创建新的类。这个 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
        // 属性实际上是一个开关,当你设置了这个属性,CGLIB在生成新的类时,会在指定的目录下生成相应的.class文件,这样就可以查看或者进一步
        // 分析这些自动生成的类了。
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest");
        DaoTrascation trascation = new DaoTrascation();
        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

在这里插入图片描述
下面是代理类class文件:

public class IStudentServiceImpl$$EnhancerByCGLIB$$8b9b66bd extends IStudentServiceImpl implements Factory {
    
    
 public final void save(Student var1) {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            var10000.intercept(this, CGLIB$save$0$Method, new Object[]{
    
    var1}, CGLIB$save$0$Proxy);
        } else {
    
    
            super.save(var1);
        }
    }
    public final Student query() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (Student)var10000.intercept(this, CGLIB$query$1$Method, CGLIB$emptyArgs, CGLIB$query$1$Proxy) : super.query();
    }
   }
  • 代理类继承了代理对象IStudentServiceImpl,并实现了其方法,所以说这种模式是用继承来实现的
  • 然后在save和query中都调用了拦截器中的interceptor方法实现了增强

猜你喜欢

转载自blog.csdn.net/qq_43456605/article/details/131076909