Spring

在同类中直接调用 Spring Aop 代理方法

1、概览 在使用 Spring AOP 时有许多错综复杂的问题。其中一个常见问题是如何处理同一类中的方法调用,因为这种直接调用会使 AOP 功能失效。 本文将带你了解 Spring AOP 的工作原理以及如何解决同类中 AOP 方法直接调用导致的 AOP 功能失效的问题。 2、代理 和 Spring AOP 代理对象可以看作是一个 “包装”,它可以在调用目标对象时添加功能。 在 Spring 中,当一个 Bean 需要额外功能时,就会创建一个代理,并将代理注入到其他 Bean 中。例如,如果某个方法具有事务(Transactional)注解或任何缓存注解,那么该代理就会被用来在调用目标对象时添加所需的功能。 3、AOP:内部方法调用和外部方法调用 如上所述,Spring 创建的代理添加了所需的 AOP 功能。 来看一个缓存示例,看看在内部和在外部调用方法有何不同: @Component @CacheConfig(cacheNames = "addOne") public class AddComponent { private int counter = 0; @Cacheable public int addOne(int n) { counter++; return n + 1; } @CacheEvict public void resetCache() { counter = 0; } } 当 Spring 创建 AddComponent Bean 时,也会创建一个代理对象,以添加用于缓存的 AOP 功能。当另一个对象需要 AddComponent Bean 时,Spring 会提供代理对象用于注入。

使用 Spring Validator 验证 Map

1、简介 Spring 的 Validation 框架主要设计用于 JavaBean,其中每个字段都可以通过注解来定义验证约束。 本文将带你了解如何使用 Spring 的 Validator 接口验证 Map<String,String>。 2、理解问题 - Hibernate Validator 和 Map 在实现自定义验证器之前,我们会自然地尝试直接在 Map 结构上使用标准约束注解,通过 Hibernate Validator 和 Spring 内置的 @Valid 和 @Validated 等验证机制来进行校验。不幸的是,这种方法并不像我们想象的那样有效。 来看一个示例: Map<@Length(min = 10) String, @NotBlank String> givenMap = new HashMap<>(); givenMap.put("tooShort", ""); Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<Map<String, String>>> violations = validator.validate(givenMap); Assertions.assertThat(violations).isNotEmpty(); // 测试失败 尽管在 Map 泛型上添加了约束注解,但校验结果 violations 仍是空的,即未检测到违反约束的情况。 2.1、为什么会校验失败? Hibernate Validator 或一般的 Bean Validation 是根据 JavaBean 惯例运行的,这意味着它会验证通过 getter 访问的对象属性。由于 Map 不会将键和值作为属性公开,因此 像 @Length 或 @NotBlank 这样的约束注解并不直接适用,在验证过程中会被忽略。

Spring WebClient 设置 Header

