本文将带你深入了解Spring框架的核心原理,通过300行代码的迷你版本来展示Spring最核心的特性:IoC(控制反转)、DI(依赖注入)和MVC(模型-视图-控制器)模式的实现。
mini版Spring实现思路
实现过程
自定义注解
在Spring框架中,注解是非常重要的组成部分。我们的迷你版也实现了几个关键注解- // 控制器注解,标记控制器类
- @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、@RequestMapping
IoC容器和DI依赖注入的实现
IoC(控制反转)和DI(依赖注入)是Spring框架的核心特性。
IoC容器的初始化过程:IoC容器本质上是一个Map集合,用来存储所有被管理的对象实例。在SevenDispatcherServlet中,我们定义了一个简单的IoC容器:- //传说中的IOC容器,我们来揭开它的神秘面纱
- //为了简化程序,暂时不考虑ConcurrentHashMap
- // 主要还是关注设计思想和原理
- private Map<String,Object> ioc = new HashMap<String,Object>();
复制代码 IoC容器的初始化分为四个关键步骤:
- 加载配置文件 - 读取application.properties文件
- 扫描相关类 - 扫描指定包下的所有类
- 实例化类 - 创建被注解标记的类的实例
- 依赖注入 - 将依赖关系注入到对象中
- //加载配置文件
- 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();
- }
- }
- }
- }
复制代码- //扫描出相关的类
- 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", ""));
- }
- }
- }
复制代码- private 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);
- }
复制代码- private void doAutowired() {
- if(ioc.isEmpty()){return;}
- for (Map.Entry<String,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模式的实现
MVC(Model-View-Controller)模式是Web开发中的经典架构模式。在我们的迷你版Spring框架中,MVC的实现主要体现在HandlerMapping的建立和请求处理两个方面。
HandlerMapping的初始化
HandlerMapping是MVC模式中的核心组件,它建立了URL请求与控制器方法之间的映射关系:- //初始化url和Method的一对一对应关系
- private void doInitHandlerMapping() {
- if(ioc.isEmpty()){return;}
- for (Map.Entry<String,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找到对应的方法并执行:
[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 |