查看原文
科技

如何使用 CGLIB 在 Spring Boot 3.3 中实现动态代理

编程疏影 路条编程
2024-09-05


如何使用 CGLIB 在 Spring Boot 3.3 中实现动态代理

在 Java 开发中,代理模式是一种重要的设计模式,通过代理对象来控制对目标对象的访问。代理模式在 AOP(面向切面编程)中得到了广泛应用,尤其是在 Spring 框架中。Spring 提供了两种主要的代理机制:JDK 动态代理和 CGLIB 动态代理。其中,JDK 动态代理仅能代理实现了接口的类,而 CGLIB 动态代理则没有这一限制,可以代理任何普通的类。因此,CGLIB 动态代理在实际开发中非常实用,特别是在需要代理没有实现接口的类时。

本文将深入探讨如何在 Spring Boot 3.3 中使用 CGLIB 实现动态代理。我们将通过具体的代码示例,展示如何在应用程序中集成 CGLIB,并解释其在 AOP 编程中的应用场景和优势。同时,我们还将展示如何通过前后端协作,将代理后的效果展示在 Web 页面上,从而帮助开发者更好地理解和运用 CGLIB 动态代理。

CGLIB 简介

CGLIB(Code Generation Library)是一个强大的高性能代码生成库,主要用于在运行时动态生成类和代理对象。CGLIB 通过使用底层的 ASM 字节码操纵框架,直接操作字节码文件,生成新的类或增强现有的类。与 JDK 动态代理不同,CGLIB 不需要目标类实现任何接口,这使得它在处理代理普通类时显得非常灵活和强大。

CGLIB 动态代理的工作原理是通过生成目标类的子类,并在子类中重写目标类的方法来实现对方法调用的拦截。CGLIB 可以在方法调用的前后添加自定义逻辑,例如日志记录、性能监控、事务管理等。这使得它在实现 AOP 编程时具有极大的优势,尤其是在 Spring 框架中被广泛应用。

值得注意的是,由于 CGLIB 是通过继承的方式实现代理,因此目标类不能是 final 的,否则会导致代理失败。此外,目标类中的 final 方法也无法被代理,因为 final 方法不能被重写。

运行效果:

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

项目结构

在开始之前,我们需要设置一个 Spring Boot 3.3 项目。项目结构如下:

cglib-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── icoderoad
│ │ │ └── cglib
│ │ │ ├── service
│ │ │ │ └── CglibDemoService.java
│ │ │ ├── proxy
│ │ │ │ └── CglibProxy.java
│ │ │ └── CglibDemoApplication.java
│ │ └── resources
│ │ ├── application.yaml
│ │ └── templates
│ │ └── index.html
└── pom.xml

配置文件

pom.xml 配置

首先,在 pom.xml 文件中引入必要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>cglib-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cglib-demo</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- CGLIB Dependency -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version> <!-- 或者更高的版本 -->
</dependency>

<!-- Bootstrap CSS -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.3.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
application.yaml 配置

在 src/main/resources/application.yaml 文件中,我们可以加入一些简单的配置:

server:
port: 8080

spring:
thymeleaf:
cache: false

CGLIB 动态代理实现

创建一个简单的服务类

首先,我们创建一个服务类 CglibDemoService,这个类将被代理:

package com.icoderoad.cglib_demo.service;

public class CglibDemoService {

public String sayHello(String name) {
return "你好, " + name;
}

public String sayGoodbye(String name) {
return "再见, " + name;
}
}
创建 CGLIB 代理类

接下来,我们创建一个 CGLIB 代理类 CglibProxy,用于拦截方法调用并进行处理:

package com.icoderoad.cglib_demo.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

// 被代理的目标对象
private final Object target;

// 构造方法,传入目标对象
public CglibProxy(Object target) {
this.target = target;
}

// 拦截方法,在目标方法执行前后加入自定义逻辑
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法执行前: " + method.getName());
Object result = proxy.invoke(target, args);
System.out.println("方法执行后: " + method.getName());
return result;
}

// 获取代理对象
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
使用代理类

在应用的启动类中,我们将使用 CglibProxy 来代理 CglibDemoService

package com.icoderoad.cglib_demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.icoderoad.cglib_demo.proxy.CglibProxy;
import com.icoderoad.cglib_demo.service.CglibDemoService;

