一、 mini 版 Spring MVC 基本架构设计通过简单的代码,来学习 Spring 的设计思想,掌握 Spring 框架的基本脉络。主要目的是为了学习思想,有了思想之后,我们后面再学习源码的时候,就会相对轻松许多。
基本思路分为三步,因为是 mini 版,所以只是简单的实现基本功能。具体步骤如下:
1、配置阶段
配置 web.xml --> DispatchServlet
设定 init-param --> contextConfigLocation=classPath:application.xml
设定 url-pattern --> @MmtService public class DemoService implements IDemoService { @Override public String get(String name) { return "Hello MVC : " + name + ",from service."; } }
配置请求入口类 DemoAction :
package com.mmt.mini.demo.action; import com.mmt.mini.demo.service.IDemoService; import com.mmt.mini.mvc.framework.annotation.MmtAutowired; import com.mmt.mini.mvc.framework.annotation.MmtController; import com.mmt.mini.mvc.framework.annotation.MmtRequestMapping; import com.mmt.mini.mvc.framework.annotation.MmtRequestParam; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @MmtController @MmtRequestMapping("/demo") public class DemoAction { @MmtAutowired IDemoService demoService; @MmtRequestMapping("/query") public void query(HttpServletRequest req, HttpServletResponse resp, @MmtRequestParam("name") String name) { String result = demoService.get(name); try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MmtRequestMapping("/add") public void add(HttpServletRequest req, HttpServletResponse resp, @MmtRequestParam("a") Integer a, @MmtRequestParam("b") Integer b) { try { resp.getWriter().write(a + "+" + b + "=" + (a + b)); } catch (IOException e) { e.printStackTrace(); } } @MmtRequestMapping("/sub") public void add(HttpServletRequest req, HttpServletResponse resp, @MmtRequestParam("a") Double a, @MmtRequestParam("b") Double b) { try { resp.getWriter().write(a + "-" + b + "=" + (a - b)); } catch (IOException e) { e.printStackTrace(); } } @MmtRequestMapping("/remove") public String remove(@MmtRequestParam("id") Integer id) { return "" + id; } }大家注意看,这里使用的均是上面我们自己定义的注解,和 Spring 没有关系,看起来,是不是有点那个意思了?
好了,配置完了之后,配置阶段就完毕了,下面要进入容器初始化过程。
2. 容器初始化【核心内容】初始化话容器的时候,进入到核心类 MmtDispatchServlet 中,利用 HttpServlet 的生命中周期,进行自定义的初始化过程,这里直接上代码,通过代码和注释,有助于理解:
package com.mmt.mini.mvc.framework; import com.mmt.mini.mvc.framework.annotation.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class MmtDispatchServlet extends HttpServlet { private Properties contextConfig = new Properties(); private List3. 运行阶段classNames = new ArrayList (); private Map ioc = new HashMap (); private List handlerMapping = new ArrayList (); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //6、调用,运行阶段 try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace())); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { Handler handler = getHandler(req); if (handler == null) { resp.getWriter().write("404 Not Found!!!"); return; } //获得方法的形参列表 Class>[] paramTypes = handler.getParamTypes(); Object[] paramValues = new Object[paramTypes.length]; Map params = req.getParameterMap(); for (Map.Entry parm : params.entrySet()) { String value = Arrays.toString(parm.getValue()).replaceAll("\[|\]", "").replaceAll("\s", ","); if (!handler.paramIndexMapping.containsKey(parm.getKey())) { continue; } int index = handler.paramIndexMapping.get(parm.getKey()); paramValues[index] = convert(paramTypes[index], value); } if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) { int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName()); paramValues[reqIndex] = req; } if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) { int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName()); paramValues[respIndex] = resp; } // 通过反射执行方法 method.invoke Object returnValue = handler.method.invoke(handler.controller, paramValues); if (returnValue == null || returnValue instanceof Void) { return; } // 这里写的比较简单,直接返回浏览器了,其实还会有视图解析器的步骤,理解思想即可 resp.getWriter().write(returnValue.toString()); } private Handler getHandler(HttpServletRequest req) { if (handlerMapping.isEmpty()) { return null; } //绝对路径 String url = req.getRequestURI(); //处理成相对路径 String contextPath = req.getContextPath(); url = url.replaceAll(contextPath, "").replaceAll("/+", "/"); for (Handler handler : this.handlerMapping) { Matcher matcher = handler.getPattern().matcher(url); if (!matcher.matches()) { continue; } return handler; } return null; } private Object convert(Class> type, String value) { //如果是int if (Integer.class == type) { return Integer.valueOf(value); } else if (Double.class == type) { return Double.valueOf(value); } //如果还有double或者其他类型,继续加if //这时候,我们应该想到策略模式了 //在这里暂时不实现,希望小伙伴自己来实现 return value; } @Override public void init(ServletConfig config) throws ServletException { //1、加载配置文件 contextConfigLocation doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化扫描到的类,并且将它们放入到ICO容器之中 doInstance(); //4、完成依赖注入 doAutowired(); //5、初始化HandlerMapping initHandlerMapping(); System.out.println("MMT MINI Spring framework is init."); } private void doLoadConfig(String contextConfigLocation) { //直接从类路径下找到Spring主配置文件所在的路径 //并且将其读取出来放到Properties对象中 //相对于scanPackage=com.mmt.mini 从文件中保存到了内存中 // 需要对路径进行处理,web.xml中配置的路径为 classpath:application.properties InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replace("classpath:", "")); try { contextConfig.load(fis); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fis) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } private void doScanner(String scanPackage) { //scanPackage = com.mmt.mini ,存储的是包路径 //转换为文件路径,实际上就是把.替换为/就OK了 //classpath URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/")); File classPath = new File(url.getFile()); for (File file : classPath.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { continue; } String className = (scanPackage + "." + file.getName().replace(".class", "")); classNames.add(className); } } } private void doInstance() { //初始化,为DI做准备 if (classNames.isEmpty()) { return; } try { for (String className : classNames) { Class> clazz = Class.forName(className); //什么样的类才需要初始化呢? //加了注解的类,才初始化,怎么判断? //为了简化代码逻辑,主要体会设计思想,只举例 @Controller和@Service, // @Componment...就一一举例了 if (clazz.isAnnotationPresent(MmtController.class)) { Object instance = clazz.newInstance(); //Spring默认类名首字母小写 String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MmtService.class)) { //1、自定义的beanName MmtService service = clazz.getAnnotation(MmtService.class); String beanName = service.value(); //2、默认类名首字母小写 if ("".equals(beanName.trim())) { beanName = toLowerFirstCase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); //3、根据类型自动赋值,投机取巧的方式 for (Class> i : clazz.getInterfaces()) { if (ioc.containsKey(i.getName())) { throw new Exception("The “" + i.getName() + "” is exists!!"); } //把接口的类型直接当成key了 ioc.put(i.getName(), instance); } } else { continue; } } } catch (Exception e) { e.printStackTrace(); } } private void doAutowired() { if (ioc.isEmpty()) { return; } for (Map.Entryentry : ioc.entrySet()) { //Declared 所有的,特定的 字段,包括private/protected/default //正常来说,普通的OOP编程只能拿到public的属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MmtAutowired.class)) { continue; } MmtAutowired autowired = field.getAnnotation(MmtAutowired.class); //如果用户没有自定义beanName,默认就根据类型注入 //这个地方省去了对类名首字母小写的情况的判断 String beanName = autowired.value().trim(); if ("".equals(beanName)) { //获得接口的类型,作为key待会拿这个key到ioc容器中去取值 beanName = field.getType().getName(); } //如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值 //反射中叫做暴力访问, 强吻 field.setAccessible(true); try { //用反射机制,动态给字段赋值 field.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } private void initHandlerMapping() { if (ioc.isEmpty()) { return; } for (Map.Entry entry : ioc.entrySet()) { Class> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MmtController.class)) { continue; } //保存写在类上面的@MmtRequestMapping("/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(MmtRequestMapping.class)) { MmtRequestMapping requestMapping = clazz.getAnnotation(MmtRequestMapping.class); baseUrl = requestMapping.value(); } //默认获取所有的public方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MmtRequestMapping.class)) { continue; } MmtRequestMapping requestMapping = method.getAnnotation(MmtRequestMapping.class); //优化,防止 requestmapping中的请求路径没有写/ 或者都写了 / ,在这里,不管写没写,通统一处理一下 String regex = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); this.handlerMapping.add(new Handler(pattern, entry.getValue(), method)); System.out.println("Mapped :" + pattern + "," + method); } } } private String toLowerFirstCase(String simpleName) { char[] chars = simpleName.toCharArray(); //之所以加,是因为大小写字母的ASCII码相差32, // 而且大写字母的ASCII码要小于小写字母的ASCII码 //在Java中,对char做算学运算,实际上就是对ASCII码做算学运算 chars[0] += 32; return String.valueOf(chars); } public class Handler { //必须把url放到HandlerMapping才好理解吧 private Pattern pattern; private Method method; private Object controller; private Class>[] paramTypes; public Pattern getPattern() { return pattern; } public Method getMethod() { return method; } public Object getController() { return controller; } public Class>[] getParamTypes() { return paramTypes; } //形参列表 //参数的名字作为key,参数的顺序,位置作为值 private Map paramIndexMapping; public Handler(Pattern pattern, Object controller, Method method) { this.pattern = pattern; this.method = method; this.controller = controller; paramTypes = method.getParameterTypes(); paramIndexMapping = new HashMap (); putParamIndexMapping(method); } private void putParamIndexMapping(Method method) { //提取方法中加了注解的参数 //把方法上的注解拿到,得到的是一个二维数组 //因为一个参数可以有多个注解,而一个方法又有多个参数 Annotation[][] pa = method.getParameterAnnotations(); for (int i = 0; i < pa.length; i++) { for (Annotation a : pa[i]) { if (a instanceof MmtRequestParam) { String paramName = ((MmtRequestParam) a).value(); if (!"".equals(paramName.trim())) { paramIndexMapping.put(paramName, i); } } } } //提取方法中的request和response参数 Class>[] paramsTypes = method.getParameterTypes(); for (int i = 0; i < paramsTypes.length; i++) { Class> type = paramsTypes[i]; if (type == HttpServletRequest.class || type == HttpServletResponse.class) { paramIndexMapping.put(type.getName(), i); } } } } } 运行阶段的代码,已经放在了上面代码中,也就是第六步,doPost 方法中,利用委派墨水,通过 doDispatch方法,从 request 中获取对应的请求及参数,拿到请求后,通过我们自定义的HandlerMapping 中进行匹配,如果匹配成功,说明请求正确,存在我们的系统中,通过 method.invoker反射执行方法,从而返回响应。
以上,mini 版的 springMvc 框架就写完了,虽然功能不是很完善,但是基本的脉络思想已经描述清楚。
4. 运行效果图真正的 Spring 要复杂很多,本文通过手写简易版的 SpringMVC,了解其基本的设计思想以及设计模式的简单使用,在 Spring 的源码中,使用了大量的设计模式,所以他的代码才会如此的优雅。



