本文将带你深入了解Spring框架的核心原理通过300行代码的迷你版本来展示Spring最核心的特性IoC控制反转、DI依赖注入和MVC模型-视图-控制器模式的实现。mini版Spring实现思路实现过程自定义注解在Spring框架中注解是非常重要的组成部分。我们的迷你版也实现了几个关键注解java// 控制器注解标记控制器类 Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented public interface SevenController { String value() default ; } // 服务注解标记服务类 Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented public interface SevenService { String value() default ; } // 请求映射注解可用于类或方法 Target({ElementType.TYPE,ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Documented public interface SevenRequestMapping { String value() default ; } // 参数映射注解用于方法参数 Target({ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Documented public interface SevenRequestParam { String value() default ; } // 自动装配注解用于依赖注入 Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Documented public interface SevenAutowired { String value() default ; }这些注解的实现非常简单但它们构成了整个框架的基础。通过运行时保留策略我们可以在运行时通过反射机制来识别这些注解并执行相应的操作。SevenController 和 SevenService 用于标识需要被IoC容器管理的类SevenAutowired 用于标识需要自动注入的依赖SevenRequestMapping 用于映射请求URL到具体的方法SevenRequestParam 用于映射请求参数到方法参数这些自定义注解的设计思路完全模仿了Spring框架中的标准注解如Controller、Service、Autowired、RequestMappingIoC容器和DI依赖注入的实现IoC控制反转和DI依赖注入是Spring框架的核心特性。IoC容器的初始化过程IoC容器本质上是一个Map集合用来存储所有被管理的对象实例。在SevenDispatcherServlet中我们定义了一个简单的IoC容器java//传说中的IOC容器我们来揭开它的神秘面纱 //为了简化程序暂时不考虑ConcurrentHashMap // 主要还是关注设计思想和原理 private MapString,Object ioc new HashMapString,Object();IoC容器的初始化分为四个关键步骤加载配置文件 - 读取application.properties文件扫描相关类 - 扫描指定包下的所有类实例化类 - 创建被注解标记的类的实例依赖注入 - 将依赖关系注入到对象中加载配置文件java//加载配置文件 private void doLoadConfig(String contextConfigLocation) { //直接从类路径下找到Spring主配置文件所在的路径 //并且将其读取出来放到Properties对象中 InputStream is this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { contextConfig.load(is); } catch (IOException e) { e.printStackTrace(); } finally { if(null ! is){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }扫描相关类java//扫描出相关的类 private void doScanner(String scanPackage) { //scanPackage com.seven.minispringsourcecod.demo 存储的是包路径 URL url this.getClass().getClassLoader().getResource(/ scanPackage.replaceAll(\\.,/)); //转换为文件路径实际上就是把.替换为/就OK了 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; } classNames.add(scanPackage . file.getName().replace(.class, )); } } }实例化类并存入容器javaprivate void doInstance() { if(classNames.isEmpty()){return;} try { for (String className : classNames) { Class? clazz Class.forName(className); //什么样的类才需要初始化呢 //加了注解的类才初始化 if(clazz.isAnnotationPresent(SevenController.class)){ Object instance clazz.newInstance(); String beanName toLowerFirstCase(clazz.getSimpleName()); //key-value //class类名的首字母小写 ioc.put(beanName,instance); } else if(clazz.isAnnotationPresent(SevenService.class)) { //1、默认就根据beanName类名首字母小写 String beanName toLowerFirstCase(clazz.getSimpleName()); //2、使用自定义的beanName SevenService service clazz.getAnnotation(SevenService.class); if(!.equals(service.value())){ beanName service.value(); } Object instance clazz.newInstance(); ioc.put(beanName,instance); //3、根据包名.类名作为beanName for (Class? i : clazz.getInterfaces()) { if(ioc.containsKey(i.getName())){ throw new Exception(The beanName is exists!!); } //把接口的类型直接当成key了 ioc.put(i.getName(),instance); } } else { continue; } } } catch (Exception e){ e.printStackTrace(); } } //工具方法将类名首字母转为小写 private String toLowerFirstCase(String simpleName) { char [] chars simpleName.toCharArray(); //之所以加是因为大小写字母的ASCII码相差32 // 而且大写字母的ASCII码要小于小写字母的ASCII码 //在Java中对char做算学运算实际上就是对ASCII码做算学运算 chars[0] 32; return String.valueOf(chars); }依赖注入DIjavaprivate void doAutowired() { if(ioc.isEmpty()){return;} for (Map.EntryString,Object entry : ioc.entrySet()) { //拿到实例的所有的字段 Field[] fields entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if(!field.isAnnotationPresent(SevenAutowired.class)){ continue; } SevenAutowired autowired field.getAnnotation(SevenAutowired.class); //如果用户没有自定义beanName默认就根据类型注入 String beanName autowired.value().trim(); if(.equals(beanName)){ //获得接口的类型作为key待会拿这个key到ioc容器中去取值 beanName field.getType().getName(); } //如果是public以外的修饰符只要加了Autowired注解都要强制赋值 //反射中叫做暴力访问 field.setAccessible(true); //反射调用的方式 //给entry.getValue()这个对象的field字段赋ioc.get(beanName)这个值 try { field.set(entry.getValue(),ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); continue; } } } }依赖注入的过程遍历IoC容器中的每个对象获取对象的所有字段检查字段是否标注了SevenAutowired注解如果有注解则从IoC容器中查找对应的依赖对象使用反射将依赖对象注入到当前对象的字段中通过这种方式我们就实现了控制反转——对象不再需要手动创建依赖而是由容器负责创建和注入。MVC模式的实现MVCModel-View-Controller模式是Web开发中的经典架构模式。在我们的迷你版Spring框架中MVC的实现主要体现在HandlerMapping的建立和请求处理两个方面。HandlerMapping的初始化HandlerMapping是MVC模式中的核心组件它建立了URL请求与控制器方法之间的映射关系java//初始化url和Method的一对一对应关系 private void doInitHandlerMapping() { if(ioc.isEmpty()){return;} for (Map.EntryString,Object entry : ioc.entrySet()) { Class? clazz entry.getValue().getClass(); if(!clazz.isAnnotationPresent(SevenController.class)){ continue; } //保存写在类上面的SevenRequestMapping(/demo) String baseUrl ; if(clazz.isAnnotationPresent(SevenRequestMapping.class)){ SevenRequestMapping requestMapping clazz.getAnnotation(SevenRequestMapping.class); baseUrl requestMapping.value(); } //默认获取所有的public方法 for (Method method : clazz.getMethods()) { if(!method.isAnnotationPresent(SevenRequestMapping.class)){continue;} SevenRequestMapping requestMapping method.getAnnotation(SevenRequestMapping.class); // 无斜杠:demoquery // 多个斜杠://demo//query String url (/ baseUrl / requestMapping.value()).replaceAll(/,/); handlerMapping.put(url,method); System.out.println(Mapped url , method); } } }这段代码的执行逻辑如下遍历IoC容器中的所有对象找到被SevenController注解标记的类提取类上的SevenRequestMapping注解作为基础路径遍历类中的所有方法找到被SevenRequestMapping注解标记的方法将基础路径和方法路径组合成完整的URL路径建立URL路径与方法的映射关系并存储在handlerMapping中请求处理过程当HTTP请求到达时系统会根据URL找到对应的方法并执行javaprivate void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { String url req.getRequestURI(); String contextPath req.getContextPath(); url url.replaceAll(contextPath,).replaceAll(/,/); if(!this.handlerMapping.containsKey(url)){ resp.getWriter().write(404 Not Found!!); return; } Method method this.handlerMapping.get(url); MapString,String[] paramsMap req.getParameterMap(); //实参列表 //实参列表要根据形参列表才能决定首先得拿到形参列表 Class? [] paramterTypes method.getParameterTypes(); Object [] parameValues new Object[paramterTypes.length]; for (int i 0; i paramterTypes.length; i ){ Class paramterType paramterTypes[i]; if(paramterType HttpServletRequest.class){ parameValues[i] req; continue; } else if(paramterType HttpServletResponse.class){