java注解處理器學(xué)習(xí)在編譯期修改語(yǔ)法樹(shù)教程
從需求說(shuō)起
由于相關(guān)政策,最近公司安全部要求各系統(tǒng)在rpc接口調(diào)用的交互過(guò)程中把相應(yīng)的參數(shù)及結(jié)果以相應(yīng)的格式發(fā)送到安全部統(tǒng)一記錄,例如參數(shù)或結(jié)果含手機(jī)號(hào)和郵箱則格式如:“mail:axxx@126.com,phone:183xxxx1967”,其它系統(tǒng)信息等先忽略。
以便在數(shù)據(jù)泄露時(shí)可據(jù)此分析出數(shù)據(jù)的泄露源頭,以及若有黑客攻克有些接口時(shí)公司能有跡可循。
總體架構(gòu)是各個(gè)接口把入?yún)⒑徒Y(jié)果打印日志,然后由統(tǒng)一的日志收集器收集日志通過(guò)mq發(fā)送到安全部。這樣每個(gè)系統(tǒng)只用在接口中添加參數(shù)和結(jié)果的打日志代碼。
添加打印日志代碼的方案
第一種方案,硬編碼
即直接在接口中編寫(xiě)打印日志的代碼。這種工作量太大,公司各個(gè)部門(mén),以往積累了眾多的項(xiàng)目,這樣改造的工作量太大。
第二種方案,AOP
利用aop框架,在切面類中打印日志??梢允褂胹pring 支持的aop功能或其他aop框架。
這個(gè)方案應(yīng)該來(lái)說(shuō)改動(dòng)及工作量都大大降低,公司也是采用的這種方案。但是其弊端也很明顯,
一、是對(duì)框架的依賴(如用spring aop的話則非spring項(xiàng)目則不適用)
二、就是不同的項(xiàng)目或接口,入?yún)⒒蚪Y(jié)果變量名不同,如手機(jī)號(hào):有的叫mobilePhone, 有的叫telephone等;但打印日志時(shí)要統(tǒng)一打印,如:phone:183xxxx1967; 所以要在參數(shù)上加注解,以表明打印日志時(shí)的名稱。這個(gè)重復(fù)工作量也不小。
第三種方案,修改class文件
針對(duì)第二種方案的弊端,我設(shè)計(jì)出這第三種方案。
利用相關(guān)技術(shù),直接修改class文件,在接口中添加打印日志的字節(jié)碼。例如Javassist,asm等技術(shù)。
通過(guò)調(diào)研,在編譯期通過(guò)修改語(yǔ)法樹(shù)來(lái)達(dá)到修改class文件的效果,這種對(duì)用戶來(lái)說(shuō)完全透明,不依賴任何框架。針對(duì)弊端二則發(fā)明名稱分析模塊,讓程序自動(dòng)分析出參數(shù)的含義,從而避免了手工添加注解的麻煩。
下面就具體說(shuō)明第三種方案的實(shí)現(xiàn):
利用JDK的注解處理器,可在編譯期間對(duì)注解處理,可以讀取、修改、添加抽象語(yǔ)法樹(shù)中的任意元素。
注解處理器是JDK1.6開(kāi)始提供的功能,利用注解處理器可以干涉編譯器的行為,只要有足夠的創(chuàng)意,可以利用注解處理器實(shí)現(xiàn)許多原本只能在編碼中完成的事情。
注解處理器的用法:

1、實(shí)現(xiàn)AbstractProcessor
實(shí)現(xiàn)init和process方法
顧名思義,init是完成一些初始化工作;process完成具體的邏輯處理。后邊會(huì)有具體的例子說(shuō)明。
2、添加注解
@SupportedAnnotationTypes 指定此注解處理器支持的注解,可用*指定所有注解
@SupportedSourceVersion 指定支持的java的版本
注解實(shí)例:

