中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何寫簡單的mvc框架

發布時間:2021-09-28 17:52:55 來源:億速云 閱讀:104 作者:iii 欄目:開發技術

本篇內容介紹了“如何寫簡單的mvc框架”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

定義一個注解Request

相關代碼:

import com.hebaibai.amvc.RequestType;
import java.lang.annotation.*;

/**
 * 表示這個類中的,添加了@Request注解的method被映射為一個http地址。
 *
 * @author hjx
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Request {

  /**
   * 請求類型
   * 支持GET,POST,DELETE,PUT
   *
   * @return
   */
  RequestType[] type() default {RequestType.GET, RequestType.POST, RequestType.DELETE, RequestType.PUT};

  /**
   * 請求地址
   * 添加在class上時,會將value中的值添加在其他方法上的@Request.value()的值前,作為基礎地址。
   *
   * @return
   */
  String value() default "/";
}

定義一個注解,需要用到一下幾個東西:

1:@interface:說明這個類是一個注解。

2:@Retention:注解的保留策略,有這么幾個取值范圍:

代碼說明
@Retention(RetentionPolicy.SOURCE)注解僅存在于源碼中
@Retention(RetentionPolicy.CLASS)注解會在class字節碼文件中存在
@Retention(RetentionPolicy.RUNTIME)注解會在class字節碼文件中存在,運行時可以通過反射獲取到

因為我們在程序中需要取到自定義的注解,所以使用:RetentionPolicy.RUNTIME。

3:@Target:作用目標,表示注解可以添加在什么地方,取值范圍有:

代碼說明
@Target(ElementType.TYPE)接口、類、枚舉、注解
@Target(ElementType.FIELD)字段、枚舉的常量
@Target(ElementType.METHOD)方法
@Target(ElementType.PARAMETER)方法參數
@Target(ElementType.CONSTRUCTOR)構造函數
@Target(ElementType.LOCAL_VARIABLE)局部變量
@Target(ElementType.ANNOTATION_TYPE)注解
@Target(ElementType.PACKAGE)

3:@Documented:這個主要是讓自定義注解保留在文檔中,沒啥實際意義,一般都給加上。

4:default:是給注解中的屬性(看起來像是一個方法,也可能就是一個方法,但是我就是叫屬性,略略略~~~)一個默認值。

上面大致上講了一下怎么定義一個注解,現在注解寫完了,講一下這個注解的用處吧。

首先這個注解可以加在class和method上。加在class上的時候表示這個類中會有method將要被處理成為一個UrlMethodMapping,然后其中的value屬性將作為這個class中所有UrlMethodMapping的基礎地址,type屬性不起作用。加在method上的時候,就是說明這個method將被處理成一個UrlMethodMapping,注解的兩個屬性發揮其正常的作用。

注解寫完了,下面把配置文件改一改吧。

修改框架的配置文件

只需要添加一個屬性就好了,修改完的配置文件這個樣子:

{
 "annotationSupport": true,
 "annotationPackage": "com.hebaibai.demo.web",
// "mapping": [
//  {
//   "url": "/index",
//   "requestType": [
//    "get"
//   ],
//   "method": "index",
//   "objectClass": "com.hebaibai.demo.web.IndexController",
//   "paramTypes": [
//    "java.lang.String",
//    "int"
//   ]
//  }
// ]
}

1:annotationSupport 值是true的時候表示開啟注解。

2:annotationPackage 表示需要掃描的包的路徑。

3:因為開了注解支持,為了防止重復注冊 UrlMethodMapping,所以我把下面的配置注釋掉了。

寫一個包掃描的方法

這個方法需要將項目中jar文件和文件夾下所有符合條件的class找到,會用到遞歸,代碼在ClassUtils.java中,由三個方法構成,分別是:

