模仿SpringMVC的DispatcherServlet 手撸300行代码提炼精华设计思想并保证功能可用(1.0版本)
前言
1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)
3、内容为手写SpringMVC的DistapcherServlet的核心功能,从V1版本到V2版本再到V3版本。
1、SpringMVC中的DispatcherServlet的核心功能是哪些?
首先看一下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);//handlerMappinginitHandlerMappings(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@Overrideprotected 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为空,则返回404if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.//3. 获取处理request的处理器适配器handler adapterHandlerAdapter 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 afterCompletionif (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的核心代码
2、配置阶段
配置 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 {@MyAutowiredprivate 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 {@Overridepublic String get(String name) {return "My name is " + name;}
}
3、容器初始化
实现V1版本
所有的核心逻辑全部写在一个 init()方法中,没有设计模式可言,代码比较长,混乱。不能一目了然。
public class MyDispatcherServletBak extends HttpServlet {private Map<String, Object> mapping = new HashMap<String, Object>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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@Overridepublic 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容器,为了简化程序只做演示,不考虑线程安全问题,不考虑ConcurrentHashMapprivate Map<String, Object> ioc = new HashMap<>();//保存url和method 的对应关系private Map<String, Method> handlerMapping = new HashMap<>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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,再获取beanNameString 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) {//如果是intif (Integer.class == type) {return Integer.valueOf(value[0]);} else if (Integer[].class == type) {//do somethingreturn null;} else if (String.class == type) {return value[0];}else if (String[].class == type) {return value;}//如果还有double或者其他类型,继续加if//这时候,我们应该想到策略模式了,在这里暂时不实现return value;}//初始化阶段@Overridepublic void init(ServletConfig config) throws ServletException {// 1、加载配置文件doLoadConfig(config.getInitParameter("contextConfigLocation"));//2、扫描相关的类doScanner(contextConfig.getProperty("scanPackage"));//3、初始化扫描到的类,并且将它们放到IOC容器之中doInstance();//4、完成依赖注入doAutowired();//5、初始化HandlerMappinginitHandlerMapping();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、自定义beanNameMyService 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!!!");// 把接口的类型直接当成keyioc.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容器,为了简化程序只做演示,不考虑线程安全问题,不考虑ConcurrentHashMapprivate Map<String, Object> ioc = new HashMap<>();//保存url和method 的对应关系// 为什么不用map?// 用Map,key只能是url ,但是HandlerMapping本身功能就是把url跟method关系对应,已经具备map的功能//根据设计原则:单一职能原则,最少知道原则。private List<HandlerMapping> handlerMapping = new ArrayList<>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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) {//如果是intif (Integer.class == type) {return Integer.valueOf(value[0]);} else if (Integer[].class == type) {//do somethingreturn null;} else if (String.class == type) {return value[0];} else if (String[].class == type) {return value;}//如果还有double或者其他类型,继续加if//这时候,我们应该想到策略模式了,在这里暂时不实现return value;}//初始化阶段@Overridepublic void init(ServletConfig config) throws ServletException {// 1、加载配置文件doLoadConfig(config.getInitParameter("contextConfigLocation"));//2、扫描相关的类doScanner(contextConfig.getProperty("scanPackage"));//3、初始化扫描到的类,并且将它们放到IOC容器之中doInstance();//4、完成依赖注入doAutowired();//5、初始化HandlerMappinginitHandlerMapping();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、自定义beanNameMyService 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!!!");// 把接口的类型直接当成keyioc.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 {//请求urlprivate String url;// url对应的methodprivate 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;}}}
模仿SpringMVC的DispatcherServlet 手撸300行代码提炼精华设计思想并保证功能可用(1.0版本)相关推荐
- 300行代码实现 微信或QQ 抢红包功能
每次看到别人发红包,但是没有抢到,这就很尴尬了.于是想着做一个红包插件,嘿嘿,再也不怕我反应迟钝了. 首先,想到做这个功能时,我联想到的是这个是不是lua写出来的脚本呢?但是条件不成立,因为可以不需要 ...
- 30个类仿真手写spring框架V2.0版本
相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...
- Weex 300行代码开发一款简易的跑步App
通过Weex 300行代码开发一款简易的跑步App 2017-03-28 Weex正如它的目标, 一套构建高性能.可扩展的原生应用的跨平台开发方案 Weex 给大家带来的无疑是客户端开发效率的提升,我 ...
- 特斯拉AI总监用300行代码实现“迷你版GPT”,上线GitHub三天收获3.3k星
晓查 发自 凹非寺 量子位 报道 | 公众号 QbitAI "GPT并不是一个复杂的模型." 前OpenAI科学家.现任特斯拉AI总监的Andrej Karpathy在自己的Gi ...
- 一天star量破千,300行代码,特斯拉AI总监Karpathy写了个GPT的Pytorch训练库
点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 整理:公众号@机器之心 本文仅做学术分享,如有侵权,请联系删除. 如果说 GPT 模型是所向披靡的战舰 ...
- 通过Mesos、Docker和Go,使用300行代码创建一个分布式系统
[摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...
- python编程小游戏代码-Python小游戏之300行代码实现俄罗斯方块
前言 本文代码基于 python3.6 和 pygame1.9.4. 俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块.但是想到旋转,停靠,消除等操作,感觉好像很 ...
- python小游戏代码大全-Python小游戏之300行代码实现俄罗斯方块
前言 本文代码基于 python3.6 和 pygame1.9.4. 俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块.但是想到旋转,停靠,消除等操作,感觉好像很 ...
- python小游戏源码-Python小游戏之300行代码实现俄罗斯方块
Python小游戏之300行代码实现俄罗斯方块 来源:中文源码网 浏览: 次 日期:2019年11月5日 [下载文档: Python小游戏之300行代码实现俄罗斯方块.txt ] (友情提示:右键点上 ...
最新文章
- 公司GitHub被封号,只因员工在伊朗开电脑,官方:将撤销被美制裁国家限制
- ASP.NET Core + Docker + Jenkins + gogs + CentOS 从零开始搭建持续集成
- Objective-C 学习笔记
- mysql 索引原理_MySQL InnoDB索引原理和算法
- 《Android游戏开发详解》——第2章,第2.10节使用对象
- js室内地图开发_如何组件化开发WebGIS系统
- 一个action类中写多个方法需要继承MappingDispatchAction
- 修改android的wifi客户端名称的两种方法
- 【Dairy】2016.11.5
- webstorm2017.1.3破解教程
- MapReduce job任务源码提交流程
- 三天研读《中兴电路设计规范》精华总结
- 如何下载centos7的iso文件
- 关于DTCC数据库技术大会
- 【收藏的一些博客地址-后端开发】
- AutoCAD Civil 3D 中缓和曲线的定义
- 数字化时代,如何提高企业的交易管理效率?
- 从OPPO Finder看手机产品的差异化体现
- 2021年小红书KOL营销白皮书
- matlab与化学,MATLAB与化学
热门文章
- 揭露一种通过网络实施ATM诈骗的手段!
- Microsoft Active Directory(LDAP)连接常见错误代码
- HTTPS 加密算法原理详解
- 最长公共子序列(POJ1458)
- fg jobs bg
- 数塔 简单的 动态规划
- 实验3 OpenGL几何变换
- shell脚本批量导出MYSQL数据库日志/按照最近N天的形式导出二进制日志[连载之构建百万访问量电子商务网站]...
- Proliant server setup redhat7.1(DL560)
- 速修复!开源编辑器CKEditor 中存在两个严重XSS漏洞,影响Drupal 和其它下游应用...