教程

Spring Boot 中的静态资源乱码

通过浏览器直接访问 Spring Boot 中的静态文件(如 js、css),如果静态文件包含中文的话则会显示乱码。 静态文件 在 src/main/resources/public 目录下创建一个 test.js 文件,用于测试: public 目录是 Spring Boot 默认的静态资源目录,里面的文件可以被客户端直接访问。 test.js 文件内容如下: // 输出中文内容 (function(){ console.log("你好 Spring Doc"); })(); 这个文件中包含两段中文内容。 测试 启动应用,打开浏览器访问上述 JS 文件:http://localhost:8080/test.js。 如你所见,文件中的中文内容全部乱码。尽管这个 js 文件本身就是 UTF-8 编码。 解决办法 究其原因,是因为服务器响应的 Content-Type: application/javascript 头中没有指定文本文件的编码类型。 我们可以在配置文件中对编码类型进行设置: server: servlet: encoding: force: true server.servlet.encoding.force 配置指定了是否在 HTTP 请求和响应中强制使用配置的字符集编码。默认为 false,这里设置为 true。 字符集编码可以通过 server.servlet.encoding.charset 属性进行配置,它默认就是 UTF-8。 重启应用,再次访问 test.js 文件: Content-Type Header 指定了正确的编码,文件中的中文内容已经正常显示了。

Spring Boot 中的 Startup Actuator 端点

1、概览 Spring Boot 的启动过程可能涉及到繁琐的资源初始化。本文将带你了解如何通过 Spring Boot Actuator 的 Startup 端点追踪和监控这些启动信息。 2、应用启动追踪 追踪应用启动过程中的各个步骤可以提供有用的信息,帮助我们了解应用启动各个阶段所花费的时间。这种工具还能提高我们对上下文生命周期和应用启动顺序的理解。 Spring 提供了 记录应用启动 功能。此外,Spring Boot Actuator 还通过 HTTP 或 JMX 提供了多种生产级监控和管理功能。 从 Spring Boot 2.4 开始,应用启动追踪指标可通过 /actuator/startup 端点获得。 3、设置 在 pom.xml 中添加 spring-boot-starter-actuator 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 还需要 spring-boot-starter-web 依赖项,用于暴露 HTTP 端点: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.5.4</version> </dependency> 在 application.properties 文件中配置属性,在 HTTP 上公开所需的端点: management.endpoints.web.exposure.include=startup 最后,使用 curl 查询 Actuator 的 HTTP 端点,使用 jq 解析 JSON 响应。 4、Actuator 端点 为了捕获启动事件,需要使用 @ApplicationStartup 接口的实现来配置应用。默认情况下,管理应用生命周期的 ApplicationContext 使用 “空” 操作的实现。这显然不会执行启动检测和追踪,从而将开销降至最低。

OncePerRequestFilter 的用法

1、概览 本文将带你了解 Spring 中一种特殊类型的 Filter(过滤器)OncePerRequestFilter。 通过实例了解它的功能和用法。 2、OncePerRequestFilter 是什么? 回顾一下 Filter 的工作原理。Filter 可以在 Servlet 执行之前或之后调用。当请求被调度给一个 Servlet 时,RequestDispatcher 可能会将其转发给另一个 Servlet。另一个 Servlet 也有可能使用相同的 Filter。在这种情况下,同一个 Filter 会被调用多次。 但是,有时需要确保每个请求只调用一次特定的 Filter。一个常见的用例是在使用 Spring Security 时。当请求通过过滤器链(Filter Chain)时,对请求的身份证认证应该只执行一次。 在这种情况下,可以继承 OncePerRequestFilter。Spring 保证 OncePerRequestFilter 只对指定请求执行一次。 3、使用 OncePerRequestFilter 处理同步请求 定义一个继承了 OncePerRequestFilter 的 AuthenticationFilter Filter 类,并覆写 doFilterInternal() 方法: public class AuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String usrName = request.getHeader(“userName”); logger.info("Successfully authenticated user " + userName); filterChain.

在 Spring Webflux 中使用 @Cacheable 注解缓存结果

