找回密码
 立即注册
首页 业界区 业界 用300行代码手写Spring核心原理

用300行代码手写Spring核心原理

汪之亦 9 小时前
本文将带你深入了解Spring框架的核心原理,通过300行代码的迷你版本来展示Spring最核心的特性:IoC(控制反转)、DI(依赖注入)和MVC(模型-视图-控制器)模式的实现。
mini版Spring实现思路

1.png

实现过程

自定义注解

在Spring框架中,注解是非常重要的组成部分。我们的迷你版也实现了几个关键注解
  1. // 控制器注解,标记控制器类
  2. @Target({ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. public @interface SevenController {
  6.     String value() default "";
  7. }
  8. // 服务注解,标记服务类
  9. @Target({ElementType.TYPE})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface SevenService {
  13.     String value() default "";
  14. }
  15. // 请求映射注解,可用于类或方法
  16. @Target({ElementType.TYPE,ElementType.METHOD})
  17. @Retention(RetentionPolicy.RUNTIME)
  18. @Documented
  19. public @interface SevenRequestMapping {
  20.     String value() default "";
  21. }
  22. // 参数映射注解,用于方法参数
  23. @Target({ElementType.PARAMETER})
  24. @Retention(RetentionPolicy.RUNTIME)
  25. @Documented
  26. public @interface SevenRequestParam {
  27.     String value() default "";
  28. }
  29. // 自动装配注解,用于依赖注入
  30. @Target({ElementType.FIELD})
  31. @Retention(RetentionPolicy.RUNTIME)
  32. @Documented
  33. public @interface SevenAutowired {
  34.     String value() default "";
  35. }
复制代码
这些注解的实现非常简单,但它们构成了整个框架的基础。通过运行时保留策略,我们可以在运行时通过反射机制来识别这些注解并执行相应的操作。

  • @SevenController 和 @SevenService 用于标识需要被IoC容器管理的类
  • @SevenAutowired 用于标识需要自动注入的依赖
  • @SevenRequestMapping 用于映射请求URL到具体的方法
  • @SevenRequestParam 用于映射请求参数到方法参数
这些自定义注解的设计思路完全模仿了Spring框架中的标准注解,如@Controller、@Service、@Autowired、@RequestMapping
IoC容器和DI依赖注入的实现

IoC(控制反转)和DI(依赖注入)是Spring框架的核心特性。
IoC容器的初始化过程:IoC容器本质上是一个Map集合,用来存储所有被管理的对象实例。在SevenDispatcherServlet中,我们定义了一个简单的IoC容器:
  1. //传说中的IOC容器,我们来揭开它的神秘面纱
  2. //为了简化程序,暂时不考虑ConcurrentHashMap
  3. // 主要还是关注设计思想和原理
  4. private Map<String,Object> ioc = new HashMap<String,Object>();
复制代码
IoC容器的初始化分为四个关键步骤:

  • 加载配置文件 - 读取application.properties文件
  • 扫描相关类 - 扫描指定包下的所有类
  • 实例化类 - 创建被注解标记的类的实例
  • 依赖注入 - 将依赖关系注入到对象中


  • 加载配置文件
  1. //加载配置文件
  2. private void doLoadConfig(String contextConfigLocation) {
  3.     //直接从类路径下找到Spring主配置文件所在的路径
  4.     //并且将其读取出来放到Properties对象中
  5.     InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
  6.     try {
  7.         contextConfig.load(is);
  8.     } catch (IOException e) {
  9.         e.printStackTrace();
  10.     } finally {
  11.         if(null != is){
  12.             try {
  13.                 is.close();
  14.             } catch (IOException e) {
  15.                 e.printStackTrace();
  16.             }
  17.         }
  18.     }
  19. }
复制代码

  • 扫描相关类
  1. //扫描出相关的类
  2. private void doScanner(String scanPackage) {
  3.     //scanPackage = com.seven.minispringsourcecod.demo ,存储的是包路径
  4.     URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));
  5.    
  6.     //转换为文件路径,实际上就是把.替换为/就OK了
  7.     File classPath = new File(url.getFile());
  8.     for (File file : classPath.listFiles()) {
  9.         if(file.isDirectory()){
  10.             doScanner(scanPackage + "." + file.getName());
  11.         } else {
  12.             //变成包名.类名
  13.             if (!file.getName().endsWith(".class")) {  continue; }
  14.             classNames.add(scanPackage + "." + file.getName().replace(".class", ""));
  15.         }
  16.     }
  17. }
复制代码

  • 实例化类并存入容器
  1. private void doInstance() {
  2.     if(classNames.isEmpty()){return;}
  3.     try {
  4.         for (String className : classNames) {
  5.             Class<?> clazz = Class.forName(className);
  6.             //什么样的类才需要初始化呢?
  7.             //加了注解的类,才初始化
  8.             if(clazz.isAnnotationPresent(SevenController.class)){
  9.                 Object instance = clazz.newInstance();
  10.                 String beanName = toLowerFirstCase(clazz.getSimpleName());
  11.                 //key-value
  12.                 //class类名的首字母小写
  13.                 ioc.put(beanName,instance);
  14.             } else if(clazz.isAnnotationPresent(SevenService.class)) {
  15.                 //1、默认就根据beanName类名首字母小写
  16.                 String beanName = toLowerFirstCase(clazz.getSimpleName());
  17.                 //2、使用自定义的beanName
  18.                 SevenService service = clazz.getAnnotation(SevenService.class);
  19.                 if(!"".equals(service.value())){
  20.                     beanName = service.value();
  21.                 }
  22.                 Object instance = clazz.newInstance();
  23.                 ioc.put(beanName,instance);
  24.                 //3、根据包名.类名作为beanName
  25.                 for (Class<?> i : clazz.getInterfaces()) {
  26.                     if(ioc.containsKey(i.getName())){
  27.                         throw new Exception("The beanName is exists!!");
  28.                     }
  29.                     //把接口的类型直接当成key了
  30.                     ioc.put(i.getName(),instance);
  31.                 }
  32.             } else {
  33.                 continue;
  34.             }
  35.         }
  36.     } catch (Exception e){
  37.         e.printStackTrace();
  38.     }
  39. }
  40. //工具方法:将类名首字母转为小写
  41. private String toLowerFirstCase(String simpleName) {
  42.     char [] chars = simpleName.toCharArray();
  43.     //之所以加,是因为大小写字母的ASCII码相差32,
  44.     // 而且大写字母的ASCII码要小于小写字母的ASCII码
  45.     //在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
  46.     chars[0] += 32;
  47.     return String.valueOf(chars);
  48. }
复制代码

  • 依赖注入(DI)
  1. private void doAutowired() {
  2.     if(ioc.isEmpty()){return;}
  3.     for (Map.Entry<String,Object> entry : ioc.entrySet()) {
  4.         //拿到实例的所有的字段
  5.         Field[] fields = entry.getValue().getClass().getDeclaredFields();
  6.         for (Field field : fields) {
  7.             if(!field.isAnnotationPresent(SevenAutowired.class)){
  8.                 continue;
  9.             }
  10.             SevenAutowired autowired = field.getAnnotation(SevenAutowired.class);
  11.             //如果用户没有自定义beanName,默认就根据类型注入
  12.             String beanName = autowired.value().trim();
  13.             if("".equals(beanName)){
  14.                 //获得接口的类型,作为key待会拿这个key到ioc容器中去取值
  15.                 beanName = field.getType().getName();
  16.             }
  17.             //如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值
  18.             //反射中叫做暴力访问
  19.             field.setAccessible(true);
  20.             //反射调用的方式
  21.             //给entry.getValue()这个对象的field字段,赋ioc.get(beanName)这个值
  22.             try {
  23.                 field.set(entry.getValue(),ioc.get(beanName));
  24.             } catch (IllegalAccessException e) {
  25.                 e.printStackTrace();
  26.                 continue;
  27.             }
  28.         }
  29.     }
  30. }
复制代码
依赖注入的过程:

  • 遍历IoC容器中的每个对象
  • 获取对象的所有字段
  • 检查字段是否标注了@SevenAutowired注解
  • 如果有注解,则从IoC容器中查找对应的依赖对象
  • 使用反射将依赖对象注入到当前对象的字段中
通过这种方式,我们就实现了控制反转——对象不再需要手动创建依赖,而是由容器负责创建和注入。
MVC模式的实现

MVC(Model-View-Controller)模式是Web开发中的经典架构模式。在我们的迷你版Spring框架中,MVC的实现主要体现在HandlerMapping的建立和请求处理两个方面。
HandlerMapping的初始化

HandlerMapping是MVC模式中的核心组件,它建立了URL请求与控制器方法之间的映射关系:
  1. //初始化url和Method的一对一对应关系
  2. private void doInitHandlerMapping() {
  3.     if(ioc.isEmpty()){return;}
  4.     for (Map.Entry<String,Object> entry : ioc.entrySet()) {
  5.        Class<?> clazz = entry.getValue().getClass();
  6.        if(!clazz.isAnnotationPresent(SevenController.class)){ continue; }
  7.         //保存写在类上面的@SevenRequestMapping("/demo")
  8.        String baseUrl = "";
  9.        if(clazz.isAnnotationPresent(SevenRequestMapping.class)){
  10.            SevenRequestMapping requestMapping = clazz.getAnnotation(SevenRequestMapping.class);
  11.            baseUrl = requestMapping.value();
  12.        }
  13.         //默认获取所有的public方法
  14.         for (Method method : clazz.getMethods()) {
  15.             if(!method.isAnnotationPresent(SevenRequestMapping.class)){continue;}
  16.             SevenRequestMapping requestMapping = method.getAnnotation(SevenRequestMapping.class);
  17.             //  无斜杠:demoquery
  18.             //  多个斜杠://demo//query
  19.             String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/");
  20.             handlerMapping.put(url,method);
  21.             System.out.println("Mapped " + url + "," + method);
  22.         }
  23.     }
  24. }
复制代码
这段代码的执行逻辑如下:

  • 遍历IoC容器中的所有对象
  • 找到被@SevenController注解标记的类
  • 提取类上的@SevenRequestMapping注解作为基础路径
  • 遍历类中的所有方法
  • 找到被@SevenRequestMapping注解标记的方法
  • 将基础路径和方法路径组合成完整的URL路径
  • 建立URL路径与方法的映射关系并存储在handlerMapping中
请求处理过程

当HTTP请求到达时,系统会根据URL找到对应的方法并执行:
[code]private 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);    Map paramsMap = req.getParameterMap();    //实参列表    //实参列表要根据形参列表才能决定,首先得拿到形参列表    Class [] paramterTypes = method.getParameterTypes();    Object [] parameValues = new Object[paramterTypes.length];    for (int i = 0; i

相关推荐

您需要登录后才可以回帖 登录 | 立即注册