戴老师的博客 - Spring
type: channel
link: https://www.tushu.info/tag/Spring/
items: 使用 Spring Data JPA 简化 JPA 开发
items: JTA 深度历险 - 原理与实现
items: @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
items: 基于 RBAC 的 Web Security Framework 的研究与应用
items: Spring MVC 3.2 技术预览（三）：动手写一个异步Controller方法
items: Spring Framework 中 RestClient 的详细介绍及示例
items: Spring AOP实现原理与CGLIB应用
items: Spring源码学习 ------ IoC——AOP
items: 探索 Spring AOP：全面解析与实战应用
items: spring aop jdk vs cglib

使用 Spring Data JPA 简化 JPA 开发
type: item
date: 2026-07-07T08:36:42+00:00
description: 从一个简单的 JPA 示例开始本文主要讲述 Spring Data JPA。为了不至于给 JPA 和 Spring 的初学者造成较大的学习曲线，我们首先从 JPA 开始，简单介绍一个 JPA 示例；接着重构该示例，并引入 Spring 框架。这两部分不会涉及过多的篇幅，如果希望能够深入学习 Spring 和 JPA，可以根据本文最后提供的参考资料进一步学习。自 JPA 伴随 Java EE 5 发布以来，受到了各大厂商及开源社区的追捧，各种商用的和开源的 JPA 框架如雨后春笋般出现，为开发者提供了丰富的选择。它一改之前 EJB 2.x 中实体 Bean 笨重且难以使用的形象，充分吸收了在开源社区已经相对成熟的 ORM 思想。另外，它并不依赖于 EJB 容器，可以作为一个独立的持久层技术而存在。目前比较成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐献给 Eclipse 社区的 EclipseLink、Apache 的 OpenJPA 等。本文的示例代码基于 Hibernate EntityManager 开发，但是读者几乎不用修改任何代码，便可以非常容易地切换到其他 JPA 框架，因为代码中使用到的都是 JPA 规范提供的接口 / 类，并没有使用到框架本身的私有特性。示例主要涉及七个文件，但是很清晰：业务层包含一个接口和一个实现；持久层包含一个接口、一个实现、一个实体类；另外加上一个 JPA 配置文件和一个测试类。相关类 / 接口代码如下：清单 1. 实体类 AccountInfo.java@Entity @Table(name = "t_accountinfo".html) public class AccountInfo implements Serializable { private Long accountId; private Integer balance; // 此处省略 getter 和 setter 方法。 }清单 2. 业务层接口 UserService.javapublic interface UserService { public AccountInfo createNewAccount(String user, String pwd, Integer init.html); }清单 3. 业务层的实现类 UserServiceImpl.javaimport com.xcar.nvwa.grpc.bidding.UserInfo; public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public AccountInfo createNewAccount(String user, String pwd, Integer init.html){ // 封装域对象 AccountInfo accountInfo = new AccountInfo(); UserInfo userInfo = new UserInfo(); userInfo.setUsername(user.html); userInfo.setPassword(pwd.html); accountInfo.setBalance(init.html); accountInfo.setUserInfo(userInfo.html); // 调用持久层，完成数据的保存 return userDao.save(accountInfo.html); } }清单 4. 持久层接口public interface UserDao { public AccountInfo save(AccountInfo accountInfo.html); }清单 5. 持久层的实现类public class UserDaoImpl implements UserDao { public AccountInfo save(AccountInfo accountInfo.html) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("SimplePU".html); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(accountInfo.html); em.getTransaction().commit(); emf.close(); return acc…

JTA 深度历险 - 原理与实现
type: item
date: 2026-07-07T08:36:10+00:00
description: 什么是事务处理事务是计算机应用中不可或缺的组件模型，它保证了用户操作的原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）和持久性（Durability）。关于事务最经典的示例莫过于信用卡转账：将用户 A 账户中的 500 元人民币转移到用户 B 的账户中，其操作流程如下：将 A 账户中的金额减少 500；将 B 账户中的金额增加 500。这两个操作必须保证 ACID 的事务属性：即要么全部成功，要么全部失败。假若没有事务保障，用户的账号金额将可能发生问题：假如第一步操作成功而第二步失败，那么用户 A 账户中的金额就将减少 500 元，而用户 B 的账号却没有任何增加（不翼而飞）；同样如果第一步出错而第二步成功，那么用户 A 的账户金额不变，而用户 B 的账号将增加 500 元（凭空而生）。上述任何一种错误都会产生严重的数据不一致问题，事务的缺失对于一个稳定的生产系统是不可接受的。J2EE 事务处理方式1. 本地事务本地事务紧密依赖于底层资源管理器（例如数据库连接），事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖，因而部署灵活，但无法支持多数据源的分布式事务。在数据库连接中使用本地事务示例如下：清单 1. 本地事务处理实例public void transferAccount() { Connection conn = null; Statement stmt = null; try { conn = getDataSource().getConnection(); // 将自动提交设置为 false， // 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交 conn.setAutoCommit(false.html); stmt = conn.createStatement(); // 将 A 账户中的金额减少 500 stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'".html); // 将 B 账户中的金额增加 500 stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'".html); // 提交事务 conn.commit(); // 事务提交：转账的两步操作同时成功 } catch (SQLException sqle.html) { try { // 发生异常，回滚在本事务中的操作 conn.rollback(); // 事务回滚：转账的两步操作完全撤销 stmt.close(); conn.close(); } catch (Exception ignore.html) { // 忽略关闭异常 } sqle.printStackTrace(); } }2. 分布式事务处理Java 事务编程接口（JTA：Java Transaction API）和 Java 事务服务（JTS：Java Transaction Service）为 J2EE 平台提供了分布式事务服务。分布式事务（Distributed Transaction）包括事务管理器（Transaction Manager）和一个或多个支持 XA 协议的资源管理器（Resource Manager）。我们可以将资源管理器看做任意类型的持久化数据存储；事务管理器承担着所有事务参与单元的协调与控制。JTA 事务有效地屏蔽了底层事务资源，使应用可以以透明的方式参与到事务处理中。但是与本地事务相比，XA 协议的系统开销大，在系统开发过程中应慎重考虑是否确实需要分布式事务。若确实需要分布式事务以协调多个事务资源，则应实现和配置所支持 XA 协议的事务资源，如 JMS、JDBC 数据库连接池等。使用 JTA 处理事务的示例如下（注意：connA 和 connB 是来自不同数据库的连接）：清单 2. JTA 事务处理public void transferAccount() { UserTransaction userTx = null; Connection connA = null; Statement stmtA = null; Connection connB = null; Statement stmtB = null; try { // 获得 Transaction 管理对象 userTx = (UserTransaction.html) getContext().lookup("java:comp/UserTransaction".html); // 从数据库…

@RequestParam @RequestBody @PathVariable 等参数绑定注解详解
type: item
date: 2026-07-07T08:35:46+00:00
description: 引言承接上一篇文章对 @RequestMapping 地址映射的讲解，本文主要讲解请求数据（Request）到处理器方法（Handler Method）参数数据绑定时所用到的注解，以及它们各自适用的情形。注解分类简介Handler Method 参数绑定常用的注解，根据它们处理的 Request 不同内容部分，主要分为以下四类（本文主要讲解常用类型）：处理 Request URI 部分（指 URI Template 中的 variable，不含 queryString 部分）：@PathVariable处理 Request Header 部分：@RequestHeader, @CookieValue处理 Request Body 部分：@RequestParam, @RequestBody处理 Attribute 类型：@SessionAttributes, @ModelAttribute1. @PathVariable当使用 @RequestMapping 的 URI Template 样式映射时（例如 someUrl/{paramId}），可通过 @PathVariable 注解将 URI 中的变量值绑定到方法的参数上。示例代码：@Controller @RequestMapping("/owners/{ownerId}".html) public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}".html) public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model.html) { // implementation omitted } }上述代码将 URI Template 中的变量 ownerId 和 petId 的值，分别绑定到方法的参数上。若方法参数名称与 URI Template 中的变量名称不一致，需要在 @PathVariable("name".html) 中指定 URI Template 中的名称。2. @RequestHeader、@CookieValue@RequestHeader@RequestHeader 注解可以把 Request 请求 Header 部分的值绑定到方法的参数上。示例场景：这是一个 Request 的 Header 部分：Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300绑定代码：@RequestMapping("/displayHeaderInfo.do".html) public void displayHeaderInfo(@RequestHeader("Accept-Encoding".html) String encoding, @RequestHeader("Keep-Alive".html) long keepAlive) { //... }上面的代码将 Request Header 部分的 Accept-Encoding 值绑定到参数 encoding 上，Keep-Alive Header 的值绑定到参数 keepAlive 上。@CookieValue@CookieValue 可以把 Request Header 中关于 Cookie 的值绑定到方法的参数上。示例场景：例如有如下 Cookie 值：JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84参数绑定代码：@RequestMapping("/displayHeaderInfo.do".html) public void displayHeaderInfo(@CookieValue("JSESSIONID".html) String cookie) { //... }即把 JSESSIONID 的值绑定到参数 cookie 上。3. @RequestParam, @RequestBody@RequestParam常用来处理简单类型的绑定。通过 Request.getParameter() 获取的 String 可直接转换为简单类型（String 到简单类型的转换操作由 ConversionSer…

基于 RBAC 的 Web Security Framework 的研究与应用
type: item
date: 2026-07-07T08:34:12+00:00
description: 访问控制与 RBAC 模型访问控制通常的多用户系统都会涉及到访问控制（Access Control）。所谓访问控制，是指通过某种方式允许或限制用户访问能力及范围的一种方法。这主要是由于系统需要对关键资源进行保护，防止由于非法入侵或者误操作对业务系统造成破坏。简而言之，访问控制即定义哪些用户可以访问哪些资源。一般而言，访问控制系统包括三个组成部分：主体（Subject）：发出访问请求的实体，通常指用户或者用户进程。客体（Object）：被保护的资源，通常是程序、数据、文件或者设备。访问策略（Access Policy）：主体和客体的映射规则，确定一个主体是否对客体具有访问能力。RBAC 模型基于角色的访问控制（Role-Based Access Control, RBAC）的概念在七十年代就已经提出，但是直到九十年代由于安全需求的发展才又引起了广泛关注。RBAC 的核心思想是将系统资源的访问权限进行分类或者建立层次关系，抽象为角色的概念，然后根据安全策略将用户和角色关联，从而实现了用户和权限之间的对照。RBAC 通过引入角色并将其作为权限管理的中介，将访问控制系统分为两个部分，即权限与角色的关联和角色与用户的关联，具有灵活易控制的优点。RBAC 模型的建立和实现是基于角色实现访问控制研究中的两大热点。RBAC96 模型因全面系统地描述了多层次的含义而得到广泛认可。美国国家标准技术协会（NIST）基于 RBAC96 制定了 RBAC 标准，将 RBAC 主要分为：Core RBAC、Hierarchical RBAC、Constraint RBAC。RBAC96 模型包含 4 个层次，如图 1 所示：图 1. RBAC96 模型其中：RBAC0：包含 RBAC 的核心部分即 Core RBAC，是最基本的模型，仅包含基本的 RBAC 元素，即用户、角色、权限、会话。角色之间是平级关系，对象之间没有附加的约束。RBAC1：在 RBAC0 的基础上定义了角色继承关系。RBAC2：同样包含了 RBAC0，并且定义了约束，实际中需要考虑的常见的约束包括角色互斥和角色基数两种。RBAC3：包含了 RBAC1 和 RBAC2，即既有角色的继承关系，又包含约束关系，是最复杂的一种模型。图 2 清晰描述了 RBAC 模型中的用户、角色、权限、继承关系及约束关系。图 2. RBAC 模型关系图约束是 RBAC 的一个重要组成部分，比较常见的约束包括：互斥角色约束：指的是同一用户只可以分配到互斥角色集合中最多一个角色，以支持“职责分离”的原则。必备角色约束：也称为先决条件约束，是定义在用户角色分配关系（UA）和权限角色分配关系（PA）上的重要约束，是指所分配的角色或者权限依赖于另外一种角色或者权限。基数约束：同样也是定义在 UA 和 PA 上的约束，规定了一个角色可以被分配的最大用户数或者一项权限可允许分配的角色数目，从而来控制高级权限在系统中的分配。流行的 Web 安全框架随着网络应用的不断发展，越来越多的应用系统开始移植到 Web 环境，并且具有不同程度的访问控制的需求。随着这种需求的积累，出现了很多基于 RBAC 的 Web 访问控制框架。这些框架的核心理论就是 RBAC，并且为开发人员提供了方便的接口来完成定制化的需求。Membership in ASP.NETASP.NET 中提供了非常好的用户身份认证的管理框架——Membership，提供的功能包括创建新用户、身份认证、密码管理等。默认情况下，ASP.NET Membership 可以支持所有的 ASP.NET 应用程序，并提供了非常友好的使用方式，甚至提供了向导来创建所需要的数据结构——aspnet_regsql.exe。图 3. Membership 数据模型.NET 平台的易用性毋庸置疑，开发者只需要配置 web.config 文件，引入 authentication 配置，增加 membership 节点，再配置其角色管理 roleManager 基本上就完成了对 Membership 的引用。并且 .NET 甚至提供了用户控件 login 和 createUserWizard 等，通过属性的简单配置即可激活 Membership 的认证，完成创建用户的功能等等。本文中的企业应用没有基于 .NET 平台的 Membership 来实现，因此 Membership 并不是本文的重点，不再赘述。Spring Security in JavaJava Web 开发领域中的用于进行认证授权的框架为数众多，单就从 RBAC 的角度而言，Spring Security 已经占据重要的地位，并且被越来越广泛地应用在各种 Web 系统中，为基于 J2EE 企业应用软件提供了全面安全服务。Spri…

Spring MVC 3.2 技术预览（三）：动手写一个异步Controller方法
type: item
date: 2026-07-07T08:33:34+00:00
description: 引言在前面的文章中，我介绍了 Servlet 3 与 Spring MVC 3.2 中支持异步的新特性，并探讨了一些实时更新的技术背景。在这篇文章中，我将展示 Spring MVC 3.2 新特性的技术细节，以及它们对 Spring MVC 请求生命周期的多方面影响。异步 Controller 方法基础如果需要将 Controller 层的方法转变为异步方法，只需将方法的返回值类型改为 Callable 即可。例如：原本返回视图名 String 的方法，可以改为返回 Callable<String>。原本返回 ResponseEntity 的方法，可以改为返回 Callable<ResponseEntity>。其他返回值类型依此类推。在这种处理方式中，除了 Controller 层方法会在另一个线程中处理完成外，其他工作方式没有发生任何变化。当方法改为异步处理后，保持处理逻辑的简单非常重要。因为即便只是将方法改为异步方式，仍有诸多相关问题需要考虑。示例代码GitHub 上 spring-mvc-showcase 项目的 spring-mvc-async 分支里，提供了许多 Controller 层异步方法的示例。返回 @ResponseBody例如下面的 @ResponseBody 方法，返回了视图名 String：@RequestMapping(value="/response/annotation", method=RequestMethod.GET) public @ResponseBody Callable<String> responseBody() { return new Callable<String>() { public String call() throws Exception { // Do some work.. Thread.sleep(3000L.html); return "The String ResponseBody"; } }; }返回 ResponseEntity以及下面的 ResponseEntity 方法：@RequestMapping(value="/response/entity/headers", method=RequestMethod.GET) public Callable<ResponseEntity<String>> responseEntityCustomHeaders() { return new Callable<ResponseEntity<String>>() { public ResponseEntity<String> call() throws Exception { // Do some work.. Thread.sleep(3000L.html); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN.html); return new ResponseEntity<String>( "The String ResponseBody with custom header Content-Type=text/plain", headers, HttpStatus.OK); } }; }重定向视图还有 redirect 类型的视图名方法：@RequestMapping(value="/uriTemplate", method=RequestMethod.GET) public Callable<String> uriTemplate(final RedirectAttributes redirectAttrs.html) { return new Callable<String>() { public String call() throws Exception { // Do some work.. Thread.sleep(3000L.html); redirectAttrs.addAttribute("account", "a123".html); // Used as URI template variable redirectAttrs.addAttribute("date", new LocalDate(2011, 12, 31.html)); // Appended as a query parameter return "redirect:/redirect/{account}"; } }; }异常处理添加了 @RequestMapping 注解和 @ResponseBody 注解的方法中，这些注解同样会应用到返回值 …

Spring Framework 中 RestClient 的详细介绍及示例
type: item
date: 2026-07-07T08:33:17+00:00
description: 什么是 RestClient？RestClient 是 Spring Framework 6.1 引入的一个同步 HTTP 客户端，旨在替代传统的 RestTemplate。它提供了更简洁、现代的 API 设计，专注于同步请求场景，支持链式调用（Fluent API），并能够与 Spring 生态（如错误处理、拦截器等）无缝集成。适用版本：本文内容适用于 Spring Framework 6.1+ 及 Spring Boot 3.2+。核心特性同步请求：专为传统阻塞式调用场景设计，线程模型简单直观。链式调用：采用 Fluent API 风格，代码结构更清晰，可读性更强。灵活配置：支持自定义请求头、拦截器、错误处理策略等。生态集成：可直接作为 Bean 注入，或复用 RestTemplate 的相关组件（如 HttpMessageConverter）。依赖配置确保项目使用 Spring Boot 3.2+，或手动引入 spring-web 6.1+。在 Spring Boot 项目中，通常无需指定版本号，由父工程管理依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>快速使用示例1. 创建 RestClient 实例通过 Builder 模式进行基础配置，包括基础 URL 和默认请求头。import org.springframework.web.client.RestClient; // 基础配置 RestClient client = RestClient.builder() .baseUrl("https://api.example.com".html) // 设置基础 URL .defaultHeader("Accept", "application/json".html) // 设置默认请求头 .build();2. 发起 GET 请求获取 JSON 响应并自动反序列化为 Java 对象。// 获取 JSON 响应并反序列化为对象 User user = client.get() .uri("/users/{id}", 1.html) // 设置 URI 及路径变量 .retrieve() // 发起请求并准备提取响应 .body(User.class); // 将响应体反序列化为 User 对象 System.out.println(user.getName(.html));3. 发起 POST 请求提交 JSON 数据到服务器，并接收创建后的资源信息。// 提交 JSON 数据 User newUser = new User("Alice", 30.html); User createdUser = client.post() .uri("/users".html) .contentType(MediaType.APPLICATION_JSON.html) .body(newUser.html) // 设置请求体 .retrieve() .body(User.class);4. 处理错误自定义状态码处理逻辑，针对特定 HTTP 状态抛出业务异常。// 自定义错误处理 client.get() .uri("/users/{id}", 999.html) .retrieve() .onStatus(status -> status.value(.html) == 404, (req, res.html) -> { throw new UserNotFoundException("User not found".html); }) .body(User.class);高级功能1. 添加拦截器通过拦截器统一处理认证头或其他跨切面逻辑。RestClient client = RestClient.builder() .baseUrl("https://api.example.com".html) .requestInterceptor(headers -> { // 添加认证头 headers.set("Authorization", "Bearer token".html); }) .build();2. 自定义消息转换器注册或自定义 HTTP 消息转换器，以支持特殊的数据格式序列化与反序列化。RestClient client = RestClient.builder() .messageConverters(converters -> { converters.add(new MappingJackson2HttpMessage…

Spring AOP实现原理与CGLIB应用
type: item
date: 2026-07-07T08:32:28+00:00
description: AOP 概述AOP（Aspect Oriented Programming，面向切面编程）作为面向对象编程（OOP）的一种补充，广泛应用于处理具有横切性质的系统级服务，如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键在于 AOP 框架自动创建的 AOP 代理。AOP 代理可分为 静态代理 和 动态代理 两大类：静态代理：指使用 AOP 框架提供的命令进行编译，在编译阶段生成 AOP 代理类，因此也称为 编译时增强。动态代理：指在运行时借助 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类，因此也称为 运行时增强。AOP 的存在价值在传统 OOP 编程中，软件系统由一系列相互依赖的对象组成，这些对象被抽象成类，并通过继承管理类与类之间一般到特殊的关系。我们可以通过分析、抽象出具有特定属性与行为的对象，并通过对象间的协作形成完整的软件功能。然而，随着软件规模增大和应用升级，OOP 逐渐暴露出一些难以解决的问题。假设系统中有 3 段完全相似的代码，传统开发通常采用“复制”、“粘贴”的方式来完成。通过这种方式开发出来的软件结构如图 1 所示。图 1. 多个地方包含相同代码的软件点击查看大图如图 1 所示，这种做法存在明显不足：如果深色代码段需要修改，必须打开 3 个地方进行修改。若这段代码分布在 100 个甚至 1000 个地方，维护后果将不堪设想。为了解决这个问题，通常会将深色代码部分定义成一个方法，然后在 3 个代码段中分别调用该方法。在这种方式下，软件系统的结构如图 2 所示。图 2. 通过方法调用实现系统功能点击查看大图对于如图 2 所示的系统，如果需要修改深色部分的代码，只需修改被调用的方法即可，大大降低了软件后期维护的复杂度。然而，对于方法 1、方法 2、方法 3 依然需要显式调用深色方法。在某些特殊情况下，应用需要业务方法与横切逻辑彻底分离——即业务方法无须直接调用深色方法。例如，系统前期设计时只实现了核心业务功能，随后需求变更频繁：需要为方法增加事务控制；需要进行用户合法性验证；需要增加日志记录；......面对这样的情况，通常有两种做法：根据需求说明书，直接拒绝客户要求。拥抱需求，满足客户的需求。第一种做法显然不可取。若采用第二种做法，是否每次都要定义一个新方法，然后修改所有业务方法增加调用？工作量巨大。我们希望有一种机制：只要定义该方法，无须在业务方法中显式调用，系统会“自动”执行该特殊方法。实现这个需求的技术就是 AOP。AOP 专门用于处理系统中分布于各个模块（不同方法）中的交叉关注点问题。在 Java EE 应用中，常常通过 AOP 来处理具有横切性质的系统级服务，AOP 已经成为一种非常常用的解决方案。使用 AspectJ 的编译时增强进行 AOPAspectJ 是一个基于 Java 语言的 AOP 框架，提供了强大的 AOP 功能，其他很多 AOP 框架都借鉴或采纳了其中的一些思想。AspectJ 主要包括两个部分：语言规范：定义了如何表达、定义 AOP 编程中的语法规范，方便地用 AOP 来解决 Java 语言中存在的交叉关注点问题。工具部分：包括编译器、调试工具等。AspectJ 是最早、功能比较强大的 AOP 实现之一。在 Java 领域，AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。下载、安装 AspectJ 比较简单，读者登录 AspectJ 官网（http://www.eclipse.org/aspectj），即可下载到一个可执行的 JAR 包，使用 java -jar aspectj-1.x.x.jar 命令、多次单击"Next"按钮即可成功安装。成功安装 AspectJ 之后，将会在安装路径（例如 E:\Java\AOP\aspectj1.6）下看到如下文件结构：bin：存放了 aj、aj5、ajc、ajdoc、ajbrowser 等命令。其中 ajc 命令最常用，作用类似于 javac，用于对普通 Java 类进行编译时增强。docs：存放了 AspectJ 的使用说明、参考手册、API 文档等。lib：存放了 4 个 JAR 文件，是 AspectJ 的核心类库。相关授权文件。虽然 AspectJ 是 Eclipse 基金组织的开源项目，且提供了 Eclipse 的 AJDT 插件（AspectJ Development Tools），但 AspectJ 绝对无须依赖于 Eclipse 工具。实际上，AspectJ 的用法非常简单，就像使用 JDK 编译、运行 Java 程序一样。下面通过一个简单的程序来示范 AspectJ 的用法，并分析 AspectJ 如何在编译时进行增强。首先编写一个简单的 Java 类，模拟一个业务组件。…

Spring源码学习 ------ IoC——AOP
type: item
date: 2026-07-07T08:31:16+00:00
description: 编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。前言一直想抽空研读 Spring 源码，但真正着手时发现并非易事。Spring 发展多年，其规模已非一般开源框架可比，主要架构和流程错综复杂，难以轻易抓住要害。但可以肯定的是，IoC（控制反转）和 AOP（面向切面编程）是其根基，所有的功能扩展及对其它开源框架的支持均基于这两点构建。因此，掌握 Spring 源码的核心在于搞定 IoC 和 AOP 这两块内容。IoC 容器核心从原理上来说，IoC 非常简单，无非是从配置文件解析开始，到最后在内置容器中管理各个对象。但从 Spring IoC 源码来看，这是一个非常庞大的体系。因为 Spring 支持的特性太多，我们不太可能仔细阅读每一个细节，也不太可能在有限篇幅中表达所有细节，只能抓住关键生命周期中的关键步骤。在我看来，IoC 最核心的是两个过程：IoC 容器初始化和IoC 依赖注入。下面通过简单的图示来表述其中的关键过程。相关延伸阅读：Spring 源码学习（二）------ AOPAOP 实现机制AOP 有一些特有的概念，如 advisor、advice 和 pointcut 等，使用或配置起来略显复杂，让人感觉有些距离感。其实，它的实现就是一组标准设计模式的组合使用：Factory、Proxy、Chain of Responsibility。只要搞清楚这几个设计模式，阅读 AOP 源码是比较容易的。设计模式视角首先看看 ProxyFactoryBean 这个类，这是 AOP 使用的入口。从 AOP 拿到的 bean object 就是通过 ProxyFactoryBean.getObject() 得到的。顺着这条线下去，会发现 AOP 就是通过 Proxy 模式 从实际要执行的 target 做了包装。而 Proxy 还不止一套方案，通过 Factory 封装了两套 Proxy 实现方案：JDK 动态 Proxy 和 Cglib Proxy。之所以有两套实现，主要是因为 JDK 动态 Proxy 必须要 target 实现某个接口；如果不满足这个条件，就会用 Cglib 增强字节码的方式来实现 proxy。代理模式应用就拿 JDK Proxy 为例，Spring AOP 使用了标准的 JDK 提供的动态 Proxy 方案。我们先看看标准的动态 Proxy 是什么样子，看下面类图：Client 通过 Proxy.newProxyInstance(classLoader, proxiedInterfaces, invocationHandler.html) 就能拿到 target 的 proxy object。在执行 target 的方法时，就会先执行到 DynamicProxy 中的 invoke 方法，从而实现代理包装。基于这个道理来看 Spring AOP 的实现，实际上就是标准地基于这个方式来做的。Spring AOP 的所有“花招”都体现在 JdkDynamicAopProxy.invoke 中（当然在 Cglib 中是通过 callback 来做的，道理类似）。责任链模式应用通过看 JdkDynamicAopProxy.invoke 的源码会发现，Spring AOP 的各种逻辑是通过 Chain of Responsibility（责任链）模式 串起来的。先看看一个标准的 Chain of Responsibility 是什么样子，看下面的类图：Chain of Responsibility 的关键在于 Invocation 与 Interceptor 的配合，主要原则就两条：Invocation 需要维护 Interceptor 集合和游标，每次调用 invoke 时需要先调用游标所在的 Interceptor.invoke；如果游标已超过最后一个 Interceptor，则调用实际 target 的方法。Interceptor 的 invoke 中除了要执行自己的拦截逻辑，还要通过 Invocation.invoke 把调用传递下去。拦截的灵活性就体现在 Invocation.invoke 执行与否和执行的顺序。以上逻辑通过时序图来看，如下图所示：理解 Chain of Responsibility 后再来看 Spring AOP，JdkDynamicAopProxy.invoke 做的事情就是以上 Client 做的事情：首先通过 this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass.html) 组装 interceptor chain。…

探索 Spring AOP：全面解析与实战应用
type: item
date: 2026-07-07T08:30:17+00:00
description: 在现代 Java 开发领域，Spring 框架占据着核心地位。Spring AOP（Aspect-Oriented Programming，面向切面编程）作为其关键特性之一，为开发者提供了一种强大的编程范式，用于实现横切关注点（Cross-Cutting Concerns）的模块化。无论是日志记录、事务管理、性能监控还是安全控制，Spring AOP 都能让代码更加简洁、可维护且易于扩展。本文将深入探索 Spring AOP 的奥秘，从基础概念到高级应用，助你在开发之旅中掌握这一利器。1. Spring AOP 简介1.1 什么是 AOPAOP 是一种编程思想，旨在将横切关注点（如日志记录、安全检查、事务管理等）从核心业务逻辑中分离出来，以提高代码的模块化程度和可维护性。与传统的面向对象编程（OOP）关注于类和对象的封装、继承和多态不同，AOP 关注的是在不修改源代码的情况下，对程序的运行时行为进行增强。1.2 Spring AOP 的作用Spring AOP 通过在运行时动态地将横切关注点织入（Weaving）到目标对象的方法执行过程中，实现了对目标对象行为的增强。例如，在电商系统中，我们可以使用 Spring AOP 记录用户操作日志，而无需在每个业务方法中手动添加日志代码。这样，当业务逻辑发生变化时，日志逻辑可以独立维护，不会干扰核心业务代码。1.3 核心术语理解 AOP 的核心术语是掌握 Spring AOP 的基础：术语英文说明切面Aspect模块化的横切关注点，包含了切点和通知。例如，一个日志切面定义了在哪里记录日志以及如何记录。连接点Join Point程序执行过程中的特定点，如方法调用、方法执行结束、异常抛出等。Spring AOP 中主要指方法执行。切点Pointcut一组连接点的集合，用于指定在哪些连接点上应用切面的通知。例如，匹配所有以 "get" 开头的方法。通知Advice切面在特定连接点上执行的代码。包括前置、后置、环绕、返回和异常通知等。目标对象Target Object被切面增强的对象，即实际执行业务逻辑的对象。代理ProxySpring AOP 通过代理模式实现切面功能。代理对象包裹目标对象，在方法执行前后执行通知逻辑。2. Spring AOP 的实现方式2.1 基于代理的实现Spring AOP 默认使用基于代理的方式来实现切面功能。它为目标对象创建一个代理对象，当调用目标对象的方法时，实际上是调用代理对象的方法。代理对象会在方法执行前后或异常抛出时，执行切面的通知逻辑。2.2 AspectJ 框架集成除了基于代理的方式，Spring AOP 还支持与 AspectJ 框架集成。AspectJ 是一个功能强大的 AOP 框架，提供了更丰富的 AOP 功能，如编译时织入（Compile-time Weaving）和加载时织入（Load-time Weaving）。使用 AspectJ，我们可以在编译时或类加载时将切面织入到目标类中，而不局限于运行时动态代理。2.3 如何选择在实际应用中，需根据具体需求选择合适的实现方式：基于代理：如果对性能要求不是特别高，且主要关注运行时动态增强，默认方式通常足够满足需求。AspectJ 集成：如果需要更强大的 AOP 功能（如对构造函数、字段等进行增强），或者希望在编译时或加载时进行织入，可以考虑使用 AspectJ 与 Spring AOP 集成。3. Spring AOP 的配置方式3.1 XML 配置在早期的 Spring 项目中，XML 配置是常见方式。我们可以在 Spring 配置文件中定义切面、切点和通知等元素。虽然目前较少使用，但了解其结构有助于理解 AOP 原理。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userS…

spring aop jdk vs cglib
type: item
date: 2026-07-07T08:30:12+00:00
description: Spring AOP 机制：JDK Dynamic Proxy vs CGLIB简而言之，Spring AOP 是基于代理（Proxy-based）的。也就是说，AOP 是通过代理实现的。Spring 使用以下两种方式之一为给定的目标 Bean 创建代理：JDK 动态代理（JDK Dynamic Proxies）：当被代理的目标实现了至少一个接口时，首选此方式。CGLIB：如果目标对象没有实现任何接口，可以使用 CGLIB。也可以通过以下方式强制使用：将 <aop:config> 元素的 proxy-target-class 属性设置为 true。在使用 @AspectJ 自动代理支持时，将 proxy-target-class 属性设置为 true。注意事项与局限性Spring AOP 与 AspectJ 的关系：Spring AOP 其实只是兼容了 AspectJ 的注解，但是底层跟 AspectJ 一点关系都没有。Spring AOP 的局限性：因为 Spring AOP 是基于代理（proxy-based）和方法（method-based）的，所以它有如下局限性：不能增强 final 或者静态方法。内部方法调用（self-call）不会被 AOP 拦截。因为目标对象（target-object）没有被增强，this 引用的是目标对象本身。参考：Spring AOP top problem #1 - aspects are not applied使用 AspectJ 可以无限制地使用 AOP，但是使用起来相对复杂很多，需要仔细权衡：10.4 Choosing which AOP declaration style to use10.8 Using AspectJ with Spring applicationsSpring AOP vs AspectJ AOPSpring AOP：运行时织入（Runtime weaving）。如果存在接口则使用动态代理概念，如果提供了直接实现则使用 CGLIB 库。AspectJ：编译时织入（Compile time weaving）。如果源码可用，通过 AspectJ Java 工具（ajc 编译器）进行；或者进行后编译织入（post compilation weaving，使用编译后的文件）。此外，还可以启用与 Spring 结合的加载时织入（load time weaving）——这需要 aspectj 定义文件并提供灵活性。编译时织入在某些情况下可以提供性能优势，而且 Spring AOP 中的连接点（joinpoint）定义仅限于方法定义，而 AspectJ 则不受此限制。织入时机（Weaving Process Time）The AspectJ weaver takes class files as input and produces class files as output. The weaving process itself can take place at one of three different times: compile-time, post-compile time, and load-time. The class files produced by the weaving process (and hence the run-time behaviour of an application.html) are the same regardless of the approach chosen.Compile-time weaving is the simplest approach. When you have the source code for an application, ajc will compile from source and produce woven class files as output. The invocation of the weaver is integral to the ajc compilation process. The aspects themselves may be in source or binary form. If the aspects are required for the affected classes to compile, then you must weave at compile-time. Aspects are required, e.g., when they add members to a class and other classes being co…