【Spring】详细了解静态代理和动态代理的使用

目录

1.代理模式介绍

[2. 静态代理](#2. 静态代理)

3.动态代理

[3.1 JDK动态代理](#3.1 JDK动态代理)

[3.2 CGLIB动态代理](#3.2 CGLIB动态代理)

[4. 动态代理和静态代理的区别](#4. 动态代理和静态代理的区别)


1.代理模式介绍

代理模式分为动态代理和静态代理,目的是在不直接暴露真实对象的情况下,通过代理对象来间接访问真实对象,从而可以在访问前后添加额外的功能,比如日志记录、性能监控、事务管理等。核心思想:不改变原有的代码模式下,去增加一些功能。

通过一张图来了解代理模式:在这张图中,房东要出租房,我要租房,但是房东不想去搞一系列复杂的事情,于是找了一个代理的中介角色,客户直接和中介去租房就可以。房东和代理中介有一个共同的接口就是出租房。

2. 静态代理

静态代理是指在程序运行前就已经确定了代理类的具体实现,也就是说代理类是在编译期就创建好的,并且需要显式地为每个接口或类编写一个代理类。

以上面事件为例,实现静态代理

(1)首先定义一个接口House,定义一个租房的方法 rent()

java 复制代码
package com.its.ex02.poxy01;

public interface House {
    /*
       1. 静态代理:
    *       以房东出租房子为例:
            房东,被代理的人。中介:代理人。租客:需要与被代理人沟通的人。
    * */
    void rent();
}

(2)定义一个实现类,代表房东,实现接口方法

java 复制代码
package com.its.ex02.poxy01;

public class MyHouse implements House{
    @Override
    public void rent() {
        System.out.println("我要出租房子");
    }
}

(3)定义一个代理对象,用于帮被代理人出租房子

java 复制代码
package com.its.ex02.poxy01;

public class Poxy implements House {
    private House ownerHouse;

    public Poxy(House ownerHouse) {
        this.ownerHouse = ownerHouse;
    }

    @Override
    public void rent() {
        lookHouse();
        talkPrice();
        //帮被代理人出租房子
        ownerHouse.rent();
        takeMoney();

    }
  //代理对象也可以有自己的一些功能
  private void lookHouse(){
      System.out.println("带客户去看房子");
  }
    private void talkPrice(){
        System.out.println("跟客户谈价格");
    }
    private void takeMoney(){
        System.out.println("拿提成");
    }
}

(4)执行测试,租客直接找代理人租房即可

java 复制代码
package com.its.ex02.poxy01;

public class Client {
    public static void main(String[] args) {
//        找房子的客户
        MyHouse client = new MyHouse();
//        通过代理人租房
        Poxy poxy = new Poxy(client);
//        代理人给客户租房,其中也能做自己的事情
        poxy.rent();
    }
}

测试结果如下,静态代理完成。

静态代理的特点

  • 固定代理类:对于每个接口或类都需要手动创建一个代理类。(造成代码臃肿)
  • 编译期生成:代理类在编译时就已经存在。
  • 不可扩展:一旦定义了代理类,要扩展其功能就需要修改代码并重新编译。
  • 易于理解:代码结构清晰,容易理解。

3.动态代理

动态代理是指在运行时动态创建代理对象,它不需要为每个接口编写单独的代理类。Java 提供了两种动态代理的方式:

  1. 基于接口的动态代理,JDK代理(使用 java.lang.reflect.Proxy 类)
  2. 基于类的动态代理,CGLIB代理(使用 CGLIB 库或其他第三方库)。

3.1 JDK动态代理

通过接口的方式,让代理类和实际业务类实现统一的接口,并且借助代理类统一管理接口 InvocationHandler(房子接口)进行动态代理机制的实现。这种方式依赖于接口,如果一个类不 实现接口则无法实现代理。

(1)首先定义一个接口House,定义一个租房的方法 rent()写一个

java 复制代码
package com.its.ex02.poxy02;

public interface House {
    void rent();
}

(2)然后定义一个房东类实现这个接口,重写租房的方法。

java 复制代码
package com.its.ex02.poxy02;

public class MyHouse implements House {
    @Override
    public void rent() {
        System.out.println("我要出租房子");
    }
}

(3)定义proxyJDK这个类,这个类实现了 InvocationHandler 接口,它是用来处理代理对象方法调用的。

java 复制代码
package com.its.ex02.poxy02;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


//这个类实现了 InvocationHandler 接口,它是用来处理代理对象方法调用的
public class PoxyJDK implements InvocationHandler {
    private House house;

//    这个构造函数接收一个 House 类型的对象,并将其赋值给成员变量 house
//    接口类型的好处是只要实现了House接口的实现类都可以被代理
    public PoxyJDK(House house) {
        this.house = house;
    }

    //实现InvocationHandler接口,用于处理 代理对象上(房东) 的方法调用。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用方法前可以执行动态代理人自己的事情");
        //使用反射机制调用实际对象 House接口 上的方法。
        Object o = method.invoke(house, args);
        System.out.println("调用方法后也可以执行动态代理人自己的事情");
        //返回方法调用的结果
        return o;
    }
}

(4)定义一个客户类,用于演示如何创建代理对象并调用其方法。

java 复制代码
package com.its.ex02.poxy02;

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        //1.被代理的类
        MyHouse myHouse = new MyHouse();
        //2.代理类, 创建一个 PoxyJDK 实例,传入 myHouse 作为被代理对象。
        PoxyJDK poxyJDK = new PoxyJDK(myHouse);
        //3.获取myHouse的类加载器接口,这是使用 Proxy.newProxyInstance 方法创建代理对象所必需的
        ClassLoader classLoader = MyHouse.class.getClassLoader();
        //4.获取 MyHouse 类实现的所有接口,通常这里只有一个接口,即 House
        Class<?>[] interfaces = MyHouse.class.getInterfaces();
        //5.使用 Proxy.newProxyInstance 方法创建代理对象 house
        House house = (House) Proxy.newProxyInstance(classLoader, interfaces, poxyJDK);
        //调用代理对象的 rent 方法。
        house.rent();
    }
}

(5)执行测试代码,结果如下,演示成功

3.2 CGLIB动态代理

CGLIB概念

  • CGLIB是一个强大的高性能的代码生成库。它允许你在运行时创建一个现有类的子类。CGLIB 主要用于实现动态代理,尤其适用于那些没有实现接口的类
  • 与JDK动态代理机制不同,CGLIB 不是通过接口来创建代理对象,而是通过继承来创建一个子类,因此它可以为任何类创建代理,只要这个类不是 final 的。

原理

GLIB 使用字节码技术为指定的类创建一个子类,并覆盖其中的方法。当调用代理对象的方法时,实际上会调用 CGLIB 生成的方法拦截器(MethodInterceptor)来处理方法调用。这个方法拦截器可以让你在方法调用前后添加额外的逻辑。

(1)首先定义一个接口House,定义一个租房的方法 rent()写一个

java 复制代码
package com.its.ex02.poxy03;

public interface House {
    void rent();
}

(2)然后定义一个房东类实现这个接口,重写租房的方法。

java 复制代码
package com.its.ex02.poxy03;

public class MyHouse implements House {
    @Override
    public void rent() {
        System.out.println("我要出租房子");
    }
}

(3)接下来,我们需要创建一个实现了 MethodInterceptor 接口的类PoxyCGLIB,用于处理方法调用。

java 复制代码
package com.its.ex02.poxy03;

import com.its.ex02.poxy01.House;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class poxyCGLIB implements MethodInterceptor {
    public House house;

    public poxyCGLIB(House house) {
        this.house = house;
    }
    /*
        o: 当前代理对象实例。
        method: 被调用的方法的 Method 对象。
        objects: 被调用的方法的参数列表。
        methodProxy: 用于调用原始方法的代理对象。
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行方法前做的事情,方法名:"+method.getName());
        Object result = methodProxy.invoke(house, objects);
        System.out.println("执行方法后做的事情方,法名:"+method.getName());
        return result;
    }
}

注:上面的method参数的使用:

  1. 基于方法名的不同执行不同的前置或后置逻辑:以根据方法名来判断是否需要执行特定的前置逻辑或后置逻辑。

  2. 验证方法参数:可以通过检查方法的参数来确保它们符合预期。

  3. 修改方法行为:如果需要的话,您可以根据方法签名来改变方法的行为,例如,改变方法的返回值。

  4. 记录方法调用:可以记录哪些方法被调用了以及它们被调用时的参数信息。

(4)定义一个客户类,用于测试创建代理对象并调用代理对象的方法。

java 复制代码
package com.its.ex02.poxy03;
import com.its.ex02.poxy03.House;
import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;

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

        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
                "D:\\code");
        // Enhancer 是 CGLIB 中用于创建代理对象的类。
        Enhancer enhancer = new Enhancer();
        // 设置 Enhancer 的父类为 MyHouse 类,这表示代理类将继承 MyHouse 类。
        enhancer.setSuperclass(MyHouse.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new poxyCGLIB(myHouse));
        // 创建代理对象,类型转换为 House 接口
        House house = (House) enhancer.create();
        // 通过代理对象调用目标方法
        house.rent();
    }
}

CGLIB 与 JDK 动态代理的区别

  • 适用场景 :JDK 动态代理要求目标对象必须实现至少一个接口,而 CGLIB 可以为任何非 final 类创建代理。
  • 代理机制:JDK 动态代理是基于接口的,而 CGLIB 是基于继承的。
  • 性能:通常情况下,CGLIB 产生的代理类可能比 JDK 动态代理慢一点,因为它涉及到了字节码级别的操作。
  • 兼容性:CGLIB 是一个第三方库,需要在项目中引入相应的依赖

4. 动态代理和静态代理的区别

区别

  1. 代理类的创建时机

    • 静态代理:代理类在编译期就已经创建好了。
    • 动态代理:代理类是在运行时动态创建的。
  2. 代理类的创建方式

    • 静态代理:需要为每个接口或类手动编写代理类。
    • 动态代理:通过反射机制在运行时创建代理类。
  3. 代理类的数量

    • 静态代理:每个接口或类都有一个对应的代理类。
    • 动态代理:可以为多个接口或类创建一个代理类。
  4. 代码的可维护性和扩展性

    • 静态代理:代码结构简单,但扩展性较差,因为每次都需要修改代理类。
    • 动态代理 :更易于扩展,因为可以复用同一个 InvocationHandler
  5. 性能考虑

    • 静态代理:由于代理类是提前编译好的,所以在运行时的性能可能略优于动态代理。
    • 动态代理:创建代理对象和执行方法调用时会有一些额外开销,但在大多数情况下这些开销是可以接受的。

动态代理可以动态的生成代理类,静态代理则需要预先定义代理类,对于每一个接口都需要有一个对应的代理类,下面举例说明。

假设我们有两个接口 ServiceAServiceB,我们希望为这两个接口创建代理,以便在调用实际服务之前和之后添加一些通用的逻辑(例如日志记录)。

静态代理

java 复制代码
// ServiceA 接口
public interface ServiceA {
    void doSomethingA();
}

// ServiceB 接口
public interface ServiceB {
    void doSomethingB();
}

// ServiceA 的实现
public class ServiceAImpl implements ServiceA {
    @Override
    public void doSomethingA() {
        System.out.println("Doing something A...");
    }
}

// ServiceB 的实现
public class ServiceBImpl implements ServiceB {
    @Override
    public void doSomethingB() {
        System.out.println("Doing something B...");
    }
}

// ServiceA 的静态代理类
public class ServiceAProxy implements ServiceA {
    private ServiceA serviceA;

    public ServiceAProxy(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    @Override
    public void doSomethingA() {
        System.out.println("Before doSomethingA");
        serviceA.doSomethingA();
        System.out.println("After doSomethingA");
    }
}

// ServiceB 的静态代理类
public class ServiceBProxy implements ServiceB {
    private ServiceB serviceB;

    public ServiceBProxy(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    @Override
    public void doSomethingB() {
        System.out.println("Before doSomethingB");
        serviceB.doSomethingB();
        System.out.println("After doSomethingB");
    }
}

动态代理

java 复制代码
// 公共的日志记录 InvocationHandler
public class LoggingInvocationHandler implements InvocationHandler {
    private Object target;

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

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

// 创建代理对象并使用
public class ProxyDemo {
    public static void main(String[] args) {
        ServiceA serviceA = new ServiceAImpl();
        ServiceA serviceAProxy = (ServiceA) Proxy.newProxyInstance(
                ServiceA.class.getClassLoader(),
                new Class<?>[]{ServiceA.class},
                new LoggingInvocationHandler(serviceA)
        );
        
        ServiceB serviceB = new ServiceBImpl();
        ServiceB serviceBProxy = (ServiceB) Proxy.newProxyInstance(
                ServiceB.class.getClassLoader(),
                new Class<?>[]{ServiceB.class},
                new LoggingInvocationHandler(serviceB)
        );

        serviceAProxy.doSomethingA();
        serviceBProxy.doSomethingB();
    }
}
相关推荐
斗-匕几秒前
Spring事务管理
数据库·spring·oracle
颜淡慕潇1 小时前
【K8S系列】kubectl describe pod显示ImagePullBackOff,如何进一步排查?
后端·云原生·容器·kubernetes
Clarify2 小时前
docker部署go游戏服务器(进阶版)
后端
IT书架2 小时前
golang面试题
开发语言·后端·golang
机器之心3 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
kikyo哎哟喂3 小时前
Java 代理模式详解
java·开发语言·代理模式
Doker 多克3 小时前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt
潜洋4 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
St_Ludwig5 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
vener_5 小时前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet