<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 11237字,需瀏覽 23分鐘

           ·

          2021-06-02 02:36

          前言

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

          服務器啟動的時候,會自動去掃描帶有controller注解的類,然后根據(jù)controller再去掃描requestMapping注解,最后生成一個keyurlvalue為方法的map

          當后端接收到前端請求后,根據(jù)請求地址調用相應的方法,如果地址不存在,就返回404。目前,調用方法這塊目前只實現(xiàn)了簡單方法的調用,帶入參的方法還沒實現(xiàn),也是同樣的思路,通過反射直接調用,然后將返回值寫入響應即可。

          下面讓我們一起看下我是如何實現(xiàn)的。

          Controller注解

          首先定義一個注解,加了兩個元注解,一個是表明我們的注解是加在類上面的,一個表明我們的類要保留到運行時。

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          public @interface Controller {
              String value() default "";
          }

          同時,我們還為注解指定了一個方法(我不知道這個應該叫屬性還是方法),目的是接收controller的名字。

          RequestMapping注解

          這個注解和上面的注解類似,因為這個類是要加到方法和類上的,所以這個注解我在target上多加了一個ElementType.METHODvalue()是用來接收url的,后期可能還有增加請求方法這個字段,這個后期再說。

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

          加了上面兩個注解的controller長這個樣子:

          @Controller("test")
          public class TestController {

              @RequestMapping("/test")
              public String testRequstMapping() {
                  return "hello syske-boot";
              }
          }

          另一個

          @Controller
          public class Test2Controller {

              @RequestMapping("/test2")
              public String test2() {
                  return "test2";
              }
          }

          包掃描器

          這里才是關鍵了,所有的類掃描都是基于這里實現(xiàn)的。后期實現(xiàn)IoCAop也要用到。

          現(xiàn)在controller的包路徑是寫死的,后面可以通過注解加在服務器主入口上,就和springboot差不多,這個也很好實現(xiàn)。

          這里的邏輯也很簡單,就是掃描給定的包路徑,判斷類是否有controller注解,有就把它放進controllerSet

          然后再循環(huán)遍歷controllerSet,將加了@RequsetMapping注解的方法放進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 啟動異常:", 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);
              }

          }

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

          SyskeRequestHandler調整

          上面的掃描最終的目的都是為了響應請求的時候能夠更靈活,也是為這里服務的,所以需要對doDispatcher方法調整。

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

          如果方法存在,拿出對應的方法,反射調用即可。

          現(xiàn)在是在doDispatcher方法內部實例化了controller,后面實現(xiàn)簡單IoC之后,就可以從我們的容器中直接獲取實例了。

          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();
              }

          我們看下請求效果,我們分別調用上面兩個controller接口試下,先看/test

          再看/test2

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

          總結

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

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

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


          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www狠狠干| 青草青青精品视频在线观看 | 影音先锋拍拍视频网站 | 麻豆精品三级电影 | 久热99r视频在线 |