怎样用框架去写java_自己写一个java的mvc框架吧(五)

自己写一个mvc框架吧(五)

给框架添加注解的支持

一段废话

上一章本来是说这一章要写视图处理的部分,但是由于我在测试代码的时候需要频繁的修改配置文件,太麻烦了。所以这一章先把支持注解的功能加上,这样就不需要经常地修改配置文件了。

至于视图处理的地方,就还是先用json吧,找时间再写。

怎么写呢?

因为在之前写代码的时候,我把每个类要做的事情分的比较清楚,所以在添加这个功能的时候写起来还是比较简单的,需要修改的地方也比较小。

这一章里我们需要干的事情有:

定义一个注解,标识某一个class中的被添加注解的方法是一个UrlMethodMapping。

修改配置文件,添加需要扫描的package。

写一个方法,根据package中值找到其中所有的class。

在UrlMethodMapping的工厂类UrlMethodMappingFactory中新加一个根据注解创建UrlMethodMapping的方法。

在Application中的init()方法中,根据是否开启注解支持,执行新的工厂类方法。

完了。

多么简单呀~~~

现在开始写

定义一个注解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, Setclasses);

这个方法接收两个参数,一个是包名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 classes) {

Assert.notNull(classes);

String packagePath = packageName.replace(DOT, SLASH);

Enumeration 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, Setclasses)

将文件夹中的全部符合条件的class找到,用到递归。需要将class文件的绝对路径截取成class的全限定名,代码这个样子:

/**

* 在文件夹中递归找出该文件夹中在package中的class

*

* @param packageName

* @param filePath

* @param classes

*/

static void getClassByFilePath(

String packageName,

String filePath,

Set 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, Setclasses)

将jar文件中的全部符合条件的class找到。没啥说的,下面是代码:

/**

* 在jar文件中找出该文件夹中在package中的class

*

* @param packageName

* @param filePath

* @param classes

*/

@SneakyThrows({IOException.class})

static void getClassByJarPath(

String packageName,

String filePath,

Set classes

) {

JarFile jarFile = new URLJarFile(new File(filePath));

Enumeration 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

这里新添加一个方法:

ListgetUrlMethodMappingListByClass(ClassaClass),将扫描包之后获取到的Class对象作为参数,返回一个UrlMethodMapping集合就好了。代码如下:

/**

* 通过解析Class 获取映射

*

* @param aClass

* @return

*/

public List getUrlMethodMappingListByClass(Class aClass) {

List 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 classes = new HashSet<>();

ClassUtils.getClassByPackage(annotationPackage, classes);

Iterator iterator = classes.iterator();

while (iterator.hasNext()) {

Class aClass = iterator.next();

List 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);

}

}

这里只是根据配置做了一下判断就好了。这样就写完了。

最后

是不是很简单啊~~~

关于视图处理的部分看看下一章再写吧~~~

最新修改一下

没人看,不写了。等我先给我自己的小网站框架换成自己写的再说。

中间这个框架可能会经常行的修改~

拜拜~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值