当初看了《从零开始写一个Java Web框架》,也跟着写了一遍,但当时学艺不精,真正进脑子里的并不是很多,作者将依赖注入框架和MVC框架写在一起也给我造成了不小的困扰。近刚好看了一遍springMVC的官方文档,对过去一段时间的使用做了一下总结,总结了一些MVC的使用需求,打算自己开坑写一个MVC框架,虽然是重复造轮子的过程,但也是学习提高的过程。
  1.我们可能需要一个什么样的MVC框架
  (1)用户一:我讨厌配置文件,好能用注解的全用注解注解,能扫描直接扫描
  (2)用户二:好我导入一个jar包,有默认的servlet配置,也可以按自己的需要配置,然后直接写这样的代码可以处理请求了
@Controller
public class Test {
@MapURL(value = "/as.do")
public String main(HttpServletRequest req,HttpServletResponse resp,ModelMap model,String name) throws IOException, ServletException {
System.out.println("as1");
model.put("time",new Date(System.currentTimeMillis()));
return "test";
}
}
  (3)用户三:返回的话,直接返回一个字符串,在配置中写明页面根路径,直接返回页面名字好了
  (4)用户四:springMVC里想用什么参数都可以直接写到函数里,好方便,好也实现一下
  每个新出的框架都会说自己简单,性能好不会像已经成熟的框架那样复杂,冗余,而且用不到的功能很多,但会降低系统性能,但随着需求越来越多,每个框架都会越来越复杂,功能越来越多,只有适合自己的才是好的框架。
  2.从annotation说起
  annotation是从jdk1.5起引入的新机制,annotation旨在将类,方法,变量与特定的信息或是元数据相关联,注解可以理解为一个框架可以识别的注释或是标签,当我在类上标了@Controller时,框架知道了这是个控制器,扫描类的时候遇到有这个注解的放进来。
  一个简单的演示是我定义一个注解类,使用@interface,设置对应的元数据属性,然后创建一个类,在类上面标上@Controller,
  @Target(ElementType.TYPE)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface Controller {}
  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface MapURL {
  String value();
  String method() default "GET";
  }
  然后我获得这个类,可以new完了获取class,也可以直接Class.forName,调用class.isAnnotationPresent(Controller.class),通过得到的布尔值来判断这个类的注解是不是Controller。对于方法级别的注释,采用@MapURL标签,设置2个属性value和method,分别代表短url和http的请求方式,默认是get,不是的话需要单独设置。在实际的项目中,Controller的类一定会有很多,我们需要在初始化的时候把这些类从全部类中拿出来,放到集合中,然后在这个class的集合中拿出所有的被注解的方法,将url作为key,方法作为value存到map中(http请求方式暂未考虑,以后再重构),这个map是整个框架的核心。
  在拿出全部类的过程中,用户可以在config.ini中设置要扫描的包,程序会通过文件操作不停的递归找到所有的class文件,然后从这个包的所有类中挑选出Controller的类。
public static void  scanClassSetByPackage(String packageName)
{
methodMap=new HashMap<String, MethodPro>();
classMap=new HashMap<String, Class<?>>();
classSet=new HashSet<Class<?>>();
String filePath=Config.getProPath()+ StringUtils.modifyPackagePath(packageName);
FileUtils.getClassSet(filePath,classSet,packageName);
for(Class<?> clazz:classSet)
{
if(clazz.isAnnotationPresent(Controller.class))
{
Method[] methods=clazz.getDeclaredMethods();
for(Method method:methods)
{
if(method.isAnnotationPresent(MapURL.class))
{
MapURL mapURL=method.getAnnotation(MapURL.class);
MethodPro mp=new MethodPro(method,mapURL.value(),mapURL.method());
methodMap.put(mapURL.value(),mp);
classMap.put(mapURL.value(),clazz);
}
}
}
}
}
  3.建立转发servlet
  框架归根到底还是servlet这一套东西,请求进来,处理请求,返回结果。现在框架只是屏蔽了servlet的一些东西,让开发者能够使用更友好的,更简单的方式来实现业务逻辑。框架需要servlet,一个够了,这个servlet要把传进来的请求交给别人处理,交给那些别标记了@Controller中的被标记了@MapURL的方法来处理,这里用到了刚才用到的map,map什么时候生成,可是放在静态块里加载的时候生成,也可以放在servlet的init方法中初始化生成。在servlet的service中,会通过request的getPathInfo(),或是getServletPath(),来获得当前请求的短路径,通过map拿到这个路径url对应的method,反射,将request和response传入,然后可以调用任何@Controller的任何方法,一个简单的MVC框架到这也算完成了。这个框架配置简单,只需要一个servlet可以处理各种不同的请求,开发者不必再写一大堆servlet,只需要在@Controller中加一大堆方法好了。现在的问题是即使我不必写servlet,但我在方法中写的代码太servlet化了,简直没什么区别,我写个跳转jsp的页面还得request.getDispatcher("test.jsp").forward(request.response)要找一个传进来的值还得去request中拿,想传出去还得再放到request中。
  4.实现一个springMVC式的参数填充
  在开始使用springMVC的时候,十分惊讶于这种设计,一直在想这是怎么做到的,我为什么可以使用任意多个参数,在form提交的表单中为什么同名的会直接被赋值,为什么把模型类写到参数里会自动填充类中的属性,还没来得及看springMVC这块的实现代码,先把自己理解的和用到的整理了一下逻辑,实现了一遍。
  public Object invoke(Object obj, Object... args),在method的反射调用中采用的是可变长参数,这个机制很有意思,我可以在反射中使用很多参数比如invoke(obj,req,resp,model,name);或是把后面几项放到数组中invoke(obj,obj[]);因为在@Controller的函数的长度是任意的,所以需要先得到对应method的信息,得到参数的个数,并生成相应长度的数组供调用。然后得到了2个问题: