Spring源码--关于AccessController.doPrivileged

本文深入探讨Spring中AccessController.doPrivileged的使用,涉及Java安全模型的场景、SecurityManager、AccessController的概念和方法。文章详细解释了代码源、权限、策略和保护域,并分析了checkPermission和doPrivileged在权限控制中的角色。通过对Spring源码的分析,帮助读者理解如何在Spring框架中实现安全的权限管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Spring里发现一段代码,位置在,
DefaultListableBeanFactory -> preInstantiateSingletons()方法里。
如下:

if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
        public Boolean run() {
            return ((SmartFactoryBean<?>) factory).isEagerInit();
        }
    }, getAccessControlContext());
}

对于这个AccessController.doPrivileged,表示完全不懂是怎么回事。现在具体分析一下。

场景

当一个代码片段加载到现有的系统中,如果不对这个代码加以访问控制,那么它极有可能对当前系统安全造成破坏。比如这段代码会偷偷读取磁盘上的所有文件,然后保存到另外一台服务器上,造成机密信息泄露。
最常见的就是applet程序,它会被限制在一个沙箱里运行。或者导入其他jar包到系统,而这个jar包中的程序也有可能是恶意的。
所以,如果对这些代码进行权限控制是理所当然的。

安全管理器SecurityManager

作用是用于检查代码执行权限。其实日常的很多API都涉及到安全管理器,它的工作原理一般是:
1. 请求Java API
2. Java API使用安全管理器判断许可权限
3. 通过则顺序执行,否则抛出一个Exception

经典的FileInputStream,会调用SecurityManager的checkRead方法,用于检查是否有读取某个文件的权限。

public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    this.path = name;
    open(name);
}

SecurityManager里的方法列表如下,

checkAccept(String, int)
checkAccess(Thread)
checkAccess(ThreadGroup)
checkAwtEventQueueAccess()
checkConnect(String, int)
checkConnect(String, int, Object)
checkCreateClassLoader()
checkDelete(String)
checkExec(String)
checkExit(int)
checkLink(String)
checkListen(int)
checkMemberAccess(Class<?>, int)
checkMulticast(InetAddress)
checkMulticast(InetAddress, byte)
checkPackageAccess(String)
checkPackageDefinition(String)
checkPermission(Permission)
checkPermission(Permission, Object)
checkPrintJobAccess()
checkPropertiesAccess()
checkPropertyAccess(String)
checkRead(FileDescriptor)
checkRead(String)
checkRead(String, Object)
checkSecurityAccess(String)
checkSetFactory()
checkSystemClipboardAccess()
checkTopLevelWindow(Object)
checkWrite(FileDescriptor)
checkWrite(String)

都是check方法,分别囊括了文件的读写删除和执行、网络的连接和监听、线程的访问、以及其他包括打印机剪贴板等系统功能。而这些check代码也基本横叉到了所有的核心Java API上。

访问控制器AccessController

上面的SecurityManager都是基于AccessController实现的,还是继续上面的FileInputStream。
security.checkRead()方法的细节,

public void checkRead(String file) {
    checkPermission(new FilePermission(file,
        SecurityConstants.FILE_READ_ACTION));
}

checkPermission()的细节,

public void checkPermission(Permission perm) {
    java.security.AccessController.checkPermission(perm);
}

可以看出,SecurityManager会调用AccessController里的方法。

概念


需要理解4个概念:代码源、权限、策略和保护域。


代码源CodeSource

CodeSource就是一个简单的类,用来声明从哪里加载类。


权限Permission

Permission类是AccessController处理的基本实体。Permission类本身是抽象的,它的一个实例代表一个具体的权限。权限有两个作用,一个是允许Java API完成对某些资源的访问。另一个是可以为自定义权限提供一个范本。权限包含了权限类型、权限名和一组权限操作。具体可以看看BasicPermission类的代码。典型的也可以参看FilePermission的实现。


策略Policy

策略是一组权限的总称,用于确定权限应该用于哪些代码源。话说回来,代码源标识了类的来源,权限声明了具体的限制。那么策略就是将二者联系起来,策略类Policy主要的方法就是getPermissions(CodeSource)和refresh()方法。Policy类在老版本中是abstract的,且这两个方法也是。在jdk1.8中已经不再有abstract方法。这两个方法也都有了默认实现。

在JVM中,任何情况下只能安装一个策略类的实例。安装策略类可以通过Policy.setPolicy()方法来进行,也可以通过java.security文件里的policy.provider=sun.security.provider.PolicyFile来进行。jdk1.6以后,Policy引入了PolicySpi,后续的扩展基于SPI进行。


保护域ProtectionDomain

保护域可以理解为代码源和相应权限的一个组合。表示指派给一个代码源的所有权限。看概念,感觉和策略很像,其实策略要比这个大一点,保护域是一个代码源的一组权限,而策略是所有的代码源对应的所有的权限的关系。

JVM中的每一个类都一定属于且仅属于一个保护域,这由ClassLoader在define class的时候决定。但不是每个ClassLoader都有相应的保护域,核心Java API的ClassLoader就没有指定保护域,可以理解为属于系统保护域。

使用方法


这是一个无法实例化的类——仅仅可以使用其static方法。

checkPermission

AccessController最重要的方法就是checkPermission()方法,作用是基于已经安装的Policy对象,能否得到某个权限。

doPrivileged

还有一个方法,就是doPrivileged()方法,获取特权,用于绕过权限检查。

参考

Java安全——安全管理器、访问控制器和类装载器
Java 授权内幕

