网站首页 编程语言 正文
目录
- 引言
- Demo
- ReactDOM.render
- ReactDOM.hydrate
- hydrate 过程
- 事件绑定
- hydrate 源码剖析
- beginWork
- HostRoot Fiber
- HostComponent
- HostText Fiber
- tryToClaimNextHydratableInstance
- completeUnitOfWork
- popHydrationState
- prepareToHydrateHostInstance
- prepareToHydrateHostTextInstance
- prepareToHydrateHostTextInstance
引言
React 渲染过程,即ReactDOM.render执行过程分为两个大的阶段:render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多,两者之间最大的区别就是,ReactDOM.hydrate 在 render 阶段,会尝试复用(hydrate)浏览器现有的 dom 节点,并相互关联 dom 实例和 fiber,以及找出 dom 属性和 fiber 属性之间的差异。
Demo
这里,我们在 index.html 中直接返回一段 html,以模拟服务端渲染生成的 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Mini React</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root"><div id="root"><div id="container"><h1 id="A">1<div id="A2">A2</div></h1><p id="B"><span id="B1">B1</span></p><span id="C">C</span></div></div></div>
</body>
</html>
注意,root 里面的内容不能换行,不然客户端hydrate的时候会提示服务端和客户端的模版不一致。
新建 index.jsx:
import React from "react";
import ReactDOM from "react-dom";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 1,
};
}
render() {
const { count } = this.state;
return (
<div id="container">
<div id="A">
{count} <div id="A2">A2</div>
</div>
<p id="B">
<span id="B1">B1</span>
</p>
</div>
);
}
}
ReactDOM.hydrate(<Home />, document.getElementById("root"));
对比服务端和客户端的内容可知,服务端h1#A和客户端的div#A不同,同时服务端比客户端多了一个span#C
在客户端开始执行之前,即 ReactDOM.hydrate 开始执行前,由于服务端已经返回了 html 内容,浏览器会立马显示内容。对应的真实 DOM 树如下:

注意,这不是 fiber 树!!
ReactDOM.render
先来回顾一下 React 渲染更新过程,分为两大阶段,五小阶段:
render 阶段
- beginWork
- completeUnitOfWork
commit 阶段。
- commitBeforeMutationEffects
- commitMutationEffects
- commitLayoutEffects
React 在 render 阶段会根据新的 element tree 构建 workInProgress 树,收集具有副作用的 fiber 节点,构建副作用链表。
特别是,当我们调用ReactDOM.render函数在客户端进行第一次渲染时,render阶段的completeUnitOfWork函数针对HostComponent以及HostText类型的 fiber 执行以下 dom 相关的操作:
- 调用
document.createElement为HostComponent类型的 fiber 节点创建真实的 DOM 实例。或者调用document.createTextNode为HostText类型的 fiber 节点创建真实的 DOM 实例 - 将 fiber 节点关联到真实 dom 的
__reactFiber$rsdw3t27flk(后面是随机数)属性上。 - 将 fiber 节点的
pendingProps属性关联到真实 dom 的__reactProps$rsdw3t27flk(后面是随机数)属性上 - 将真实的 dom 实例关联到
fiber.stateNode属性上:fiber.stateNode = dom。 - 遍历
pendingProps,给真实的dom设置属性,比如设置 id、textContent 等
React 渲染更新完成后,React 会为每个真实的 dom 实例挂载两个私有的属性:__reactFiber$和__reactProps$,以div#container为例:

ReactDOM.hydrate
hydrate中文意思是水合物,这样理解有点抽象。根据源码,我更乐意将hydrate的过程描述为:React 在 render 阶段,构造 workInProgress 树时,同时按相同的顺序遍历真实的 DOM 树,判断当前的 workInProgress fiber 节点和同一位置的 dom 实例是否满足hydrate的条件,如果满足,则直接复用当前位置的 DOM 实例,并相互关联 workInProgress fiber 节点和真实的 dom 实例,比如:
fiber.stateNode = dom; dom.__reactProps$ = fiber.pendingProps; dom.__reactFiber$ = fiber;
如果 fiber 和 dom 满足hydrate的条件,则还需要找出dom.attributes和fiber.pendingProps之间的属性差异。
遍历真实 DOM 树的顺序和构建 workInProgress 树的顺序是一致的。都是深度优先遍历,先遍历当前节点的子节点,子节点都遍历完了以后,再遍历当前节点的兄弟节点。因为只有按相同的顺序,fiber 树同一位置的 fiber 节点和 dom 树同一位置的 dom 节点才能保持一致
只有类型为HostComponent或者HostText类型的 fiber 节点才能hydrate。这一点也很好理解,React 在 commit 阶段,也就只有这两个类型的 fiber 节点才需要执行 dom 操作。
fiber 节点和 dom 实例是否满足hydrate的条件:
- 对于类型为
HostComponent的 fiber 节点,如果当前位置对应的 DOM 实例nodeType为ELEMENT_NODE,并且fiber.type === dom.nodeName,那么当前的 fiber 可以混合(hydrate) - 对于类型为
HostText的 fiber 节点,如果当前位置对应的 DOM 实例nodeType为TEXT_NODE,同时fiber.pendingProps不为空,那么当前的 fiber 可以混合(hydrate)
hydrate的终极目标就是,在构造 workInProgress 树的过程中,尽可能的复用当前浏览器已经存在的 DOM 实例以及 DOM 上的属性,这样就无需再为 fiber 节点创建 DOM 实例,同时对比现有的 DOM 的attribute以及 fiber 的pendingProps,找出差异的属性。然后将 dom 实例和 fiber 节点相互关联(通过 dom 实例的__reactFiber$以及__reactProps$,fiber 的 stateNode 相互关联)
hydrate 过程
React 在 render 阶段构造HostComponent或者HostText类型的 fiber 节点时,会首先调用 tryToClaimNextHydratableInstance(workInProgress) 方法尝试给当前 fiber 混合(hydrate)DOM 实例。如果当前 fiber 不能被混合,那当前节点的所有子节点在后续的 render 过程中都不再进行hydrate,而是直接创建 dom 实例。等到当前节点所有子节点都调用completeUnitOfWork完成工作后,又会从当前节点的兄弟节点开始尝试混合。
以下面的 demo 为例
// 服务端返回的DOM结构,这里为了直观,我格式化了一下,按理服务端返回的内容,是不允许换行或者有空字符串的
<body>
<div id="root">
<div id="container">
<h1 id="A">
1 <div id="A2">A2</div>
</h1>
<p id="B">
<span id="B1">B1</span>
</p>
<span id="C">C</span>
</div>
</div>
</body>
// 客户端生成的内容
<div id="container">
<div id="A">
1 <div id="A2">A2</div>
</div>
<p id="B">
<span id="B1">B1</span>
</p>
</div>
render 阶段,按以下顺序:
div#container 满足hydrate的条件,因此关联 dom,fiber.stateNode = div#container。然后使用hydrationParentFiber记录当前混合的 fiber 节点:hydrationParentFiber = fiber。获取下一个 DOM 实例,这里是h1#A,保存在变量nextHydratableInstance中,nextHydratableInstance = h1#A。
这里,hydrationParentFiber 和 nextHydratableInstance 都是全局变量。
-
div#A和h1#A不能混合,这时并不会立即结束混合的过程,React 继续对比h1#A的兄弟节点,即p#B,发现div#A还是不能和p#B混合,经过最多两次对比,React 认为 dom 树中已经没有 dom 实例满足和div#A这个 fiber 混合的条件,于是div#A节点及其所有子孙节点都不再进行混合的过程,此时将isHydrating设置为 false 表明div#A这棵子树都不再走混合的过程,直接走创建 dom 实例。同时控制台提示:Expected server HTML to contain a matching..之类的错误。 - beginWork 执行到文本节点
1时,发现isHydrating = false,因此直接跳过混合的过程,在completeUnitOfWork阶段直接调用document.createTextNode直接为其创建文本节点 - 同样的,beginWork 执行到节点
div#A2时,发现isHydrating = false,因此直接跳过混合的过程,在completeUnitOfWork阶段直接调用document.createElement直接为其创建真实 dom 实例,并设置属性 - 由于
div#A的子节点都已经completeUnitWork了,轮到div#A调用completeUnitWork完成工作,将hydrationParentFiber指向其父节点,即div#container这个 dom 实例。设置isHydrating = true表明可以为当前节点的兄弟节点继续混合的过程了。div#A没有混合的 dom 实例,因此调用document.createElement为其创建真实的 dom 实例。 - 为
p#B执行 beginWork。由于nextHydratableInstance保存的还是h1#Adom 实例,因此p#B和h1#A对比发现不能复用,React 尝试和h1#A的兄弟节点p#B对比,发现 fiberp#B和 domp#B能混,因此将h1#A标记为删除,同时关联 dom 实例:fiber.stateNode = p#B,保存hydrationParentFiber = fiber,nextHydratableInstance指向p#B的第一个子节点,即span#B1
...省略了后续的过程。
从上面的执行过程可以看出,hydrate 的过程如下:
- 调用
tryToClaimNextHydratableInstance开始混合 - 判断当前 fiber 节点和同一位置的 dom 实例是否满足混合的条件。
- 如果当前位置的 dom 实例不满足混合条件,则继续比较当前 dom 的兄弟元素,如果兄弟元素和当前的 fiber 也不能混合,则当前 fiber 及其所有子孙节点都不能混合,后续 render 过程将会跳过混合。直到当前 fiber 节点的兄弟节点 render,才会继续混合的过程。
事件绑定
React在初次渲染时,不论是ReactDOM.render还是ReactDOM.hydrate,会调用createRootImpl函数创建fiber的容器,在这个函数中调用listenToAllSupportedEvents注册所有原生的事件。
function createRootImpl(container, tag, options) {
// ...
var root = createContainer(container, tag, hydrate);
// ...
listenToAllSupportedEvents(container);
// ...
return root;
}
这里container就是div#root节点。listenToAllSupportedEvents会给div#root节点注册浏览器支持的所有原生事件,比如onclick等。React合成事件一文介绍过,React采用的是事件委托的机制,将所有事件代理到div#root节点上。以下面的为例:
<div id="A" onClick={this.handleClick}>
button
<div>
我们知道React在渲染时,会将fiber的props关联到真实的dom的__reactProps$属性上,此时
div#A.__reactProps$ = {
onClick: this.handleClick
}
当我们点击按钮时,会触发div#root上的事件监听器:
function onclick(e){
const target = e.target
const fiberProps = target.__reactProps$
const clickhandle = fiberProps.onClick
if(clickhandle){
clickhandle(e)
}
}
这样我们就可以实现事件的委托。这其中最重要的就是将fiber的props挂载到真实的dom实例的__reactProps$属性上。因此,只要我们在hydrate阶段能够成功关联dom和fiber,就自然也实现了事件的“绑定”
hydrate 源码剖析
hydrate 的过程发生在 render 阶段,commit 阶段几乎没有和 hydrate 相关的逻辑。render 阶段又分为两个小阶段:beginWork 和 completeUnitOfWork。只有HostRoot、HostComponent、HostText三种类型的 fiber 节点才需要 hydrate,因此源码只针对这三种类型的 fiber 节点剖析
beginWork
beginWork 阶段判断 fiber 和 dom 实例是否满足混合的条件,如果满足,则为 fiber 关联 dom 实例:fiber.stateNode = dom
function beginWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
}
}
HostRoot Fiber
HostRoot fiber 是容器root的 fiber 节点。
这里主要是判断当前 render 是ReactDOM.render还是ReactDOM.hydrate,我们调用ReactDOM.hydrate渲染时,root.hydrate为 true。
如果是调用的ReactDOM.hydrate,则调用enterHydrationState函数进入hydrate的过程。这个函数主要是初始化几个全局变量:
- isHydrating。表示当前正处于 hydrate 的过程。如果当前节点及其所有子孙节点都不满足 hydrate 的条件时,这个变量为 false
- hydrationParentFiber。当前混合的 fiber。正常情况下,该变量和
HostComponent或者HostText类型的 workInProgress 一致。 - nextHydratableInstance。下一个可以混合的 dom 实例。当前 dom 实例的第一个子元素或者兄弟元素。
注意getNextHydratable会判断 dom 实例是否是ELEMENT_NODE类型(对应的 fiber 类型是HostComponent)或者TEXT_NODE类型(对应的 fiber 类型是HostText)。只有ELEMENT_NODE或者HostText类型的 dom 实例才是可以 hydrate 的
function updateHostRoot(current, workInProgress, renderLanes) {
if (root.hydrate && enterHydrationState(workInProgress)) {
var child = mountChildFibers(workInProgress, null, nextChildren);
}
return workInProgress.child;
}
function getNextHydratable(node) {
// 跳过 non-hydratable 节点.
for (; node != null; node = node.nextSibling) {
var nodeType = node.nodeType;
if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) {
break;
}
}
return node;
}
function enterHydrationState() {
var parentInstance = fiber.stateNode.containerInfo;
nextHydratableInstance = getNextHydratable(parentInstance.firstChild);
hydrationParentFiber = fiber;
isHydrating = true;
}
HostComponent
function updateHostComponent(current, workInProgress, renderLanes) {
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
HostText Fiber
function updateHostText(current, workInProgress) {
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
return null;
}
tryToClaimNextHydratableInstance
假设当前 fiberA 对应位置的 dom 为 domA,tryToClaimNextHydratableInstance 会首先调用tryHydrate判断 fiberA 和 domA 是否满足混合的条件:
如果 fiberA 和 domA 满足混合的条件,则将hydrationParentFiber = fiberA;。并且获取 domA 的第一个子元素赋值给nextHydratableInstance
如果 fiberA 和 domA 不满足混合的条件,则获取 domA 的兄弟节点,即 domB,调用tryHydrate判断 fiberA 和 domB 是否满足混合条件:
- 如果 domB 满足和 fiberA 混合的条件,则将 domA 标记为删除,并获取 domB 的第一个子元素赋值给
nextHydratableInstance - 如果 domB 不满足和 fiberA 混合的条件,则调用
insertNonHydratedInstance提示错误:"Warning: Expected server HTML to contain a matching",同时将isHydrating标记为 false 退出。
这里可以看出,tryToClaimNextHydratableInstance最多比较两个 dom 节点,如果两个 dom 节点都无法满足和 fiberA 混合的条件,则说明当前 fiberA 及其所有的子孙节点都无需再进行混合的过程,因此将isHydrating标记为 false。等到当前 fiberA 节点及其子节点都完成了工作,即都执行了completeWork,isHydrating才会被设置为 true,以便继续比较 fiberA 的兄弟节点
这里还需要注意一点,如果两个 dom 都无法满足和 fiberA 混合,那么nextHydratableInstance依然保存的是 domA,domA 会继续和 fiberA 的兄弟节点比对。
function tryToClaimNextHydratableInstance(fiber) {
if (!isHydrating) {
return;
}
var nextInstance = nextHydratableInstance;
var firstAttemptedInstance = nextInstance;
if (!tryHydrate(fiber, nextInstance)) {
// 如果第一次调用tryHydrate发现当前fiber和dom不满足hydrate的条件,则获取dom的兄弟节点
// 然后调用 tryHydrate 继续对比fiber和兄弟节点是否满足混合
nextInstance = getNextHydratableSibling(firstAttemptedInstance);
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
// 对比了两个dom发现都无法和fiber混合,因此调用insertNonHydratedInstance控制台提示错误
insertNonHydratedInstance(hydrationParentFiber, fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
// 如果第一次tryHydrate不满足,第二次tryHydrate满足,则说明兄弟节点和当前fiber是可以混合的,此时需要删除当前位置的dom
deleteHydratableInstance(hydrationParentFiber, firstAttemptedInstance);
}
hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild(nextInstance);
}
// 将dom实例保存在 fiber.stateNode上
function tryHydrate(fiber, nextInstance) {
switch (fiber.tag) {
case HostComponent: {
if (
nextInstance.nodeType === ELEMENT_NODE &&
fiber.type.toLowerCase() === nextInstance.nodeName.toLowerCase()
) {
fiber.stateNode = nextInstance;
return true;
}
return false;
}
case HostText: {
var text = fiber.pendingProps;
if (text !== "" && nextInstance.nodeType === TEXT_NODE) {
fiber.stateNode = nextInstance;
return true;
}
return false;
}
default:
return false;
}
}
completeUnitOfWork
completeUnitOfWork 阶段主要是给 dom 关联 fiber 以及 props:dom.__reactProps$ = fiber.pendingProps;dom.__reactFiber$ = fiber;同时对比fiber.pendingProps和dom.attributes的差异
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
next = completeWork(current, completedWork, subtreeRenderLanes);
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
}
function completeWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case HostRoot: {
if (current === null) {
var wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
markUpdate(workInProgress);
}
}
return null;
}
case HostComponent:
// 第一次渲染
if (current === null) {
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
// 如果存在差异的属性,则将fiber副作用标记为更新
if (prepareToHydrateHostInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
}
}
case HostText: {
var newText = newProps;
if (current === null) {
var _wasHydrated2 = popHydrationState(workInProgress);
if (_wasHydrated2) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
}
}
return null;
}
}
}
popHydrationState
function popHydrationState(fiber) {
if (fiber !== hydrationParentFiber) {
return false;
}
if (!isHydrating) {
popToNextHostParent(fiber);
isHydrating = true;
return false;
}
var type = fiber.type;
if (
fiber.tag !== HostComponent ||
!shouldSetTextContent(type, fiber.memoizedProps)
) {
var nextInstance = nextHydratableInstance;
while (nextInstance) {
deleteHydratableInstance(fiber, nextInstance);
nextInstance = getNextHydratableSibling(nextInstance);
}
}
popToNextHostParent(fiber);
nextHydratableInstance = hydrationParentFiber
? getNextHydratableSibling(fiber.stateNode)
: null;
return true;
}
以下图为例:

