当前位置: 移动技术网 > IT编程>开发语言>.net > 写简单的mvc框架实例讲解

写简单的mvc框架实例讲解

2019年03月20日  | 移动技术网IT编程  | 我要评论

淘宝信誉查询,泷泽劳拉,魔心玄奘

这一章先把支持注解的功能加上,这样就不需要经常地修改配置文件了。

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

项目地址在:https://github.com/hjx601496320/amvc

测试代码在:。

怎么写呢?

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

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

  • 定义一个注解,标识某一个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, 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);
  }
}

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网