Spring Security 6.x 系列(7)—— 源码分析之Builder设计模式

一、Builder设计模式

WebSecurityHttpSecurityAuthenticationManagerBuilder 都是框架中的构建者,把他们放到一起看看他们的共同特点:

查看AuthenticationManagerBuilder的继承结构图:

在这里插入图片描述

查看HttpSecurity的继承结构图:

在这里插入图片描述
查看WebSecurity的继承结构图:

在这里插入图片描述
可以看出他们都有这样一条继承树:

|- SecurityBuilder
	|- AbstractSecurityBuilder
		|- AbstractConfiguredSecurityBuilder

二、SecurityBuilder

/**
 * Interface for building an Object
 *
 * @param <O> The type of the Object being built
 * @author Rob Winch
 * @since 3.2
 */
public interface SecurityBuilder<O> {

	/**
	 * Builds the object and returns it or null.
	 * @return the Object to be built or null if the implementation allows it.
	 * @throws Exception if an error occurred when building the Object
	 */
	O build() throws Exception;

}

SecurityBuilder是一个接口,当调用它的 build() 方法时,会创建一个对象。将要创建的对象由泛型 O 限制。这个接口是所有构建者的顶级接口,也是Spring Security 框架中使用的建造者模式的基础接口。

三、AbstractSecurityBuilder

/**
 * A base {@link SecurityBuilder} that ensures the object being built is only built one
 * time.
 *
 * @param <O> the type of Object that is being built
 * @author Rob Winch
 *
 */
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {

	// 标记对象是否处于创建中
	private AtomicBoolean building = new AtomicBoolean();

	private O object;

	@Override
	public final O build() throws Exception {
		if (this.building.compareAndSet(false, true)) {
			// 对象的实际底构建过程再 doBuild() 方法中实现
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

	/**
	 * 获取已生成的对象。如果尚未构建,则会引发异常。
	 * @return the Object that was built
	 */
	public final O getObject() {
		if (!this.building.get()) {
			throw new IllegalStateException("This object has not been built");
		}
		return this.object;
	}

	/**
	 * 子类需实现这个方法来执行对象构建。
	 * @return the object that should be returned by {@link #build()}.
	 * @throws Exception if an error occurs
	 */
	protected abstract O doBuild() throws Exception;

}

AbstractSecurityBuilderSecurityBuilder的一个实现抽象类,提供建造的基础流程和控制,能够确保对象只被创建一次。

这个类很简单:

  • 定义了一个原子操作的对象,用来标记当前对象是否处于构建中

    private AtomicBoolean building = new AtomicBoolean();
    
  • 实现SecurityBuilder接口的 build() 方法。

    调用 doBuild() 方法完成构建,并且在调用 doBuild() 之前需要原子修改 buildingtrue,只有修改成功才能执行 doBuild() 方法,这间接的保证了:对象只构建一次,构建的唯一性和原子性。

  • 定义一个 getObject() 方法,方便获取构建的对象。

  • 定义抽象方法 doBuild() ,加入模板模式,交给子类自行实现。

四、AbstractConfiguredSecurityBuilder

源码注释:
A base SecurityBuilder that allows SecurityConfigurer to be applied to it. This makes modifying the SecurityBuilder a strategy that can be customized and broken up into a number of SecurityConfigurer objects that have more specific goals than that of the SecurityBuilder.

一个基本的SecurityBuilder,允许将SecurityConfigurer应用于它。这使得修改SecurityBuilder的策略可以自定义并分解为许多SecurityConfigurer对象,这些对象具有比SecurityBuilder更具体的目标。

For example, a SecurityBuilder may build an DelegatingFilterProxy, but a SecurityConfigurer might populate the SecurityBuilder with the filters necessary for session management, form based login, authorization, etc.
请参阅:
WebSecurity
作者:
Rob Winch
类型形参:
<O> – The object that this builder returns 此生成器返回的对象
<B> – The type of this builder (that is returned by the base class) 此生成器的类型(由基类返回)

它继承自 AbstractSecurityBuilder ,在此之上又做了一些扩展。先来看看里面都有什么:

在这里插入图片描述

4.1 内部静态枚举类 BuildState

这个枚举类用来表示应用程序(构建器构建对象)的状态,代码相对简单,就不粘贴源码了。

枚举类中只有一个 int 类型的成员变量 order 表示状态编号:

  • UNBUILT(0) :未构建

    构建器的 build 方法被调用之前的状态

  • INITIALIZING(1) : 初始化中

    构建器的 build 方法第一次被调用,到所有 SecurityConfigurerinit 方法都被调用完这期间都是 INITIALIZING 状态

  • CONFIGURING(2): 配置中

    表示从所有的 SecurityConfigurerinit 方法都被调用完,直到所有 configure 方法都被调用

    意思就是所有配置器都初始化了,直到配置都被调用这段时间都时 CONFIGURING 状态

  • BUILDING(3) :对象构建中

    表示已经执行完所有的 SecurityConfigurerconfigure 方法,到刚刚执行完 AbstractConfiguredSecurityBuilderperformBuild 方法这期间

    意思就是从将所有配置器的配置都配置完成开始,到构建完这个对象这段时间都是 BUILDING 状态

  • BUILT(4) :对象已经构建完成

    表示对象已经构建完成。

枚举类中还有两个方法:

  • isInitializingINITIALIZING状态时返回true

  • isConfigured:大于等于 CONFIGURING 的时候返回 true

    也就是说配置器初始化完成时在构建者看来就算以配置状态了。

4.2 成员变量

private final Log logger = LogFactory.getLog(getClass());

private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>();

private final Map<Class<?>, Object> sharedObjects = new HashMap<>();

private final boolean allowConfigurersOfSameType;

private BuildState buildState = BuildState.UNBUILT;

private ObjectPostProcessor<Object> objectPostProcessor;
  • configurers:所要应用到当前 SecurityBuilder 上的所有的 SecurityConfigurer
  • configurersAddedInInitializing:用于记录在初始化期间添加进来的 SecurityConfigurer
  • sharedObjects:共享对象。
  • ObjectPostProcessor:由外部调用者提供,这是一个后置处理对象,在创建完对象后会用到这个后置处理对象。

4.3 构造方法

/***
 * Creates a new instance with the provided {@link ObjectPostProcessor}. This post
 * processor must support Object since there are many types of objects that may be
 * post processed.
 * @param objectPostProcessor the {@link ObjectPostProcessor} to use
 */
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
	this(objectPostProcessor, false);
}

/***
 * Creates a new instance with the provided {@link ObjectPostProcessor}. This post
 * processor must support Object since there are many types of objects that may be
 * post processed.
 * @param objectPostProcessor the {@link ObjectPostProcessor} to use
 * @param allowConfigurersOfSameType if true, will not override other
 * {@link SecurityConfigurer}'s when performing apply
 */
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor,
		boolean allowConfigurersOfSameType) {
	Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
	this.objectPostProcessor = objectPostProcessor;
	this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}

构造函数只对两个成员变量进行了赋值:

  • objectPostProcessor:由外部调用者提供,这是一个后置处理对象,在创建完对象后会用到这个后置处理对象。

  • allowConfigurersOfSameType:从源码可以看出,默认情况下 allowConfigurersOfSameTypefalse

    这个成员变量的含义:

    • true 表示允许相同类型的配置器,在应用配置器时不会覆盖相同类型的配。

4.4 方法

4.4.1 getOrBuild 方法

想要用构建器获取构建的对象时,通过构建器对象调用这个方法。

这个方法的逻辑很简单,调用 isUnbuilt() 方法判断对象创建状态是否未创建完成( return buildState == BuildState.UNBUILT ):

  • 如果对象已经创建就直接返回已经构建好的对象,
  • 否则调用构建器的 build() 方法构建对象并返回构建完成的对象。

从刚才看到的父类 AbstractSecurityBuilder 代码中可以知道真正的构建过程是调用子类 doBuild() 方法完成的。

isUnbuilt() 方法中,对 configurers 成员变量加了锁(synchronized),保证获取到的构建完成状态时,对象真的已经构建好了。

/**
 * Similar to {@link #build()} and {@link #getObject()} but checks the state to
 * determine if {@link #build()} needs to be called first.
 * @return the result of {@link #build()} or {@link #getObject()}. If an error occurs
 * while building, returns null.
 */
public O getOrBuild() {
	if (!isUnbuilt()) {
		return getObject();
	}
	try {
		return build();
	}
	catch (Exception ex) {
		this.logger.debug("Failed to perform build. Returning null", ex);
		return null;
	}
}

/**
 * Determines if the object is unbuilt.
 * @return true, if unbuilt else false
 */
private boolean isUnbuilt() {
	synchronized (this.configurers) {
		return this.buildState == BuildState.UNBUILT;
	}
}

4.4.2 doBuild 方法

使用以下步骤对configurers执行生成:

/**
 * Executes the build using the {@link SecurityConfigurer}'s that have been applied
 * using the following steps:
 *
 * <ul>
 * <li>Invokes {@link #beforeInit()} for any subclass to hook into</li>
 * <li>Invokes {@link SecurityConfigurer#init(SecurityBuilder)} for any
 * {@link SecurityConfigurer} that was applied to this builder.</li>
 * <li>Invokes {@link #beforeConfigure()} for any subclass to hook into</li>
 * <li>Invokes {@link #performBuild()} which actually builds the Object</li>
 * </ul>
 */
@Override
protected final O doBuild() throws Exception {
	synchronized (this.configurers) {
		this.buildState = BuildState.INITIALIZING;
		beforeInit();
		init();
		this.buildState = BuildState.CONFIGURING;
		beforeConfigure();
		configure();
		this.buildState = BuildState.BUILDING;
		O result = performBuild();
		this.buildState = BuildState.BUILT;
		return result;
	}
}
  • 构建过程对 configurers 加锁。
  • 方法体中时构建对象的整个流程,包括状态变化。
  • 构建过程大致分为构建器初始化 beforeInit()init(),构建器配置 beforeConfigure()configure(),构建对象 performBuild()

构建过程对 configurers 加锁,也就意味着进入构建方法后 configurers 中的构建器应该都准备好了。这个时候如果再添加或者修改配置器都会失败。

4.4.3 beforeInit 方法 和 beforeConfigure 方法

这两个方法是抽象方法,由子类实现。子类通过覆盖这两个方法可以挂钩到对象构建的生命周期中,实现:在配置器(SecurityConfigurer)调用初始化方法或者配置方法之前做用户自定义的操作。

/**
 * Invoked prior to invoking each {@link SecurityConfigurer#init(SecurityBuilder)}
 * method. Subclasses may override this method to hook into the lifecycle without
 * using a {@link SecurityConfigurer}.
 */
protected void beforeInit() throws Exception {
}

/**
 * Invoked prior to invoking each
 * {@link SecurityConfigurer#configure(SecurityBuilder)} method. Subclasses may
 * override this method to hook into the lifecycle without using a
 * {@link SecurityConfigurer}.
 */
protected void beforeConfigure() throws Exception {
}

在这里插入图片描述

4.4.4 init 方法

@SuppressWarnings("unchecked")
private void init() throws Exception {
	Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
	for (SecurityConfigurer<O, B> configurer : configurers) {
		configurer.init((B) this);
	}
	for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
		configurer.init((B) this);
	}
}

方法很简单功能很简单,就是遍历 configurersconfigurersAddedInInitializing ,对里面存储的配置器进行初始化。

配置器初始化的详细内容到看配置器源码时在了解。

4.4.5 configure 方法

@SuppressWarnings("unchecked")
private void configure() throws Exception {
	Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
	for (SecurityConfigurer<O, B> configurer : configurers) {
		configurer.configure((B) this);
	}
}

private Collection<SecurityConfigurer<O, B>> getConfigurers() {
	List<SecurityConfigurer<O, B>> result = new ArrayList<>();
	for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
		result.addAll(configs);
	}
	return result;
}

遍历 configurers ,调用所有配置器的 configure(SecurityBuilder b) 方法对当前的构建器(this)进行配置。

配置器配置详细内容到看配置器源码时在了解。

4.4.6 performBuild 方法

这也是一个抽象方法,需要子类实现,完成对象的创建并返回。
在这里插入图片描述

4.4.7 apply 方法

/**
 * Applies a {@link SecurityConfigurerAdapter} to this {@link SecurityBuilder} and
 * invokes {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
 * @param configurer
 * @return the {@link SecurityConfigurerAdapter} for further customizations
 * @throws Exception
 */
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
	configurer.addObjectPostProcessor(this.objectPostProcessor);
	configurer.setBuilder((B) this);
	add(configurer);
	return configurer;
}

/**
 * Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder} overriding any
 * {@link SecurityConfigurer} of the exact same class. Note that object hierarchies
 * are not considered.
 * @param configurer
 * @return the {@link SecurityConfigurerAdapter} for further customizations
 * @throws Exception
 */
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
	add(configurer);
	return configurer;
}

在这里插入图片描述

这个方法的作用是将 SecurityConfigurerAdapter (配置器的适配器)或者 SecurityConfigurer (配置器)应用到当前的构建器。这两个方法是相互重载的,他们最后都调用了 add(configurer) 方法,将配置器添加到构建器,方便构建时使用(初始,配置)。

关于 SecurityConfigurerAdapterSecurityConfigurer 后面再详细了解。这里观察可以看出,他们实现了相同的接口,都可以作为add方法的参数。

而且 public <C extends SecurityConfigurerAdapter<O,B>> C apply(C configurer)throws Exception方法在6.2 版本标记为废弃。

4.4.8 add 方法

/**
 * Adds {@link SecurityConfigurer} ensuring that it is allowed and invoking
 * {@link SecurityConfigurer#init(SecurityBuilder)} immediately if necessary.
 * @param configurer the {@link SecurityConfigurer} to add
 */
@SuppressWarnings("unchecked")
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
	Assert.notNull(configurer, "configurer cannot be null");
	Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
			.getClass();
	synchronized (this.configurers) {
		if (this.buildState.isConfigured()) {
			throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
		}
		List<SecurityConfigurer<O, B>> configs = null;
		if (this.allowConfigurersOfSameType) {
			configs = this.configurers.get(clazz);
		}
		configs = (configs != null) ? configs : new ArrayList<>(1);
		configs.add(configurer);
		this.configurers.put(clazz, configs);
		if (this.buildState.isInitializing()) {
			this.configurersAddedInInitializing.add(configurer);
		}
	}
}

这个方法将配置器添加到一个map集合里面,这个map中以配置器的类名为 Key,以存放这个类型的配置器的 List 集合为 Value

  • 在执行添加操作时会对 configurers 加锁(synchronized )。

  • 通过构造方法中设置的 allowConfigurersOfSameType 值判断是否允许添加相同类型的配置器,如果是 true ,那么在添加之前会根据类名先从 map 中获取该类型配置器链表(List),如果获取到了就把要添加的配置器追加到后面,然后把追加了新配置器的List再放回到 map 里面,如果获取到 null ,接创建一个新的 List 来存放配置器。

  • 添加配置器时,如果该构建器已经处于以配置状态(大于等于 CONFIGURING.order ),那么会抛出异常;如果该构建器已经处于 INITIALIZING 状态,那么久将这个适配器链表存放到 configurersAddedInInitializing 这个map中;否则将适配器链表存放到 configurers 这个 map 集合中。

遗留一个问题,没有看出来为什么要使用 configurersAddedInInitializing ,如果没有 configurersAddedInInitializing 这个设计会出现什么并发问题吗?

4.4.9 其他方法

在这里插入图片描述