1:void getClassByPackage(String packageName, Set

這個方法接收兩個參數,一個是包名packageName,一個是一個空的Set(不是null),在方法執行完畢會將包下的所有class填充進Set中。這里主要是判斷了一下這個包中有那些類型的文件,并根據文件類型分別處理。

注意:如果是jar文件的類型,獲取到的filePath是這樣的:

file:/home/hjx/idea-IU/lib/idea_rt.jar!/com

需要去掉頭和尾,然后就可以吃了,雞肉味!嘎嘣脆~~ 處理之后的是這個樣子:

/home/hjx/idea-IU/lib/idea_rt.jar

下面是方法代碼:

/**
 * 從給定的報名中找出所有的class
 *
 * @param packageName
 * @param classes
 */
@SneakyThrows({IOException.class})
public static void getClassByPackage(String packageName, Set<Class> classes) {
  Assert.notNull(classes);
  String packagePath = packageName.replace(DOT, SLASH);
  Enumeration<URL> resources = ClassUtils.getClassLoader().getResources(packagePath);
  while (resources.hasMoreElements()) {
    URL url = resources.nextElement();
    //文件類型
    String protocol = url.getProtocol();
    String filePath = URLDecoder.decode(url.getFile(), CHARSET_UTF_8);
    if (TYPE_FILE.equals(protocol)) {
      getClassByFilePath(packageName, filePath, classes);
    }
    if (TYPE_JAR.equals(protocol)) {
      //截取文件的路徑
      filePath = filePath.substring(filePath.indexOf(":") + 1, filePath.indexOf("!"));
      getClassByJarPath(packageName, filePath, classes);
    }
  }
}

2:void getClassByFilePath(String packageName, String filePath, Set

將文件夾中的全部符合條件的class找到,用到遞歸。需要將class文件的絕對路徑截取成class的全限定名,代碼這個樣子:

/**
 * 在文件夾中遞歸找出該文件夾中在package中的class
 *
 * @param packageName
 * @param filePath
 * @param classes
 */
static void getClassByFilePath(
  String packageName,
  String filePath,
  Set<Class> classes
) {
  File targetFile = new File(filePath);
  if (!targetFile.exists()) {
    return;
  }
  if (targetFile.isDirectory()) {
    File[] files = targetFile.listFiles();
    for (File file : files) {
      String path = file.getPath();
      getClassByFilePath(packageName, path, classes);
    }
  } else {
    //如果是一個class文件
    boolean trueClass = filePath.endsWith(CLASS_MARK);
    if (trueClass) {
      //提取完整的類名
      filePath = filePath.replace(SLASH, DOT);
      int i = filePath.indexOf(packageName);
      String className = filePath.substring(i, filePath.length() - 6);
      //不是一個內部類
      boolean notInnerClass = className.indexOf("$") == -1;
      if (notInnerClass) {
        //根據類名加載class對象
        Class aClass = ClassUtils.forName(className);
        if (aClass != null) {
          classes.add(aClass);
        }
      }
    }
  }
}

3:void getClassByJarPath(String packageName, String filePath, Set

將jar文件中的全部符合條件的class找到。沒啥說的,下面是代碼:

/**
 * 在jar文件中找出該文件夾中在package中的class
 *
 * @param packageName
 * @param filePath
 * @param classes
 */
@SneakyThrows({IOException.class})
static void getClassByJarPath(
  String packageName,
  String filePath,
  Set<Class> classes
) {
  JarFile jarFile = new URLJarFile(new File(filePath));
  Enumeration<JarEntry> entries = jarFile.entries();
  while (entries.hasMoreElements()) {
    JarEntry jarEntry = entries.nextElement();
    String jarEntryName = jarEntry.getName().replace(SLASH, DOT);
    //在package下的class
    boolean trueClass = jarEntryName.endsWith(CLASS_MARK) && jarEntryName.startsWith(packageName);
    //不是一個內部類
    boolean notInnerClass = jarEntryName.indexOf("$") == -1;
    if (trueClass && notInnerClass) {
      String className = jarEntryName.substring(0, jarEntryName.length() - 6);
      System.out.println(className);
      //根據類名加載class對象
      Class aClass = ClassUtils.forName(className);
      if (aClass != null) {
        classes.add(aClass);
      }
    }
  }
}

這樣,獲取包名下的class就寫完了~

修改UrlMethodMappingFactory

這里新添加一個方法:

List,將掃描包之后獲取到的Class對象作為參數,返回一個UrlMethodMapping集合就好了。代碼如下:

/**
 * 通過解析Class 獲取映射
 *
 * @param aClass
 * @return
 */
public List<UrlMethodMapping> getUrlMethodMappingListByClass(Class<Request> aClass) {
  List<UrlMethodMapping> mappings = new ArrayList<>();
  Request request = aClass.getDeclaredAnnotation(Request.class);
  if (request == null) {
    return mappings;
  }
  String basePath = request.value();
  for (Method classMethod : aClass.getDeclaredMethods()) {
    UrlMethodMapping urlMethodMapping = getUrlMethodMappingListByMethod(classMethod);
    if (urlMethodMapping == null) {
      continue;
    }
    //將添加在class上的Request中的path作為基礎路徑
    String url = UrlUtils.makeUrl(basePath + "/" + urlMethodMapping.getUrl());
    urlMethodMapping.setUrl(url);
    mappings.add(urlMethodMapping);
  }
  return mappings;
}

/**
 * 通過解析Method 獲取映射
 * 注解Request不存在時跳出
 *
 * @param method
 * @return
 */
private UrlMethodMapping getUrlMethodMappingListByMethod(Method method) {
  Request request = method.getDeclaredAnnotation(Request.class);
  if (request == null) {
    return null;
  }
  Class<?> declaringClass = method.getDeclaringClass();
  String path = request.value();
  for (char c : path.toCharArray()) {
    Assert.isTrue(c != ' ', declaringClass + "." + method.getName() + "請求路徑異常:" + path + " !");
  }
  return getUrlMethodMapping(
      path,
      request.type(),
      declaringClass,
      method,
      method.getParameterTypes()
  );
}

在這里校驗了一下注解Request中的value的值,如果中間有空格的話會拋出異常。UrlUtils.makeUrl() 這個方法主要是將url中的多余”/”去掉,代碼長這個樣子:

private static final String SLASH = "/";

/**
 * 處理url
 * 1:去掉連接中相鄰并重復的“/”,
 * 2:鏈接開頭沒有“/”,則添加。
 * 3:鏈接結尾有“/”,則去掉。
 *
 * @param url
 * @return
 */
public static String makeUrl(@NonNull String url) {
  char[] chars = url.toCharArray();
  StringBuilder newUrl = new StringBuilder();
  if (!url.startsWith(SLASH)) {
    newUrl.append(SLASH);
  }
  for (int i = 0; i < chars.length; i++) {
    if (i != 0 && chars[i] == chars[i - 1] && chars[i] == '/') {
      continue;
    }
    if (i == chars.length - 1 && chars[i] == '/') {
      continue;
    }
    newUrl.append(chars[i]);
  }
  return newUrl.toString();
}

這樣通過注解獲取UrlMethodMapping的工廠方法就寫完了,下面開始修改加載框架的代碼。

修改Application中的init

這里因為添加了一種使用注解方式獲取UrlMethodMapping的方法,所以新建一個方法:

void addApplicationUrlMappingByAnnotationConfig(JSONObject configJson) 。在這里獲取框架配置中的包名以及做一些配置上的校驗,代碼如下:

/**
 * 使用注解來加載UrlMethodMapping
 *
 * @param configJson
 */
private void addApplicationUrlMappingByAnnotationConfig(JSONObject configJson) {
  String annotationPackage = configJson.getString(ANNOTATION_PACKAGE_NODE);
  Assert.notNull(annotationPackage, ANNOTATION_PACKAGE_NODE + NOT_FIND);
  //獲取添加了@Request的類
  Set<Class> classes = new HashSet<>();
  ClassUtils.getClassByPackage(annotationPackage, classes);
  Iterator<Class> iterator = classes.iterator();
  while (iterator.hasNext()) {
    Class aClass = iterator.next();
    List<UrlMethodMapping> mappings = urlMethodMappingFactory.getUrlMethodMappingListByClass(aClass);
    if (mappings.size() == 0) {
      continue;
    }
    for (UrlMethodMapping mapping : mappings) {
      addApplicationUrlMapping(mapping);
    }
  }
}

之后把先前寫的讀取json配置生成urlMappin的代碼摘出來,單獨寫一個方法:

void addApplicationUrlMappingByJsonConfig(JSONObject configJson),這樣使代碼中的每個方法的功能都獨立出來,看起來比較整潔,清楚。代碼如下:

/**
 * 使用文件配置來加載UrlMethodMapping
 * 配置中找不到的話不執行。
 *
 * @param configJson
 */
private void addApplicationUrlMappingByJsonConfig(JSONObject configJson) {
  JSONArray jsonArray = configJson.getJSONArray(MAPPING_NODE);
  if (jsonArray == null || jsonArray.size() == 0) {
    return;
  }
  for (int i = 0; i < jsonArray.size(); i++) {
    UrlMethodMapping mapping = urlMethodMappingFactory.getUrlMethodMappingByJson(jsonArray.getJSONObject(i));
    addApplicationUrlMapping(mapping);
  }
}

最后只要吧init()稍微修改一下就好了,修改完之后是這樣的:

/**
 * 初始化配置
 */
@SneakyThrows(IOException.class)
protected void init() {
  String configFileName = applicationName + ".json";
  InputStream inputStream = ClassUtils.getClassLoader().getResourceAsStream(configFileName);
  byte[] bytes = new byte[inputStream.available()];
  inputStream.read(bytes);
  String config = new String(bytes, "utf-8");
  //應用配置
  JSONObject configJson = JSONObject.parseObject(config);

  //TODO:生成對象的工廠類(先默認為每次都new一個新的對象)
  this.objectFactory = new AlwaysNewObjectFactory();
  //TODO:不同的入參名稱獲取類(當前默認為asm)
  urlMethodMappingFactory.setParamNameGetter(new AsmParamNameGetter());
  //通過文件配置加載
  addApplicationUrlMappingByJsonConfig(configJson);
  //是否開啟注解支持
  Boolean annotationSupport = configJson.getBoolean(ANNOTATION_SUPPORT_NODE);
  Assert.notNull(annotationSupport, ANNOTATION_SUPPORT_NODE + NOT_FIND);
  if (annotationSupport) {
    addApplicationUrlMappingByAnnotationConfig(configJson);
  }
}

“如何寫簡單的mvc框架”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

mvc
AI

方山县| 阿鲁科尔沁旗| 白沙| 莱西市| 渝中区| 鄂温| 大渡口区| 鹰潭市| 曲麻莱县| 彭阳县| 菏泽市| 黄石市| 台前县| 鹤岗市| 马公市| 顺昌县| 双柏县| 辽宁省| 兴山县| 万全县| 临泉县| 夏津县| 潮安县| 临夏市| 巴里| 吉水县| 三亚市| 怀柔区| 长治市| 木兰县| 石楼县| 吴旗县| 贡嘎县| 广汉市| 佛山市| 平原县| 大丰市| 玉屏| 连州市| 汾阳市| 岳普湖县|