一個demo展示如何應用java動態程式碼載入技術
一。前言
說到語言的動態性,這個是指令碼語言的一大優勢,沒有中間環節,原始碼即時執行。大家一般不會把它和java聯絡在一起,從java本身語言來看,java確實具有指令碼語言的一些特性,即可以即時編譯和執行。java相關的動態載入技術也非常的成熟,在android客戶端,可以用這種技術熱修復,動態替換有bug的相關程式碼;在服務端也有廣泛的應用,像java的外掛技術,感興趣的可以參考
https://
github。com/pf4j/pf4j
;包括我們使用的開發工具,比如idea的熱替換,幫助我們在開發過程中,修改完程式碼熱替換檔案,不用每次都重啟專案。但是java的熱替換有一些限制,比如不能修改方法的簽名,只能修改方法體裡面的內容。
二。demo
示例程式碼是簡化後的,完整的參考
https://
github。com/jsdman/dynam
ic-code
我們結合springboot框架簡單展示下動態載入程式碼技術的具體實現。動態載入程式碼的核心是類載入引擎,該類的作用是動態載入類的原始檔,編譯成class檔案並且載入到jvm中。
public
class
DynamicEngine
{
public
Class
<?>
compile
(
String
className
,
String
javaCodes
)
{
if
(
classReloadNum
。
containsKey
(
className
))
{
classReloadNum
。
get
(
className
)。
incrementAndGet
();
}
else
{
classReloadNum
。
put
(
className
,
new
AtomicLong
(
startTime
));
}
String
newClassName
=
className
+
“_”
+
classReloadNum
。
get
(
className
);
String
[]
classNameArr
=
className
。
split
(
“\\。”
);
String
[]
newClassNameArr
=
newClassName
。
split
(
“\\。”
);
String
newJavaCode
=
javaCodes
。
replaceAll
(
classNameArr
[
classNameArr
。
length
-
1
],
newClassNameArr
[
newClassNameArr
。
length
-
1
]);
JavaCompiler
compiler
=
ToolProvider
。
getSystemJavaCompiler
();
StandardJavaFileManager
fileManager
=
compiler
。
getStandardFileManager
(
null
,
null
,
null
);
StrSrcJavaObject
srcObject
=
new
StrSrcJavaObject
(
newClassName
,
newJavaCode
);
Iterable
<?
extends
JavaFileObject
>
fileObjects
=
Collections
。
singletonList
(
srcObject
);
String
flag
=
“-d”
;
String
outDir
;
File
classPath
;
try
{
classPath
=
new
File
(
Objects
。
requireNonNull
(
Thread
。
currentThread
()。
getContextClassLoader
()。
getResource
(
“”
))。
toURI
());
outDir
=
classPath
。
getAbsolutePath
()
+
File
。
separator
;
Iterable
<
String
>
options
=
Arrays
。
asList
(
flag
,
outDir
);
JavaCompiler
。
CompilationTask
task
=
compiler
。
getTask
(
null
,
fileManager
,
null
,
options
,
null
,
fileObjects
);
boolean
result
=
task
。
call
();
if
(
result
)
{
try
{
return
myClassLoader
。
loadClass
(
newClassName
);
}
catch
(
ClassNotFoundException
e
)
{
System
。
out
。
println
(
“載入失敗!”
);
e
。
printStackTrace
();
}
}
}
catch
(
URISyntaxException
e
)
{
e
。
printStackTrace
();
}
return
null
;
}
}
再定義一個介面類,我們的動態載入的類都是繼承於這個介面。
public
interface
BaseSay
{
String
say
();
}
寫個controller提供動態載入的入口和我們動態載入類的執行動作。
@RestController
public
class
IndexController
{
@Resource
private
DynamicEngine
dynamicEngine
;
private
BaseSay
baseSay
;
@PostMapping
(
“/loadClass”
)
public
String
loadClass
(
String
className
,
String
code
)
throws
IllegalAccessException
,
InstantiationException
{
Class
<
BaseSay
>
baseSayClass
=
(
Class
<
BaseSay
>)
dynamicEngine
。
compile
(
className
,
code
);
baseSay
=
baseSayClass
。
newInstance
();
return
“ok”
;
}
@GetMapping
(
“/say”
)
public
String
say
()
{
if
(
baseSay
==
null
)
{
return
“baseSay was null!”
;
}
return
baseSay
。
say
();
}
再做個html頁面去測試下我們的動態載入好使不:
我們點選動態載入按鈕,然後點選say:
say返回字串修改下,動態載入:
我們點選動態載入按鈕,然後點選say,可以看到返回值是我們動態定義的類了。
也可以在程式碼裡面加任意的其他程式碼,比如我想在執行前列印一些日誌:
我們點選動態載入按鈕,然後點選say,可以看到控制檯打印出我們的程式碼了
三。demo地址
四。應用場景
大家可以一起討論下,是否有一些場景可以應用此方式進行開發。