寫簡(jiǎn)單的mvc框架實(shí)例講解
這一章先把支持注解的功能加上,這樣就不需要經(jīng)常地修改配置文件了。
至于視圖處理的地方,就還是先用json吧,找時(shí)間再寫。
項(xiàng)目地址在:https://github.com/hjx601496320/aMvc 。
測(cè)試代碼在:https://github.com/hjx601496320/amvc-test 。
怎么寫呢?
因?yàn)樵谥皩懘a的時(shí)候,我把每個(gè)類要做的事情分的比較清楚,所以在添加這個(gè)功能的時(shí)候?qū)懫饋?lái)還是比較簡(jiǎn)單的,需要修改的地方也比較小。
這一章里我們需要干的事情有:
- 定義一個(gè)注解,標(biāo)識(shí)某一個(gè)class中的被添加注解的方法是一個(gè)UrlMethodMapping。
- 修改配置文件,添加需要掃描的package。
- 寫一個(gè)方法,根據(jù)package中值找到其中所有的class。
- 在UrlMethodMapping的工廠類UrlMethodMappingFactory中新加一個(gè)根據(jù)注解創(chuàng)建UrlMethodMapping的方法。
- 在Application中的init()方法中,根據(jù)是否開(kāi)啟注解支持,執(zhí)行新的工廠類方法。
- 完了。
多么簡(jiǎn)單呀~~~
現(xiàn)在開(kāi)始寫
定義一個(gè)注解Request
關(guān)于怎樣自定義注這件事,大家可以上網(wǎng)搜一下,比較簡(jiǎn)單。我這里只是簡(jiǎn)單的說(shuō)一下。我先把代碼貼出來(lái):
import com.hebaibai.amvc.RequestType;
import java.lang.annotation.*;
/**
* 表示這個(gè)類中的,添加了@Request注解的method被映射為一個(gè)http地址。
*
* @author hjx
*/
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Request {
/**
* 請(qǐng)求類型
* 支持GET,POST,DELETE,PUT
*
* @return
*/
RequestType[] type() default {RequestType.GET, RequestType.POST, RequestType.DELETE, RequestType.PUT};
/**
* 請(qǐng)求地址
* 添加在class上時(shí),會(huì)將value中的值添加在其他方法上的@Request.value()的值前,作為基礎(chǔ)地址。
*
* @return
*/
String value() default "/";
}
定義一個(gè)注解,需要用到一下幾個(gè)東西:
1:@interface:說(shuō)明這個(gè)類是一個(gè)注解。
2:@Retention:注解的保留策略,有這么幾個(gè)取值范圍:
| 代碼 | 說(shuō)明 |
|---|---|
| @Retention(RetentionPolicy.SOURCE) | 注解僅存在于源碼中 |
| @Retention(RetentionPolicy.CLASS) | 注解會(huì)在class字節(jié)碼文件中存在 |
| @Retention(RetentionPolicy.RUNTIME) | 注解會(huì)在class字節(jié)碼文件中存在,運(yùn)行時(shí)可以通過(guò)反射獲取到 |
因?yàn)槲覀冊(cè)诔绦蛑行枰〉阶远x的注解,所以使用:RetentionPolicy.RUNTIME。
3:@Target:作用目標(biāo),表示注解可以添加在什么地方,取值范圍有:
| 代碼 | 說(shuō)明 |
|---|---|
| @Target(ElementType.TYPE) | 接口、類、枚舉、注解 |
| @Target(ElementType.FIELD) | 字段、枚舉的常量 |
| @Target(ElementType.METHOD) | 方法 |
| @Target(ElementType.PARAMETER) | 方法參數(shù) |
| @Target(ElementType.CONSTRUCTOR) | 構(gòu)造函數(shù) |
| @Target(ElementType.LOCAL_VARIABLE) | 局部變量 |
| @Target(ElementType.ANNOTATION_TYPE) | 注解 |
| @Target(ElementType.PACKAGE) | 包 |
3:@Documented:這個(gè)主要是讓自定義注解保留在文檔中,沒(méi)啥實(shí)際意義,一般都給加上。
4:default:是給注解中的屬性(看起來(lái)像是一個(gè)方法,也可能就是一個(gè)方法,但是我就是叫屬性,略略略~~~)一個(gè)默認(rèn)值。
上面大致上講了一下怎么定義一個(gè)注解,現(xiàn)在注解寫完了,講一下這個(gè)注解的用處吧。
首先這個(gè)注解可以加在class和method上。加在class上的時(shí)候表示這個(gè)類中會(huì)有method將要被處理成為一個(gè)UrlMethodMapping,然后其中的value屬性將作為這個(gè)class中所有UrlMethodMapping的基礎(chǔ)地址,type屬性不起作用。加在method上的時(shí)候,就是說(shuō)明這個(gè)method將被處理成一個(gè)UrlMethodMapping,注解的兩個(gè)屬性發(fā)揮其正常的作用。
注解寫完了,下面把配置文件改一改吧。
修改框架的配置文件
只需要添加一個(gè)屬性就好了,修改完的配置文件這個(gè)樣子:
{
"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的時(shí)候表示開(kāi)啟注解。
2:annotationPackage 表示需要掃描的包的路徑。
3:因?yàn)殚_(kāi)了注解支持,為了防止重復(fù)注冊(cè) UrlMethodMapping,所以我把下面的配置注釋掉了。
寫一個(gè)包掃描的方法
這個(gè)方法需要將項(xiàng)目中jar文件和文件夾下所有符合條件的class找到,會(huì)用到遞歸,代碼在ClassUtils.java中,由三個(gè)方法構(gòu)成,分別是:
1:void getClassByPackage(String packageName, Set
這個(gè)方法接收兩個(gè)參數(shù),一個(gè)是包名packageName,一個(gè)是一個(gè)空的Set(不是null),在方法執(zhí)行完畢會(huì)將包下的所有class填充進(jìn)Set中。這里主要是判斷了一下這個(gè)包中有那些類型的文件,并根據(jù)文件類型分別處理。
注意:如果是jar文件的類型,獲取到的filePath是這樣的:
file:/home/hjx/idea-IU/lib/idea_rt.jar!/com
需要去掉頭和尾,然后就可以吃了,雞肉味!嘎嘣脆~~ 處理之后的是這個(gè)樣子:
/home/hjx/idea-IU/lib/idea_rt.jar
下面是方法代碼:
/**
* 從給定的報(bào)名中找出所有的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文件的絕對(duì)路徑截取成class的全限定名,代碼這個(gè)樣子:
/**
* 在文件夾中遞歸找出該文件夾中在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 {
//如果是一個(gè)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);
//不是一個(gè)內(nèi)部類
boolean notInnerClass = className.indexOf("$") == -1;
if (notInnerClass) {
//根據(jù)類名加載class對(duì)象
Class aClass = ClassUtils.forName(className);
if (aClass != null) {
classes.add(aClass);
}
}
}
}
}
3:void getClassByJarPath(String packageName, String filePath, Set
將jar文件中的全部符合條件的class找到。沒(méi)啥說(shuō)的,下面是代碼:
/**
* 在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);
//不是一個(gè)內(nèi)部類
boolean notInnerClass = jarEntryName.indexOf("$") == -1;
if (trueClass && notInnerClass) {
String className = jarEntryName.substring(0, jarEntryName.length() - 6);
System.out.println(className);
//根據(jù)類名加載class對(duì)象
Class aClass = ClassUtils.forName(className);
if (aClass != null) {
classes.add(aClass);
}
}
}
}
這樣,獲取包名下的class就寫完了~
修改UrlMethodMappingFactory
這里新添加一個(gè)方法:
List,將掃描包之后獲取到的Class對(duì)象作為參數(shù),返回一個(gè)UrlMethodMapping集合就好了。代碼如下:
/**
* 通過(guò)解析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作為基礎(chǔ)路徑
String url = UrlUtils.makeUrl(basePath + "/" + urlMethodMapping.getUrl());
urlMethodMapping.setUrl(url);
mappings.add(urlMethodMapping);
}
return mappings;
}
/**
* 通過(guò)解析Method 獲取映射
* 注解Request不存在時(shí)跳出
*
* @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() + "請(qǐng)求路徑異常:" + path + " !");
}
return getUrlMethodMapping(
path,
request.type(),
declaringClass,
method,
method.getParameterTypes()
);
}
在這里校驗(yàn)了一下注解Request中的value的值,如果中間有空格的話會(huì)拋出異常。UrlUtils.makeUrl() 這個(gè)方法主要是將url中的多余”/”去掉,代碼長(zhǎng)這個(gè)樣子:
private static final String SLASH = "/";
/**
* 處理url
* 1:去掉連接中相鄰并重復(fù)的“/”,
* 2:鏈接開(kāi)頭沒(méi)有“/”,則添加。
* 3:鏈接結(jié)尾有“/”,則去掉。
*
* @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();
}
這樣通過(guò)注解獲取UrlMethodMapping的工廠方法就寫完了,下面開(kāi)始修改加載框架的代碼。
修改Application中的init
這里因?yàn)樘砑恿艘环N使用注解方式獲取UrlMethodMapping的方法,所以新建一個(gè)方法:
void addApplicationUrlMappingByAnnotationConfig(JSONObject configJson) 。在這里獲取框架配置中的包名以及做一些配置上的校驗(yàn),代碼如下:
/**
* 使用注解來(lái)加載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的代碼摘出來(lái),單獨(dú)寫一個(gè)方法:
void addApplicationUrlMappingByJsonConfig(JSONObject configJson),這樣使代碼中的每個(gè)方法的功能都獨(dú)立出來(lái),看起來(lái)比較整潔,清楚。代碼如下:
/**
* 使用文件配置來(lái)加載UrlMethodMapping
* 配置中找不到的話不執(zhí)行。
*
* @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");
//應(yīng)用配置
JSONObject configJson = JSONObject.parseObject(config);
//TODO:生成對(duì)象的工廠類(先默認(rèn)為每次都new一個(gè)新的對(duì)象)
this.objectFactory = new AlwaysNewObjectFactory();
//TODO:不同的入?yún)⒚Q獲取類(當(dāng)前默認(rèn)為asm)
urlMethodMappingFactory.setParamNameGetter(new AsmParamNameGetter());
//通過(guò)文件配置加載
addApplicationUrlMappingByJsonConfig(configJson);
//是否開(kāi)啟注解支持
Boolean annotationSupport = configJson.getBoolean(ANNOTATION_SUPPORT_NODE);
Assert.notNull(annotationSupport, ANNOTATION_SUPPORT_NODE + NOT_FIND);
if (annotationSupport) {
addApplicationUrlMappingByAnnotationConfig(configJson);
}
}
相關(guān)文章
.NET根據(jù)文件的哈希值篩選重復(fù)文件的實(shí)現(xiàn)思路
文章介紹了文件哈希值的概念,包括哈希算法、唯一性、固定長(zhǎng)度、不可逆性以及其在文件驗(yàn)證、數(shù)據(jù)完整性檢查、文件去重、密碼存儲(chǔ)等領(lǐng)域的應(yīng)用,接著,通過(guò)一個(gè)示例代碼說(shuō)明了如何通過(guò)文件大小分組并比對(duì)哈希值來(lái)篩選出重復(fù)文件,感興趣的朋友一起看看吧2025-03-03
Global.asax取物理路徑/取絕對(duì)路徑具體方法
本文章來(lái)給大家簡(jiǎn)單介紹利用Global.asax取物理路徑和取絕對(duì)路徑代碼,有需要了解的朋友可參考參考2013-08-08
ASP.Net Post方式獲取數(shù)據(jù)流的一種簡(jiǎn)單寫法
這篇文章主要介紹了ASP.Net Post方式獲取數(shù)據(jù)流的一種簡(jiǎn)單寫法,本文直接給出代碼實(shí)例,需要的朋友可以參考下2015-05-05
.NET+PostgreSQL實(shí)踐與避坑指南(推薦)
這篇文章主要介紹了.NET+PostgreSQL實(shí)踐與避坑指南,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
ASP.NET使用Subtract方法獲取兩個(gè)日期之間的天數(shù)
本節(jié)主要介紹了ASP.NET使用Subtract方法獲取兩個(gè)日期之間的天數(shù),需要的朋友可以參考下2014-08-08
將Excel中數(shù)據(jù)導(dǎo)入到Access數(shù)據(jù)庫(kù)中的方法
將Excel中數(shù)據(jù)導(dǎo)入到Access數(shù)據(jù)庫(kù)中的方法,需要的朋友可以參考一下2013-03-03
web.config中配置數(shù)據(jù)庫(kù)連接的方式
Web.config文件是一個(gè)XML文本文件,它用來(lái)儲(chǔ)存 ASP.NET Web 應(yīng)用程序的配置信息(如最常用的設(shè)置ASP.NET Web 應(yīng)用程序的身份驗(yàn)證方式),它可以出現(xiàn)在應(yīng)用程序的每一個(gè)目錄中。本文主要介紹web.config中配置數(shù)據(jù)庫(kù)連接的兩種方式,一起來(lái)看。2015-10-10
ASP.NET Core 集成 React SPA應(yīng)用的步驟
這篇文章主要介紹了ASP.NET Core 集成 React SPA應(yīng)用的步驟,幫助大家更好的理解和學(xué)習(xí)使用.net技術(shù),感興趣的朋友可以了解下2021-04-04
Cookies的各方面知識(shí)(基礎(chǔ)/高級(jí))深度了解
Cookies想必所有人都了解本文將圍繞Cookies基礎(chǔ)知識(shí)(什么是Cookies/Cookies如何傳遞/Cookies如何存儲(chǔ)/Cookies如何查看)Cookies高級(jí)知識(shí)/Cookie的限制等等方方面面深入了解,感興趣的朋友可以參考下,或許對(duì)你學(xué)習(xí)cookies有所幫助2013-02-02

