先看整体架构
因为我们这个 MVC 框架要依赖 IOC/DI 容器,所以我们在 pom 文件里要将自己的 Spring 框架引入进来。
实现 MVC 的整体功能
首先我们定义两个 MVC 专用的注解,RequestMapping 用来做 url 匹配,RequestParam 做参数转换:
packagecom.paul.annotation;importjava.lang.annotation.*;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestMapping {Stringvalue()default"";}
packagecom.paul.annotation;importjava.lang.annotation.*;@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceRequestParam {Stringvalue()default"";}
我们知道 SpringMVC 的核心是 DispatcherServlet,用来做核心路由控制,我们也定义这样一个类,并且在初始化方法里初始化一个 IOC/DI 容器,看过前面文章的同学应该知道,初始化容器后我们已经将 Bean 放到容器中而且完成了依赖注入。
package com.paul.servlet;importcom.paul.annotation.RequestMapping;importcom.paul.annotation.RequestParam;importorg.springframework.ioc.annotation.Controller;importorg.springframework.ioc.bean.AnnotationApplicationContext;importorg.springframework.ioc.bean.ApplicationContext;importjavax.servlet.ServletConfig;importjavax.servlet.ServletException;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.lang.annotation.Annotation;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Method;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;//实现 servlet,和我们以前使用 servlet 一样。publicclassDispatcherServletextendsHttpServlet{// 完整路径和 方法的 mappingprivateMap handleMapping =newConcurrentHashMap<>();// 类路径和controller 的 mappingprivateMap controllerMapping =newConcurrentHashMap<>(); privateMap beanFactory =newConcurrentHashMap<>(); @Override publicvoidinit(ServletConfig config) throws ServletException {super.init(config);//实例化 IOC 容器ApplicationContext ctx =newAnnotationApplicationContext("applicationContext.xml");this.beanFactory = ((AnnotationApplicationContext) ctx).beanFactory;//上一步已经完成了 Controller,service,respostry,autowired 等注解的扫描和注入//遍历容器,将 requestmapping 注解的路径和对应的方法以及 contoller 实例对应起来for(Map.Entry entry:beanFactory.entrySet()){Objectinstance = entry.getValue(); Class clazz = instance.getClass();if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);StringclassPath = requestMapping.value(); Method[] methods = clazz.getMethods();for(Method method:methods){if(method.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping2 = method.getAnnotation(RequestMapping.class);StringmethodPath = requestMapping2.value();StringrequestPath = classPath + methodPath; handleMapping.put(requestPath,method); controllerMapping.put(requestPath,instance); }else{continue; } } }else{continue; } } } @Override protectedvoiddoGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp); }//根据上一步获取到的 mapping,根据 url 找到对应的 controller 和方法去执行。@Override protectedvoiddoPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Stringuri = req.getRequestURI();// /paul-mvc/com.paul.controller/method-com.paul.controllerStringcontext = req.getContextPath();// /paul-vmcStringpath = uri.replace(context,"");// /com.paul.controller/method-com.paul.controllerMethod m = (Method) handleMapping.get(path);//从容器里拿到controller 实例Objectinstance = controllerMapping.get(path);Object[] args = handle(req,resp,m);for(Objecta:args){ System.out.println("Object:"+a); }try{ m.invoke(instance,args); }catch(IllegalAccessException e) { e.printStackTrace(); }catch(InvocationTargetException e) { e.printStackTrace(); } } privatestaticObject[] handle(HttpServletRequest req, HttpServletResponse resp,Method method){//拿到当前执行的方法有哪些参数Class[] paramClazzs = method.getParameterTypes();//根据参数的个数,new 一个参数的数据Object[] args =newObject[paramClazzs.length]; int args_i =0; int index =0;for(Class paramClazz:paramClazzs){if(ServletRequest.class.isAssignableFrom(paramClazz)){ args[args_i++] = req; }if(ServletResponse.class.isAssignableFrom(paramClazz)){ args[args_i++] = resp; }//判断requestParam 注解Annotation[] paramAns = method.getParameterAnnotations()[index];if(paramAns.length >0){ System.out.println("my");for(Annotation paramAn:paramAns){if(RequestParam.class.isAssignableFrom(paramAn.getClass())){ System.out.println("13mj"); RequestParam rp = (RequestParam) paramAn; args[args_i++] = req.getParameter(rp.value()); } } } index ++; }returnargs; }}
测试代码
新建一个 WebDemo,Maven web 项目。
先来看我们需要测试的 Controller 和 Service。
@Controller@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserService userService;@RequestMapping("/query")publicvoidget(HttpServletRequest request, HttpServletResponse response, @RequestParam("name")String name,@RequestParam("age")String age){try{ PrintWriter pw = response.getWriter(); String res = userService.query(name,age); pw.write(res); }catch(IOException e) { e.printStackTrace(); } }}
importorg.springframework.ioc.annotation.Service;@Service("userService")publicclassUserServiceImplimplementsUserService{@OverridepublicStringquery(String name, String age){return"name="+name+"age="+age; }}
在 resources 目录我们需要写一个名字为 applicationContext 的配置文件来指明扫描包路径。
<?xml version="1.0"encoding="UTF-8"?>
在 web.xml 中配置我们自己的 DispatcherServlet。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >Archetype Created Web ApplicationDispatcherServletcom.paul.servlet.DispatcherServlet0DispatcherServlet/
在浏览器中测试结果:
结果和我们想的一样。