在 beginWork 阶段对 p#B fiber 工作时,发现 dom 树中同一位置的h1#B不满足混合的条件,于是继续对比h1#B的兄弟节点,即div#C,仍然无法混合,经过最多两轮对比后发现p#B这个 fiber 没有可以混合的 dom 节点,于是将 isHydrating 标记为 false,hydrationParentFiber = fiberP#B。p#B的子孙节点都不再进行混合的过程。
div#B1fiber 没有子节点,因此它可以调用completeUnitOfWork完成工作,completeUnitOfWork 阶段调用 popHydrationState 方法,在popHydrationState方法内部,首先判断 fiber !== hydrationParentFiber,由于此时的hydrationParentFiber等于p#B,因此条件成立,不用往下执行。
由于p#B fiber 的子节点都已经完成了工作,因此它也可以调用completeUnitOfWork完成工作。同样的,在popHydrationState函数内部,第一个判断fiber !== hydrationParentFiber不成立,两者是相等的。第二个条件!isHydrating成立,进入条件语句,首先调用popToNextHostParent将hydrationParentFiber设置为p#B的第一个类型为HostComponent的祖先元素,这里是div#A fiber,然后将isHydrating设置为 true,指示可以为p#B的兄弟节点进行混合。
如果服务端返回的 DOM 有多余的情况,则调用deleteHydratableInstance将其删除,比如下图中div#D节点将会在div#Afiber 的completeUnitOfWork阶段删除

