陆陆续续用闲暇时间写的自用 MVC 框架,本来想利用十一期间整理整理代码放出来,结果一直拖延症终于缓慢地完工了(然后这帖又写了两天……)

先放地址:https://github.com/cloudeecn/mocha-mvc 欢迎大家clone下来吐嘈,如果觉得博主写得好的话,欢迎Star/Fork

运行环境要求:

  • Java7
  • Servlet3.0容器

运行Example

clone工程,用eclipse/IDEA/whatever你喜欢的IDE导入maven工程,用支持Servlet3.0的容器启动里面的example子工程就OK啦

在web.xml中有使用SpringGuice作为DI的两套配置,默认打开的是用Guice作DI的,也可以打开Spring来试一下。

Spring的配置在example工程中src/main/resources/works/cirno/mocha/example/spring/applicationContext.xml

Guice的配置在 works.cirno.mocha.example.guice 包下

运行起来后,可以尝试一下 /example/parameter 和 /example/+(随便你起个名字) 两个地址来感受一下Bean绑定上传文件和RESTful地址的支持

快速上手创建自己的工程

  1. git clone Mocha工程,maven clean install安装到本地库中

  2. 创建一个web maven工程,假设命名为mocha-example,加上如下依赖:

<dependency>
  <groupId>works.cirno.mocha</groupId>
  <artifactId>mvc-core</artifactId>
  <version>0.1-SNAPSHOT</version>
</dependency>
  1. 创建MVCConfigurator的子类
package example;

import ...

public class HelloConfigurator extends MVCConfigurator {
  public void configure(){
    serve("/hello/${name}").with(HelloController.class, "hello");
  }
}
  1. 创建Controller类
package example;

import ...

public class HelloController {
  void hello(PrintWriter out, String name){
    out.print("Hello " + name);
  }
}
  1. web.xml中加入如下配置:
<filter>
  <filter-name>dispatcher</filter-name>
  <filter-class>works.cirno.mocha.DispatcherFilter</filter-class>
  <init-param>
    <param-name>configurator</param-name>
    <param-value>example.HelloController</param-value>
  </init-param>
</filter>

完成. 用Servlet容器运行这个工程,访问

http://127.0.0.1:8080/mocha-example/hello/user

输出为

Hello user

更复杂的配置请参考工程中的Example工程和下面的章节:

(GitHub上的详细文档还没写好,请耐心等待orz)

略微详细的配置介绍

用过Guice的同学应该对这种方式很熟悉,创建一个works.cirno.mocha.MVCConfigurator的子类,并在public void configure()方法中进行配置。

配置路由项

serve({路径}[, "GET/POST/PUT..."])

会产生一个路由项,利用方法链的方式可以进一步对匹配这个路径的访问进行配置

配置响应

serve({路径}[, "GET/POST/PUT..."]).with({controller}, {method});

中的with方法给路由项配置响应的Controller和其中的方法method

Controller返回

Controller方法可以支持返回一下几个类型

Integer:直接用response.sendError返回返回码

String:作为view的名字寻找对应的jsp进行渲染

View对象:根据view的名字寻找对应的jsp进行渲染,view对象中有attribute(key, value)方法可以给request的attribute提供值

null或者方法返回类型为void:不做后续处理

配置view返回

可以通过forward方法执行响应view名对应的jsp文件(forward方法返回一个支持to方法的接口,其中的to方法执行JSP的位置)

可以配置多个

serve({路径}[, "GET/POST/PUT..."]).with({controller}, {method})
  .forward("view名").to("JSP位置")
  .forward("view名").to("JSP位置")
  ...
  ;

支持Restful风格的api

serve("/parameter/\\+${userId}").with(ParameterController.class, "user").forward("success").to("/WEB-INF/jsp/parameter-rest.jsp");

serve("/parameter/\\+${userId}", "POST").with(ParameterController.class, "userPost");

serve("/parameter/\\+${userId}.json").with(ParameterController.class, "userJson");

会匹配/parameter/+后面的任何内容,并作为userId参数和候选

目前有两种写法:

JDK7终于支持了的namedGroup:写法如下:(?&lt;name&gt;pattern),会匹配pattern正则并且作为name参数的候选

另外支持一种比较基本的${name}写法,会被翻译成(?&lt;name&gt;.*?),最小匹配接下来的内容作为name参数的候选

参数获取

Controller方法中的参数目前会根据以下的规则获取:

  1. 按类型匹配

    • 类型为HttpServletRequest的参数会给予request对象
    • 类型为HttpServletResponse的参数会给予response对象
    • 类型为ServletOutputStream或OutputStream的参数会给予response.getOutputStream()的结果
    • 类型为PrintWriter或Writer的参数会给予response.getWriter()的结果
  2. 如果是multipart请求,如果参数类型是InputStream或者Reader,根据参数名匹配上传文件的内容

  3. 根据参数的名称,按照url中的变量 - multipart中解析到的表单域 - request.getParameter的顺序获取字符串,并尝试通过系统中的PropertyEditor进行类型转换,如果转换成功将转换结果给予参数

  4. 将这个类型用无参构造函数实例化,如果成功作为JavaBean遍历其中的属性,根据规则1-4获取参数名.属性名的参数值进行填充,如果能获取到至少一个,将这个实例给予参数

  5. 以上都不匹配返回参数的默认值(基本类型给false/0,对象类型给null)

TODO

  • 写文档(README.md居然只写了一半的内容!)
  • 写文档(注释简直是没有)
  • 写文档(Wiki也没有!)
  • 修改路径匹配的规则,现在是用正则匹配的,对带正则元字符的的路径不友好
  • 优化获取参数的性能……从request中填充一个5个属性的Bean居然要16毫秒简直不能忍
  • 重构一下View的结构,现在的View机制是在Renderer之下的,结构很诡异
  • 用一个比较优雅的方式封装参数获取和转换的配置(目前是写死的规则……)
  • 继续把之前的连载填上

随后我会慢慢在GitHub的README.md和Wiki中补充上各种文档,请大家期待;)