本文的目的是使用300行代码实现Spring MVC 核心原理,读取配置文件,实现自己的ioc容器,实现依赖注入,再根据注解和对应的方法实例化HandlerMapping,更重要的是为之后的源码的阅读提供一个大概的轮廓.
实现的最终结果图 :
详细代码将会单独列出
总体概览
@Override public void init(ServletConfig config) throws ServletException { // 初始化阶段 //1.加载配置文件 //ServletConfig通过这个可以获得web.xml中的信息 // 拿到路径值 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2. 扫描相关的类 doScanner(contextConfig.getProperty("scanPackage")); // 3. 初始化扫描到的类,并且将其放入IOC容器之中 doInstance(); // 4. 完成依赖注入 doAutowired(); //5. 初始化HandlerMappering initHandlermapping(); System.out.println("MIMO Spring framework is init ."); } 加载配置文件 这一步的主要实现了将application.properties文件转换为输入流对象.并传给一个Properties对象[一种使用键值对保存信息的对象] private void doLoadConfig(String contextConfigLocation) { // 读取application.properties文件,并将转换为Properties对象 InputStream fis = null; fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(fis); } catch (IOException e) { e.printStackTrace(); } } 扫描相关的类 这一步的目的是获得所有的类的className的classNames’List /** * 扫描出相关的类 * * @param scanPackage */ private void doScanner(String scanPackage) { //scanPackage=com.gupaoedu.demo是包路径 //装换为文件路径,将'.'替换为'/' //这里是replaceall记住要全部进行替换 URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/")); File classpath = new File(url.getFile()); //遍历classpath下的所有文件 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); } } }3.初始化扫描到的类,并将其存到IOC容器之中
private void doInstance() { //初始化,会为DI做准备 if (classNames.isEmpty()) { return; } for (String classname : classNames ) { try { Class<?> clazz = Class.forName(classname); // 什么样的类才需要初始化[加了注解的类需要初始化] // 这里只有拥有GPContriller 和 GPService 这两个注解的类才会被初始化 //为了简化代码逻辑 if (clazz.isAnnotationPresent(GPController.class)) { try { Object instance = clazz.newInstance(); // Spring默认首字母小写 key className首字母小写 String beanName = toLowerFirstcase(clazz.getSimpleName()); ioc.put(beanName, instance); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } else if (clazz.isAnnotationPresent(GPService.class)) { GPService service = clazz.getAnnotation(GPService.class); //1. 获取自定义的beanName String beanName = service.value(); // 默认取value值 // 2. 默认类名首字母小写 //如果自定义的注解名为空,用注解名替换beanName if ("".equals(beanName.trim())) { // trim()去掉了字符串两端的空白字符 beanName = toLowerFirstcase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); //3. 根据类型自动赋值,投机取巧的方式 // 用其接口名作为key for (Class<?> i : clazz.getInterfaces() ) { if (ioc.containsKey(i.getName())) { // 判断是否有一个接口被多个类继承了 throw new Exception("The" + i.getName() + "is exist"); } ioc.put(i.getName(), instance); } } else { continue; } } catch (Exception e) { e.printStackTrace(); } } }4.依赖注入
/** * 依赖注入 */ private void doAutowired() { if (ioc.isEmpty()) { return; } for (Map.Entry<String, Object> entry : ioc.entrySet() ) { //Declared 所有的特定的 字段包括 private/protected/default //正常来说普通的OOP编程只能拿到public属性 // fields保存每个类的属性字段 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { // 判断字段上有没有注解 if (!field.isAnnotationPresent(GPAutowired.class)) { continue; } GPAutowired autowired = field.getAnnotation(GPAutowired.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(); } } } }5.初始化url和method一一对应关系
/** * 初始化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(GPController.class)) { continue; } // 保存写在类上面的@GPRequestMapping("/demo") String baseUrl = ""; if (clazz.isAnnotationPresent(GPRequestMapping.class)) { GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class); baseUrl = requestMapping.value(); } // 默认获取所有的public方法 ,判断其方法上是否有@RequestMapping注解 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(GPRequestMapping.class)) { continue; } GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class); String url = (baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); handlerMapping.put(url, method); System.out.println("Mapped" + url + "," + method); } } }6.调用doDispatch()分发请求
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { //绝对路径 String url = req.getRequestURI(); System.out.println("============URL============"); System.out.println(url); //处理成相对路径 String contextPath = req.getContextPath(); System.out.println("============contextPath============"); System.out.println(contextPath); System.out.println("============afterURl============"); url.replaceAll(contextPath, "").replaceAll("/+", "/"); System.out.println(url); System.out.println(this.handlerMapping.get(url)); if (!this.handlerMapping.containsValue(url)) { // resp.getWriter().write("404 NOT FOUND"); } toLowerFirstcase(method.getDeclaringClass().getSimpleName()); Method method = this.handlerMapping.get(url); //投机取巧的方式 //通过反射拿到method所在class,拿到class之后还是拿到class的名称 System.out.println("==== method's simple name ===="); // System.out.println(method.getDeclaringClass().getSimpleName()); String beanName = toLowerFirstcase(method.getDeclaringClass().getSimpleName()); // 投机取巧,暂时写死 System.out.println(req.toString()); Map<String, String[]> params = req.getParameterMap(); System.out.println("++++++++++++++++++++++++++"); System.out.println(params.get("name")[0]); System.out.println("++++++++++++++++++++++++++"); method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]}); }到此为止关键流程已经全部结束.