prepareToHydrateHostInstance
对于HostComponent类型的fiber会调用这个方法,这里只要是关联 dom 和 fiber:
- 设置
domInstance.__reactFiber$w63z5ormsqk = fiber - 设置
domInstance.__reactProps$w63z5ormsqk = props - 对比服务端和客户端的属性
function prepareToHydrateHostInstance(fiber) {
var domInstance = fiber.stateNode;
var updatePayload = hydrateInstance(
domInstance,
fiber.type,
fiber.memoizedProps,
fiber
);
fiber.updateQueue = updatePayload;
if (updatePayload !== null) {
return true;
}
return false;
}
function hydrateInstance(domInstance, type, props, fiber) {
precacheFiberNode(fiber, domInstance); // domInstance.__reactFiber$w63z5ormsqk = fiber
updateFiberProps(domInstance, props); // domInstance.__reactProps$w63z5ormsqk = props
// 比较dom.attributes和props的差异,如果dom.attributes的属性比props多,说明服务端添加了额外的属性,此时控制台提示。
// 注意,在对比过程中,只有服务端和客户端的children属性(即文本内容)不同时,控制台才会提示错误,同时在commit阶段,客户端会纠正这个错误,以客户端的文本为主。
// 但是,如果是id不同,则客户端并不会纠正。
return diffHydratedProperties(domInstance, type, props);
}
这里重点讲下diffHydratedProperties,以下面的demo为例:
// 服务端对应的dom
<div id="root"><div extra="server attr" id="server">客户端的文本</div></div>
// 客户端
render() {
const { count } = this.state;
return <div id="client">客户端的文本</div>;
}
在diffHydratedProperties的过程中发现,服务端返回的id和客户端的id不同,控制台提示id不匹配,但是客户端并不会纠正这个,可以看到浏览器的id依然是server。
同时,服务端多返回了一个extra属性,因此需要控制台提示,但由于已经提示了id不同的错误,这个错误就不会提示。
最后,客户端的文本和服务端的children不同,即文本内容不同,也需要提示错误,同时,客户端会纠正这个文本,以客户端的为主。