<think>嗯,用户问的是Spring中FactoryBean和SmartFactoryBean的初始化逻辑,特别是isEagerInit方法的调用流程。首先,我得回忆一下Spring处理Bean初始化的流程。用户提到的引用里提到,FactoryBean在容器启动时会被处理,但具体怎么初始化的呢?特别是SmartFactoryBean的isEagerInit方法什么时候被调用。 首先,FactoryBean本身是用来创建复杂对象的,用户可能知道普通的FactoryBean需要调用getObject来获取Bean实例。而SmartFactoryBean是它的扩展,添加了像isEagerInit这样的方法,用于控制是否在容器启动时就初始化。这时候,我需要确认Spring在初始化阶段是如何处理这两种Bean的。 根据引用[4],工厂Bean在容器启动时保存的是工厂本身,第一次获取组件时会调用getObject方法。那SmartFactoryBean的isEagerInit方法可能在容器启动时就被调用来决定是否需要提前初始化。比如,如果isEagerInit返回true,Spring会在容器刷新阶段就初始化这个Bean,而不是等到第一次请求的时候。 接下来,我需要梳理调用流程。可能涉及到BeanFactory的预处理,比如在AbstractApplicationContext的refresh方法里,会调用finishBeanFactoryInitialization方法,这里会初始化所有非懒加载的单例Bean。对于SmartFactoryBean,可能在判断是否需要立即初始化时会检查isEagerInit方法。 引用[3]提到getBean方法最终会调用doGetBean,而FactoryBean的处理在getObjectForBeanInstance方法中。但SmartFactoryBean的isEagerInit可能是在BeanDefinition加载阶段就被处理,或者在预实例化单例的时候。 另外,引用[5]指出FactoryBean的getObject方法返回的对象会被缓存,而SmartFactoryBean的eagerInit属性可能影响这个缓存的时间点。如果isEagerInit返回true,可能在容器启动时就调用getObject,并缓存到factoryBeanObjectCache中,而不是等到第一次请求。 可能需要查看Spring源码中的相关部分,比如在DefaultListableBeanFactory的preInstantiateSingletons方法里,遍历所有Bean定义,如果是FactoryBean,并且是SmartFactoryBean且isEagerInit为true,则立即调用getBean,触发getObject的执行,提前初始化。 总结起来,流程大概是:在容器启动阶段初始化单例Bean时,检查如果是SmartFactoryBean,并且isEagerInit返回true,就会立即初始化其getObject返回的对象。而普通FactoryBean则是延迟到第一次getBean时才初始化。</think>### Spring框架中FactoryBean与SmartFactoryBean初始化逻辑解析 #### 一、FactoryBean的基础初始化流程 1. **容器启动阶段** Spring容器初始化时,所有$FactoryBean$的实现类会被注册为**工厂对象**而非目标对象。例如,$TestFactoryBean$在容器中保存的是工厂实例本身,而非$getObject()$返回的对象[^2][^4]。 2. **首次获取目标对象** 当调用$getBean("test")$时,Spring通过以下步骤处理: - 检查缓存$singletonObjects$和$factoryBeanObjectCache$[^4] - 若未命中缓存,调用$FactoryBean.getObject()$创建目标对象 - 将目标对象存入$factoryBeanObjectCache$并返回[^4] ```java // AbstractBeanFactory.getObjectForBeanInstance() 核心逻辑 if (beanInstance instanceof FactoryBean) { Object object = getCachedObjectForFactoryBean(beanName); if (object == null) { object = ((FactoryBean<?>) beanInstance).getObject(); storeCachedObjectForFactoryBean(beanName, object); } return object; } ``` #### 二、SmartFactoryBean的特殊处理 $SmartFactoryBean$继承自$FactoryBean$,新增了三个方法: ```java public interface SmartFactoryBean<T> extends FactoryBean<T> { boolean isPrototype(); // 是否原型模式 boolean isEagerInit(); // 是否立即初始化 boolean isSingleton(); // 是否单例模式 } ``` **核心差异点**: 1. **$isEagerInit()$调用时机** 在Spring容器**完成Bean定义加载**后,通过$DefaultListableBeanFactory.preInstantiateSingletons()$方法触发: ```java // DefaultListableBeanFactory.preInstantiateSingletons() for (String beanName : beanNames) { if (isSmartFactoryBean(beanName)) { SmartFactoryBean<?> smartFactoryBean = (SmartFactoryBean<?>) getBean(beanName); if (smartFactoryBean.isEagerInit()) { getBean(beanName); // 强制触发目标对象初始化 } } } ``` - 该方法在$ApplicationContext$刷新阶段执行[^4] - 返回$true$时,目标对象会在容器启动阶段立即初始化,而非延迟到首次请求时[^5] 2. **与普通FactoryBean的对比** | 特性 | FactoryBean | SmartFactoryBean | |--------------------|-------------------|----------------------| | 初始化触发时机 | 首次$getBean()$时 | 容器启动阶段 | | 缓存策略控制 | 不支持 | 通过$isSingleton()$ | | 原型模式支持 | 手动实现 | 通过$isPrototype()$ | #### 三、完整调用流程示例 以$SmartFactoryBean$实现类$EagerInitFactory$为例: 1. 容器启动时解析Bean定义 2. 调用$isEagerInit()$返回$true$ 3. 触发$getBean()$调用链: $$AbstractBeanFactory.doGetBean() \rightarrow FactoryBeanRegistrySupport.getObjectFromFactoryBean()$$ 4. 最终通过$getObject()$创建目标对象并缓存[^3][^5]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值