写简单的mvc框架实例讲解

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

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

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

测试代码在:https://github.com/hjx601496320/amvc-test

怎么写呢?

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

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

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

相关推荐

  • SpringMVC框架实现上传图片的示例代码

    一.创建图片虚拟目录 在上传图片之前,先要设置虚拟目录(以IDEA为例) 打开工具栏的运行配置Edit Configurations 添加物理目录和并设置虚拟目录路径 添加img图片在img文件夹内 测试访问:http://localhost:8080/img/img.jpg 二.SpringMVC上传头像 1.SpringMVC对多部件类型的解析 上传图片SpringMVC.xml配置 在页面form中提交enctype="multipart/form-data"的数据时,需要spr

  • MVC框架是什么 这里为你解答

    MVC (Model View Controler)本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器.使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式.比如一批统计数据你可以分别用柱状图.饼图来表示.C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新. 模型-视图-控制器(MVC)是Xerox PARC在八十年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已被广泛使用.最近几年被推荐为Sun公司J2EE平

  • mvc框架打造笔记之wsgi协议的优缺点以及接口实现

    前言: 又是WSGI ,这是我曾经比较熟悉的协议,以前针对实现了wsgi server的unicorn和uwsgi都写过源码解析的文章.  其实他们的实现也很简单,就是给flask.django这样的application传递environ,start_response . 什么是WSGI协议,什么是WSGI Server,他们的区别是什么? 上线的架构图很容易误导别人,乍一看有nginx这样的web服务器,又有gunicorn这样的wsgi server.  我们先说明wsgi 跟 wsgi

  • ASP.NET 之 MVC框架及搭建教程(推荐)

    一.MVC简介 MVC:Model-View-Controller(模型-视图-控制器),MVC是一种软件开发架构模式. 1.模型(Model) 模型对象是实现应用程序数据域逻辑的应用程序部件. 通常,模型对象会检索模型状态并将其存储在数据库中. 例如,Product 对象可能会从数据库中检索信息,操作该信息,然后将更新的信息写回到数据库内的 Product 表中. 2.视图(View) 视图是显示应用程序用户界面 (UI) 的组件.通常,此 UI 是用模型数据创建的.Product表的编辑视图

  • 写简单的mvc框架实例讲解

    这一章先把支持注解的功能加上,这样就不需要经常地修改配置文件了. 至于视图处理的地方,就还是先用json吧,找时间再写. 项目地址在:https://github.com/hjx601496320/aMvc . 测试代码在:https://github.com/hjx601496320/amvc-test. 怎么写呢? 因为在之前写代码的时候,我把每个类要做的事情分的比较清楚,所以在添加这个功能的时候写起来还是比较简单的,需要修改的地方也比较小. 这一章里我们需要干的事情有: 定义一个注解,标识

  • php实现简单的MVC框架实例

    本文实例讲述了php实现简单的MVC框架.分享给大家供大家参考.具体如下: 在开始之前需要知道的知识 1.php基础知识 2.单一入口, 不知道的可以看看这里 (http://www.jb51.net/article/72621.htm) 具备以上两点, 那我们就可以开始啦. 哈哈! 先来说一下程序的执行流程 首先有个入口文件, 然后初始化一些程序, 之后根据请求调用不同的类和方法 首先我们弄一个入口文件 Index.php 来看看代码 <?php require "Init.php&qu

  • php实现最简单的MVC框架实例教程

    本文以一个实例的形式讲述了PHP实现MVC框架的过程,比较浅显易懂.现分享给大家供大家参考之用.具体分析如下: 首先,在学习一个框架之前,基本上我们都需要知道什么是mvc,即model-view-control,说白了就是数据控制以及页面的分离实现,mvc就是这样应运而生的,mvc分为了三个层次,而且三个层次各司其职,互不干扰,首先简单介绍下,各个层次:view即是视图,也就是web页面,control即是控制器 向系统发出指令的工具,model 简单说是从数据库中取出数据进行处理. MVC的工

  • 打造通用的匀速运动框架(实例讲解)

    本文,是接着上 基于匀速运动的实例讲解(侧边栏,淡入淡出) 继续的,在这篇文章的最后,我们做了2个小实例:侧边栏与改变透明度的淡入淡出效果,本文我们把上文的animate函数,继续改造,让他变得更加的通用和强大: 1,支持多个物体的运动 2,同时运动 3,顺序运动 这三种运动方式也是jquery中animate函数支持的 一.animate函数中怎么区分变化不同的样式? 上文中,侧边栏效果 用的animate函数 改变的是left值 function animate(obj, target, s

  • Python字典实现简单的三级菜单(实例讲解)

    如下所示: data = { "北京":{ "昌平":{"沙河":["oldboy","test"],"天通苑":["链接地产","我爱我家"]}, "朝阳":{"望京":["奔驰","陌陌"],"国贸":["CICC",&quo

  • 运用jQuery写的验证表单(实例讲解)

    //运用jQuery写的验证表单 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equ

  • python django使用haystack:全文检索的框架(实例讲解)

    haystack:全文检索的框架 whoosh:纯Python编写的全文搜索引擎 jieba:一款免费的中文分词包 首先安装这三个包 pip install django-haystack pip install whoosh pip install jieba 1.修改settings.py文件,安装应用haystack, 2.在settings.py文件中配置搜索引擎 HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎 'ENGINE': '

  • js最简单的双向绑定实例讲解

    把代码复制放到页面里面运行看一下效果就好了 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="text" id="myinput" > <sc

  • Python 网络爬虫--关于简单的模拟登录实例讲解

    和获取网页上的信息不同,想要进行模拟登录还需要向服务器发送一些信息,如账号.密码等等. 模拟登录一个网站大致分为这么几步: 1.先将登录网站的隐藏信息找到,并将其内容先进行保存(由于我这里登录的网站并没有额外信息,所以这里没有进行信息筛选保存) 2.将信息进行提交 3.获取登录后的信息 先给上源码 <span style="font-size: 14px;"># -*- coding: utf-8 -*- import requests def login(): sessi

  • 对Python实现简单的API接口实例讲解

    get方法 代码实现 # coding:utf-8 import json from urlparse import parse_qs from wsgiref.simple_server import make_server # 定义函数,参数是函数的两个参数,都是python本身定义的,默认就行了. def application(environ, start_response): # 定义文件请求的类型和当前请求成功的code start_response('200 OK', [('Con

随机推荐