prepareToHydrateHostTextInstance
对于HostText类型的fiber会调用这个方法,这个方法逻辑比较简单,就不详细介绍了 务端对应的dom
<div id="root"><div extra="server attr" id="server">客户端的文本</div></div>
// 客户端
render() {
const { count } = this.state;
return <div id="client">客户端的文本</div>;
}
在diffHydratedProperties的过程中发现,服务端返回的id和客户端的id不同,控制台提示id不匹配,但是客户端并不会纠正这个,可以看到浏览器的id依然是server。
同时,服务端多返回了一个extra属性,因此需要控制台提示,但由于已经提示了id不同的错误,这个错误就不会提示。
最后,客户端的文本和服务端的children不同,即文本内容不同,也需要提示错误,同时,客户端会纠正这个文本,以客户端的为主。

prepareToHydrateHostTextInstance
对于HostText类型的fiber会调用这个方法,这个方法逻辑比较简单,就不详细介绍了
原文链接:https://juejin.cn/post/7184966395762278437
相关推荐
- 2023-03-15 k8s中pod使用详解(云原生kubernetes)_云其它
- 2022-11-05 一篇文章说清楚 go get 使用私有库的方法_Golang
- 2022-10-13 Python线性表种的单链表详解_python
- 2022-06-22 Docker中 container 和 image 的命名_docker
- 2024-01-12 Springboot测试类中 @Before与@BeforeEach的区别
- 2022-09-16 浅析python中5个带key的内置函数_python
- 2022-06-13 C语言指针超详细讲解上篇_C 语言
- 2022-05-10 @requestmapping获取请求参数
- 最近更新
-
- window11 系统安装 yarn
- 超详细win安装深度学习环境2025年最新版(
- Linux 中运行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存储小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基础操作-- 运算符,流程控制 Flo
- 1. Int 和Integer 的区别,Jav
- spring @retryable不生效的一种
- Spring Security之认证信息的处理
- Spring Security之认证过滤器
- Spring Security概述快速入门
- Spring Security之配置体系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置权
- redisson分布式锁中waittime的设
- maven:解决release错误:Artif
- restTemplate使用总结
- Spring Security之安全异常处理
- MybatisPlus优雅实现加密?
- Spring ioc容器与Bean的生命周期。
- 【探索SpringCloud】服务发现-Nac
- Spring Security之基于HttpR
- Redis 底层数据结构-简单动态字符串(SD
- arthas操作spring被代理目标对象命令
- Spring中的单例模式应用详解
- 聊聊消息队列,发送消息的4种方式
- bootspring第三方资源配置管理
- GIT同步修改后的远程分支