剩下的方法都是一些getsetremove 方法很好理解,不做多余追述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/206901.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java实现飞翔的鸟小游戏

Java实现飞翔的鸟小游戏 1.准备工作 创建一个新的Java项目命名为“飞翔的鸟”&#xff0c;并在src中创建一个包命名为“com.qiku.bird"&#xff0c;在这个包内分别创建4个类命名为**“Bird”、“BirdGame”、“Column”、“Ground”&#xff0c;并向需要的图片**素材导入…

Redis7--基础篇4(Redis事务)

Redis事务是什么 可以一次执行多个命令&#xff0c;本质是一组命令的集合&#xff0c;一个事务中的所有命令都会序列化&#xff0c;按顺序串行&#xff0c;而不会被其他命令插入。 其作用就是在一个队列中&#xff0c;一次性、顺序、排他的执行一系列命令。 Redis事务 VS 数据…

【Java】文件I/O-文件内容操作-输入输出流-Reader/Writer/InputStream/OutputStream四种流

导读 在文件I/O这一节的知识里&#xff0c;对文件的操作主要分为两大类&#xff1a; ☑️针对文件系统进行的操作 ☑️针对文件内容进行的操作 上文已经讲了针对文件系统即File类的操作&#xff0c;这篇文章里博主就来带了解针对文件内容的操作&#xff0c;即输入输出流&am…

润申信息企业标准化管理系统 SQL注入漏洞复现

0x01 产品简介 润申信息科技企业标准化管理系统通过给客户提供各种灵活的标准法规信息化管理解决方案&#xff0c;帮助他们实现了高效的标准法规管理&#xff0c;完成个性化标准法规库的信息化建设。 0x02 漏洞概述 润申信息科技企业标准化管理系统 CommentStandardHandler.as…

视频分割方法:批量剪辑高效分割视频,提取m3u8视频技巧

随着互联网的快速发展&#xff0c;视频已成为获取信息、娱乐、学习等多种需求的重要载体。然而&#xff0c;很多时候&#xff0c;需要的只是视频的一部分&#xff0c;这就要对视频进行分割。而m3u8视频是一种常见的流媒体文件格式&#xff0c;通常用于在线视频播放。本文将分享…

linux提权_永久提权_临时提权

2.3 提权 2.3.1 su&#xff1a;永久提权 su命令用于切换当前用户身份到指定用户或者以指定用户的身份执行命令或程序。 ​ 普通用户切换到root用户&#xff0c;可以使用su - 或su root,但是必须输入root密码才能完成切换。root用户切换到普通用户&#xff0c;可以使用su user…

什么是HTML语义化,有什么好处?

HTML 语义化就是让页面的内容结构化&#xff0c;便于对浏览器 、搜索引擎解析。 用正确的标签做正确的事情&#xff0c;语义类标签是对内容的补充&#xff0c;表达标题摘要、文章结构、强调重点、丰富含义、避免歧义。 HTML 语义化的好处&#xff1a;HTML 语义化不是一定要执…

【OpenGL】Clion配置

OpenGL简介 OpenGL&#xff08;Open Graphics Library&#xff09;是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像&#xff08;二维的亦可&#xff09;&#xff0c;是一个功能强大&#xff0c;调用方便的底层图形库。OpenGL是行业领域中…

SSD-Single Shot Detector

文章目录 SSD模型主要改进点模型说明 训练Choosing scales and aspect ratios for default boxesMatching strategyTraining objectiveHard negative miningData augmentation 实验结果基本网络参数PASCAL VOC2007模型消融实验PASCAL VOC2012COCO推理速度比较 前面提到了两种经…

Netfilter中的NAT

目录 前瞻 SNAT和DNAT SNAT DNAT 实验 前瞻 NAT: &#xff08;network address translation&#xff09;&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 NAT分为SNAT和DNAT SNAT&#xff1a;支持POSTROUTING, INPUT&…

Element UI 实战:跨页保存表格选中状态与判断状态可选性的高效方案

