每日一例 | 手寫(xiě)web服務(wù)器:實(shí)現(xiàn)全包掃描和簡(jiǎn)易IOC

前言
最近寫(xiě)web服務(wù)器優(yōu)點(diǎn)上頭,根本停不下來(lái),對(duì),我就是卷王本卷,昨天實(shí)現(xiàn)了前端有參方法的調(diào)用,趁著這股熱勁,今天我們來(lái)實(shí)現(xiàn)下IOC容器,從原理和實(shí)現(xiàn)上來(lái)講,都不難了,因?yàn)槲覀冊(cè)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">controller和requestMapping注解實(shí)現(xiàn)的時(shí)候已經(jīng)驗(yàn)證過(guò)了,沿著同樣的思路搞就行了。好了話(huà)不多說(shuō),直接開(kāi)整。
開(kāi)整
同樣是基于我們之前的代碼實(shí)現(xiàn),感興趣的小伙伴可以去看完整代碼,文末有項(xiàng)目地址。
Serive注解
寫(xiě)這個(gè)注解主要是為了測(cè)試,本來(lái)要實(shí)現(xiàn)Component,一時(shí)半會(huì)沒(méi)想起來(lái)單詞如何拼寫(xiě),所以就選擇了service。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String value() default "";
}
注解的內(nèi)容和前面的都差不多,很簡(jiǎn)單,就是加了個(gè)target和Retention,然后把這個(gè)類(lèi)加在我們的service上即可:
@Service
public class TestService {
public void helloIoc(String name) {
System.out.println("hello ioc, " + name);
}
}
優(yōu)化包掃描器
之前的包掃描器只能掃描一層目錄,這樣每層都指定包名就很繁瑣,所以我把它簡(jiǎn)單優(yōu)化了下,這樣只用輸入根包名,就可以實(shí)現(xiàn)整包掃描。
這里用到了遞歸,當(dāng)路徑是文件夾時(shí),就會(huì)再次調(diào)用自己。
private static void scanPackageToIoc(String packageName, Set<Class> classSet)
throws IOException, ClassNotFoundException {
logger.info("start to scanPackage, packageName = {}", packageName);
Enumeration<URL> classes = ClassLoader.getSystemResources(packageName.replace('.', '/'));
while (classes.hasMoreElements()) {
URL url = classes.nextElement();
File packagePath = new File(url.getPath());
if (packagePath.isDirectory()) {
File[] files = packagePath.listFiles();
for (File file : files) {
String fileName = file.getName();
if (file.isDirectory()) {
String newPackageName = String.format("%s.%s", packageName, fileName);
scanPackageToIoc(newPackageName, classSet);
} else {
String className = fileName.substring(0, fileName.lastIndexOf('.'));
String fullClassName = String.format("%s.%s", packageName, className);
classSet.add(Class.forName(fullClassName));
}
}
} else {
String className = url.getPath().substring(0, url.getPath().lastIndexOf('.'));
String fullClassName = String.format("%s.%s", packageName, className);
classSet.add(Class.forName(fullClassName));
}
}
logger.info("scanPackage end, classSet = {}", classSet);
}
測(cè)試
我們指定個(gè)包路徑測(cè)試下,順便測(cè)試下通過(guò)Ioc拿到對(duì)象,實(shí)現(xiàn)方法調(diào)用:
scanPackageToIoc("io.github.syske.boot", classSet);
logger.info("classSet = {}", classSet);
scanRequestMapping(classSet);
logger.info("requestMappingMap = {}", requestMappingMap);
initSyskeBootContent(classSet);
logger.info("contentMap = {}", contentMap);
Object o = contentMap.get("io.github.syske.boot.service.TestService");
if (o instanceof TestService) {
((TestService)o).helloIoc("云中志");
}
包掃描完后,生成一個(gè)class的set集合;
通過(guò)scanRequestMapping方法從class集合中拿出controller的類(lèi),并生成requestMapping和方法的集合;
通過(guò)initSyskeBootContent方法從class集合中拿出service的類(lèi),并創(chuàng)建實(shí)例,放進(jìn)contentMap,這樣在你需要實(shí)例的時(shí)候,直接通過(guò)全類(lèi)名(包名 + 類(lèi)名)就可以拿到,然后執(zhí)行你想要執(zhí)行的方法即可。
看下效果:
883 [main] INFO i.g.s.b.h.SyskeBootContentScanHandler - scanRequestMapping end, requestMappingMap = {/sayHello2=public java.lang.String io.github.syske.boot.controller.TestController.test(java.lang.String,java.lang.String), /sayHello=public java.lang.String io.github.syske.boot.controller.TestController.testName(java.lang.String), /test2=public java.lang.String io.github.syske.boot.controller.Test2Controller.test2(), /test=public java.lang.String io.github.syske.boot.controller.TestController.testRequstMapping()}
883 [main] INFO i.g.s.b.h.SyskeBootContentScanHandler - requestMappingMap = {/sayHello2=public java.lang.String io.github.syske.boot.controller.TestController.test(java.lang.String,java.lang.String), /sayHello=public java.lang.String io.github.syske.boot.controller.TestController.testName(java.lang.String), /test2=public java.lang.String io.github.syske.boot.controller.Test2Controller.test2(), /test=public java.lang.String io.github.syske.boot.controller.TestController.testRequstMapping()}
899 [main] INFO i.g.s.b.h.SyskeBootContentScanHandler - contentMap = {io.github.syske.boot.service.TestService=io.github.syske.boot.service.TestService@2812cbfa}
hello ioc, 云中志
方法完美被執(zhí)行,想法實(shí)現(xiàn),打完收工。
總結(jié)
又是看起來(lái)復(fù)雜、寫(xiě)起來(lái)不難的一次需求,但是通過(guò)這樣的方式,能讓你更深入的理解spring的ioc原理,當(dāng)然原理可能會(huì)有差異,但是也大同小異,再退一步來(lái)說(shuō),就算不一樣,面試的時(shí)候,面試官問(wèn)你懂不懂Ioc底層原理,你也可以大膽地我自己做過(guò)類(lèi)似于Ioc東西,這一點(diǎn)就很牛皮了。
最近內(nèi)卷這個(gè)詞特別火,所有的平臺(tái)都在討論,但是IT這個(gè)行業(yè)不早都在內(nèi)卷了嗎?面試造火箭,進(jìn)門(mén)擰螺絲,太卷了。
前幾天看到一個(gè)段子,說(shuō)是一個(gè)公司招司機(jī),面試官問(wèn)司機(jī),你知道汽車(chē)的啟動(dòng)過(guò)程嗎?能大概說(shuō)一下嗎?然后司機(jī)巴拉巴拉說(shuō)了一大堆:

段子原文地址,有興趣的小伙伴自己去看:
https://blog.csdn.net/dfskhgalshgkajghljgh/article/details/106457745
下面是項(xiàng)目的開(kāi)源倉(cāng)庫(kù),有興趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推薦你自己動(dòng)個(gè)手,自己寫(xiě)一下,真的感覺(jué)不錯(cuò):
https://github.com/Syske/syske-boot