1、简介 WebClient 是 Spring WebFlux 中的一个 HTTP 客户端工具类,可以实现同步和异步 HTTP 请求。 本文将带你了解在 Spring WebClient 中设置 Header 的几种方式。 2、WebClient 如何处理 Header? 一般来说,HTTP 请求中的 Header 起到元数据的作用。它们包含认证详细信息、内容类型、版本等信息。 在 WebClient 中,HttpHeaders 类负责管理 Header。这是一个 Spring 框架类,专门用于表示请求和响应头。该类实现了 MultiValueMap<String, String>,允许一个 Header Key 有多个 Value。 这为需要多个值的 Header(如 Accept)提供了灵活性。 3、在 WebClient 中设置 Header 有几种方法可以为请求添加 Header。根据使用情况,我们可以为单个请求设置 Header,为整个 WebClient 实例定义全局 Header,或动态修改 Header。 3.1、为单个请求设置 Header 如果 Header 是针对单个请求的,并且因端点而异,那么直接的方法就是在请求中直接设置它们。 一个简单的示例如下。例化 WebClient,在请求中添加了两个 Header,并断言这些 Header 已通过请求发送。使用 okhttp3 库中的 MockWebServer 来模拟服务器响应并验证 WebClient 的行为: @Test public void givenRequestWithHeaders_whenSendingRequest_thenAssertHeadersAreSent() throws Exception { mockWebServer.

使用 JSpecify 和 NullAway 实现 Spring 应用 Null Safety

Null Safety 旨在防止空指针(NullPointerException)异常。 最初在 Spring 中引入 Null Safety 支持要追溯到 2017 年发布的 Spring Framework 5.0。2025 年,我们会继续完善这一功能,为 Java 或 Kotlin 的 Spring 开发人员带来更多附加值。 我们要解决什么问题? 举一个具体的例子,假设我们正在使用一个提供 TokenExtractor 接口的库,其定义如下: interface TokenExtractor { /** * Extract a token from a {@link String}. * @param input the input to process * @return the extracted token */ String extractToken(String input); } 如果由于某种原因实现返回 null 值,在 token.length() 中访问 null 引用(如下所示)会导致 NullPointerException 异常,通常会在运行时产生状态码为 500 Internal Server Error 的 HTTP 响应。

在 Spring 中校验 List 参数

1、概览 在 Spring 中,可以通过内置工具和注解简化数据验证,使我们能够轻松实现强大的验证逻辑。 本文将带你了解如何在 Spring 中验证 List 类型的参数值。 2、在 Spring 中配置 Validation 要启用验证,需要在 pom.xml 中添加以下 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 该依赖会自动将 Hibernate Validator 整合到我们的 Spring 应用程序中。 3、验证 List 中的每个元素 假设我们需要验证员工角色列表。每个角色都应以 ROLE_ 开头。 首先在 Employee DTO 类中添加一个 roles 变量: @NotEmpty(message = "Roles list cannot be empty") @Valid private List<@Pattern(regexp = "ROLE_[A-Z]+", message = "Each role must start with 'ROLE_' and contain uppercase letters only") String> roles; @NotEmpty 注解确保列表不是空的,而 @Pattern 注解则验证每个角色都符合指定的格式(正则)。

Spring Filter 中的 chain.doFilter() 方法

1、简介 本文将带你了解 Spring 中 chain.doFilter() 方法的作用。 2、什么是 Spring Filter 在 Spring 应用中,Filter 过滤器以 Java Servlet Filter 为基础,后者代表拦截请求和响应的对象。Filter 是 Java Servlet API 的一部分,在 Web 应用中扮演着重要角色,因为它们位于客户端和服务器处理逻辑之间。 利用它们,我们可以在请求到达 servlet 之前或生成响应之后执行任务。过滤器的常见用例包括 认证和授权 审计和日志 修改请求/响应 过滤器虽然不是 Spring 框架的一部分,但与 Spring 框架完全兼容。我们可以将它们注册为 Spring Bean,并在应用中使用它们。Spring 提供了一些过滤器的实现,其中常见的包括 OncePerRequestFilter 和 CorsFilter。 3、理解 chain.doFilter() 方法 在了解 chain.doFilter() 方法之前,首先要了解过滤器链(Filter Chain)的概念及其在过滤过程中的作用。 过滤器链表示应用于传入请求或传出响应的处理逻辑顺序流。换句话说,它是用于预处理请求或后处理响应的过滤器集合。这些过滤器以明确定义的顺序排列,确保每个过滤器都能在将请求或响应传递到链中的下一阶段之前执行其处理逻辑。 要定义过滤器链,可以使用了 Java Servlet API 中的 FilterChain 接口,其中包含了我们感兴趣的方法。查看该方法的签名,就会发现请求(request)和响应(response)对象被定义为输入参数: void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; chain.doFilter() 方法将请求和响应传递给链中的下一个过滤器。如果过滤器链中没有过滤器,则会将请求转发给目标资源(通常是 Servlet),并将响应发送给客户端。 3.1、为什么调用 chain.doFilter() 非常重要? 我们可以看到,chain.

使用 JdbcTemplate 调用存储过程

1、概览 本文将带你了解如何使用 Spring JDBC 框架的 JdbcTemplate 来调用存储过程。数据库存储过程类似于函数。函数支持输入参数并有返回类型,而存储过程同时支持输入和输出参数。 2、先决条件 来看看 PostgreSQL 数据库中一个简单的存储过程: CREATE OR REPLACE PROCEDURE sum_two_numbers( IN num1 INTEGER, IN num2 INTEGER, OUT result INTEGER ) LANGUAGE plpgsql AS ' BEGIN sum_result := num1 + num2; END; '; 存储过程 sum_twoo_numbers 接收两个输入数字,并在输出参数 sum_result 中返回它们的和。一般来说,存储过程可以支持多个输入和输出参数。但在本例中,我们只考虑了一个输出参数。 3、使用 JdbcTemplate#call() 方法 来看看如何使用 JdbcTemplate#call() 方法调用数据库存储过程: void givenStoredProc_whenCallableStatement_thenExecProcUsingJdbcTemplateCallMethod() { List<SqlParameter> procedureParams = List.of(new SqlParameter("num1", Types.INTEGER), new SqlParameter("num2", Types.NUMERIC), new SqlOutParameter("result", Types.NUMERIC) ); Map<String, Object> resultMap = jdbcTemplate.call(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { CallableStatement callableStatement = con.

理解 Spring Reactive 中的 switchIfEmpty()

1、概览 本文将带你了解 Spring Reactive 中的 switchIfEmpty() 操作符及其在使用和不使用 defer() 操作符时的行为,了解这些操作符在不同场景中的交互方式,并通过实际示例来说明它们对响应式流(Reactive Stream)的影响。 2、switchIfEmpty() 和 Defer() 的使用 switchIfEmpty() 是 Mono 和 Flux 中的一个操作符,用于在源生产者为空时执行备用生产者流。如果主源 Publisher 没有发布数据,该操作符就会切换到替代源的数据发布。 考虑一个从大型文件中通过 ID 检索用户详细信息的场景。当请求文件中的用户详细信息时,遍历文件会消耗大量时间。因此,对于经常访问的 ID,将其详细信息缓存起来更有意义。 当端点收到请求时,首先搜索缓存。如果用户详细信息可用,返回响应。如果没有,则从文件中获取数据并缓存起来,以备后续请求。 在这种情况下,主数据提供者(Primary Data Provider)是检查缓存中 KEY 是否存在的流,而备用数据提供者是检查文件中的 KEY 并更新缓存的流。switchIfEmpty() 操作符可以根据缓存中数据的可用性高效地切换数据源提供者。 了解 defer() 操作符的使用也很重要,它可以推迟函数的求值,直到发生订阅。如果我们不在 switchIfEmpty() 中使用 defer() 操作符,表达式就会立即(急切地)求值,从而可能导致意想不到的副作用。 3、示例设置 通过示例来了解 switchIfEmpty() 操作符在不同情况下的行为。 3.1、Data Model 首先,定义一个 User 模型类,其中包含一些详细信息,如 id、email、username 和 roles: public class User { @JsonProperty("id") private String id; @JsonProperty("email") private String email; @JsonProperty("username") private String username; @JsonProperty("roles") private String roles; // Getter / Setter 省略 } 3.

Spring 中的 Fallback Bean

1、概览 本文将带你了解 Spring 中 Fallback Bean 的概念。 Fallback Bean 是在 Spring Framework 6.2.0-M1 中引入的,当另一个相同类型的 Bean 不可用或无法初始化时,它提供了一种替代实现。 2、Primary Bean 和 Fallback Bean 在 Spring 中,我们可以定义多个相同类型的 Bean。默认情况下,Spring 使用 Bean 名称和类型来标识 Bean。当有多个名称和类型相同的 Bean 时,可以使用 @Primary 注解将其中一个标记为 Primary(主要)Bean,使其优先于其他 Bean。如果在 Application Context 初始化时创建了多个相同类型的 Bean,而我们又想指定默认使用哪个 Bean,那么这就非常有用了。 同样,我们可以定义一个 Fallback Bean,以便在没有其他合格 Bean 时提供替代实现。我们可以使用 @Fallback 注解将一个 Bean 标记为 Fallback(后备) Bean。只有当没有其他同名的 Bean 可用时,才会将后备 Bean 注入到 Application Context。 3、示例代码 来看一个示例,演示如何在 Spring 应用中使用 Primary Bean 和 Fallback Bean。 我们要创建一个使用不同 MQ 服务发送消息的小应用。假设我们在生产环境和非生产环境中拥有多个 MQ 服务,并且需要在它们之间切换以优化性能和成本。

通过 Spring 5 中 Supplier 来获取 Bean

今天遇到了一个面试题:Spring 中 Bean 的实例化有哪些方式? 大家知道,Spring 中 Bean 的配置方式有很多种,但是正常来说,无论你是 XML 文件配置,还是用类似 @Service 注解这种配置,本质上最终都是通过反射去完成 Bean 的初始化的;@Bean 注解则稍微特殊一点,往往我们在 @Bean 注解中是自己 new 出来目标 Bean,但是 @Bean 注解所标记的方法也是通过反射调用的。 似乎 Bean 的实例化离不开反射。 那么除了上面这些方案,还有没有其他方案呢?本文和大家探讨一下这个问题。 以下内容基于 Spring6.0.4。 总所周知,当使用 Spring 容器的时候,如果遇到一些特殊的 Bean,一般来说可以通过如下三种方式进行配置: 静态工厂方法 实例工厂方法 FactoryBean 不过从 Spring5 开始,在 AbstractBeandefinition 类中多了一个属性,对于特殊的 Bean 我们有了更多的选择: /** * Specify a callback for creating an instance of the bean, * as an alternative to a declaratively specified factory method. * <p>If such a callback is set, it will override any other constructor * or factory method metadata.