简介
为了更深入的了解 Spring 的实现原理和设计思想,一直打算出个系列文章,从零开始重新学习 Spring。有兴趣的小伙伴可以持续关注更新。
平台
地址
CSDN
https://blog.csdn.net/sinat_28690417
简书
https://www.jianshu.com/u/3032cc862300
个人博客
https://yiyuer.github.io/NoteBooks/
正文
基本思路
配置阶段配置 web.xmlDispatchSevlet设定 init-paramcontextConfigLocation = classpath:application.xml设定url-pattern/*定义Annotation@Controller@Service@Autowried@RequestMapping初始化阶段调用 init() 方法加载配置文件IOC容器初始化Map扫描相关的类Scan-package="com.yido"创建实例化并保存至容器通过反射机制将类实例化放入 IOC 容器进行 DI扫描 IOC 容器中的实例,给没有赋值的属性自动赋值初始化 HandlerMapping将 URL 和 Method 建立一对一的映射关系运行阶段调用 doPost() / doGet()Web 容器调用 doPost() / doGet() ,获得 request / response 对象匹配 HandlerMapping从 request 对象中获取用户输入的 url , 找到对应的 Method反射调用 method.invoke()利用反射调用方法并返回结果返回结果利用 response.getWriter().write(), 将返回结果输出到浏览器V1 版本准备工作
pom.xml
sevlet-api 依赖jetty 插件V1.0.0 版本 注解定义定义 Controller 和 Service
定义配置
src/main/resources/application.propertiesscanPackage=com.yido.demoweb.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>Sun Web Application</display-name> <!--定义核心调度 servlet --> <servlet> <servlet-name>mvc-servlet</servlet-name> <servlet-class>com.yido.mvcframework.v1.servlet.XDispatchServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-servlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <!--直接通过 servlet api 使用--> <servlet> <servlet-name>sun-servlet</servlet-name> <servlet-class>com.yido.simple.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>sun-servlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>定义 DispatchServlet/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou Ashe Dev, Ltd. All Right Reserved. * @address: https://yiyuery.github.io/NoteBooks/ * @date: 2020/4/12 5:30 下午 * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.yido.mvcframework.v1.servlet; import com.yido.mvcframework.annotation.XAutowired; import com.yido.mvcframework.annotation.XController; import com.yido.mvcframework.annotation.XRequestMapping; import com.yido.mvcframework.annotation.XService; 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.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.*; /** * <p> * 手写一个 请求转发器 DispatchServlet * </p> * * @author Helios * @date 2020/4/12 5:30 下午 */ public class XDispatchServlet extends HttpServlet { /** * key: 请求路由 * value: * - 对应 Controller 实例 * - Method 方法 */ private Map<String, Object> mapping = new HashMap<String, Object>(); /** * Get 请求处理转发 * * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } /** * Post请求处理转发 * * @param req * @param resp * @throws ServletException * @throws IOException */ @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())); } } /** * 统一转发处理所有请求数据 * * @param req * @param resp * @throws ServletException * @throws IOException */ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, InvocationTargetException, IllegalAccessException { //1. 获取参数和请求路径 String url = req.getRequestURI(); String contextPath = req.getContextPath(); //替换请求上下文 url = url.replace(contextPath, "") //替换多余 '/' .replaceAll("/+", "/"); // 2. 获取请求处理器 /** * {@link XDispatchServlet#init(ServletConfig)} * 从缓存的 requestMapping 中加载指定包路径下的请求路径对应的 Controller 处理器 * - 配置 web.xml * - 初始化时 加载配置文件 */ if (!this.mapping.containsKey(url)) { resp.getWriter().write("404 Not Found!"); return; } Method method = (Method) this.mapping.get(url); // 3. 解析参数并通过反射执行方法 Map<String, String[]> parameterMap = req.getParameterMap(); Object controller = this.mapping.get(method.getDeclaringClass().getName()); method.invoke(controller, new Object[]{req, resp, parameterMap.get("name")[0]}); } /** * 加载配置并缓存 Controller 实例和 初始化请求对应的 Method映射 * * @param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { InputStream is = null; try { //1. 读取参数 Properties configContext = new Properties(); is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation")); configContext.load(is); //2. IOC 构建容器, 扫描所有实例和请求方法映射 doIoc(configContext.getProperty("scanPackage")); //3. DI 依赖注入 doInjection(); } catch (Exception e){ e.printStackTrace(); } finally { if (is != null) { try{ is.close(); } catch (IOException e) { e.printStackTrace(); } } } System.out.println("XSpring MVC Framework has been initialed"); } /** * 依赖注入 */ private void doInjection() { Collection<Object> values = mapping.values(); for (Object value : values) { if (null == value) { continue; } Class clazz = value.getClass(); if (clazz.isAnnotationPresent(XController.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(XAutowired.class)) { continue; } XAutowired autowired = field.getAnnotation(XAutowired.class); String beanName = autowired.value(); if ("".equals(beanName)) { beanName = field.getType().getName(); } //注入依赖实例 field.setAccessible(true); try { field.set(value, mapping.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } /** * IOC 加载所有请求处理方法映射和实例 */ private void doIoc(String scanPackage) throws ClassNotFoundException, IllegalAccessException, InstantiationException { doScanTask(scanPackage); Map<String, Object> cacheMap = new HashMap<String, Object>(); for (String clazzName : mapping.keySet()) { if (!clazzName.contains(".")) { continue; } Class<?> clazz = Class.forName(clazzName); // 1. 处理 XController String baseUrl = ""; if (clazz.isAnnotationPresent(XController.class)) { cacheMap.put(clazzName, clazz.newInstance()); // 1.1 解析请求路径前缀 if (clazz.isAnnotationPresent(XRequestMapping.class)) { XRequestMapping requestMapping = clazz.getAnnotation(XRequestMapping.class); baseUrl = requestMapping.value(); } // 1.2 解析方法 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(XRequestMapping.class)) { XRequestMapping annotation = method.getAnnotation(XRequestMapping.class); String url = baseUrl + annotation.value(); cacheMap.put(url, method); System.out.println("> Mapped--------->url: " + url + "," + method.getName()); } } // 2. 处理 XService } else if (clazz.isAnnotationPresent(XService.class)) { XService service = clazz.getAnnotation(XService.class); String beanName = service.value(); if ("".equals(beanName)) { beanName = clazzName.getClass().getName(); } Object instance = clazz.newInstance(); cacheMap.put(beanName, instance); for (Class<?> i : clazz.getInterfaces()) { cacheMap.put(i.getName(),instance); } } } if (!cacheMap.isEmpty()) { this.mapping.putAll(cacheMap); } } /** * 扫描指定包路径下所有需实例类 * @param scanPackage */ private void doScanTask(String scanPackage) { URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/")); File rootDir = new File(url.getFile()); for (File file : rootDir.listFiles()) { if (file.isDirectory()) { doScanTask(scanPackage+"."+file.getName()); }else{ if (!file.getName().endsWith(".class")) { continue; } String clazzName = scanPackage + "." + file.getName().replace(".class", ""); mapping.put(clazzName, null); } } } }效果演示/** * 返回欢迎信息 * support: * spring-v1 * @param name */ @XRequestMapping("/v1/welcome") public void welcome(HttpServletRequest req, HttpServletResponse resp, @XRequestParam(value = "name") String name) { String result = helloService.welcome(name); try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } }.
小结XDispatchServlet 职责不够单一流程方法混乱,不清晰XDispatchServlet 需要进行重构没有自动解析注入 Request 、Response 对象Spring 思想没有体现,没有 ApplicationContext、BeanDefinition、BeanDefinitionReader,没有解决循环依赖问题没有 Aop 逻辑To do Continue….
---来自腾讯云社区的---架构探险之道
微信扫一扫打赏
支付宝扫一扫打赏