@SpringBootApplication
public class CglibDemoApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(CglibDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
CglibDemoService targetService = new CglibDemoService();
CglibProxy proxy = new CglibProxy(targetService);
CglibDemoService proxyService = (CglibDemoService) proxy.getProxy();

// 调用代理对象的方法
System.out.println(proxyService.sayHello("小明"));
System.out.println(proxyService.sayGoodbye("小明"));
}
}

在这个例子中,我们通过 CglibProxy 代理 CglibDemoService,并在方法调用前后添加了自定义逻辑。

后端控制器

为了将数据传递到前端页面,我们需要创建一个控制器:

package com.icoderoad.cglib_demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.icoderoad.cglib_demo.proxy.CglibProxy;
import com.icoderoad.cglib_demo.service.CglibDemoService;

@Controller
public class DemoController {

@GetMapping("/")
public String index(Model model) {
// 创建目标对象
CglibDemoService demoService = new CglibDemoService();
// 创建代理对象
CglibProxy proxy = new CglibProxy(demoService);
CglibDemoService proxyService = (CglibDemoService) proxy.getProxy();

// 将方法调用结果传递给前端页面
model.addAttribute("helloMessage", proxyService.sayHello("路条编程"));
model.addAttribute("goodbyeMessage", proxyService.sayGoodbye("路条编程"));
return "index";
}
}

前端页面展示

Thymeleaf 模板

在 src/main/resources/templates/index.html 文件中,创建一个简单的前端页面:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>CGLIB 代理演示</title>
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.3.0/css/bootstrap.min.css}">
</head>
<body>
<div class="container">
<h1>CGLIB 代理演示</h1>
<p th:text="'欢迎消息: ' + ${helloMessage}"></p>
<p th:text="'告别消息: ' + ${goodbyeMessage}"></p>
</div>
<script th:src="@{/webjars/bootstrap/5.3.0/js/bootstrap.bundle.min.js}"></script>
</body>
</html>

使用 --add-opens JVM 参数

在启动你的应用时,添加 --add-opens 参数以允许访问被封闭的模块:

java --add-opens java.base/java.lang=ALL-UNNAMED -jar your-application.jar

如果你是在 IDE 中运行应用程序,可以在 IDE 的运行配置中添加这个参数。

在 Eclipse 中配置 JVM 参数来解决 CGLIB 与 Java 模块系统兼容性问题,可以按照以下步骤操作:

配置 JVM 参数

  1. 打开 Eclipse 项目属性

  • 在 Eclipse 中,右键点击你的项目,选择 Properties(属性)。

  • 进入 Run/Debug Settings

    • 在左侧面板中,选择 Run/Debug Settings。

  • 选择或创建运行配置

    • 如果已有运行配置,选择你要修改的配置,然后点击 Edit(编辑)。

    • 如果没有,点击 New Configuration(新建配置),然后选择 Java Application 或 Spring Boot App,点击 New(新建)。

  • 配置 VM Arguments

    • 在 Arguments 标签页中,找到 VM arguments 输入框。在这里你可以添加 JVM 启动参数。

    • 在 VM arguments 输入框中,添加如下参数:

      --add-opens java.base/java.lang=ALL-UNNAMED
    • 这个参数允许你访问 Java 内部 API,解决 CGLIB 在模块系统中的兼容性问题。

  • 保存配置

    • 点击 Apply(应用),然后点击 Run(运行)以保存并应用你的配置。

    运行效果

    启动 Spring Boot 项目后,访问 http://localhost:8080,页面上将显示通过 CGLIB 动态代理处理后的消息,控制台中可以看到方法执行前后的日志输出。

    总结

    本文详细介绍了如何在 Spring Boot 3.3 中使用 CGLIB 实现动态代理。通过实际的代码示例,展示了 CGLIB 在动态代理中的应用,以及如何在 Spring Boot 项目中集成 CGLIB。我们还演示了如何通过 Thymeleaf 和 Bootstrap 实现一个简单的前端页面,以展示代理后的效果。希望通过这篇文章,您能对 CGLIB 动态代理有一个更深入的理解。


    今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。


    AI资源聚合站已经正式上线,该平台不仅仅是一个AI资源聚合站,更是一个为追求知识深度和广度的人们打造的智慧聚集地。通过访问 AI 资源聚合网站 https://ai-ziyuan.techwisdom.cn/,你将进入一个全方位涵盖人工智能和语言模型领域的宝藏库。


    作者:路条编程(转载请获本公众号授权,并注明作者与出处)


    继续滑动看下一个
    路条编程
    向上滑动看下一个

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存