1、Spring Bean 和 Java 堆内存 Java 堆是一个全局共享内存,应用中的所有运行线程都可以访问它。当 Spring 容器创建 Singleton Scope Bean 时,该 Bean 将存储在堆中。这样,所有并发线程都能指向同一个 Bean 实例。
2、如何处理并发请求? 举例来说,Spring 应用中有一个名为 ProductService 的单例 Bean:
@Service public class ProductService { private final static List<Product> productRepository = asList( new Product(1, "Product 1", new Stock(100)), new Product(2, "Product 2", new Stock(50)) ); public Optional<Product> getProductById(int id) { Optional<Product> product = productRepository.stream() .filter(p -> p.getId() == id) .findFirst(); String productName = product.map(Product::getName) .orElse(null); System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().
1、概览 在 Spring Cloud 中可以通过配置文件来启用、禁用服务发现,而不需要改动代码。
2、 设置 Eureka Server 和 Eureka Client 先创建一个 Eureka 服务器和一个 Discovery Client。
Eureka 服务创建过程,略!
2.1、Discovery Client 设置 创建 “Discovery Client” 应用,在 Eureka Server 上进行注册。
在 pom.xml 中添加 Web 和 Eureka Client starter 依赖:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> 还需要在 dependencyManagement 中添加 spring-cloud-starter-parent 依赖,用于定义 cloud 组件的版本。
如果使用 Spring Initializr 创建项目,这些参数已经设置好了。如果没有,可以手动添加到 pom.xml 文件中:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-parent</artifactId> <version>${spring-cloud-dependencies.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <properties> <spring-cloud-dependencies.
1、简介 本文将带你了解 spring-boot-properties-migrator 模块,它是 Spring 为促进 Spring Boot 升级而提供的支持。用于帮助迁移 application properties。
随着每个 Spring Boot 版本的升级,可能会有一些属性被标记为已弃用、不再支持或新引入的属性。Spring 为每个升级发布了详细的 变更日志。然而,阅读这些变更日志可能会有些繁琐。这就该 spring-boot-properties-migrator 出场了,它通过为我们的设置提供个性化的信息来帮助我们进行属性迁移。
让我们来看看如何使用。
2、Demo 应用 把 Spring Boot 应用从 2.3.0 升级到 2.6.3。
2.1、Properties 在演示应用中,有两个 properties 文件。在默认 properties 文件 application.properties 中,添加如下配置:
spring.resources.cache.period=31536000 spring.resources.chain.compressed=false spring.resources.chain.html-application-cache=false dev Profile YAML 文件 application-dev.yaml:
spring: resources: cache: period: 31536000 chain: compressed: true html-application-cache: true properties 文件包含了几个在 Spring Boot 2.3.0 和 2.6.3 之间被替换或移除的属性。
同时提供了 .properties 和 .yaml 文件,以便更好地进行演示。
2.2、添加依赖 添加 spring-boot-properties-migrator 模块。
1、概览 本文通过一个示例来带你了解如何处理 Spring Security Resource Server 产生的 Spring Security 异常。
2、Spring Security Spring Security 是 Spring 的一个子项目。它试图将 Spring 项目中的所有用户访问控制功能进行整合。访问控制允许限制特定用户或角色在应用中可以执行的选项。
在本例中,我们重点关注 Exception Handler 的配置。Spring Security 提供了三种不同的接口来实现这一目的并控制产生的事件:
Authentication Success(认证成功)Handler Authentication Failure(认证失败)Handler Access Denied(拒绝访问)Handler 3、Security Configuration 首先,配置类必须创建一个 SecurityFilterChain Bean。它将负责管理应用的所有安全配置。因此,我们必须在这里引入 Handler。
定义所需的配置:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf() .disable() .httpBasic() .disable() .authorizeRequests() .antMatchers("/login") .permitAll() .antMatchers("/customError") .permitAll() .antMatchers("/access-denied") .permitAll() .antMatchers("/secured") .hasRole("ADMIN") .anyRequest() .authenticated() .and() .formLogin() .failureHandler(authenticationFailureHandler()) .successHandler(authenticationSuccessHandler()) .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()) .and() .logout(); return http.
1、概览 本文将带你了解如何在 Spring Boot 中处理 GraphQL 异常,以及 GraphQL 规范中关于错误响应的规定。
2、根据 GraphQL 规范进行响应 根据 GraphQL 规范,每个接收到的请求都必须返回一个格式良好的响应。这个格式良好的响应是一个 Map,包含了来自相应成功或失败的请求操作的数据或错误。此外,响应可能包含部分成功的结果数据和字段错误。
响应 Map 的关键 key 是 errors、data 和 extensions。
响应中的 errors 描述了请求操作过程中出现的任何异常、错误。如果没有错误发生,则响应中不得出现 errors 字段。接下来,我们将了解规范中描述的不同类型的错误。
data 字段描述了成功执行所请求操作的结果。如果操作是 Query,该字段就是 Query Root Operation 类型的对象。如果操作是 Mutation,则该字段是 Mutation Root Operation 类型的对象。
如果请求的操作在执行前就因信息缺失、验证错误或语法错误而失败,那么响应中就必须没有 data 字段。如果操作在执行过程中失败,且结果不成功,则 data 必须为 null。
响应 map 可能包含一个名为 extensions 的附加字段,它是一个 map 对象。该字段便于实现者在响应中提供他们认为合适的其他自定义内容。因此,对其内容格式没有额外的限制。
如果响应中没有 data 字段,则必须有 errors 字段,并且必须至少包含一个 error。此外,它还应说明失败的原因。
下面是一个 GraphQL 错误示例:
mutation { addVehicle(vin: "NDXT155NDFTV59834", year: 2021, make: "Toyota", model: "Camry", trim: "XLE", location: {zipcode: "75024", city: "Dallas", state: "TX"}) { vin year make model trim } } 当违反唯一性约束时,错误响应如下所示:
1、概览 本文将带你了解如何在 Spring 中获取到所有带自定义注解的 Bean。
不同的 Spring 版本,实现方式也不同。
2、使用 Spring Boot 2.2 或更高版本 从 Spring Boot 2.2 起,可以使用 getBeansWithAnnotation 方法。
创建一个示例。首先,定义自定义注解。注意,要使用 @Retention(RetentionPolicy.RUNTIME) 元注解,以确保程序在运行时可以访问注解:
@Retention(RetentionPolicy.RUNTIME) public @interface MyCustomAnnotation { } 现在,用注解定义第一个 Bean(还要添加 @Component 注解):
@Component @MyCustomAnnotation public class MyComponent { } 然后,定义另一个带有注解的 Bean。不过,这次通过 @Configuration 中的 @Bean 方法来创建:
public class MyService { } @Configuration public class MyConfigurationBean { @Bean @MyCustomAnnotation MyService myService() { return new MyService(); } } 现在,写一个测试,测试 getBeansWithAnnotation 方法能否获取到上述两个 Bean:
1、简介 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端,它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应,这意味着它会等待每个请求完成后才继续下一个请求。
本文将带你了解 RestClient 的功能以及它与 RestTemplate 的比较。
2、RestClient 和 RestTemplate RestTemplate,顾名思义,是基于模板设计模式构建的。它是一种行为设计模式,用于在方法中定义算法的框架,允许子类为某些步骤提供特定的实现。虽然这是一种强大的模式,但它会导致需要进行方法覆写,这可能是不方便的。
为了改进这一点,RestClient 采用了 Fluent API。Fluent API 也是一种设计模式,它允许以一种使代码更易读和表达的方式进行方法链式调用,通常无需使用中间变量。
从创建一个基本的 RestClient 开始:
RestClient restClient = RestClient.create(); 3、使用各种 HTTP 方法发起请求 与 RestTemplate 或其他 REST 客户端类似,RestClient 可以使用不同的 HTTP 方法发起请求。
创建一个用于操作的 Article 类:
public class Article { Integer id; String title; // 构造函数和 getter/setter } 3.1、使用 GET 请求获取资源 GET 请求用于检索 Web 服务器上指定资源的数据,而不对其进行修改。通常,这是一个只读的操作!
获取服务器响应的字符串(String):
对于文件的下载功能来说,我归纳为 2 头 1 流。极其简单。
Content-Type 头,告诉客户端文件类型。 Content-Disposition 头,告诉客户端对于这个文件的处理方式(在浏览器中显示,还是下载)。 Output 流,写入文件内容到客户端。 文件名称乱码的问题 Content-Disposition 头有三个属性值,可以指定文件的名称。
name filename filename* 至于区别,我也说不大清楚。反正是有个规范,我感觉很乱。具体可以参考如下链接:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
这就是容易出问题的地方了,由于文件名称设置方法不正确。或者是浏览器不兼容问题,如果 文件名称包含中文,可能会导致 下载文件名称乱码:
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + "中文の文件.txt"); 如上,直接用 + 拼接中文字符串,在谷歌浏览器中下载就是乱码。
常见的解决方案 以下我是在网上看到的一些解决方案。
对文件名称进行URI编码
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode("中文の文件.txt", StandardCharsets.UTF_8)); 修改字符编码
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + new String("中文の文件.txt".getBytes("GBK"),"ISO-8859-1")); 经过测试,上面2种办法都OK。
比较优雅的方式 Spring 提供了一个工具类: org.springframework.http.ContentDisposition,专门用于处理文件下载中的 Content-Disposition Header,它会自动处理好中文文件名称的编码问题。
ContentDisposition 使用实在是太简单了,这里就不多说了,给一个完整的使用示例就能看明白。
package io.springboot.demo.web.controller; import java.io.IOException; import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletResponse; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.GetMapping; import org.
1、简介 在容器化大行其道的今天,越来越多人选择把 Spring Boot 应用部署到 Docker 中。
本文将带你了解在 Docker 容器中启动 Spring Boot 应用时,如何设置其 Profile。
2、基本的 Dockerfile 一般来说,要容器化 Spring Boot 应用,只需提供一个 Dockerfile。
Spring Boot 应用的最小 Dockerfile,如下:
FROM openjdk:11 COPY target/*.jar app.jar ENTRYPOINT ["java", "-jar", "/app.jar"] 然后,可以通过 docker build 来构建 Docker 镜像:
docker build --tag=docker-with-spring-profile:latest . 接着,我们就可以通过 docker-with-spring-profile 镜像运行应用了:
docker run docker-with-spring-profile:latest 你可以注意到,Spring Boot 应用以 “default” Profile 启动:
2022-04-22 22:34:25.268 INFO 1 --- [main] c.b.docker.spring.DemoApplication: Starting DemoApplication using Java 11.0.14.1 on ea8851bea75f with PID 1 (/app.
简介 本文将带你了解,当无法依赖 CascadeType 机制来将状态转换从父实体传播到子实体时,如何使用 Spring Data JPA 级联删除单向关联,
Domain Model 假设我们的系统中有以下实体:
Post 实体是该实体层次结构的根(root),子实体只使用单向 @ManyToOne 或 @OneToOne 关联,用于映射一个一对多或一对一表关系的底层外键。
Post root 实体如下:
@Entity @Table(name = "post") public class Post { @Id private Long id; private String title; // getter/setter 方法省略 } 请注意,没有双向的 @OneToMany 或 @OneToOne 关联,可以允许我们从父级 Post 级联 DELETE 操作到 PostComment、PostDetails 或 PostTag 子实体。
PostComment 实体使用单向 @ManyToOne 关联映射 post_id 外键列:
@Entity @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.