如何使用 CGLIB 在 Spring Boot 3.3 中實(shí)現(xiàn)動(dòng)態(tài)代理
在 Java 開發(fā)中,代理模式是一種重要的設(shè)計(jì)模式,通過代理對(duì)象來(lái)控制對(duì)目標(biāo)對(duì)象的訪問。代理模式在 AOP(面向切面編程)中得到了廣泛應(yīng)用,尤其是在 Spring 框架中。Spring 提供了兩種主要的代理機(jī)制:JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理。其中,JDK 動(dòng)態(tài)代理僅能代理實(shí)現(xiàn)了接口的類,而 CGLIB 動(dòng)態(tài)代理則沒有這一限制,可以代理任何普通的類。因此,CGLIB 動(dòng)態(tài)代理在實(shí)際開發(fā)中非常實(shí)用,特別是在需要代理沒有實(shí)現(xiàn)接口的類時(shí)。
本文將深入探討如何在 Spring Boot 3.3 中使用 CGLIB 實(shí)現(xiàn)動(dòng)態(tài)代理。我們將通過具體的代碼示例,展示如何在應(yīng)用程序中集成 CGLIB,并解釋其在 AOP 編程中的應(yīng)用場(chǎng)景和優(yōu)勢(shì)。同時(shí),我們還將展示如何通過前后端協(xié)作,將代理后的效果展示在 Web 頁(yè)面上,從而幫助開發(fā)者更好地理解和運(yùn)用 CGLIB 動(dòng)態(tài)代理。
CGLIB 簡(jiǎn)介
CGLIB(Code Generation Library)是一個(gè)強(qiáng)大的高性能代碼生成庫(kù),主要用于在運(yùn)行時(shí)動(dòng)態(tài)生成類和代理對(duì)象。CGLIB 通過使用底層的 ASM 字節(jié)碼操縱框架,直接操作字節(jié)碼文件,生成新的類或增強(qiáng)現(xiàn)有的類。與 JDK 動(dòng)態(tài)代理不同,CGLIB 不需要目標(biāo)類實(shí)現(xiàn)任何接口,這使得它在處理代理普通類時(shí)顯得非常靈活和強(qiáng)大。
CGLIB 動(dòng)態(tài)代理的工作原理是通過生成目標(biāo)類的子類,并在子類中重寫目標(biāo)類的方法來(lái)實(shí)現(xiàn)對(duì)方法調(diào)用的攔截。CGLIB 可以在方法調(diào)用的前后添加自定義邏輯,例如日志記錄、性能監(jiān)控、事務(wù)管理等。這使得它在實(shí)現(xiàn) AOP 編程時(shí)具有極大的優(yōu)勢(shì),尤其是在 Spring 框架中被廣泛應(yīng)用。
值得注意的是,由于 CGLIB 是通過繼承的方式實(shí)現(xiàn)代理,因此目標(biāo)類不能是 final 的,否則會(huì)導(dǎo)致代理失敗。此外,目標(biāo)類中的 final 方法也無(wú)法被代理,因?yàn)?nbsp;final 方法不能被重寫。
運(yùn)行效果:
圖片
若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫時(shí)遇到問題需要咨詢交流,歡迎加入下方的知識(shí)星球。
項(xiàng)目結(jié)構(gòu)
在開始之前,我們需要設(shè)置一個(gè) Spring Boot 3.3 項(xiàng)目。項(xiàng)目結(jié)構(gòu)如下:
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 versinotallow="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 文件中,我們可以加入一些簡(jiǎn)單的配置:
server:
port: 8080
spring:
thymeleaf:
cache: false
CGLIB 動(dòng)態(tài)代理實(shí)現(xiàn)
創(chuàng)建一個(gè)簡(jiǎn)單的服務(wù)類
首先,我們創(chuàng)建一個(gè)服務(wù)類 CglibDemoService,這個(gè)類將被代理:
package com.icoderoad.cglib_demo.service;
public class CglibDemoService {
public String sayHello(String name) {
return "你好, " + name;
}
public String sayGoodbye(String name) {
return "再見, " + name;
}
}
創(chuàng)建 CGLIB 代理類
接下來(lái),我們創(chuàng)建一個(gè) CGLIB 代理類 CglibProxy,用于攔截方法調(diào)用并進(jìn)行處理:
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 {
// 被代理的目標(biāo)對(duì)象
private final Object target;
// 構(gòu)造方法,傳入目標(biāo)對(duì)象
public CglibProxy(Object target) {
this.target = target;
}
// 攔截方法,在目標(biāo)方法執(zhí)行前后加入自定義邏輯
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("方法執(zhí)行前: " + method.getName());
Object result = proxy.invoke(target, args);
System.out.println("方法執(zhí)行后: " + method.getName());
return result;
}
// 獲取代理對(duì)象
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
使用代理類
在應(yīng)用的啟動(dòng)類中,我們將使用 CglibProxy 來(lái)代理 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();
// 調(diào)用代理對(duì)象的方法
System.out.println(proxyService.sayHello("小明"));
System.out.println(proxyService.sayGoodbye("小明"));
}
}
在這個(gè)例子中,我們通過 CglibProxy 代理 CglibDemoService,并在方法調(diào)用前后添加了自定義邏輯。
后端控制器
為了將數(shù)據(jù)傳遞到前端頁(yè)面,我們需要?jiǎng)?chuàng)建一個(gè)控制器:
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) {
// 創(chuàng)建目標(biāo)對(duì)象
CglibDemoService demoService = new CglibDemoService();
// 創(chuàng)建代理對(duì)象
CglibProxy proxy = new CglibProxy(demoService);
CglibDemoService proxyService = (CglibDemoService) proxy.getProxy();
// 將方法調(diào)用結(jié)果傳遞給前端頁(yè)面
model.addAttribute("helloMessage", proxyService.sayHello("路條編程"));
model.addAttribute("goodbyeMessage", proxyService.sayGoodbye("路條編程"));
return "index";
}
}
前端頁(yè)面展示
Thymeleaf 模板
在 src/main/resources/templates/index.html 文件中,創(chuàng)建一個(gè)簡(jiǎn)單的前端頁(yè)面:
<!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 參數(shù)
在啟動(dòng)你的應(yīng)用時(shí),添加 --add-opens 參數(shù)以允許訪問被封閉的模塊:
java --add-opens java.base/java.lang=ALL-UNNAMED -jar your-application.jar
如果你是在 IDE 中運(yùn)行應(yīng)用程序,可以在 IDE 的運(yùn)行配置中添加這個(gè)參數(shù)。
在 Eclipse 中配置 JVM 參數(shù)來(lái)解決 CGLIB 與 Java 模塊系統(tǒng)兼容性問題,可以按照以下步驟操作:
配置 JVM 參數(shù)
- 打開 Eclipse 項(xiàng)目屬性
- 在 Eclipse 中,右鍵點(diǎn)擊你的項(xiàng)目,選擇 Properties(屬性)。
- 進(jìn)入 Run/Debug Settings
在左側(cè)面板中,選擇 Run/Debug Settings。
選擇或創(chuàng)建運(yùn)行配置
如果已有運(yùn)行配置,選擇你要修改的配置,然后點(diǎn)擊 Edit(編輯)。
如果沒有,點(diǎn)擊 New Configuration(新建配置),然后選擇 Java Application 或 Spring Boot App,點(diǎn)擊 New(新建)。
配置 VM Arguments
在 Arguments 標(biāo)簽頁(yè)中,找到 VM arguments 輸入框。在這里你可以添加 JVM 啟動(dòng)參數(shù)。
在 VM arguments 輸入框中,添加如下參數(shù):
--add-opens java.base/java.lang=ALL-UNNAMED
這個(gè)參數(shù)允許你訪問 Java 內(nèi)部 API,解決 CGLIB 在模塊系統(tǒng)中的兼容性問題。
保存配置
點(diǎn)擊 Apply(應(yīng)用),然后點(diǎn)擊 Run(運(yùn)行)以保存并應(yīng)用你的配置。
運(yùn)行效果
啟動(dòng) Spring Boot 項(xiàng)目后,訪問 http://localhost:8080,頁(yè)面上將顯示通過 CGLIB 動(dòng)態(tài)代理處理后的消息,控制臺(tái)中可以看到方法執(zhí)行前后的日志輸出。
總結(jié)
本文詳細(xì)介紹了如何在 Spring Boot 3.3 中使用 CGLIB 實(shí)現(xiàn)動(dòng)態(tài)代理。通過實(shí)際的代碼示例,展示了 CGLIB 在動(dòng)態(tài)代理中的應(yīng)用,以及如何在 Spring Boot 項(xiàng)目中集成 CGLIB。我們還演示了如何通過 Thymeleaf 和 Bootstrap 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的前端頁(yè)面,以展示代理后的效果。希望通過這篇文章,您能對(duì) CGLIB 動(dòng)態(tài)代理有一個(gè)更深入的理解。