Spring

Spring Email(邮件)发送指南

1、概览 本文将带你了解如何通过标准 Spring 应用及 Spring Boot 应用发送邮件。前者基于 JavaMail 库实现,后者则使用 spring-boot-starter-mail 依赖。 2、Maven 依赖 首先需在 pom.xml 中添加依赖。 2.1、Spring 以下是标准 Spring 框架所需的依赖配置: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>6.1.5</version> </dependency> 最新版本可在 此处 获取。 2.2、Spring Boot 而 Spring Boot 则需要添加: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <version>3.1.5</version> </dependency> 最新版本可在 Maven 中央仓库 获取。 3、邮件服务器配置属性 Spring 框架中邮件支持的接口与类按以下结构组织: MailSender 接口:顶层接口,提供发送简单邮件的基础功能。 JavaMailSender 接口:MailSender 的子接口,支持 MIME 消息,通常与。MimeMessageHelper 类配合创建 MimeMessage。建议通过 MimeMessagePreparator 机制使用该接口。 JavaMailSenderImpl 类:实现 JavaMailSender 接口,支持 MimeMessage 和 SimpleMailMessage。 SimpleMailMessage 类:用于创建包含发件人、收件人、抄送、主题和文本内容的简单邮件。 MimeMessagePreparator 接口:为 MIME 消息提供准备机制的回调接口。 MimeMessageHelper 类:创建 MIME 消息的辅助类,支持图片、典型邮件附件及 HTML 格式的文本内容。 以下章节将演示如何使用这些接口与类。

使用 Spring 发送邮件时异常“Could Not Autowire org.springframework.mail.javamail.JavaMailSender”

1、简介 本文将带你了解使用 Spring Boot 实现邮件功能时遇到 “Could not autowire org.springframework.mail.javamail.JavaMailSender” 异常的原因以及解决办法。 2、理解异常 首先解释该错误的含义。JavaMailSender 是 Spring 提供的接口,用于抽象邮件发送过程。它继承自 MailSender 接口(提供简单文本邮件的基础功能),特别支持 MIME 消息、附件和 HTML 内容等高级特性。 Spring 的依赖注入机制会自动装配所需 Bean。当遇到 @Autowired 注解或更推荐的构造器注入时,它会从应用上下文(Application Context)中查找匹配的 Bean: @Service public class EmailService { private final JavaMailSender javaMailSender; public EmailService(final JavaMailSender javaMailSender) { this.javaMailSender = javaMailSender; } } 当我们注入 Spring 无法找到的 JavaMailSender Bean 并运行应用时,会抛出以下错误: Field javaMailSender in com.baeldung.email.EmailService required a bean of type 'org.springframework.mail.javamail.JavaMailSender' that could not be found. 或者: Parameter 0 of constructor in com.

在同类中直接调用 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.