Part 1: 理论基础篇 (Foundation)
场景再现
业务痛点
传统的业务系统中,业务操作产生的相关记录的可信度并不高。业务操作方有很大的空间可以自己捏造改造假的数据,另外就算依靠第三方机构,也并不安全,非常考验第三方机构本身的专业性,以及可信度。
这两种方式都有一个特点,就是真实性非常依赖一方。为了解决这个痛点,去中心化的方案应运而生。
传统方案的局限性
- 业务系统拥有方/操作方捏造/修改数据非常容易
- 第三方机构可能存在可信度,以及数据安全性低的问题
传统的业务系统中,业务操作产生的相关记录的可信度并不高。业务操作方有很大的空间可以自己捏造改造假的数据,另外就算依靠第三方机构,也并不安全,非常考验第三方机构本身的专业性,以及可信度。
这两种方式都有一个特点,就是真实性非常依赖一方。为了解决这个痛点,去中心化的方案应运而生。
这里将记录我在开发过程中,遇到的一些小的盲点。待我有时间的时候,便会解决它们。
提示
不要过度抽取方法 DO NOT OVER-EXTRACT METHODS
在实际的开开发项目中,我们可能只需要某些属性是注入的,新建一个属性类显得又有点多余了。在一般情况下我们都会使用 Spring 的 @Value 注解,以实现配置注入的效果。虽然很好用,但是它是项目启动时一次性注入的,而不能随配置的变化而变化。
这篇文章我们将从 Spring 源码开始梳理,一步一步实现一个简单的动态配置刷新。
AccessibleObject (父类)
├── Field (字段)
├── Method (方法)
└── Constructor (构造函数)
动态配置刷新的重要性不言而喻,解决方案也有很多,比如 Nacos。有了它们,我们可以非常方便的实现配置修改,而不用重新重启服务,大大提高了配置效率。但是也不是所有的项目都需要这么重的服务,而且一个系统如果引入越多外部依赖,就越不可控。所以我们可以自己手动实现一个动态配置刷新,以实现更高的灵活性(同时也是一个学习的好机会)
ThreadLocal 本质上是一个线程隔离的变量存储机制,它的核心实现主要依赖以下几个关键部分:
ThreadLocal 对象本身不存储值,而是作为一个 key 来访问线程中的ThreadLocalMap
Thread 类中有一个 ThreadLocalMap 类型的成员变量 threadLocals:
class Thread {
ThreadLocalMap threadLocals = null;
//...
}
在现代 Web 应用开发中,请求处理流程的复杂性日益增长。从简单的 CRUD 操作到复杂的业务逻辑处理,每一个 HTTP 请求都需要经历多个处理层次:跨域处理、身份认证、权限校验、参数验证、业务逻辑执行、日志记录等。这些横切关注点的处理通常依赖于 Spring 框架提供的过滤器(Filter)和拦截器(Interceptor)机制。
然而,在实际开发过程中,我们经常遇到这样的困境:过滤器虽然执行得最早,但无法获取到控制器的详细信息;拦截器能够访问 HandlerMethod 等丰富的上下文,却无法将这些信息有效地传递回过滤器层进行统一处理。这种单向的信息流动限制了我们在架构设计上的灵活性。
BOM(Bill of Materials) 是 Maven 中的一种依赖管理机制,主要用于集中管理项目中所有依赖的版本号。它的核心作用是确保项目中的依赖版本一致,避免因版本冲突导致的问题。
在 Maven 中,BOM 是一个特殊的 POM 文件,通常以 <packaging>pom</packaging>
的形式存在,并通过 <dependencyManagement>
定义一组依赖及其版本号。其他模块可以通过 import 的方式引入这个 BOM,从而继承其中定义的依赖版本。
WebSocket 协议:
STOMP 协议:
@EnableWebSocketMessageBroker
和 WebSocketMessageBrokerConfigurer
配置了 STOMP 协议的支持。// 共用的实体类和Mapper
@Data
public class UserFoot {
...
}
public interface UserFootMapper extends BaseMapper<UserFoot> {
}
// 方式1:直接继承IService方式
public interface UserFootService extends IService<UserFoot> {
// 自定义业务方法
List<UserFoot> getUserFootprints(Long userId);
}
@Service
public class UserFootServiceImpl extends ServiceImpl<UserFootMapper, UserFoot>
implements UserFootService {
// 可以注入其他Mapper或Service
@Autowired
private UserMapper userMapper;
@Override
public List<UserFoot> getUserFootprints(Long userId) {
// 直接使用继承的方法
return this.list(new LambdaQueryWrapper<UserFoot>()
.eq(UserFoot::getUserId, userId)
.orderByDesc(UserFoot::getCreateTime));
}
}
// 方式2:组合Dao方式
public class UserFootDao extends ServiceImpl<UserFootMapper, UserFoot> {
// 在Dao层封装数据库操作
public List<UserFoot> findByUserId(Long userId) {
return list(new LambdaQueryWrapper<UserFoot>()
.eq(UserFoot::getUserId, userId)
.orderByDesc(UserFoot::getCreateTime));
}
}
public interface UserFootService {
// 定义业务方法
List<UserFoot> getUserFootprints(Long userId);
}
@Service
public class UserFootServiceImpl implements UserFootService {
private final UserFootDao userFootDao;
private final UserMapper userMapper; // 同样可以注入其他依赖
@Autowired
public UserFootServiceImpl(UserFootDao userFootDao, UserMapper userMapper) {
this.userFootDao = userFootDao;
this.userMapper = userMapper;
}
@Override
public List<UserFoot> getUserFootprints(Long userId) {
// 通过dao进行数据操作
return userFootDao.findByUserId(userId);
}
}
[!tips]
spring boot 2.4 之后为了提升对 kubernetes 的支持,整个 spring.profiles 作废了;因此我们可以通过 spring.config.import 来代替它
java -jar your-application.jar --spring.profiles.active=dev,biz
右侧的配置会覆盖左侧的配置所以 biz 配置会覆盖 dev 配置两个 profile 的配置都会覆盖默认配置文件中的值