在process方法中可獲取到注解有@Safety的類和方法。
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Safety.class);
然后可遍歷方法,獲取到方法的入?yún)ⅲ治鋈雲(yún)?,給方法的語(yǔ)法樹(shù)添加打印參數(shù)日志的代碼。對(duì)于方法的結(jié)果是同樣的道理。
關(guān)于對(duì)語(yǔ)法樹(shù)的操作,網(wǎng)上資料相對(duì)較少及較為片段,介紹起來(lái)篇幅略長(zhǎng),故放在最后面進(jìn)行介紹。
名稱分析模塊的思想及設(shè)計(jì)
剩下的一個(gè)關(guān)鍵問(wèn)題是如何把不同的參數(shù)名統(tǒng)一成打印日志時(shí)的名稱,例如參數(shù)名為mobilePhone或telephone,但要打印的是phone。如果在注解屬性中指定的話,通過(guò)注解可以獲取到,但是當(dāng)接口或參數(shù)很多的情況下也是一件重復(fù)性的力氣活。
故我設(shè)計(jì)出一種不讓開(kāi)發(fā)人員手動(dòng)指定名稱的方案,既對(duì)老的項(xiàng)目修改的少,又減輕開(kāi)發(fā)人員的工作量,對(duì)新項(xiàng)目的應(yīng)用也是高效率的。
如圖:

詞庫(kù)存儲(chǔ)(可用類的靜態(tài)字段存儲(chǔ))需要打印的日志名稱及其對(duì)應(yīng)的詞根及單詞。如:

上圖紅框?yàn)榇蛴∪罩緯r(shí)要打印的名稱。綠框中為詞根及單詞:如若業(yè)務(wù)參數(shù)有用postbox作為郵箱變量名的則也可把postbox加入到mail的詞庫(kù)中。
這樣當(dāng)業(yè)務(wù)參數(shù)為mobilePhone或telephone時(shí),名稱分析模塊能夠分析出參數(shù)名包含phone詞根,從而得到對(duì)應(yīng)的打印日志名“phone”;這就要求業(yè)務(wù)參數(shù)的名義要有具體的含義,不能隨便字母組合沒(méi)有含義的詞語(yǔ),這應(yīng)該也是每個(gè)公司開(kāi)發(fā)時(shí)的基本要求。
這里只是舉例一個(gè)簡(jiǎn)單的可行性方案,名稱分析模塊也可利用AI技術(shù),根據(jù)輸入的變量名利用智能技術(shù)分析出此變量名的含義。
語(yǔ)法樹(shù)的操作:
下面對(duì)語(yǔ)法樹(shù)的操作進(jìn)行詳細(xì)的說(shuō)明,這里需要提到三個(gè)類:
JavacTrees 提供了待處理的抽象語(yǔ)法樹(shù)TreeMaker 封裝了創(chuàng)建AST節(jié)點(diǎn)的一些方法Names 提供了創(chuàng)建標(biāo)識(shí)符的方法
可在init方法中對(duì)這三個(gè)類初始化,以便在process方法中利用它們對(duì)語(yǔ)法樹(shù)進(jìn)行操作。如圖:

AST(抽象語(yǔ)法樹(shù))是一種用來(lái)描述程序代碼語(yǔ)法結(jié)構(gòu)的樹(shù)形表示方式,語(yǔ)法樹(shù)的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu),例如包、類型、修飾符、運(yùn)算符、接口、返回值,甚至代碼注釋等都可以是一個(gè)語(yǔ)法結(jié)構(gòu)。
JCTree的介紹