1、概览 本文将带你了解如何在 Spring WebFlux 中使用 @Cacheable 注解实现缓存,以及一些常见的问题和解决办法。 2、@Cacheable 和响应式类型 在本文撰稿时,@Cacheable 还不能和响应式框架无缝整合。主要问题在于,目前还没有非阻塞式的缓存实现(JSR-107 缓存 API 是阻塞式的)。只有 Redis 提供了响应式驱动。 虽然,仍然可以在方法上使用 @Cacheable。这会缓存封装对象(Mono 或 Flux),但不会缓存方法的实际结果。 2.1、项目设置 创建一个使用响应式 MongoDB 驱动的 Spring WebFlux 项目。并且用 Testcontainers 代替真实运行的 MongoDB 进行测试。 测试类使用 @SpringBootTest 进行注解,如下: final static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10")); @DynamicPropertySource static void mongoDbProperties(DynamicPropertyRegistry registry) { mongoDBContainer.start(); registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl); } 这几行代码会启动 MongoDB 实例,并将 URI 传递给 Spring Boot 以自动配置 Mongo Repository。 创建带有保存和获取 Item 方法的 ItemService 类: @Service public class ItemService { private final ItemRepository repository; public ItemService(ItemRepository repository) { this.

Spirng Boot 返回:415 Unsupported MediaType

1、概览 本文将带你了解如何解决在 Spring Boot API 返回状态码 “415 Unsupported MediaType” 的问题,以及出现该问题的原因。 2、背景 我们一个老客户要求我们给他的系统再开发一个桌面应用,类似于用户管理。新的桌面应用,直接调用原系统的 API 服务。 3、API 请求 通过 API 来获取所有用户: curl -X GET https://baeldung.service.com/user 成功获取响应。接着,获取一个单独的用户: curl -X GET https://baeldung.service.com/user/{user-id} 响应如下: { "id": 1, "name": "Jason", "age": 23, "address": "14th Street" } 一切也OK,根据响应,可以确定用户拥有以下参数:id、name、age 和 address。 现在,尝试添加新用户: curl -X POST -d '{"name":"Abdullah", "age":28, "address":"Apartment 2201"}' https://baeldung.service.com/user/ 结果,这次服务器响应了 HTTP 状态码为 415 的错误响应: { "timestamp": "yyyy-MM-ddThh:mm:ss.SSS+00:00", "status": 415, "error": "Unsupported Media Type", "path": "/user/" } 在弄清 “为什么会出现这个错误?” 之前,需要先弄清楚 “这个错误是什么?”。

在 Spring Boot 中使用 JSP

在前后端分离架构、SPA 应用大行其道的今天,模板引擎已经逐渐被淘汰了。更别提 JSP 这种上古模板引擎了。 Spring Boot 推荐使用 FreeMarker、Groovy、Thymeleaf 或者 Mustache 作为模板引擎。不推荐 JSP,主要是 JSP 的编译方式比价特殊,它需要先把 JSP 代码编译为 Servlet,最后通过执行 Servlet 来输出模板内容。 当然,在 Spring Boot 中使用 JSP 也是可以的,只需要些许配置即可。 创建 Spring Boot 项目 创建 Spring Boot(3.0.3)项目,在 pom.xml 中添加如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> </dependency> <!-- Tomcat 嵌入式 JSP 解析器 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- JSP jstl --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> 注意,jstl 需要自己手动添加版本号,它没有被 Spring Boot 管理。 在 Spring Boot 支持的嵌入式容器中只有 Tomcat 支持使用 JSP,Undertow 和 Jetty 均不支持!

Spring Boot @ConditionalOnThreading 注解

1、简介 本文将带你了解一个相对较新的 Spring Boot 条件注解 @ConditionalOnThreading。 2、条件注解 条件注解提供了一种仅在满足各种特定条件时才在 BeanFactory 中注册 Bean 的方法。开发人员通过使用 Condition 接口为每个注解单独定义这些条件。 Spring Boot 为常见用例提供了大量预定义的条件注解。常见的示例有 @ConditionalOnProperty、@ConditionalOnBean 和 @ConditionalOnClass。 3、@ConditionalOnThreading 原理 @ConditionalOnThreading 只是 Spring Boot 中另一个预定义的条件注解。它是在 3.2 版本中添加的,在本文撰稿时,该版本本身还是候选发布版。也就是说,需要使用专用的 Spring Artifacts 仓库。 <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> @ConditionalOnThreading 注解仅允许在 Spring 内部配置为使用特定类型的线程时创建 Bean。所谓线程类型,是指平台线程或虚拟线程。回顾一下,从 Java 21 开始,我们就可以 使用虚拟线程来代替平台线程 了。