引言 在前文中&#xff0c;我们曾深入探讨了在修改数据后跨页时提醒用户可能丢失数据的问题。虽然这种方式对于一些场景是足够的&#xff0c;但当涉及选择框时&#xff0c;我们需要更为智能和高效的解决方案。在本文中&#xff0c;我们将分享一种基于 Element UI 的实际案例&am…

Java封装讯飞星火大模型历险记

问题描述与分析 现状描述与目标 在使用讯飞星火大模型API的过程中&#xff0c;API的返回结果在可以在其他线程中进行分次打印&#xff0c;但是在main方法中直接打印返回结果&#xff0c;显示为空。这种情况下不利于二次封装&#xff0c;希望在main方法中获取完整的API返回结果…

【开源视频联动物联网平台】帧率、码率和分辨率

帧率、码率和分辨率是视频和图像处理中的重要概念&#xff0c;它们直接影响到视频的带宽占用和显示效果。在进行视频项目时&#xff0c;根据应用需求对视频参数进行调整是必要的&#xff0c;因此了解这些参数的具体含义和指标是非常重要的。 在进行视频项目时&#xff0c;需要…

实战Flask+BootstrapTable后端传javascript脚本给前端实现多行编辑(ajax方式)

相信看到此文的朋友们一定会感到庆幸,总之我是用了两天死磕,才得到如下结果,且行且珍惜,祝好各位! 话不多说,有图有源码 1.看图 2.前端实现页面 <!DOCTYPE html> {% from "common/_macro.html" import static %} <html> <meta charset"utf-8&…

Python开发运维:PyMongo 连接操作 MongoDB

目录 一、理论 1.PyMongo模块 2.Mongo Shell 二、实验 1. Windows11安装MongoDB 7.0.4 2.Windows11安装MongoDB Shell 2.1.0 3.PyMongo 连接 MongoDB&#xff08;无密码方式&#xff09; 4.PyMongo 连接 MongoDB&#xff08;有密码方式&#xff09; 5.PyMongo 操作 Mo…

rabbitmq消息队列实验

实验目的&#xff1a;实现异步通信 实验条件&#xff1a; 主机名 IP地址 组件 test1 20.0.0.10 rabbitmq服务 test2 20.0.0.20 rabbitmq服务 test3 20.0.0.30 rabbitmq服务 实验步骤&#xff1a; 1、安装rabbitmq服务 2、erlang进入命令行&#xff0c;查看版本 …

zookeeper集群和kafka集群

&#xff08;一&#xff09;kafka 1、kafka3.0之前依赖于zookeeper 2、kafka3.0之后不依赖zookeeper&#xff0c;元数据由kafka节点自己管理 &#xff08;二&#xff09;zookeeper 1、zookeeper是一个开源的、分布式的架构&#xff0c;提供协调服务&#xff08;Apache项目&…

91基于matlab的以GUI实现指纹的识别和匹配百分比

基于matlab的以GUI实现指纹的识别和匹配百分比,中间有对指纹的二值化&#xff0c;M连接&#xff0c;特征提取等处理功能。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 91M连接 特征提取 (xiaohongshu.com)

国产linux单用户模式破解无密码登陆 (麒麟系统用户登录密码遗忘解决办法)

笔者手里有一批国产linu系统&#xff0c;目前开始用在日常的工作生产环境中&#xff0c;我这个老程序猿勉为其难的充当运维的或网管的角色。 国产linux系统常见的为麒麟Linux&#xff0c;统信UOS等&#xff0c;基本都是基于debian再开发的linux。 问题描述&#xff1a; 因为…

Windows系列:windows server 2016 下域环境的搭建(完整版)

windows server 2016 下域环境的搭建&#xff08;完整版&#xff09; windows server 2016 下域环境的搭建在搭建之前简单介绍一下基础知识&#xff1a;一、环境介绍 &#xff1a;1.这里用拓扑图进行展示&#xff1a;2.所有环境配置如下 二、搭建主域&#xff1a;一. 创建主域1…