1. 每日一例 | 手寫controller、requestMapping注解,實(shí)現(xiàn)簡單請求

        共 11237字,需瀏覽 23分鐘

         ·

        2021-06-02 02:36

        前言

        今天我們還是繼續(xù)研究手寫web服務(wù)器,經(jīng)過昨天一天,服務(wù)器這邊,我已經(jīng)基本實(shí)現(xiàn)了controller注解和requestMapping注解。

        服務(wù)器啟動(dòng)的時(shí)候,會(huì)自動(dòng)去掃描帶有controller注解的類,然后根據(jù)controller再去掃描requestMapping注解,最后生成一個(gè)keyurl,value為方法的map。

        當(dāng)后端接收到前端請求后,根據(jù)請求地址調(diào)用相應(yīng)的方法,如果地址不存在,就返回404。目前,調(diào)用方法這塊目前只實(shí)現(xiàn)了簡單方法的調(diào)用,帶入?yún)⒌姆椒ㄟ€沒實(shí)現(xiàn),也是同樣的思路,通過反射直接調(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.METHODvalue()是用來接收url的,后期可能還有增加請求方法這個(gè)字段,這個(gè)后期再說。

        @Target({ElementType.METHOD, ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface RequestMapping {
            String value();
        }

        加了上面兩個(gè)注解的controller長這個(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)IoCAop也要用到。

        現(xiàn)在controller的包路徑是寫死的,后面可以通過注解加在服務(wù)器主入口上,就和springboot差不多,這個(gè)也很好實(shí)現(xià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() {}

            /**
             * 獲取請求方法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);
            }

        }

        到這里,包掃描器的邏輯就完了。后期,隨著注解越來越多,考慮到兼容性,這塊的方法應(yīng)該還需要進(jìn)一步的抽象封裝。

        SyskeRequestHandler調(diào)整

        上面的掃描最終的目的都是為了響應(yīng)請求的時(shí)候能夠更靈活,也是為這里服務(wù)的,所以需要對doDispatcher方法調(diào)整。

        這里的邏輯也很簡單,就是根據(jù)請求頭中的地址,去匹配對應(yīng)的方法,如果地址不存在就返回404。

        如果方法存在,拿出對應(yīng)的方法,反射調(diào)用即可。

        現(xiàn)在是在doDispatcher方法內(nèi)部實(shí)例化了controller,后面實(shí)現(xià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("請求頭信息:{}", syskeRequest.getRequestHear());
                logger.info("請求信息:{}", 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();
            }

        我們看下請求效果,我們分別調(diào)用上面兩個(gè)controller接口試下,先看/test

        再看/test2

        result就是我們方法的返回值,說明我們的預(yù)期結(jié)果已經(jīng)完美達(dá)成,后面就是好好打磨優(yōu)化了。

        總結(jié)

        其實(shí)昨天方法調(diào)用這塊還沒實(shí)現(xiàn),是剛剛寫的,總體來說很簡單,用到了反射的相關(guān)知識(shí)。下一步考慮先實(shí)現(xiàn)有參方法的調(diào)用問題,然后再實(shí)現(xiàn)IoC??傊?,這個(gè)東西已經(jīng)慢慢變成服務(wù)器該有的樣子,一切還是讓我覺得蠻意外的,所以大家有想法的時(shí)候,一定要努力去做,做了一切才有更多可能,我們一起加油吧!

        下面是項(xiàng)目的開源倉庫,有興趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推薦你自己動(dòng)個(gè)手,自己寫一下,真的感覺不錯(cuò):

        https://github.com/Syske/syske-boot
        - END -


        瀏覽 65
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 午夜福利男女 | 国产做爰又粗又大又 | 香蕉大伊人 | 亚洲无码AV一区二区 | www黄色网址 |