Java 中的屠龍之術(shù):如何修改語法樹?

作者:不學(xué)無數(shù)的程序員?
來源:https://my.oschina.net/u/4030990/blog/3211858
在網(wǎng)上關(guān)于如何修改Java的抽象語法樹的相關(guān)API文檔并不多,于是本篇記錄一下相關(guān)的知識點(diǎn),以便隨后查閱。
JCTree的介紹
JCTree是語法樹元素的基類,包含一個(gè)重要的字段pos,該字段用于指明當(dāng)前語法樹節(jié)點(diǎn)(JCTree)在語法樹中的位置,因此我們不能直接用new關(guān)鍵字來創(chuàng)建語法樹節(jié)點(diǎn),即使創(chuàng)建了也沒有意義。
此外,結(jié)合訪問者模式,將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)的處理進(jìn)行解耦,部分源碼如下:
public?abstract?class?JCTree?implements?Tree,?Cloneable,?DiagnosticPosition?{
????public?int?pos?=?-1;
????...
????public?abstract?void?accept(JCTree.Visitor?visitor);
????...
}
我們可以看到JCTree是一個(gè)抽象類,這里重點(diǎn)介紹幾個(gè)JCTree的子類
JCStatement:聲明語法樹節(jié)點(diǎn),常見的子類如下
JCBlock:語句塊語法樹節(jié)點(diǎn) JCReturn:return語句語法樹節(jié)點(diǎn) JCClassDecl:類定義語法樹節(jié)點(diǎn) JCVariableDecl:字段/變量定義語法樹節(jié)點(diǎn) JCMethodDecl:方法定義語法樹節(jié)點(diǎn)
JCModifiers:訪問標(biāo)志語法樹節(jié)點(diǎn)
JCExpression:表達(dá)式語法樹節(jié)點(diǎn),常見的子類如下
JCAssign:賦值語句語法樹節(jié)點(diǎn) JCIdent:標(biāo)識符語法樹節(jié)點(diǎn),可以是變量,類型,關(guān)鍵字等等
TreeMaker介紹
TreeMaker用于創(chuàng)建一系列的語法樹節(jié)點(diǎn),我們上面說了創(chuàng)建JCTree不能直接使用new關(guān)鍵字來創(chuàng)建,所以Java為我們提供了一個(gè)工具,就是TreeMaker,它會在創(chuàng)建時(shí)為我們創(chuàng)建的JCTree對象設(shè)置pos字段,所以必須使用上下文相關(guān)的TreeMaker對象來創(chuàng)建語法樹節(jié)點(diǎn)。
具體的API介紹可以參照,TreeMakerAPI,接下來著重介紹一下常用的幾個(gè)方法。
TreeMaker.Modifiers
TreeMaker.Modifiers方法用于創(chuàng)建訪問標(biāo)志語法樹節(jié)點(diǎn)(JCModifiers),源碼如下
public?JCModifiers?Modifiers(long?flags)?{
????return?Modifiers(flags,?List.nil());
}
public?JCModifiers?Modifiers(long?flags,
????List?annotations)?{
????????JCModifiers?tree?=?new?JCModifiers(flags,?annotations);
????????boolean?noFlags?=?(flags?&?(Flags.ModifierFlags?|?Flags.ANNOTATION))?==?0;
????????tree.pos?=?(noFlags?&&?annotations.isEmpty())???Position.NOPOS?:?pos;
????????return?tree;
}
flags:訪問標(biāo)志 annotations:注解列表
其中flags可以使用枚舉類com.sun.tools.javac.code.Flags來表示,例如我們可以這樣用,就生成了下面的訪問標(biāo)志了。
treeMaker.Modifiers(Flags.PUBLIC?+?Flags.STATIC?+?Flags.FINAL);
public?static?final
TreeMaker.ClassDef
TreeMaker.ClassDef用于創(chuàng)建類定義語法樹節(jié)點(diǎn)(JCClassDecl),源碼如下:
public?JCClassDecl?ClassDef(JCModifiers?mods,
????Name?name,
????List?typarams,
????JCExpression?extending,
????List?implementing,
????List?defs)?{
????????JCClassDecl?tree?=?new?JCClassDecl(mods,
?????????????????????????????????????name,
?????????????????????????????????????typarams,
?????????????????????????????????????extending,
?????????????????????????????????????implementing,
?????????????????????????????????????defs,
?????????????????????????????????????null);
????????tree.pos?=?pos;
????????return?tree;
}
mods:訪問標(biāo)志,可以通過 TreeMaker.Modifiers來創(chuàng)建name:類名 typarams:泛型參數(shù)列表 extending:父類 implementing:實(shí)現(xiàn)的接口 defs:類定義的詳細(xì)語句,包括字段、方法的定義等等
TreeMaker.MethodDef
TreeMaker.MethodDef用于創(chuàng)建方法定義語法樹節(jié)點(diǎn)(JCMethodDecl),源碼如下
public?JCMethodDecl?MethodDef(JCModifiers?mods,
????Name?name,
????JCExpression?restype,
????List?typarams,
????List?params,
????List?thrown,
????JCBlock?body,
????JCExpression?defaultValue)?{
????????JCMethodDecl?tree?=?new?JCMethodDecl(mods,
???????????????????????????????????????name,
???????????????????????????????????????restype,
???????????????????????????????????????typarams,
???????????????????????????????????????params,
???????????????????????????????????????thrown,
???????????????????????????????????????body,
???????????????????????????????????????defaultValue,
???????????????????????????????????????null);
????????tree.pos?=?pos;
????????return?tree;
}
public?JCMethodDecl?MethodDef(MethodSymbol?m,
????Type?mtype,
????JCBlock?body)?{
????????return?(JCMethodDecl)
????????????new?JCMethodDecl(
????????????????Modifiers(m.flags(),?Annotations(m.getAnnotationMirrors())),
????????????????m.name,
????????????????Type(mtype.getReturnType()),
????????????????TypeParams(mtype.getTypeArguments()),
????????????????Params(mtype.getParameterTypes(),?m),
????????????????Types(mtype.getThrownTypes()),
????????????????body,
????????????????null,
????????????????m).setPos(pos).setType(mtype);
}
mods:訪問標(biāo)志 name:方法名 restype:返回類型 typarams:泛型參數(shù)列表 params:參數(shù)列表 thrown:異常聲明列表 body:方法體 defaultValue:默認(rèn)方法(可能是interface中的哪個(gè)default) m:方法符號 mtype:方法類型。包含多種類型,泛型參數(shù)類型、方法參數(shù)類型、異常參數(shù)類型、返回參數(shù)類型。
返回類型restype填寫null或者
treeMaker.TypeIdent(TypeTag.VOID)都代表返回void類型。
TreeMaker.VarDef
TreeMaker.VarDef用于創(chuàng)建字段/變量定義語法樹節(jié)點(diǎn)(JCVariableDecl),源碼如下
public?JCVariableDecl?VarDef(JCModifiers?mods,
????Name?name,
????JCExpression?vartype,
????JCExpression?init)?{
????????JCVariableDecl?tree?=?new?JCVariableDecl(mods,?name,?vartype,?init,?null);
????????tree.pos?=?pos;
????????return?tree;
}
public?JCVariableDecl?VarDef(VarSymbol?v,
????JCExpression?init)?{
????????return?(JCVariableDecl)
????????????new?JCVariableDecl(
????????????????Modifiers(v.flags(),?Annotations(v.getAnnotationMirrors())),
????????????????v.name,
????????????????Type(v.type),
????????????????init,
????????????????v).setPos(pos).setType(v.type);
}
mods:訪問標(biāo)志 name:參數(shù)名稱 vartype:類型 init:初始化語句 v:變量符號
TreeMaker.Ident
TreeMaker.Ident用于創(chuàng)建標(biāo)識符語法樹節(jié)點(diǎn)(JCIdent),源碼如下
public?JCIdent?Ident(Name?name)?{
????????JCIdent?tree?=?new?JCIdent(name,?null);
????????tree.pos?=?pos;
????????return?tree;
}
public?JCIdent?Ident(Symbol?sym)?{
????????return?(JCIdent)new?JCIdent((sym.name?!=?names.empty)
??????????????????????????????????sym.name
????????????????????????????????:?sym.flatName(),?sym)
????????????.setPos(pos)
????????????.setType(sym.type);
}
public?JCExpression?Ident(JCVariableDecl?param)?{
????????return?Ident(param.sym);
}
TreeMaker.Return
TreeMaker.Return用于創(chuàng)建return語句(JCReturn),源碼如下
public?JCReturn?Return(JCExpression?expr)?{
????????JCReturn?tree?=?new?JCReturn(expr);
????????tree.pos?=?pos;
????????return?tree;
}
TreeMaker.Select
TreeMaker.Select用于創(chuàng)建域訪問/方法訪問(這里的方法訪問只是取到名字,方法的調(diào)用需要用TreeMaker.Apply)語法樹節(jié)點(diǎn)(JCFieldAccess),源碼如下
public?JCFieldAccess?Select(JCExpression?selected,
????Name?selector)?
{
????????JCFieldAccess?tree?=?new?JCFieldAccess(selected,?selector,?null);
????????tree.pos?=?pos;
????????return?tree;
}
public?JCExpression?Select(JCExpression?base,
????Symbol?sym)?{
????????return?new?JCFieldAccess(base,?sym.name,?sym).setPos(pos).setType(sym.type);
}
selected: .運(yùn)算符左邊的表達(dá)式selector: .運(yùn)算符右邊的表達(dá)式
下面給出一個(gè)例子,一語句生成的Java語句就是二語句
一.?TreeMaker.Select(treeMaker.Ident(names.fromString("this")),?names.fromString("name"));
二.?this.name
TreeMaker.NewClass
TreeMaker.NewClass用于創(chuàng)建new語句語法樹節(jié)點(diǎn)(JCNewClass),源碼如下:
public?JCNewClass?NewClass(JCExpression?encl,
????List?typeargs,
????JCExpression?clazz,
????List?args,
????JCClassDecl?def)?{
????????JCNewClass?tree?=?new?JCNewClass(encl,?typeargs,?clazz,?args,?def);
????????tree.pos?=?pos;
????????return?tree;
}
encl:不太明白此參數(shù)的含義,我看很多例子中此參數(shù)都設(shè)置為null typeargs:參數(shù)類型列表 clazz:待創(chuàng)建對象的類型 args:參數(shù)列表 def:類定義
TreeMaker.Apply
TreeMaker.Apply用于創(chuàng)建方法調(diào)用語法樹節(jié)點(diǎn)(JCMethodInvocation),源碼如下:
public?JCMethodInvocation?Apply(List?typeargs,
????JCExpression?fn,
????List?args)?{
????????JCMethodInvocation?tree?=?new?JCMethodInvocation(typeargs,?fn,?args);
????????tree.pos?=?pos;
????????return?tree;
}
typeargs:參數(shù)類型列表 fn:調(diào)用語句 args:參數(shù)列表
TreeMaker.Assign
TreeMaker.Assign用戶創(chuàng)建賦值語句語法樹節(jié)點(diǎn)(JCAssign),源碼如下:
ublic?JCAssign?Assign(JCExpression?lhs,
????JCExpression?rhs)?{
????????JCAssign?tree?=?new?JCAssign(lhs,?rhs);
????????tree.pos?=?pos;
????????return?tree;
}
lhs:賦值語句左邊表達(dá)式 rhs:賦值語句右邊表達(dá)式
TreeMaker.Exec
TreeMaker.Exec用于創(chuàng)建可執(zhí)行語句語法樹節(jié)點(diǎn)(JCExpressionStatement),源碼如下:
public?JCExpressionStatement?Exec(JCExpression?expr)?{
????????JCExpressionStatement?tree?=?new?JCExpressionStatement(expr);
????????tree.pos?=?pos;
????????return?tree;
}
TreeMaker.Apply以及TreeMaker.Assign就需要外面包一層TreeMaker.Exec來獲得一個(gè)JCExpressionStatement。
TreeMaker.Block
TreeMaker.Block用于創(chuàng)建組合語句的語法樹節(jié)點(diǎn)(JCBlock),源碼如下:
public?JCBlock?Block(long?flags,
????List?stats)?{
????????JCBlock?tree?=?new?JCBlock(flags,?stats);
????????tree.pos?=?pos;
????????return?tree;
}
flags:訪問標(biāo)志 stats:語句列表
com.sun.tools.javac.util.List介紹
在我們操作抽象語法樹的時(shí)候,有時(shí)會涉及到關(guān)于List的操作,但是這個(gè)List不是我們經(jīng)常使用的java.util.List而是com.sun.tools.javac.util.List,這個(gè)List比較奇怪,是一個(gè)鏈?zhǔn)降慕Y(jié)構(gòu),有頭結(jié)點(diǎn)和尾節(jié)點(diǎn),但是只有尾節(jié)點(diǎn)是一個(gè)List,這里作為了解就行了。
public?class?List?extends?AbstractCollection?implements?java.util.List?{
????public?A?head;
????public?List?tail;
????private?static?final?List>?EMPTY_LIST?=?new?Listcom.sun.tools.javac.util.ListBuffer
由于com.sun.tools.javac.util.List使用起來不方便,所以又在其上面封裝了一層,這個(gè)封裝類是ListBuffer,此類的操作和我們平時(shí)經(jīng)常使用的java.util.List用法非常類似。
public?class?ListBuffer?extends?AbstractQueue?{
????public?static??ListBuffer?of(T?x)?{
????????ListBuffer?lb?=?new?ListBuffer();
????????lb.add(x);
????????return?lb;
????}
????/**?The?list?of?elements?of?this?buffer.
?????*/
????private?List?elems;
????/**?A?pointer?pointing?to?the?last?element?of?'elems'?containing?data,
?????*??or?null?if?the?list?is?empty.
?????*/
????private?List?last;
????/**?The?number?of?element?in?this?buffer.
?????*/
????private?int?count;
????/**?Has?a?list?been?created?from?this?buffer?yet?
?????*/
????private?boolean?shared;
????/**?Create?a?new?initially?empty?list?buffer.
?????*/
????public?ListBuffer()?{
????????clear();
????}
????/**?Append?an?element?to?buffer.
?????*/
????public?ListBuffer?append(A?x)?{
????????x.getClass();?//?null?check
????????if?(shared)?copy();
????????List?newLast?=?List.of(x);
????????if?(last?!=?null)?{
????????????last.tail?=?newLast;
????????????last?=?newLast;
????????}?else?{
????????????elems?=?last?=?newLast;
????????}
????????count++;
????????return?this;
????}
????........
}
com.sun.tools.javac.util.Names介紹
這個(gè)是為我們創(chuàng)建名稱的一個(gè)工具類,無論是類、方法、參數(shù)的名稱都需要通過此類來創(chuàng)建。它里面經(jīng)常被使用到的一個(gè)方法就是fromString(),一般使用方法如下所示。
Names?names??=?new?Names()
names.?fromString("setName");
實(shí)戰(zhàn)演練
上面我們大概了解了如何操作抽象語法樹,接下來我們就來寫幾個(gè)真實(shí)的案例加深理解。
變量相關(guān)
在類中我們經(jīng)常操作的參數(shù)就是變量,那么如何使用抽象語法樹的特性為我們操作變量呢?接下來我們就將一些對于變量的一些操作。
生成變量
例如生成private String age;這樣一個(gè)變量,借用我們上面講的VarDef方法
//?生成參數(shù)?例如:private String age;
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),?names.fromString("age"),?treeMaker.Ident(names.fromString("String")),?null);
對變量賦值
例如我們想生成private String name = "BuXueWuShu",還是利用VarDef方法
//?private?String?name?=?"BuXueWuShu"
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),names.fromString("name"),treeMaker.Ident(names.fromString("String")),treeMaker.Literal("BuXueWuShu"))
兩個(gè)字面量相加
例如我們生成String add = "a" + "b";,借用我們上面講的Exec方法和Assign方法
//?add?=?"a"+"b"
treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")),treeMaker.Binary(JCTree.Tag.PLUS,treeMaker.Literal("a"),treeMaker.Literal("b"))))
+=語法
例如我們想生成add += "test",則和上面字面量差不多。
//?add+="test"
treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG,?treeMaker.Ident(names.fromString("add")),?treeMaker.Literal("test")))
++語法
例如想生成++i
treeMaker.Exec(treeMaker.Unary(JCTree.Tag.PREINC,treeMaker.Ident(names.fromString("i"))))
方法相關(guān)
我們對于變量進(jìn)行了操作,那么基本上都是要生成方法的,那么如何對方法進(jìn)行生成和操作呢?我們接下來演示一下關(guān)于方法相關(guān)的操作方法。
無參無返回值
我們可以利用上面講到的MethodDef方法進(jìn)行生成
/*
????無參無返回值的方法生成
????public?void?test(){
????}
?*/
//?定義方法體
ListBuffer?testStatement?=?new?ListBuffer<>();
JCTree.JCBlock?testBody?=?treeMaker.Block(0,?testStatement.toList());
????
JCTree.JCMethodDecl?test?=?treeMaker.MethodDef(
????????treeMaker.Modifiers(Flags.PUBLIC),?//?方法限定值
????????names.fromString("test"),?//?方法名
????????treeMaker.Type(new?Type.JCVoidType()),?//?返回類型
????????com.sun.tools.javac.util.List.nil(),
????????com.sun.tools.javac.util.List.nil(),
????????com.sun.tools.javac.util.List.nil(),
????????testBody,?//?方法體
????????null
);
有參無返回值
我們可以利用上面講到的MethodDef方法進(jìn)行生成
/*
????無參無返回值的方法生成
????public?void?test2(String?name){
????????name?=?"xxxx";
????}
?*/
ListBuffer?testStatement2?=?new?ListBuffer<>();
testStatement2.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("name")),treeMaker.Literal("xxxx"))));
JCTree.JCBlock?testBody2?=?treeMaker.Block(0,?testStatement2.toList());
//?生成入?yún)?br>JCTree.JCVariableDecl?param?=?treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),?names.fromString("name"),treeMaker.Ident(names.fromString("String")),?null);
com.sun.tools.javac.util.List?parameters?=?com.sun.tools.javac.util.List.of(param);
JCTree.JCMethodDecl?test2?=?treeMaker.MethodDef(
????????treeMaker.Modifiers(Flags.PUBLIC),?//?方法限定值
????????names.fromString("test2"),?//?方法名
????????treeMaker.Type(new?Type.JCVoidType()),?//?返回類型
????????com.sun.tools.javac.util.List.nil(),
????????parameters,?//?入?yún)?br>????????com.sun.tools.javac.util.List.nil(),
????????testBody2,
????????null
);
有參有返回值
?/*
????有參有返回值
????public?String?test3(String?name){
???????return?name;
????}
?*/
ListBuffer?testStatement3?=?new?ListBuffer<>();
testStatement3.append(treeMaker.Return(treeMaker.Ident(names.fromString("name"))));
JCTree.JCBlock?testBody3?=?treeMaker.Block(0,?testStatement3.toList());
//?生成入?yún)?br>JCTree.JCVariableDecl?param3?=?treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),?names.fromString("name"),treeMaker.Ident(names.fromString("String")),?null);
com.sun.tools.javac.util.List?parameters3?=?com.sun.tools.javac.util.List.of(param3);
JCTree.JCMethodDecl?test3?=?treeMaker.MethodDef(
????????treeMaker.Modifiers(Flags.PUBLIC),?//?方法限定值
????????names.fromString("test4"),?//?方法名
????????treeMaker.Ident(names.fromString("String")),?//?返回類型
????????com.sun.tools.javac.util.List.nil(),
????????parameters3,?//?入?yún)?br>????????com.sun.tools.javac.util.List.nil(),
????????testBody3,
????????null
);
特殊的
我們學(xué)完了如何進(jìn)行定義參數(shù),如何進(jìn)行定義方法,其實(shí)還有好多語句需要學(xué)習(xí),例如如何生成new語句,如何生成方法調(diào)用的語句,如何生成if語句。j接下來我們就學(xué)習(xí)一些比較特殊的語法。
new一個(gè)對象
//?創(chuàng)建一個(gè)new語句?CombatJCTreeMain?combatJCTreeMain?=?new?CombatJCTreeMain();
JCTree.JCNewClass?combatJCTreeMain?=?treeMaker.NewClass(
????????null,
????????com.sun.tools.javac.util.List.nil(),
????????treeMaker.Ident(names.fromString("CombatJCTreeMain")),
????????com.sun.tools.javac.util.List.nil(),
????????null
);
JCTree.JCVariableDecl?jcVariableDecl1?=?treeMaker.VarDef(
????????treeMaker.Modifiers(Flags.PARAMETER),
????????names.fromString("combatJCTreeMain"),
????????treeMaker.Ident(names.fromString("CombatJCTreeMain")),
????????combatJCTreeMain
);
方法調(diào)用(無參)
JCTree.JCExpressionStatement?exec?=?treeMaker.Exec(
????????treeMaker.Apply(
????????????????com.sun.tools.javac.util.List.nil(),
????????????????treeMaker.Select(
????????????????????????treeMaker.Ident(names.fromString("combatJCTreeMain")),?//?.?左邊的內(nèi)容
????????????????????????names.fromString("test")?//?.?右邊的內(nèi)容
????????????????),
????????????????com.sun.tools.javac.util.List.nil()
????????)
);
方法調(diào)用(有參)
//?創(chuàng)建一個(gè)方法調(diào)用?combatJCTreeMain.test2("hello?world!");
JCTree.JCExpressionStatement?exec2?=?treeMaker.Exec(
????????treeMaker.Apply(
????????????????com.sun.tools.javac.util.List.nil(),
????????????????treeMaker.Select(
????????????????????????treeMaker.Ident(names.fromString("combatJCTreeMain")),?//?.?左邊的內(nèi)容
????????????????????????names.fromString("test2")?//?.?右邊的內(nèi)容
????????????????),
????????????????com.sun.tools.javac.util.List.of(treeMaker.Literal("hello?world!"))?//?方法中的內(nèi)容
????????)
);
if語句
/*
????創(chuàng)建一個(gè)if語句
????if("BuXueWuShu".equals(name)){
????????add?=?"a"?+?"b";
????}else{
????????add?+=?"test";
????}
?*/
//?"BuXueWuShu".equals(name)
JCTree.JCMethodInvocation?apply?=?treeMaker.Apply(
????????com.sun.tools.javac.util.List.nil(),
????????treeMaker.Select(
????????????????treeMaker.Literal("BuXueWuShu"),?//?.?左邊的內(nèi)容
????????????????names.fromString("equals")?//?.?右邊的內(nèi)容
????????),
????????com.sun.tools.javac.util.List.of(treeMaker.Ident(names.fromString("name")))
);
//??add?=?"a"?+?"b"
JCTree.JCExpressionStatement?exec3?=?treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")),?treeMaker.Binary(JCTree.Tag.PLUS,?treeMaker.Literal("a"),?treeMaker.Literal("b"))));
//??add?+=?"test"
JCTree.JCExpressionStatement?exec1?=?treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG,?treeMaker.Ident(names.fromString("add")),?treeMaker.Literal("test")));
JCTree.JCIf?anIf?=?treeMaker.If(
????????apply,?//?if語句里面的判斷語句
????????exec3,?//?條件成立的語句
????????exec1??//?條件不成立的語句
);
源碼地址:https://github.com/modouxiansheng/Doraemon
總結(jié)
紙上得來終覺淺,絕知此事要躬行。
希望大家看完此篇文章能夠自己在本機(jī)上自己試驗(yàn)一下。
自己設(shè)置幾個(gè)參數(shù),自己學(xué)的Lombok學(xué)著生成一下get、set方法,雖然本篇知識在日常開發(fā)中基本上不會用到,但是萬一用到了這些知識那么別人不會而你會,差距其實(shí)就慢慢的給拉開了。
本篇涉及到的所有代碼都在github上面有,拉下來以后全局搜CombatJCTreeProcessor類就可以看到了。
推薦閱讀:
世界的真實(shí)格局分析,地球人類社會底層運(yùn)行原理
不是你需要中臺,而是一名合格的架構(gòu)師(附各大廠中臺建設(shè)PPT)
企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案
論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?
企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!
【中臺實(shí)踐】華為大數(shù)據(jù)中臺架構(gòu)分享.pdf
華為如何實(shí)施數(shù)字化轉(zhuǎn)型(附PPT)
超詳細(xì)280頁Docker實(shí)戰(zhàn)文檔!開放下載
