编程上下文Context及其实现原理
author:shengfq
date:2024-07-06
title:编程上下文Context及其实现原理
category:编程思想
1.编程中的上下文Context是指什么?
在编程和软件工程领域,"上下文"(Context)是一个多义词,其含义可以根据不同的技术和框架有所变化,但总体上,上下文是指程序执行的环境或状态,它包含了影响程序行为的各种因素。下面是一些编程上下文的主要概念和应用场景:
- 线程上下文(Thread Context)
在多线程编程中,上下文通常指一个线程的执行环境,包括CPU寄存器的值、堆栈状态、线程本地存储(TLS,Thread Local Storage)等。当操作系统或运行时环境在多个线程之间切换时,会保存当前线程的上下文,并恢复下一个线程的上下文。 - 函数调用上下文
当一个函数被调用时,会创建一个调用上下文,其中包含函数的参数、局部变量和函数体。这个上下文在函数执行期间保持有效,直到函数返回,上下文被销毁。 - 作用域上下文
作用域上下文定义了变量的可见性和生命周期。在不同的编程语言中,作用域规则可能有所不同,但通常包括全局作用域、局部作用域、类作用域等。 - 安全上下文(Security Context)
在安全相关的编程中,上下文可能包含用户认证信息、权限和角色等。例如,在Spring Security中,SecurityContextHolder用于管理当前线程的安全上下文。 - 网络请求上下文
在网络编程中,上下文可能包含请求和响应的详细信息,如HTTP头部、请求体、状态码等。 - 配置上下文
应用程序运行所需的配置参数,如数据库连接字符串、API密钥、环境变量等,共同构成了配置上下文。 - 事务上下文
在数据库编程中,事务上下文包含了事务的状态、隔离级别、回滚点等信息,它对于确保数据的一致性和完整性至关重要。 - 微服务上下文
在微服务架构中,上下文可能包含服务间通信的追踪ID、请求的元数据等,用于调试和监控。 - 上下文切换(Context Switching)
上下文切换是指操作系统在不同进程或线程之间切换执行环境的过程。这通常涉及到保存和恢复CPU寄存器、堆栈和其他状态,是影响系统性能的关键因素之一。 - 函数式编程上下文
在函数式编程中,上下文可能指的是闭包(Closure)的概念,即函数可以访问并记住其定义时存在的外部变量。 - 框架和库提供的上下文
许多框架和库提供了自己的上下文概念,如Spring框架的ApplicationContext,它管理着bean的生命周期和依赖注入。
总的来说,编程上下文是理解程序行为的关键,它帮助开发者追踪和控制程序执行的不同方面。掌握上下文的概念对于编写健壮、可维护的代码至关重要。
2.编程实践中常见的Context分类,主要作用?
例如spring框架的ApplicationContext,spring WEB框架的SecurityContext,用户自定义的需要获取会话环境数据的应用场景,如:HTTP请求的会话管理,数据库连接池的事务管理,用户登录访问服务授权管理,这些场景基本都是独立线程隔离级别的会话数据,每个线程都拥有独立的变量副本,不会干扰.
3.案例SecurityContext是怎么应用的?
使用SecurityContextHolder获取当前用户信息
在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。一般情况下,我们都不需要创建这个对象,在登录过程中,Spring Security已经创建了该对象并帮我们放到了SecurityContextHolder中。从SecurityContextHolder中获取这个对象也是很简单的。比如,获取当前登录用户的用户名,可以这
类结构:
SecurityContextHolder //SecurityContext bean工厂
SecurityContext //上下文实例
Authentication //认证信息
UserDetails //用户身份
SecurityContextHolderStrategy getContext() //接口 获取上下文对象
ThreadLocalSecurityContextHolderStrategy //实现类 提供存储在ThreadLocal中的SecurityContext
InheritableThreadLocalSecurityContextHolderStrategy //提供子线程可继承访问的存储在ThreadLocal中的SecurityContext
// 获取安全上下文对象SecurityContext,就是那个保存在 ThreadLocal 里面的安全上下文对象,通过对
// 总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)
SecurityContext securityContext = SecurityContextHolder.getContext();
// 获取当前认证了的 principal(当事人),或者 request token (令牌)
// 如果没有认证,会是 null,该例子是认证之后的情况
Authentication authentication = securityContext.getAuthentication()
// 获取当事人信息对象,返回结果是 Object 类型,但实际上可以是应用程序自定义的带有更多应用相关信息的某个类型。
// 很多情况下,该对象是 Spring Security 核心接口 UserDetails 的一个实现类,你可以把 UserDetails 想像
// 成我们数据库中保存的一个用户信息到 SecurityContextHolder 中 Spring Security 需要的用户信息格式的
// 一个适配器。
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
每个线程都有一个独立的Security Context,作为环境变量保存到当前线程.
认证与授权
Authentication对象会被填充并放置到SecurityContextHolder中
访问和修改
Authentication =SecurityContextHolder.getContext().getAuthentication()
SecurityContextHolder.getContext().setAuthentication(Authentication)
清理上下文
SecurityContextHolder.clearContext()
ThreadLocal<SecurityContext>.remove()
4.SecurityContext实现原理ThreadLocal详解?
ThreadLocal 是 Java 中的一个类,用于在多线程环境下为每个线程提供独立的变量副本。它可以解决多线程并发访问共享变量时的线程安全问题。
在多线程应用程序中,多个线程可能同时访问同一个变量,如果没有适当的同步机制,就会导致数据的不一致性和竞态条件。ThreadLocal 提供了一种线程级别的变量隔离机制,使得每个线程都拥有自己独立的变量副本,互不干扰。
ThreadLocal 类提供了以下常用方法
get():获取当前线程的 ThreadLocal 变量的值。如果变量尚未被当前线程设置,则返回 null。
set(T value):设置当前线程的 ThreadLocal 变量的值为指定的值。
remove():移除当前线程的 ThreadLocal 变量。清除后,下次调用 get() 方法将返回 null。
initialValue():返回 ThreadLocal 的初始值。可以通过继承 ThreadLocal 并覆盖该方法来自定义初始值。
withInitial(Supplier<? extends T> supplier):使用指定的 Supplier 函数式接口提供的初始值创建一个 ThreadLocal 实例。
ThreadLocal 的这些方法提供了对线程局部变量的管理和访问。你可以使用 get() 和 set() 方法在当前线程中存储和获取变量的值,使用 remove() 方法清除变量,使用 initialValue() 方法自定义初始值,以及使用 withInitial() 方法创建具有自定义初始值的 ThreadLocal 实例。
ThreadLocal 使用案例
//线程安全的计数器
public class ThreadSafeCounter {
private static ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);
public static void increment() {
counter.set(counter.get() + 1);
}
public static int getCount() {
return counter.get();
}
}
线程上下文传递
public class UserContext {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
}
数据库连接管理
public class DBConnectionManager {
private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
public static void openConnection() {
// 获取数据库连接并设置到 ThreadLocal
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
connectionThreadLocal.set(connection);
}
public static Connection getConnection() {
return connectionThreadLocal.get();
}
public static void closeConnection() {
// 关闭数据库连接
Connection connection = connectionThreadLocal.get();
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// 异常处理
}
}
// 清理 ThreadLocal 变量
connectionThreadLocal.remove();
}
}
ThreadLocal 内存泄漏
ThreadLocal 是 Java 中的一个类,它提供了一个线程局部变量。这些变量不同于普通的变量,因为每个线程都有它自己的副本,而且它们之间互不影响。ThreadLocal 的目的是提供一个方便的方式来保存那些每个线程需要独立访问的数据。
内存泄漏通常指的是程序在释放了某个对象后,后续代码仍然可以访问这个对象。在使用 ThreadLocal 时,如果没有正确地移除对应的线程局部变量,可能会导致内存泄漏。因为一旦 ThreadLocal 的线程被回收,其关联的线程局部变量如果没有手动移除,那么下次当这个线程再次使用 ThreadLocal 时,它将持有一个过期的引用。
解决方法:
在使用完 ThreadLocal 后,手动调用 remove() 方法来清除线程局部变量。
如果使用的是 Java 8 或更高版本,可以利用 ThreadLocal 的新特性(目前是 preview 功能),即在 get 或 set 方法后自动清除线程局部变量。
如果是在 web 应用中,可以在请求处理完毕后的 Filter 中清理 ThreadLocal。
使用 ThreadLocal 的包装类,它们在每次 get 后自动清除 ThreadLocal 中的值。
如果使用了第三方库,检查是否有相关的工具类或注解来自动管理 ThreadLocal 的生命周期。
5.如何确定什么场景适合使用ThreadLocal实现的Context,并正确的使用
在生产实践中,我们要判断是否适合使用Context及ThreadLocal,主要判断依据有:
1.他是以空间换时间的方式实现,在高并发、高性能要求的场景下,频繁的同步操作会导致性能下降。使用 ThreadLocal 可以减少锁的使用,提高程序的并发性能。
2.每个线程都有一个独立的变量副本,需要快捷方便的从上下文中获取变量数据.可考虑使用.
那么,应用开发中,一些场景如:日志追踪,方法间共享信息,事务管理,会话信息,授权信息都可以采用上下文机制实现.
参考文档
ThreadLocal 线程局部变量
Spring Security 的基本组件 SecurityContextHolder