教程

Spring Boot 中的 @ConfigurationProperties 注解

1、简介 Spring Boot 有许多有用的功能,包括外部化配置和轻松访问 Properties 文件中定义的属性。上一篇文章 介绍了实现这一点的各种方法。 本文将带你了解 Spring Boot 中的 @ConfigurationProperties 注解。 2、项目设置 按照惯例,在 pom.xml 中将 spring-boot-starter-parent 添加为 <parent>: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> <relativePath/> </parent> 为了验证文件中定义的属性,还需要一个 JSR-380 实现,hibernate-validator 就是其中之一,它由 spring-boot-starter-validation 依赖提供。 把它也添加到 pom.xml 中: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 更多详细信息可参阅 Hibernate Validator 入门。 3、示例属性 官方文档建议将配置属性隔离到单独的 POJO 中。 如下: @Configuration @ConfigurationProperties(prefix = "mail") public class ConfigProperties { private String hostName; private int port; private String from; //Get、Set 方法 } 通过 @Configuration 注解,Spring 会在 Application Context 中创建一个 Spring Bean。

Spring 和 Spring Boot 中的属性配置

1、概览 本文将带你了解如何通过 Java 配置和 @PropertySource 在 Spring 中设置和使用 Properties,以及 Properties 在 Spring Boot 中的工作原理。 2、通过注解注册 Properties 文件 Spring 3.1 引入了新的 @PropertySource 注解,作为一种方便的机制,用于将属性源添加到环境中。 可以将此注解与 @Configuration 注解结合使用: @Configuration @PropertySource("classpath:foo.properties") public class PropertiesWithJavaConfig { //... } 另一种非常有用的注册新 Properties 文件的方法是使用占位符,它允许在运行时动态选择不同的文件: @PropertySource({ "classpath:persistence-${envTarget:mysql}.properties" }) ... 2.1、定义多个属性位置 @PropertySource 注解是可重复的(Java 8 的可重复注解特性)。因此,如果使用的是 Java 8 或更高版本,就可以使用此注解来定义多个属性位置: @PropertySource("classpath:foo.properties") @PropertySource("classpath:bar.properties") public class PropertiesWithJavaConfig { //... } 当然,也可以使用 @PropertySources 注解并指定一个 @PropertySource 数组。这不仅适用于 Java 8 或更高版本,也适用于任何受支持的 Java 版本: @PropertySources({ @PropertySource("classpath:foo.properties"), @PropertySource("classpath:bar.properties") }) public class PropertiesWithJavaConfig { //.

Spring Boot 中的测试

1、概览 本文将带你了解如何使用 Spring Boot 中的框架支持来编写测试,包括可以独立运行的单元测试,以及在执行测试之前加载 Spring Application Context 的集成测试。 2、项目设置 本文中的示例项目是一个 “雇员管理 API”,提供了对 Employee 资源的一些操作。是一个典型的MVC三层架构,从 Controller 到 Service 最后到持久层。 3、Maven 依赖 首先,添加测试依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <version>2.5.0</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> spring-boot-starter-test 是主要的依赖,它包含了测试所需的大部分依赖。 H2 DB 是内存数据库,非常方便用于测试。 3.1、JUnit 4 从 Spring Boot 2.4 开始,JUnit 5 的 Vintage 引擎已从 spring-boot-starter-test 中移除。如果仍想使用 JUnit 4 编写测试,则需要添加以下 Maven 依赖: <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency> 4、使用 @SpringBootTest 进行集成测试 顾名思义,集成测试的重点是集成应用的不同层。这也意味着不涉及模拟(Mock)。

手动写入响应后对 @ResponseBody 注解的影响

问题回溯 2023 年 Q2 某日运营反馈一个问题,商品系统商家中心某批量工具模板无法下载,导致功能无法使用(因为模板是动态变化的) 商家中心报错如下(JSON串): {"code":-1,"msg":"失败"} 负责的同事看到失败后立即与我展开讨论(因为不是关键业务,所以不需要回滚,修复即可),我们发现新功能模板下载的代码与之前的代码有所不同,恰好之前的功能又可以正常运行,所以同事对现有代码进行改造然后预发布测试完成后再次上线。 其他业务代码: /** * 模板下载 */ @RequestMapping("/doBatchWareSetAd") public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); } 上线的结果是:仍然无法使用。 其实也正常:因为两种代码在预发布都可以正常运行,在线上出错只可能是因为其他原因,只不过我们不了解底层原理,害怕它 “可能” 有问题罢了,最终查询得到的结论是权限系统管理员在线上环境没有给我们配置相应的文件,导致请求为空,导致请求失败。 探索 @ResponseBody 与主动写入响应流的关系 我们都知道 @ResponseBody 注解可以帮助我们把返回对象转化为 JSON,方便展示和交互。 那它到底是如何工作的呢,请看下面的讲解: 代码案例 1 @RequestMapping("/test1") @ResponseBody public Map<String, String> test1(HttpServletResponse response) { Map<String, String> map = new HashMap<>(); map.put("1", "1"); return map; } // 响应 JSON报文 Debug 代码,发现其核心处理类为:RequestResponseBodyMethodProcessor。 方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 会处理其相关返回值。 真正的核心处理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters。 关键 DEBUG 记录如下图所示:

Spring Security 方法安全(Method Security)简介

1、概览 简而言之,Spring Security 支持方法级别的授权语义。可以通过限制哪些角色可以执行特定方法等方式来确保 Service 层的安全。 2、启用 Method Security 要使用 Spring Method Security,需要添加 spring-security-config 依赖: <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> 可以在 Maven Central 上找到它的最新版本。 如果使用的是 Spring Boot,可以添加 spring-boot-starter-security 依赖,其中包括 spring-security-config: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 同样,它的最新版本也可以在 Maven Central 中找到。 接下来,需要启用全局 Method Security: @Configuration @EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { } prePostEnabled 属性可启用 Spring Security Pre/Post 注解。 securedEnabled 属性决定是否启用 @Secured 注解。 jsr250Enabled 属性允许使用 @RoleAllowed 注解。 3、应用 Method Security 3.

使用 Spring ResponseEntity 处理 HTTP 响应

1、概览 本文将带你了解如何使用 ResponseEntity 设置 HTTP 响应的 Body、Status 和 Header。 2、ResponseEntity ResponseEntity 表示整个 HTTP 响应:状态码、Header 和 Body。因此,可以用它来完全配置 HTTP 响应。只需从端点返回它,Spring 就会处理接下来的所有事情。 ResponseEntity 是一个泛型类。因此,可以使用任何类型作为响应体: @GetMapping("/hello") ResponseEntity<String> hello() { return new ResponseEntity<>("Hello World!", HttpStatus.OK); } 通过编程式,可以针对不同情况返回不同的 HTTP 状态码: @GetMapping("/age") ResponseEntity<String> age( @RequestParam("yearOfBirth") int yearOfBirth) { if (isInFuture(yearOfBirth)) { return new ResponseEntity<>( "Year of birth cannot be in the future", HttpStatus.BAD_REQUEST); // 400 } return new ResponseEntity<>( "Your age is " + calculateAge(yearOfBirth), HttpStatus.OK); // 200s } 还可以设置 HTTP 响应头:

Spring Boot 日志配置

1、概览 本文将带你了解 Spring Boot 应用中的日志配置。 2、初始设置 通过 Spring Initializr 初始化一个 Spring Boot 应用: 创建唯一的类 LoggingController: @RestController public class LoggingController { Logger logger = LoggerFactory.getLogger(LoggingController.class); @RequestMapping("/") public String index() { logger.trace("A TRACE Message"); logger.debug("A DEBUG Message"); logger.info("An INFO Message"); logger.warn("A WARN Message"); logger.error("An ERROR Message"); return "Howdy! Check out the Logs to see the output..."; } } 启动应用后,只需访问 http://localhost:8080/,就能触发这些日志输出。 3、日志零配置 就日志记录而言,Spring Boot 唯一必须的依赖是 Apache Commons Logging。它是由 Spring 的 spring-jcl 模块提供的。 如果使用的是 Spring Boot Starter(几乎总是使用 Spring Boot Starter),就根本不用担心导入 spring-jcl 的问题。这是因为每个 Starter,比如 spring-boot-starter-web,都依赖于 spring-boot-starter-logging,它已经导入了 spring-jcl。

Spring 中的 @RequestParam 注解

1、概览 本文将带你了解 Spring 中 @RequestParam 注解的用法。 简单地说,可以使用 @RequestParam 从请求中提取查询参数、表单参数甚至是多个参数。 2、示例端点 假设我们有一个端点 /api/foos,它接受一个名为 id 的查询参数: @GetMapping("/api/foos") @ResponseBody public String getFoos(@RequestParam String id) { return "ID: " + id; } 在本例中,使用 @RequestParam 来提取 id 查询参数。 通过 GET 请求来调用 getFoos: http://localhost:8080/spring-mvc-basics/api/foos?id=abc ---- ID: abc 接下来,看看注解的属性:name、value、required 和 defaultValue。 3、指定请求参数名称 在上一个示例中,变量名和参数名都是相同的。 如果变量名称和参数名称不同,可以使用 name 属性配置 @RequestParam 名称: @PostMapping("/api/foos") @ResponseBody public String addFoo(@RequestParam(name = "id") String fooId, @RequestParam String name) { return "ID: " + fooId + " Name: " + name; } 也可以使用 @RequestParam(value = "id") 或直接使用 @RequestParam("id")。

Spring Boot 中的数据校验

1、概览 Spring Boot 通过 Hibernate Validator(Bean Validation 的实现)对数据验证提供了强大的支持。 本文将通过一个实际的 REST 应用带你了解如何在 Spring Boot 中校验数据。 2、Maven 依赖 在 pomx.ml 中添加 spring-boot-starter-web、spring-boot-starter-jpa 和 H2 database 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> <scope>runtime</scope> </dependency> 从 Boot 2.3 开始,还需要明确添加 spring-boot-starter-validation 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 3、示例 Domain 类 定义一个 JPA 实体类,User: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @NotBlank(message = "Name is mandatory") private String name; @NotBlank(message = "Email is mandatory") private String email; // 构造函数、Set、Set 方发省略 } User 实体类很简单,它展示了如何使用 Bean Validation 的约束来限制 name 和 email 字段。

Spring Data JPA 获取最后一条记录

1、概览 本文将带你了解使用 Spring Data JPA 获取最后一条记录的多种方式。 2、初始设置 首先,创建并初始化要查询的表。 创建一个 Post 实体类: @Entity public class Post { @Id private Long id; private String title; private LocalDate publicationDate; // get、set 方法 } @Entity 表示注解的类代表数据库中的一个表。@Id 注解定义了主键。 为了简单起见,这里使用的是 H2 内存数据库。 添加一个基本的 SQL 脚本,创建 Post 类对应的 post 表: DROP TABLE IF EXISTS post; CREATE TABLE post( id INT PRIMARY KEY, title VARCHAR(200), publication_date DATE ) 接着,添加一些数据: INSERT INTO post (id, title, publication_date) VALUES(1, 'Facebook post', '2020-11-10'); INSERT INTO post (id, title, publication_date) VALUES(2, 'Instagram post', '2020-12-24'); INSERT INTO post (id, title, publication_date) VALUES(3, 'Twitter post', '2023-01-10'); INSERT INTO post (id, title, publication_date) VALUES(4, 'tiktok post', '2023-03-18'); INSERT INTO post (id, title, publication_date) VALUES(5, 'Pinterest post', '2023-09-09'); 如你所见,最后一条记录的 ID 是 5。