教程

在 Spring Security 中获取当前用户

1、概览 本文介绍了如何在 Spring Security 中检索当前用户详细信息。 2、从 Bean 中获取用户 检索当前已通过身份认证用户(Principal)的最简单方式是调用 SecurityContextHolder 的静态方法: Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String currentPrincipalName = authentication.getName(); 该代码需要改进的地方在于,在尝试访问之前,首先要检查是否存在已通过身份认证的用户: Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!(authentication instanceof AnonymousAuthenticationToken)) { String currentUserName = authentication.getName(); return currentUserName; } 当然,这样的静态调用也有缺点,代码的可测试性降低了。 3、在 Controller 中获取用户 在 @Controller 中,可以直接将 Principal 定义为方法参数,这样框架就能正确地解析: @Controller public class GetUserWithPrincipalController { @RequestMapping(value = "/username", method = RequestMethod.GET) @ResponseBody public String currentUserName(Principal principal) { return principal.getName(); } } 或者,也可以使用 Authentication Token: @Controller public class GetUserWithAuthenticationController { @RequestMapping(value = "/username", method = RequestMethod.

配置 Apache HttpClient 信任所有 SSL 证书

1、概览 本文将带你了解如何配置 Apache HttpClient 4 和 5 以支持 “Accept All”(接受所有)SSL。目标很简单 - 信任所有证书,包括无效的 SSL 证书。 2、SSLPeerUnverifiedException 如果不在 HttpClient 上配置 SSL,下面的测试(使用 HTTPS URL)就会失败: @Test void whenHttpsUrlIsConsumed_thenException() { String urlOverHttps = "https://localhost:8082/httpclient-simple"; HttpGet getMethod = new HttpGet(urlOverHttps); assertThrows(SSLPeerUnverifiedException.class, () -> { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpResponse response = httpClient.execute(getMethod, new CustomHttpClientResponseHandler()); assertThat(response.getCode(), equalTo(200)); }); } 确切的异常是: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:126) ... 如果无法为 URL 建立有效的信任链(Trust Chain),就会出现 javax.net.ssl.SSLPeerUnverifiedException 异常。 3、配置 SSL - Accept All(HttpClient 5) 现在,配置 HTTP 客户端信任所有证书链,无论其是否有效:

在 Spring 应用中为 REST API 实现异常处理

1、概览 本文将地带你了解如何在 Spring 中为 REST API 实现异常处理。 在 Spring 3.2 之前,在 Spring MVC 应用中处理异常的两种主要方法是 HandlerExceptionResolver 或 @ExceptionHandler 注解。这两种方法都有一些明显的缺点。 自 3.2 以来,可以使用 @ControllerAdvice 注解来解决前两种解决方案的局限性,并促进整个应用中统一的异常处理。 Spring 5 引入了 ResponseStatusException 类,一种在 REST API 中进行基本错误处理的快速方法。 所有这些都有一个共同点:它们都很好地处理了关注点的分离。应用通常可以抛出异常来表示某种失败,然后再单独进行处理。 2、解决方案 1:Controller 级的 @ExceptionHandler 第一种解决方案适用于 @Controller 层面。定义一个处理异常的方法,并用 @ExceptionHandler 进行注解: public class FooController{ //... @ExceptionHandler({ CustomException1.class, CustomException2.class }) public void handleException() { // } } 这种方法有一个很大的缺点:@ExceptionHandler 注解方法仅对特定 Controller 有效,而不是对整个应用全局有效。当然,可以将其添加到每个 Controller 中,但这并不适合作为通用的异常处理机制。 也可以通过让所有 Controller 都继承一个 Base Controller 类来绕过这一限制。 然而,对于某些原因无法实现上述方法的应用来说,这种解决方案可能会成为一个问题。例如,Controller 可能已经从另一个 Base 类继承而来,而该 Base 类可能在另一个 Jar 中或不可直接修改,或者 Controller 本身不可直接修改。

Spring 中的 @Scheduled 注解

1、概览 本文将带你了解如何使用 Spring @Scheduled 注解来配置和调度定时任务。 使用 @Scheduled 对方法进行注解时,需要遵循如下简单的规则: 方法的返回类型通常应为 void(如果不是,返回值将被忽略) 方法不应有任何参数 2、启用定时调度 可以在配置类上使用 @EnableScheduling 注解来启用 Spring 中的定时任务和 @Scheduled 注解的支持: @Configuration @EnableScheduling public class SpringConfig { ... } 也可以在 XML 中启用,如下: <task:annotation-driven> 3、以固定延迟调度任务 配置一个任务,使其在固定延迟后运行: @Scheduled(fixedDelay = 1000) public void scheduleFixedDelayTask() { System.out.println( "Fixed delay task - " + System.currentTimeMillis() / 1000); } 如上,上一次执行结束与下一次执行开始之间的持续时间是固定的。任务会一直等待到前一个任务结束。 在必须确保上一次执行完成后再次运行的情况下,应使用此选项。 4、以固定频率调度任务 在固定的时间间隔内执行一项任务: @Scheduled(fixedRate = 1000) public void scheduleFixedRateTask() { System.out.println( "Fixed rate task - " + System.currentTimeMillis() / 1000); } 如果任务的每次执行都是独立的,则应使用该选项。

Spring Data JPA @Query 注解中的 SpEL 支持

1、概览 SpEL 是 Spring Expression Language(Spring 表达式语言)的缩写,它是一种强大的工具,能显著增强与 Spring 的交互,并为配置、属性设置和查询操作提供额外的抽象。 本文将带你了解如何使用该工具使自定义查询更具动态性,通过 @Query 注解,可以使用 JPQL 或原生 SQL 来定制与数据库的交互。 2、访问参数 首先先看看如何使用 SpEL 处理方法参数。 2.1、通过索引访问 通过索引访问参数并不是最佳选择,因为这可能会给代码带来难以调试的问题。尤其是当参数类型相同时。 同时,这也提供了更大的灵活性,特别是在开发阶段,当参数的名称经常发生变化时。IDE 可能无法正确处理代码和查询的更新。 JDBC 提供了 ? 占位符,可以用它来确定参数在查询中的位置。Spring 支持这一约定,并允许以如下方式来访问参数: @Modifying @Transactional @Query(value = "INSERT INTO articles (id, title, content, language) " + "VALUES (?1, ?2, ?3, ?4)", nativeQuery = true) void saveWithPositionalArguments(Long id, String title, String content, String language); 到目前为止,使用的方法与之前在 JDBC 应用中使用的方法相同。注意,在数据库中进行更改的任何查询都需要 @Modifying 和 @Transactional 注解,INSERT 就是其中之一。所有 INSERT 的示例都将使用原生查询,因为 JPQL 不支持。

Spring Profile 指南

1、概览 本文将带你了解 Spring 中的 Profile(配置文件),这是 Spring 的核心功能之一。可以把 Bean 配置在不同的 Profile,例如:dev、test、prod。然后,可以在不同的环境中激活指定的 Profile,以便只加载当前环境所需的 Bean。 2、在 Bean 上使用 @Profile 从简单的开始,看看如何使用 @Profile 注解将 Bean 映射到特定的 Profile。 该注解接受一个(或多个) Profile 名称。 考虑一个场景:有一个 Bean,它只能在开发过程中激活,不能在生产过程中部署。 用 dev Profile 注解该 Bean,它只会在开发环境中被加载到容器。在生产环境中, dev Profile 不会被激活: @Component @Profile("dev") public class DevDatasourceConfig Profile 名称也可以用 NOT 运算符作为前缀,如 !dev,以将其从 Profile 排除。 在如下示例中,只有当 dev Profile 未激活时,组件才会被激活: @Component @Profile("!dev") public class DevDatasourceConfig 3、在 XML 中声明 Profile Profile 也可以用 XML 配置。<beans> 标签有一个 profile 属性,该属性包含以逗号分隔的 Profile 值: <beans profile="dev"> <bean id="devDatasourceConfig" class="org.

在 Spring Boot 中使用 Java Record

本文将带你了解如何在 Spring Boot 应用中利用 Java Record 来提高其效率和可读性。 Java Record 是什么? Java Record 是一种专为保存不可变数据而设计的类。它们自动提供 equals()、hashCode() 和 toString() 等方法的实现,大大减少了模板代码。因此,它们非常适合在 Spring Boot 应用中创建数据传输对象(DTO)、实体和其他模型类。 Java Record 示例 举一个简单的例子,用 Java Record 来表示一个 Person。 public record Person(String name, int age) {} 在本例中,Person 是一个 Record,包含两个字段:name 和 age。Java 自动为该 Record 提供了以下内容: 一个 public 构造函数:Person(String name, int age) 属性的 public Getter 方法:name() 和 age() 实现了 equals()、hashCode() 和 toString() 方法 使用 Getter 方法 Record 的主要特点之一是提供了用于访问字段的隐式 Getter 方法。这些方法以字段本身命名。 创建 Person 实例并使用其 Getter 方法:

Spring Bean 的生命周期

“讲一讲 Spring Bean 的生命周期”,这算是一道非常经典的面试题了! 如果没有研究过 Spring 源码,单纯去背面试题,这个问题也是可以回答出来的,但是单纯的背缺乏理解,而且面试一紧张,就容易背岔了。 在前面的文章中,给大家分析了 Spring 中 Bean 的创建是在 createBean 方法中完成的。在该方法中,真正干活的实际上是 doCreateBean 方法,具体位置在 AbstractAutowireCapableBeanFactory#doCreateBean,大家在面试时候常被问到的 Spring Bean 的生命周期,实际上就是问 doCreateBean 方法的执行逻辑。 doCreateBean 方法整体上来说,干了四件事: Bean 的实例化。 Bean 属性填充。 Bean 初始化。 Bean 销毁方法注册。 这里大家注意区分 实例化 和 初始化 两个方法,实例化是指通过反射创建出来 Bean 实例的过程,而初始化则是调用一些回调函数进行 Bean 的一些预处理。 1、实例化 // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.

Spring State Machine(状态机)入门

说起 Spring 状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring 状态机就是状态模式的一种实现,在介绍 Spring 状态机之前,让我们先来看看设计模式中的状态模式。 1、状态模式 状态模式的定义如下: 状态模式(State Pattern) 是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为。在状态模式中,一个对象的行为取决于其当前状态,而且可以随时改变这个状态。状态模式将对象的状态封装在不同的状态类中,从而使代码更加清晰和易于维护。当一个对象的状态改变时,状态模式会自动更新该对象的行为,而不需要在代码中手动进行判断和处理。 通常业务系统中会存在一些拥有状态的对象,而且这些状态之间可以进行转换,并且在不同的状态下会表现出不同的行为或者不同的功能,比如交通灯控制系统中会存在红灯、绿灯和黄灯,再比如订单系统中的订单会存在已下单、待支付、待发货、待收货等状态,这些状态会通过不同的行为进行相互转换,这时候在系统设计时就可以使用状态模式。 下面是 状态模式 的类图: 可以看到状态模式主要包含三种类型的角色: 上下文(Context):封装了状态的实例,负责维护状态实例,并将请求委托给当前的状态对象。 抽象状态(State):定义了表示不同状态的接口,并封装了该状态下的行为。所有具体状态都实现这个接口。 具体状态(Concrete State):具体实现了抽象状态角色的接口,并封装了该状态下的行为。 下面是使用状态模式实现 红绿灯 状态变更的一个简单案例: 抽象状态类: /** * @description: 抽象状态类 */ public abstract class MyState { abstract void handler(); } 具体状态类 A: /** * @description: 具体状态A */ public class RedLightState extends MyState{ @Override void handler() { System.out.println("红灯停"); } } 具体状态类 B: /** * @description: 具体状态B */ public class GreenLightState extends MyState{ @Override void handler() { System.

Spring Security - 角色和权限

1、概览 本文将带你了解如何在 Spring Security 中正确地实现 角色(Role) 和 权限(Privilege)。 2、用户、角色和权限 有如下 3 个实体: User:代表用户 Role:代表用户在系统中的高级角色。每个角色都有一组低级权限。 Privilege:代表系统中较低级别的、细粒度的特权/权限。 User 如下: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private String password; private boolean enabled; private boolean tokenExpired; @ManyToMany @JoinTable( name = "users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collection<Role> roles; } 如上,用户包含角色和一些额外的细节,这些细节对于适当的注册机制来说是必要的。