JCTree是語(yǔ)法樹(shù)元素的基類。
如上圖,它包含兩個(gè)屬性,
字段type表示語(yǔ)法結(jié)構(gòu)的類型
字段pos用于指明當(dāng)前語(yǔ)法樹(shù)節(jié)點(diǎn)(JCTree)在語(yǔ)法樹(shù)中的位置,因此我們不能直接用new關(guān)鍵字來(lái)創(chuàng)建語(yǔ)法樹(shù)節(jié)點(diǎn),即使創(chuàng)建了也沒(méi)有意義,而要用TreeMaker來(lái)進(jìn)行操作。
重點(diǎn)介紹幾個(gè)JCTree的子類:
JCStatement:聲明語(yǔ)法樹(shù)節(jié)點(diǎn),常見(jiàn)的子類如下 JCBlock:語(yǔ)句塊JCReturn:return語(yǔ)句JCClassDecl:類定義JCVariableDecl:字段/變量定義JCIf: if語(yǔ)句
2.JCMethodDecl:方法定義語(yǔ)法樹(shù)節(jié)點(diǎn)
3.JCModifiers:訪問(wèn)標(biāo)志語(yǔ)法樹(shù)節(jié)點(diǎn)
4.JCExpression:表達(dá)式語(yǔ)法樹(shù)節(jié)點(diǎn),常見(jiàn)的子類如下
JCAssign:賦值語(yǔ)句JCAssignOp:+=JCIdent:標(biāo)識(shí)符,可以是變量,類型,關(guān)鍵字等等JCLiteral: 字面量表達(dá)式,如123, “string”等JCBinary:二元操作符
JCTree的子類很多,大部分可以從字面上看出其意義

如上圖,在jdk1.8.0_65里JCTree有58個(gè)子類。
下面具體介紹對(duì)語(yǔ)法樹(shù)的操作。
TreeMaker
TreeMaker創(chuàng)建語(yǔ)法樹(shù)節(jié)點(diǎn)的所有方法,創(chuàng)建時(shí)會(huì)為創(chuàng)建出來(lái)的JCTree設(shè)置pos字段,所以必須用上下文相關(guān)的TreeMaker對(duì)象來(lái)創(chuàng)建語(yǔ)法樹(shù)節(jié)點(diǎn),而不能直接new語(yǔ)法樹(shù)節(jié)點(diǎn)。
TreeMaker.Modifiers
該方法用于創(chuàng)建訪問(wèn)標(biāo)志語(yǔ)法樹(shù)節(jié)點(diǎn)(JCModifiers),源碼如下:
public JCModifiers Modifiers(long flags, List<JCAnnotation> annotations) {
JCModifiers tree = new JCModifiers(flags, annotations);
boolean noFlags = (flags & Flags.StandardFlags) == 0;
tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
return tree;
}
public JCModifiers Modifiers(long flags) {
return Modifiers(flags, List.<JCAnnotation>nil());
}參數(shù)解釋:
flags:訪問(wèn)標(biāo)志
annotations:注解列表
其中flags可以用枚舉類型com.sun.tools.javac.code.Flags,且支持相加(Flags的值按二進(jìn)制設(shè)計(jì)),如圖:

用法示例:treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);
TreeMaker.ClassDef
該方法用于創(chuàng)建類定義語(yǔ)法樹(shù)節(jié)點(diǎn)(JCClassDef),源碼如下:
public JCClassDecl ClassDef(JCModifiers mods,
Name name,
List<JCTypeParameter> typarams,
JCTree extending,
List<JCExpression> implementing,
List<JCTree> defs)
{
JCClassDecl tree = new JCClassDecl(mods,
name,
typarams,
extending,
implementing,
defs,
null);
tree.pos = pos;
return tree;
}參數(shù)解釋:
mods:訪問(wèn)標(biāo)志
name:方法名
restype:返回類型
typarams:泛型參數(shù)列表
params:參數(shù)列表
thrown:異常聲明列表
body:方法體
defaultValue:默認(rèn)方法(可能是interface中的那個(gè)default)
m:方法符號(hào)
mtype:方法類型。包含多種類型,泛型參數(shù)類型、方法參數(shù)類型,異常參數(shù)類型、返回參數(shù)類型
TreeMaker.MethodDef
用于創(chuàng)建方法定義語(yǔ)法樹(shù)節(jié)點(diǎn)(JCMethodDecl),源碼如下:
public JCMethodDecl MethodDef(JCModifiers mods,
Name name,
JCExpression restype,
List<JCTypeParameter> typarams,
List<JCVariableDecl> params,
List<JCExpression> thrown,
JCBlock body,
JCExpression defaultValue)
{
JCMethodDecl tree = new JCMethodDecl(mods,
name,
restype,
typarams,
params,
thrown,
body,
defaultValue,
null);
tree.pos = pos;
return tree;
}參數(shù)解釋:
mods:訪問(wèn)標(biāo)志
name:方法名
restype:返回類型
typarams:泛型參數(shù)列表
params:參數(shù)列表
thrown:異常聲明列表
body:方法體
defaultValue:默認(rèn)方法(可能是interface中的那個(gè)default)
m:方法符號(hào)
mtype:方法類型。包含多種類型,泛型參數(shù)類型、方法參數(shù)類型,異常參數(shù)類型、返回參數(shù)類型
TreeMaker.VarDef
用于創(chuàng)建字段/變量定義語(yǔ)法樹(shù)節(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;
}參數(shù)解釋:
mods:訪問(wèn)標(biāo)志
vartype:類型
init:初始化語(yǔ)句
v:變量符號(hào)
TreeMaker.Ident
用于創(chuàng)建標(biāo)識(shí)符語(yǔ)法樹(shù)節(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
用于創(chuàng)建return語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)(JCReturn)
TreeMaker.NewClass
用于創(chuàng)建new語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)(JCNewClass),源碼如下:
public JCNewClass NewClass(JCExpression encl,
List<JCExpression> typeargs,
JCExpression clazz,
List<JCExpression> args,
JCClassDecl def)
{
JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
tree.pos = pos;
return tree;
}參數(shù)解釋:
encl:不太明白此參數(shù)含義
typeargs:參數(shù)類型列表
clazz:待創(chuàng)建對(duì)象的類型
args:參數(shù)列表
def:類定義
TreeMaker.Select
用于創(chuàng)建域訪問(wèn)/方法訪問(wèn)(當(dāng)是方法訪問(wèn)時(shí),常和方法的調(diào)用TreeMaker.Apply一起使用語(yǔ)法樹(shù)節(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);
}參數(shù)解釋:
selected:.運(yùn)算符左邊的表達(dá)式
selector:.運(yùn)算符右邊的名字
TreeMaker.Apply
用于創(chuàng)建方法調(diào)用語(yǔ)法樹(shù)節(jié)點(diǎn)(JCMethodInvocation),源碼如下:
public JCMethodInvocation Apply(List<JCExpression> typeargs,
JCExpression fn,
List<JCExpression> args)
{
JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
tree.pos = pos;
return tree;
}參數(shù)解釋:
typeargs:參數(shù)類型列表
fn:調(diào)用語(yǔ)句
args:參數(shù)列表
TreeMaker.Assign
用于創(chuàng)建賦值語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)(JCAssign),源碼如下:
public JCAssign Assign(JCExpression lhs, JCExpression rhs) {
JCAssign tree = new JCAssign(lhs, rhs);
tree.pos = pos;
return tree;
}參數(shù)解釋:
lhs:賦值語(yǔ)句左邊表達(dá)式
rhs:賦值語(yǔ)句右邊表達(dá)式
TreeMaker.Exec
用于創(chuàng)建可執(zhí)行語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)(JCExpressionStatement),源碼如下:
public JCExpressionStatement Exec(JCExpression expr) {
JCExpressionStatement tree = new JCExpressionStatement(expr);
tree.pos = pos;
return tree;
}TreeMaker.Block
用于創(chuàng)建組合語(yǔ)句語(yǔ)法樹(shù)節(jié)點(diǎn)(JCBlock),源碼如下:
public JCBlock Block(long flags, List<JCStatement> stats) {
JCBlock tree = new JCBlock(flags, stats);
tree.pos = pos;
return tree;
}參數(shù)解釋:
flags:訪問(wèn)標(biāo)志
stats:語(yǔ)句列表
用法實(shí)例:
下面來(lái)介紹一下實(shí)例來(lái)加深對(duì)API用法的理解:
1、根據(jù)字符串獲取Name,(利用Names的fromString靜態(tài)方法)
private Name getNameFromString(String s) { return names.fromString(s); }
2、創(chuàng)建變量語(yǔ)句
private JCTree.JCVariableDecl makeVarDef(JCTree.JCModifiers modifiers, String name, JCTree.JCExpression vartype, JCTree.JCExpression init) {
return treeMaker.VarDef(
modifiers,
getNameFromString(name), //名字
vartype, //類型
init //初始化語(yǔ)句
);
}3、創(chuàng)建 域/方法 的多級(jí)訪問(wèn), 方法的標(biāo)識(shí)只能是最后一個(gè)
例如: java.lang.System.out.println
private JCTree.JCExpression memberAccess(String components) {
String[] componentArray = components.split("\\.");
JCTree.JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
for (int i = 1; i < componentArray.length; i++) {
expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
}
return expr;
}4、聲明變量并賦值(利用以上包裝的方法)
JCTree.JCVariableDecl var = makeVarDef(treeMaker.Modifiers(0), "xiao", memberAccess("java.lang.String"), treeMaker.Literal("methodName"));生成語(yǔ)句為:String xiao = "methodName";
5、給變量賦值
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
return treeMaker.Exec(
treeMaker.Assign(
lhs,
rhs
)
);
}
makeAssignment(treeMaker.Ident(getNameFromString("xiao")), treeMaker.Literal("assignment test"));生成的賦值語(yǔ)句為:xiao = "assignment test";
6、兩個(gè)字符串字面量相加并賦值
treeMaker.Exec(
treeMaker.Assign(treeMaker.Ident(getNameFromString("xiao")),
treeMaker.Binary(
JCTree.Tag.PLUS,
treeMaker.Literal("-Binary operator one"),
treeMaker.Literal("-Binary operator two")
))
);生成語(yǔ)句為:xiao = "-Binary operator one" + "-Binary operator two";
編譯器會(huì)對(duì)此語(yǔ)句進(jìn)行優(yōu)化,因?yàn)閮蓚€(gè)字面量在編譯器即能確定,所以編譯器會(huì)把此語(yǔ)句優(yōu)化為:“xiao = "-Binary operator one-Binary operator two";”
7、+=語(yǔ)句
treeMaker.Exec(
treeMaker.Assignop(
JCTree.Tag.PLUS_ASG,
treeMaker.Ident(getNameFromString("xiao")),
treeMaker.Literal("-Assignop test")
)
);生成語(yǔ)句為:xiao += "-Assignop test";
8、聲明整型變量并賦值
makeVarDef(treeMaker.Modifiers(0), "zhen", memberAccess("java.lang.Integer"), treeMaker.Literal(1));生成語(yǔ)句為:Integer zhen = 1;
9、++語(yǔ)句
treeMaker.Exec(
treeMaker.Unary(
JCTree.Tag.PREINC,
treeMaker.Ident(getNameFromString("zhen"))
)
);生成語(yǔ)句:zhen++;
10、加法語(yǔ)句
treeMaker.Exec(
treeMaker.Unary(
JCTree.Tag.PREINC,
treeMaker.Ident(getNameFromString("zhen"))
)
);生成語(yǔ)句:zhen = zhen + 10;
11、方法調(diào)用(以輸出語(yǔ)句舉例)
treeMaker.Exec(
treeMaker.Assign(
treeMaker.Ident(getNameFromString("zhen")),
treeMaker.Binary(
JCTree.Tag.PLUS,
treeMaker.Ident(getNameFromString("zhen")),
treeMaker.Literal(10)
))
);生成語(yǔ)句:System.out.println(xiao);
12、方法調(diào)用,輸出字符串
JCTree.JCExpressionStatement printVar = treeMaker.Exec(treeMaker.Apply(
List.of(memberAccess("java.lang.String")),//參數(shù)類型
memberAccess("java.lang.System.out.println"),
List.of(treeMaker.Ident(getNameFromString("xiao")))
)
);生成語(yǔ)句:System.out.println("xiao test zhen");
13、if語(yǔ)句
treeMaker.If(
treeMaker.Binary(
JCTree.Tag.LT,
treeMaker.Ident(getNameFromString("zhen")),
treeMaker.Literal(10)
),
printVar,
printLiteral
);生成語(yǔ)句:
if (zhen < 10) {
System.out.println(xiao);
} else {
System.out.println("xiao test zhen");
}
14、if語(yǔ)句(null判斷)
treeMaker.If(
treeMaker.Parens(
treeMaker.Binary(
JCTree.Tag.NE,
treeMaker.Ident(getNameFromString("xiao")),
treeMaker.Literal(TypeTag.BOT, null))
),
printVar,
printLiteral
)生成語(yǔ)句:
if (xiao != null) {
System.out.println(xiao);
} else {
System.out.println("xiao test zhen");
}
以上列出了一下常用的語(yǔ)句的語(yǔ)法樹(shù)操作方法,希望對(duì)理解操作語(yǔ)法樹(shù)有幫助。筆者也正在研究中,對(duì)編譯原理了解的話,學(xué)起來(lái)也就容易多了。
推薦幾本書(shū),看完的話定定會(huì)受益匪淺:《編譯原理(高清龍書(shū)中文版)》、《兩周自制腳本語(yǔ)言》、《現(xiàn)代編譯器的Java實(shí)現(xiàn)(第二版)》。
更多關(guān)于java編譯期修改語(yǔ)法樹(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
多jdk環(huán)境下指定springboot外部配置文件詳解
這篇文章主要為大家介紹了多jdk環(huán)境下指定springboot外部配置文件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Java StringBuilder 實(shí)現(xiàn)原理全攻略
StringBuilder 是 Java 提供的可變字符序列類,位于 java.lang 包中,專門(mén)用于高效處理字符串的拼接和修改操作,本文給大家介紹Java StringBuilder 實(shí)現(xiàn)原理深度解析,感興趣的朋友跟隨小編一起看看吧2025-09-09
解決SpringBoot運(yùn)行Test時(shí)報(bào)錯(cuò):SpringBoot Unable to find
這篇文章主要介紹了SpringBoot運(yùn)行Test時(shí)報(bào)錯(cuò):SpringBoot Unable to find a @SpringBootConfiguration,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java數(shù)據(jù)結(jié)構(gòu)之圖的基礎(chǔ)概念和數(shù)據(jù)模型詳解
在現(xiàn)實(shí)生活中,有許多應(yīng)用場(chǎng)景會(huì)包含很多點(diǎn)以及點(diǎn)點(diǎn)之間的連接,而這些應(yīng)用場(chǎng)景我們都可以用即將要學(xué)習(xí)的圖這種數(shù)據(jù)結(jié)構(gòu)去解決。本文主要介紹了圖的基礎(chǔ)概念和數(shù)據(jù)模型,感興趣的可以了解一下2022-11-11
SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式
這篇文章主要介紹了SpringBoot中注解實(shí)現(xiàn)定時(shí)任務(wù)的兩種方式,SpringBoot 定時(shí)任務(wù)是一種在SpringBoot應(yīng)用中自動(dòng)執(zhí)行任務(wù)的機(jī)制,通過(guò)使用Spring框架提供的@Scheduled注解,我們可以輕松地創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下2023-10-10
Java跨session實(shí)現(xiàn)token接口測(cè)試過(guò)程圖解
這篇文章主要介紹了Java跨session實(shí)現(xiàn)token接口測(cè)試過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
FeignClientFactoryBean創(chuàng)建動(dòng)態(tài)代理詳細(xì)解讀
這篇文章主要介紹了FeignClientFactoryBean創(chuàng)建動(dòng)態(tài)代理詳細(xì)解讀,當(dāng)直接進(jìn)去注冊(cè)的方法中,一步步放下走,都是直接放bean的定義信息中放入值,然后轉(zhuǎn)成BeanDefinitionHolder,最后在注冊(cè)到IOC容器中,需要的朋友可以參考下2023-11-11
mybatis初始化SqlSessionFactory失敗的幾個(gè)原因分析
這篇文章主要介紹了mybatis初始化SqlSessionFactory失敗的幾個(gè)原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
microlog4android將Android Log日志寫(xiě)到SD卡文件中實(shí)現(xiàn)方法
這篇文章主要介紹了microlog4android將Android Log日志寫(xiě)到SD卡文件中實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10

