1、博客内容均出自于咕泡学院架构师第三期 2、架构师系列内容:架构师学习笔记(持续更新) 3、内容为手写SpringMVC的DistapcherServlet的核心功能,从V1版本到V2版本再到V3版本。
首先看一下mvc的流程:
用户发送请求至前端控制器DispatcherServlet。DispatcherServlet收到请求调用HandlerMapping处理器映射器。处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。DispatcherServlet调用HandlerAdapter处理器适配器。HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。Controller执行完成返回ModelAndView。HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。DispatcherServlet将ModelAndView传给ViewReslover视图解析器。ViewReslover解析后返回具体View。DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。再看DispatcherServlet 主要做了什么:
@SuppressWarnings("serial") public class DispatcherServlet extends FrameworkServlet { /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ //初始化策略 protected void initStrategies(ApplicationContext context) { //多文件上传的组件 initMultipartResolver(context); //初始化本地语言环境 initLocaleResolver(context); //初始化模板处理器 initThemeResolver(context); //handlerMapping initHandlerMappings(context); //初始化参数适配器 initHandlerAdapters(context); //初始化异常拦截器 initHandlerExceptionResolvers(context); //初始化视图预处理器 initRequestToViewNameTranslator(context); //初始化视图转换器 initViewResolvers(context); // initFlashMapManager(context); } /** * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch} * for the actual dispatching. */ //获取请求,设置一些request的参数,然后分发给doDispatch @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); } // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. /* 设置web应用上下文**/ request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); /* 国际化本地**/ request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); /* 样式**/ request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); /* 设置样式资源**/ request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); //请求刷新时保存属性 if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } //Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); //FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体. request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } try { doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } } /** * Process the actual dispatching to the handler. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ /** * 中央控制器,控制请求的转发 * 将Handler进行分发,handler会被handlerMapping有序的获得 * 通过查询servlet安装的HandlerAdapters来获得HandlerAdapters来查找第一个支持handler的类 * 所有的HTTP的方法都会被这个方法掌控。取决于HandlerAdapters 或者handlers 他们自己去决定哪些方法是可用 * **/ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 1.检查是否是文件上传的请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 2.取得处理当前请求的controller,这里也称为handler,处理器, // 第一个步骤的意义就在这里体现了.这里并不是直接返回controller, // 而是返回的HandlerExecutionChain请求处理器链对象, // 该对象封装了handler和interceptors. mappedHandler = getHandler(processedRequest); // 如果handler为空,则返回404 if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //3. 获取处理request的处理器适配器handler adapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // 处理 last-modified 请求头 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // 4.实际的处理器处理请求,返回结果视图对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // 结果视图对象的处理 applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { // 请求成功响应之后的方法 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } } }DispatcherServlet 主要做了initStrategies 初始化策略 ,doService获取请求,设置一些request的参数,然后分发给doDispatch ,doDispatch 中央控制器,控制请求的转发
再来看下面这个图: 接下来就按照DispatcherServlet 的核心代码,加上图中的流程来模拟DispatchServlet的核心代码
配置 application.properties 文件 为了解析方便,我们用 application.properties 来代替 application.xml 文件,具体配置内容如下:
scanPackage=com.jarvisy.demo配置 web.xml 文件 所有依赖于 web 容器的项目,都是从读取 web.xml 文件开始的。我们先配置好 web.xml中的内容:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--配置springmvc DispatcherServlet--> <servlet> <servlet-name>springMVC</servlet-name> // 配置成自己实现的MyDispatcherServlet类 <servlet-class>com.jarvisy.demo.v1.servlet.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> // 正常这里应该是application.xml文件 <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> // 这里拦截所以请求 <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>自定义Annotation 把SpringMVC的注解拷过来,去掉看不懂的,对目前来说无用的:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAutowired { String value() default ""; } //----------------------------分割线,不同类--------------------------------------- @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyController { String value() default ""; } //----------------------------分割线,不同类--------------------------------------- @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestMapping { String value() default ""; } //----------------------------分割线,不同类--------------------------------------- @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestParam { String value() default ""; } //----------------------------分割线,不同类--------------------------------------- @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyService { String value() default ""; }编写Controller 类,Servive类:
@MyController @MyRequestMapping("/demo") public class DemoController { @MyAutowired private DemoService demoService; @MyRequestMapping("/name") public void name(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam() String name) { String result = demoService.get(name); try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping("/name1") public void name1(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam() String[] name) { String result = Arrays.asList(name).toString(); try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping("/add") public void add(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam("a") Integer a, @MyRequestParam("b") Integer b) { try { resp.getWriter().write(a + " + " + b + " = " + (a + b)); } catch (IOException e) { e.printStackTrace(); } } } //----------------------------分割线,不同类--------------------------------------- public interface DemoService { public String get(String name ); } //----------------------------分割线,不同类--------------------------------------- @MyService public class DemoServiceImpl implements DemoService { @Override public String get(String name) { return "My name is " + name; } }实现V1版本 所有的核心逻辑全部写在一个 init()方法中,没有设计模式可言,代码比较长,混乱。不能一目了然。
public class MyDispatcherServletBak extends HttpServlet { private Map<String, Object> mapping = new HashMap<String, Object>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { doDispatch(req, resp); } catch (Exception e) { resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace())); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { String url = req.getRequestURI(); String contextPath = req.getContextPath(); url = url.replace(contextPath, "").replaceAll("/+", "/"); if (!this.mapping.containsKey(url)) { resp.getWriter().write("404 Not Found!!"); return; } Method method = (Method) this.mapping.get(url); Map<String, String[]> params = req.getParameterMap(); method.invoke(this.mapping.get(method.getDeclaringClass().getName()), new Object[]{req, resp, params.get("name")[0]}); } //init方法肯定干得的初始化的工作 //inti首先我得初始化所有的相关的类,IOC容器、servletBean @Override public void init(ServletConfig config) throws ServletException { InputStream is = null; try { Properties configContext = new Properties(); is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation")); configContext.load(is); String scanPackage = configContext.getProperty("scanPackage"); doScanner(scanPackage); for (String className : mapping.keySet()) { if (!className.contains(".")) { continue; } Class<?> clazz = Class.forName(className); if (clazz.isAnnotationPresent(MyController.class)) { mapping.put(className, clazz.newInstance()); String baseUrl = ""; if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class); baseUrl = requestMapping.value(); } Method[] methods = clazz.getMethods(); for (Method method : methods) { if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; } MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class); String url = (baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); mapping.put(url, method); System.out.println("Mapped " + url + "," + method); } } else if (clazz.isAnnotationPresent(MyService.class)) { MyService service = clazz.getAnnotation(MyService.class); String beanName = service.value(); if ("".equals(beanName)) { beanName = clazz.getName(); } Object instance = clazz.newInstance(); mapping.put(beanName, instance); for (Class<?> i : clazz.getInterfaces()) { mapping.put(i.getName(), instance); } } else { continue; } } for (Object object : mapping.values()) { if (object == null) { continue; } Class clazz = object.getClass(); if (clazz.isAnnotationPresent(MyController.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MyAutowired.class)) { continue; } MyAutowired autowired = field.getAnnotation(MyAutowired.class); String beanName = autowired.value(); if ("".equals(beanName)) { beanName = field.getType().getName(); } field.setAccessible(true); try { field.set(mapping.get(clazz.getName()), mapping.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } catch (Exception e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } System.out.print("My MVC Framework is init"); } private void doScanner(String scanPackage) { URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/")); File classDir = new File(url.getFile()); for (File file : classDir.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { if (!file.getName().endsWith(".class")) { continue; } String clazzName = (scanPackage + "." + file.getName().replace(".class", "")); mapping.put(clazzName, null); } } } }实现V2版本 在 V1 版本上进了优化,采用了常用的设计模式(工厂模式、单例模式、委派模式、策略模式),将 init()方法中的代码进行封装。按照之前的实现思路,先搭基础框架,再填肉注血,具体代码如下 其中: doInstance() : IOC容器就是注册式单例,工厂模式应用案例 initHandlerMapping() : handlerMapping 就是策略模式的应用案例 doPost() : 用了委派模式,委派模式的具体逻辑在 doDispatch()方法中
public class MyDispatcherServlet extends HttpServlet { //保存application.properties配置文件中的内容 private Properties contextConfig = new Properties(); //保存扫描的所有的类名 private List<String> classNames = new ArrayList<>(); // 传说中的IOC容器,为了简化程序只做演示,不考虑线程安全问题,不考虑ConcurrentHashMap private Map<String, Object> ioc = new HashMap<>(); //保存url和method 的对应关系 private Map<String, Method> handlerMapping = new HashMap<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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 Exception , Detail:" + Arrays.toString(e.getStackTrace())); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { // 绝对路径->处理成相对路径 String url = req.getRequestURI().replaceAll(req.getContextPath(), "").replaceAll("/+", "/"); if (!this.handlerMapping.containsKey(url)) { resp.getWriter().write("My DispatcherServlet 404 Not Found"); return; } Method method = this.handlerMapping.get(url); //通过反射拿到method所在的class,拿到className,再获取beanName String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); //从req中拿到url传过来的参数 Map<String, String[]> params = req.getParameterMap(); DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer(); //获取方法参数的真实名称 String[] parameterNames = discover.getParameterNames(method); //获取方法的形参列表 Class<?>[] parameterTypes = method.getParameterTypes(); //获取参数上的所有注解 二维数组,可以有多个参数,每个参数可以有多个注解 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); // 这里不考虑对象赋值,只是简化的版本 Object[] paramValues = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { Class parameterType = parameterTypes[i]; //不能用instanceof,parameterType它不是实参,而是形参 if (parameterType == HttpServletRequest.class) { paramValues[i] = req; continue; } else if (parameterType == HttpServletResponse.class) { paramValues[i] = resp; continue; } else {// 这里不考虑对象赋值等, 只做简化演示 // 如果没有MyRequestParam注解,或者MyRequestParam的value 为默认值的话,就直接用形参name去获取 String paramName = ""; for (Annotation a : parameterAnnotations[i]) { if (a instanceof MyRequestParam) { paramName = ((MyRequestParam) a).value(); break; } } if ("".equals(paramName)) paramName = parameterNames[i]; if (params.containsKey(paramName)) { paramValues[i] = convert(parameterType, params.get(paramName)); } } } method.invoke(ioc.get(beanName), paramValues); } //进行数据类型转换 private Object convert(Class<?> type, String[] value) { //如果是int if (Integer.class == type) { return Integer.valueOf(value[0]); } else if (Integer[].class == type) { //do something return null; } else if (String.class == type) { return value[0]; }else if (String[].class == type) { return value; } //如果还有double或者其他类型,继续加if //这时候,我们应该想到策略模式了,在这里暂时不实现 return value; } //初始化阶段 @Override public void init(ServletConfig config) throws ServletException { // 1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化扫描到的类,并且将它们放到IOC容器之中 doInstance(); //4、完成依赖注入 doAutowired(); //5、初始化HandlerMapping initHandlerMapping(); System.out.println("My Spring Framework Is Init"); } // 初始化url和Method的一对一的关系 private void initHandlerMapping() { if (ioc.isEmpty()) return; for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MyController.class)) continue; //保存写在类上面的@MyRequestMapping("/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class); baseUrl = requestMapping.value(); } //默认获取所有的public方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MyRequestMapping.class)) continue; MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class); String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); handlerMapping.put(url, method); System.out.println("Mapped :" + url + "," + method); } } } private void doAutowired() { if (ioc.isEmpty()) return; for (Map.Entry<String, Object> entry : ioc.entrySet()) { //Declared 所有的,特定的,字段。包括private,protected,default // 正常来讲,普通的OOP编程只能拿到public的属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MyAutowired.class)) continue; MyAutowired autowired = field.getAnnotation(MyAutowired.class); // 如果没有自定义beanName 则采用类型注入 String beanName = autowired.value().trim(); //field.getType().getName() 获得接口类型作为key去取 if ("".equals(beanName)) beanName = field.getType().getName(); //强吻 暴力访问 field.setAccessible(true); try { //用反射机制,动态给字段属性复制 field.set(entry.getValue(), ioc.get(beanName)); } catch (Exception e) { e.printStackTrace(); } } } } private void doInstance() { //初始化,为DI做准备 //如果为null 就不往下走 if (classNames.isEmpty()) return; try { for (String className : classNames) { Class<?> clazz = Class.forName(className); //这里只需要初始化加了我们自定义注解的类 //这里只是做演示,体会其流程,设计思想,只举例@Controller 和@Service... if (clazz.isAnnotationPresent(MyController.class)) { //Spring默认类名首字母小写 ioc.put(toLowerFirstCase(clazz.getSimpleName()), clazz.newInstance()); } else if (clazz.isAnnotationPresent(MyService.class)) { //1、自定义beanName MyService service = clazz.getAnnotation(MyService.class); String beanName = service.value(); //2、如果没有自定义beanName,则默认类名首字母小写 if ("".equals(beanName.trim())) beanName = toLowerFirstCase(clazz.getSimpleName()); Object newInstance = clazz.newInstance(); ioc.put(beanName, newInstance); //3、根据类型自动赋值 这里是找到他的所有接口然后给他实现类的值,是为了Autowired的时候方便(在注入的时候直接用接口类型去ioc取) for (Class<?> anInterface : clazz.getInterfaces()) { if (ioc.containsKey(anInterface.getName())) throw new Exception("The" + anInterface.getName() + " is exists!!!"); // 把接口的类型直接当成key ioc.put(anInterface.getName(), newInstance); } } } } catch (Exception e) { e.printStackTrace(); } } //转换首字母小写 private String toLowerFirstCase(String beanName) { return beanName.replaceFirst("^.", String.valueOf(beanName.charAt(0)).toLowerCase()); } //扫描出相关的类 private void doScanner(String scanPackage) { //scanPackage = com.jarvisy.demo.mvc //需要把包路径转换为文件路径 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 { //只扫描class文件 if (!file.getName().endsWith(".class")) continue; String className = (scanPackage + "." + file.getName().replace(".class", "")); classNames.add(className); } } } //加载配置文件 private void doLoadConfig(String contextConfigLocation) { //直接从类路径下找到Spring主配置文件所在的路径,并且将其读取出来放到Properties对象中 //相当于把scanPackage=com.jarvisy.demo.mvc 从文件中保存到了内存中。 try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation)) { contextConfig.load(inputStream); } catch (IOException e) { e.printStackTrace(); } } }V3版本优化: 在 V2 版本中,基本功能以及完全实现,但代码的优雅程度还不如人意。譬如 HandlerMapping 还不能像 SpringMVC一样支持正则,url 参数还不支持强制类型转换,在反射调用前还需要重新获取 beanName,在 V3 版本中,继续优化 首先,改造 HandlerMapping,在真实的 Spring 源码中,HandlerMapping 其实是一个 List 而非 Map。List 中的元素是一个自定义的类型。
public class MyDispatcherServlet extends HttpServlet { //保存application.properties配置文件中的内容 private Properties contextConfig = new Properties(); //保存扫描的所有的类名 private List<String> classNames = new ArrayList<>(); // 传说中的IOC容器,为了简化程序只做演示,不考虑线程安全问题,不考虑ConcurrentHashMap private Map<String, Object> ioc = new HashMap<>(); //保存url和method 的对应关系 // 为什么不用map? // 用Map,key只能是url ,但是HandlerMapping本身功能就是把url跟method关系对应,已经具备map的功能 //根据设计原则:单一职能原则,最少知道原则。 private List<HandlerMapping> handlerMapping = new ArrayList<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.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 Exception , Detail:" + Arrays.toString(e.getStackTrace())); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { HandlerMapping handlerMapping = getHandle(req); if (handlerMapping == null) { resp.getWriter().write("My DispatcherServlet 404 Not Found"); return; } Object[] paramValues = new Object[handlerMapping.paramTypes.length]; Map<String, String[]> params = req.getParameterMap(); for (Map.Entry<String, String[]> param : params.entrySet()) { if (!handlerMapping.paramIndexMapping.containsKey(param.getKey())) continue; Integer index = handlerMapping.paramIndexMapping.get(param.getKey()); paramValues[index] = convert(handlerMapping.paramTypes[index], param.getValue()); } if (handlerMapping.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) paramValues[handlerMapping.paramIndexMapping.get(HttpServletRequest.class.getName())] = req; if (handlerMapping.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) paramValues[handlerMapping.paramIndexMapping.get(HttpServletResponse.class.getName())] = resp; handlerMapping.method.invoke(handlerMapping.controller, paramValues); } private HandlerMapping getHandle(HttpServletRequest req) { if (this.handlerMapping.isEmpty()) return null; // 绝对路径->处理成相对路径 String url = req.getRequestURI().replaceAll(req.getContextPath(), "").replaceAll("/+", "/"); for (HandlerMapping mapping : this.handlerMapping) { if (mapping.getUrl().equals(url)) return mapping; } return null; } //进行数据类型转换 //Spring 做了顶层转换策略 public interface Converter<S, T> 实现了很多转换类型 private Object convert(Class<?> type, String[] value) { //如果是int if (Integer.class == type) { return Integer.valueOf(value[0]); } else if (Integer[].class == type) { //do something return null; } else if (String.class == type) { return value[0]; } else if (String[].class == type) { return value; } //如果还有double或者其他类型,继续加if //这时候,我们应该想到策略模式了,在这里暂时不实现 return value; } //初始化阶段 @Override public void init(ServletConfig config) throws ServletException { // 1、加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2、扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); //3、初始化扫描到的类,并且将它们放到IOC容器之中 doInstance(); //4、完成依赖注入 doAutowired(); //5、初始化HandlerMapping initHandlerMapping(); System.out.println("My Spring Framework Is Init"); } // 初始化url和Method的一对一的关系 private void initHandlerMapping() { if (ioc.isEmpty()) return; for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MyController.class)) continue; //保存写在类上面的@MyRequestMapping("/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class); baseUrl = requestMapping.value(); } //默认获取所有的public方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(MyRequestMapping.class)) continue; MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class); String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); this.handlerMapping.add(new HandlerMapping(url, entry.getValue(), method)); System.out.println("Mapped :" + url + "," + method); } } } private void doAutowired() { if (ioc.isEmpty()) return; for (Map.Entry<String, Object> entry : ioc.entrySet()) { //Declared 所有的,特定的,字段。包括private,protected,default // 正常来讲,普通的OOP编程只能拿到public的属性 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(MyAutowired.class)) continue; MyAutowired autowired = field.getAnnotation(MyAutowired.class); // 如果没有自定义beanName 则采用类型注入 String beanName = autowired.value().trim(); //field.getType().getName() 获得接口类型作为key去取 if ("".equals(beanName)) beanName = field.getType().getName(); //强吻 暴力访问 field.setAccessible(true); try { //用反射机制,动态给字段属性复制 field.set(entry.getValue(), ioc.get(beanName)); } catch (Exception e) { e.printStackTrace(); } } } } private void doInstance() { //初始化,为DI做准备 //如果为null 就不往下走 if (classNames.isEmpty()) return; try { for (String className : classNames) { Class<?> clazz = Class.forName(className); //这里只需要初始化加了我们自定义注解的类 //这里只是做演示,体会其流程,设计思想,只举例@Controller 和@Service... if (clazz.isAnnotationPresent(MyController.class)) { //Spring默认类名首字母小写 ioc.put(toLowerFirstCase(clazz.getSimpleName()), clazz.newInstance()); } else if (clazz.isAnnotationPresent(MyService.class)) { //1、自定义beanName MyService service = clazz.getAnnotation(MyService.class); String beanName = service.value(); //2、如果没有自定义beanName,则默认类名首字母小写 if ("".equals(beanName.trim())) beanName = toLowerFirstCase(clazz.getSimpleName()); Object newInstance = clazz.newInstance(); ioc.put(beanName, newInstance); //3、根据类型自动赋值 这里是找到他的所有接口然后给他实现类的值,是为了Autowired的时候方便(在注入的时候直接用接口类型去ioc取) for (Class<?> anInterface : clazz.getInterfaces()) { if (ioc.containsKey(anInterface.getName())) throw new Exception("The" + anInterface.getName() + " is exists!!!"); // 把接口的类型直接当成key ioc.put(anInterface.getName(), newInstance); } } } } catch (Exception e) { e.printStackTrace(); } } //转换首字母小写 private String toLowerFirstCase(String beanName) { return beanName.replaceFirst("^.", String.valueOf(beanName.charAt(0)).toLowerCase()); } //扫描出相关的类 private void doScanner(String scanPackage) { //scanPackage = com.jarvisy.demo.mvc //需要把包路径转换为文件路径 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 { //只扫描class文件 if (!file.getName().endsWith(".class")) continue; String className = (scanPackage + "." + file.getName().replace(".class", "")); classNames.add(className); } } } //加载配置文件 private void doLoadConfig(String contextConfigLocation) { //直接从类路径下找到Spring主配置文件所在的路径,并且将其读取出来放到Properties对象中 //相当于把scanPackage=com.jarvisy.demo.mvc 从文件中保存到了内存中。 try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation)) { contextConfig.load(inputStream); } catch (IOException e) { e.printStackTrace(); } } //保存一个url和一个Method的关系 public class HandlerMapping { //请求url private String url; // url对应的method private Method method; private Object controller; //形参列表 参数的名字作为key,参数的顺序位置作为值 private Map<String, Integer> paramIndexMapping; private Class<?>[] paramTypes; public HandlerMapping(String url, Object controller, Method method) { this.url = url; this.method = method; this.controller = controller; paramIndexMapping = new HashMap<>(); paramTypes = method.getParameterTypes(); putParamIndexMapping(method); } private void putParamIndexMapping(Method method) { DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer(); //获取方法参数的真实名称 String[] parameterNames = discover.getParameterNames(method); //提取方法中加了注解的参数 Annotation[][] pa = method.getParameterAnnotations(); for (int i = 0; i < paramTypes.length; i++) { Class<?> type = paramTypes[i]; if (type == HttpServletRequest.class || type == HttpServletResponse.class) { paramIndexMapping.put(type.getName(), i); continue; } String paramName = ""; for (Annotation a : pa[i]) { if (a instanceof MyRequestParam) { paramName = ((MyRequestParam) a).value(); break; } } if ("".equals(paramName)) paramName = parameterNames[i]; paramIndexMapping.put(paramName, i); } } public String getUrl() { return url; } public Method getMethod() { return method; } public Object getController() { return controller; } public Class<?>[] getParamTypes() { return paramTypes; } } }