內(nèi)容回顧 | 手寫controller、requestMapping注解,實(shí)現(xiàn)簡(jiǎn)單請(qǐng)求

,早上又五點(diǎn)多起床,不過(guò)雖然累但是很爽,已經(jīng)好久沒有這么多人玩游戲,我記得上次這么多人玩的是狼人殺……總之又勾起了大學(xué)那些日子,確實(shí)很美好!前言
今天我們還是繼續(xù)研究手寫web服務(wù)器,經(jīng)過(guò)昨天一天,服務(wù)器這邊,我已經(jīng)基本實(shí)現(xiàn)了controller注解和requestMapping注解。
服務(wù)器啟動(dòng)的時(shí)候,會(huì)自動(dòng)去掃描帶有controller注解的類,然后根據(jù)controller再去掃描requestMapping注解,最后生成一個(gè)key為url,value為方法的map。
當(dāng)后端接收到前端請(qǐng)求后,根據(jù)請(qǐng)求地址調(diào)用相應(yīng)的方法,如果地址不存在,就返回404。目前,調(diào)用方法這塊目前只實(shí)現(xiàn)了簡(jiǎn)單方法的調(diào)用,帶入?yún)⒌姆椒ㄟ€沒實(shí)現(xiàn),也是同樣的思路,通過(guò)反射直接調(diào)用,然后將返回值寫入響應(yīng)即可。
下面讓我們一起看下我是如何實(shí)現(xiàn)的。
Controller注解
首先定義一個(gè)注解,加了兩個(gè)元注解,一個(gè)是表明我們的注解是加在類上面的,一個(gè)表明我們的類要保留到運(yùn)行時(shí)。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
String value() default "";
}
同時(shí),我們還為注解指定了一個(gè)方法(我不知道這個(gè)應(yīng)該叫屬性還是方法),目的是接收controller的名字。
RequestMapping注解
這個(gè)注解和上面的注解類似,因?yàn)檫@個(gè)類是要加到方法和類上的,所以這個(gè)注解我在target上多加了一個(gè)ElementType.METHOD。value()是用來(lái)接收url的,后期可能還有增加請(qǐng)求方法這個(gè)字段,這個(gè)后期再說(shuō)。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value();
}
加了上面兩個(gè)注解的controller長(zhǎng)這個(gè)樣子:
@Controller("test")
public class TestController {
@RequestMapping("/test")
public String testRequstMapping() {
return "hello syske-boot";
}
}
另一個(gè)
@Controller
public class Test2Controller {
@RequestMapping("/test2")
public String test2() {
return "test2";
}
}
包掃描器
這里才是關(guān)鍵了,所有的類掃描都是基于這里實(shí)現(xiàn)的。后期實(shí)現(xiàn)IoC和Aop也要用到。
現(xiàn)在controller的包路徑是寫死的,后面可以通過(guò)注解加在服務(wù)器主入口上,就和springboot差不多,這個(gè)也很好實(shí)現(xiàn)。
這里的邏輯也很簡(jiǎn)單,就是掃描給定的包路徑,判斷類是否有controller注解,有就把它放進(jìn)controllerSet。
然后再循環(huán)遍歷controllerSet,將加了@RequsetMapping注解的方法放進(jìn)requestMappingMap。
public class SyskeBootContentScanHandler {
private static final Logger logger = LoggerFactory.getLogger(SyskeBootContentScanHandler.class);
private static Set<Class> controllerSet = Sets.newHashSet();
private static Map<String, Method> requestMappingMap = Maps.newHashMap();
private SyskeBootContentScanHandler() {}
/**
* 獲取請(qǐng)求方法Map
* @return
*/
public static Map<String, Method> getRequestMappingMap() {
return requestMappingMap;
}
/**
* 類加載器初始化
*
* @throws IOException
* @throws ClassNotFoundException
*/
public static void init() {
try {
// 掃描conttoller
scanPackage("io.github.syske.boot.controller", controllerSet);
// 掃描controller的RequestMapping
scanRequestMapping(controllerSet);
} catch (Exception e) {
logger.error("syske-boot 啟動(dòng)異常:", e);
}
}
/**
* 掃描controller的RequestMapping
*
* @param controllerSet
*/
private static void scanRequestMapping(Set<Class> controllerSet) {
logger.info("start to scanRequestMapping, controllerSet = {}", controllerSet);
if (controllerSet == null) {
return;
}
controllerSet.forEach(aClass -> {
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
requestMappingMap.put(annotation.value(), method);
}
});
logger.info("scanRequestMapping end, requestMappingMap = {}", requestMappingMap);
}
/**
* 掃描指定的包名下的類
*
* @param packageName
* @param classSet
* @throws IOException
* @throws ClassNotFoundException
*/
private static void scanPackage(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()) {
String[] files = packagePath.list();
for (String fileName : files) {
String className = fileName.substring(0, fileName.lastIndexOf('.'));
String fullClassName = String.format("%s.%s", packageName, className);
classSet.add(Class.forName(fullClassName));
}
}
}
logger.info("scanPackage end, classSet = {}", classSet);
}
}
到這里,包掃描器的邏輯就完了。后期,隨著注解越來(lái)越多,考慮到兼容性,這塊的方法應(yīng)該還需要進(jìn)一步的抽象封裝。
SyskeRequestHandler調(diào)整
上面的掃描最終的目的都是為了響應(yīng)請(qǐng)求的時(shí)候能夠更靈活,也是為這里服務(wù)的,所以需要對(duì)doDispatcher方法調(diào)整。
這里的邏輯也很簡(jiǎn)單,就是根據(jù)請(qǐng)求頭中的地址,去匹配對(duì)應(yīng)的方法,如果地址不存在就返回404。
如果方法存在,拿出對(duì)應(yīng)的方法,反射調(diào)用即可。
現(xiàn)在是在doDispatcher方法內(nèi)部實(shí)例化了controller,后面實(shí)現(xiàn)簡(jiǎn)單IoC之后,就可以從我們的容器中直接獲取實(shí)例了。
private void init() throws IOException, IllegalParameterException {
this.syskeRequest = new SyskeRequest(socket.getInputStream());
this.syskeResponse = new SyskeResponse(socket.getOutputStream());
this.requestMappingMap = SyskeBootContentScanHandler.getRequestMappingMap();
}
public void doDispatcher() throws Exception{
logger.info("請(qǐng)求頭信息:{}", syskeRequest.getRequestHear());
logger.info("請(qǐng)求信息:{}", syskeRequest.getRequestAttributeMap());
String requestMapping = syskeRequest.getRequestHear().getRequestMapping();
if (requestMappingMap.containsKey(requestMapping)) {
Method method = requestMappingMap.get(requestMapping);
logger.debug("method:{}", method);
Class<?> declaringClass = method.getDeclaringClass();
Object o = declaringClass.newInstance();
Object invoke = method.invoke(o);
logger.info("invoke:{}", invoke);
syskeResponse.write(String.format("hello syskeCat, dateTime:%d\n result = %s", System.currentTimeMillis(), invoke));
} else {
syskeResponse.write(404, String.format("resources not found :%d", System.currentTimeMillis()));
}
socket.close();
}
我們看下請(qǐng)求效果,我們分別調(diào)用上面兩個(gè)controller接口試下,先看/test:

再看/test2:

result就是我們方法的返回值,說(shuō)明我們的預(yù)期結(jié)果已經(jīng)完美達(dá)成,后面就是好好打磨優(yōu)化了。
總結(jié)
其實(shí)昨天方法調(diào)用這塊還沒實(shí)現(xiàn),是剛剛寫的,總體來(lái)說(shuō)很簡(jiǎn)單,用到了反射的相關(guān)知識(shí)。下一步考慮先實(shí)現(xiàn)有參方法的調(diào)用問題,然后再實(shí)現(xiàn)IoC??傊?,這個(gè)東西已經(jīng)慢慢變成服務(wù)器該有的樣子,一切還是讓我覺得蠻意外的,所以大家有想法的時(shí)候,一定要努力去做,做了一切才有更多可能,我們一起加油吧!
下面是項(xiàng)目的開源倉(cāng)庫(kù),有興趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推薦你自己動(dòng)個(gè)手,自己寫一下,真的感覺不錯(cuò):
https://github.com/Syske/syske-boot