国际化 Bean Validation 校验失败的错误消息

1、概览 在 Web 应用中,我们通常需要通过 spring-validation 对客户端提交的数据进行校验。如果校验失败则会抛出异常。默认情况下,关于校验失败细节的异常信息是英文。本文将会带你了解如何对校验失败时的异常消息进行国际化(也称为“本地化”)。 2、依赖 首先在 pom.xml 中添加 Spring Boot Starter Web 和 Spring Boot Starter Validation: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 最新版本可在 Maven Central 上找到。 3、本地化信息存储 在 Java 应用中,通常使用 properties 文件存储本地化信息。一般称为 Resource Bundle。 这些文件是由键值对组成的纯文本文件。key 是信息检索的标识符,而对应的 value 则是相应语言的本地化消息。 接下来,我们创建 2 个 properties 文件。 CustomValidationMessages.properties 是默认的 properties 文件,文件名不包含任何语言名称。只要客户端指定的语言不支持,应用就会使用默认语言: field.personalEmail=Personal Email validation.notEmpty={field} cannot be empty validation.email.notEmpty=Email cannot be empty 再创建一个额外的中文语言的 properties 文件 - CustomValidationMessages_zh.properties。只要客户端指定 zh 或 zh-tw 等变体作为本地语言,应用语言就会切换为中文:

Spring Boot 整合 Kafka Stream

1、简介 流式数据在现实生活中的一些例子包括传感器数据、股票市场事件流和系统日志。在本文中,我们通过构建一个简单的字数统计流式应用来介绍如何在 Spring Boot 中使用 Kafka Streams。 2、概览 Kafka Streams 在 Kafka Topic 和关系型数据库表之间提供了一种对偶性。它使我们能够对一个或多个流式事件进行连接、分组、聚合和过滤等操作。 Kafka 流的一个重要概念是处理器拓扑(Processor Topology)。处理器拓扑是 Kafka Stream 对一个或多个事件流进行操作的蓝图。从本质上讲,处理器拓扑可视为有向无环图。在这个图中,节点分为源节点、处理器节点和汇节点,而边则代表流事件的流向。 位于拓扑结构顶端的源接收来自 Kafka 的流数据,将其向下传递到执行自定义操作的处理器节点,并通过汇节点流出到新的 Kafka Topicc。在进行核心处理的同时,还利用检查点(Checkpoint)定期保存数据流的状态,以实现容错和弹性。 3、依赖 首先在 POM 中添加 spring-kafka 和 kafka-streams 依赖: <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.7.8</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId <artifactId>kafka-streams</artifactId> <version>2.7.1</version> </dependency> 4、示例 示例应用从输入的 Kafka Topic 中读取流式事件。读取记录后,它会对记录进行处理,分割文本并计算单个字数。随后,它将更新的字数发送到 Kafka 输出。除了输出 Topic 外,还要创建一个简单的 REST 服务,通过 HTTP 端点公开该计数。 总之,输出 Topic 将不断更新从输入事件中提取的单词及其更新计数。 4.1、配置 在 Java 配置类中定义 Kafka Stream 配置: @Configuration @EnableKafka @EnableKafkaStreams public class KafkaConfig { @Value(value = "${spring.

Spring Boot 启动加速

1、简介 本文将带你了解如何通过调整 Spring 应用的配置、JVM 参数和使用 GraalVM 原生镜像来缩短 Spring Boot 的启动时间。 2、调整 Spring 应用 首先,创建一个 Spring Boot(2.5.4)应用,添加 Spring Web、Spring Actuator 和 Spring Security 依赖。 还要添加 spring-boot-maven-plugin 插件,并配置将应用打包到 jar 文件中: <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <configuration> <finalName>springStartupApp</finalName> <mainClass>com.baeldung.springStart.SpringStartApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> 使用标准的 java -jar 命令运行 jar 文件,并查看应用的启动时间。 c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.403 seconds (JVM running for 3.961) 如上,应用启动时间约为 3.4 秒。我们把这个时间作为下文调整的参考。 2.1、延迟初始化 Spring 支持延迟初始化。延迟初始化意味着 Spring 不会在启动时创建所有 Bean。此外,Spring 在需要 Bean 之前不会注入任何依赖。从 Spring Boot 2.