首页 > 其他分享 >用 300 行代码手写提炼 Spring 核心原理 [1]

用 300 行代码手写提炼 Spring 核心原理 [1]

时间:2024-07-21 22:51:57浏览次数:10  
标签:String 300 Spring annotation minispring import org 手写 example

手写一个 mini 版本的 Spring 框架是一个很好的实践项目,可以让你对框架的核心概念和实现有更深刻的理解。接下来我们从 0-1 逐层深入,一步一步揭开 Spring 的神秘面纱。

自定义配置

配置 application.properties

为了解析方便,我们用 application.properties 来代替 application.xml 文件,具体的配置内容如下:

scanPackage=org.example.minispring

配置 web.xml

所有依赖于 Web 容器的项目都是从读取 web.xml 文件开始的,我们先配置好 web.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/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>MiniSpring</display-name>
  <servlet>
  	<servlet-name>dispatcher-servlet</servlet-name>
  	<servlet-class>org.example.minispring.framework.v1.MyDispatcherServlet</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>dispatcher-servlet</servlet-name>
  	<url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

其中 MyDispatcherServlet 是模拟 Spring 实现的核心功能类。

自定义注解

  • @MyService 注解
package org.example.minispring.framework.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyService {
	String value() default "";	
}
  • @MyAutowired 注解
package org.example.minispring.framework.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyAutowired {
	String value() default "";	
}
  • @MyController 注解
package org.example.minispring.framework.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyController {
	String value() default "";	
}
  • @MyRequestMapping 注解
package org.example.minispring.framework.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyRequestMapping {
	String value() default "";	
}
  • @MyRequestParam 注解
package org.example.minispring.framework.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface MyRequestParam {
	String value() default "";	
}

配置注解

  • 配置业务实现类 DemoService

按照规范,先定义接口类 IDemoService

package org.example.minispring.service;

public interface IDemoService {
	public String get(String name);	
}

再定义实现类:

package org.example.minispring.service.impl;

import org.example.minispring.framework.annotation.MyService;
import org.example.minispring.service.IDemoService;

@MyService
public class DemoService implements IDemoService {

	@Override
	public String get(String name) {
		return "Hello " + name + "!";
	}
}
  • 配置请求入口类 DemoAction
package org.example.minispring.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.example.minispring.framework.annotation.MyAutowired;
import org.example.minispring.framework.annotation.MyController;
import org.example.minispring.framework.annotation.MyRequestMapping;
import org.example.minispring.framework.annotation.MyRequestParam;
import org.example.minispring.service.IDemoService;

@MyController
@MyRequestMapping("/demo")
public class DemoAction {

	@MyAutowired
	private IDemoService demoService;
	
	@MyRequestMapping("/query")
	public void query(HttpServletRequest req, HttpServletResponse resp,
			@MyRequestParam("name") String name) {
		String result = demoService.get(name);
		try {
			resp.getWriter().write("<html><h2>" + result + "</h2></html>");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

容器初始化

所有核心逻辑全部写在 MyDispatcherServlet 的 init() 方法中:

package org.example.minispring.framework.v1;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.example.minispring.framework.annotation.MyAutowired;
import org.example.minispring.framework.annotation.MyController;
import org.example.minispring.framework.annotation.MyRequestMapping;
import org.example.minispring.framework.annotation.MyService;

public class MyDispatcherServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	// 保存beanName -> bean的映射关系
	private Map<String, Object> beanMapping = new HashMap<>();
	// 保存url -> method的映射关系
	private Map<String, Object> handlerMapping = new HashMap<>();

	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req, resp);
	}
	
	@Override
	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			doDispatch(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
			resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
		}
	}
	
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		String url = req.getRequestURI();
		String contextPath = req.getContextPath();
		url = url.replace(contextPath, "").replaceAll("/+", "/");
		if (!this.handlerMapping.containsKey(url)) {
			resp.getWriter().write("404 Not Found");
			return;
		}
		// 根据url找到对应的方法
		// 此处method为DemoAction.query(HttpServletRequest, HttpServletResponse, String)
		Method method = (Method) this.handlerMapping.get(url);
		// 获取请求参数, 此处为: name = [浏览器传来的值]
		Map<String, String[]> params = req.getParameterMap();
		
		// 1. method.getDeclaringClass().getName()
        //	  本例为org.example.minispring.action.DemoAction
		// 2. beanMapping.get(beanName): 根据beanName获取到对应的bean实例,例如:
        //	  org.example.minispring.action.DemoAction@51e3ce14
		// 3. method.invoke调用的就是
        //	  [email protected](req, resp, name)
		String beanName = method.getDeclaringClass().getName();
		method.invoke(this.beanMapping.get(beanName), new Object[] { req, resp, params.get("name")[0] });
	}
    
	@Override
	public void init(ServletConfig config) throws ServletException {
		InputStream is = this.getClass().getClassLoader()
				.getResourceAsStream(config.getInitParameter("contextConfigLocation"));
		Properties configContext = new Properties();
		try {
			configContext.load(is);
			String scanPackage = configContext.getProperty("scanPackage");
            // 扫描相关的类,本例中scanPackage=org.example.minispring
			doScanner(scanPackage);
            
			for (String className : beanMapping.keySet()) {
				Class<?> clazz = Class.forName(className);
				// 解析@MyController注解
				if (clazz.isAnnotationPresent(MyController.class)) {
					// 保存className和@MyController实例的对应关系
					beanMapping.put(className, clazz.newInstance());
					String baseUrl = "";
					// 解析@MyController上的@MyRequestMapping注解,作为当前Controller的baseUrl
					if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
						MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
						baseUrl = requestMapping.value();
					}
					// 解析@MyController中方法上的@MyRequestMapping注解
					Method[] methods = clazz.getMethods();
					for (Method method : methods) {
						if (!method.isAnnotationPresent(MyRequestMapping.class)) {
							continue;
						}
						MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
						// 组合方法签名上的完整url,正则替换是为防止路径中出现多个连续多个"/"的不规范写法
						String url = (baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
						// 保存url -> method的对应关系
						handlerMapping.put(url, method);
						System.out.println("Mapped " + url + " -> " + method);
					}
				}
				// 解析@MyService注解
				else if (clazz.isAnnotationPresent(MyService.class)) {
					MyService service = clazz.getAnnotation(MyService.class);
					String beanName = service.value();
					if ("".equals(beanName)) {
						beanName = clazz.getName();
					}
					Object instance = clazz.newInstance();					
					// 保存className和@MyService实例的对应关系
					beanMapping.put(beanName, instance);
					for (Class<?> i : clazz.getInterfaces()) {
						beanMapping.put(i.getName(), instance);
					}
				}
			}
			
			// 解析对象之间的依赖关系,依赖注入
			for (Object object : beanMapping.values()) {
				if (object == null) {
					continue;
				}
				Class<?> clazz = object.getClass();
				// 向MyController中注入MyService
				if (clazz.isAnnotationPresent(MyController.class)) {
					Field[] fields = clazz.getDeclaredFields();
					for (Field field : fields) {
						if (!field.isAnnotationPresent(MyAutowired.class)) {
							continue;
						}
						MyAutowired autowired = field.getAnnotation(MyAutowired.class);
						String beanName = autowired.value();
						if ("".equals(beanName)) {
							beanName = field.getType().getName();
						}
                        // 只要加了@MyAutowired注解都要强制赋值
						// 反射中叫做暴力访问
						field.setAccessible(true);
                        // 用反射机制动态给字段赋值
						// 赋值后DemoAction.demoService = DemoService@c97ae21
						// 也即DemoService实例被注入到了DemoAction对象中,这就是依赖注入
						field.set(beanMapping.get(clazz.getName()), beanMapping.get(beanName));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 扫描相关的类,本例中scanPackage=org.example.minispring
	 */
	private void doScanner(String scanPackage) {
		URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
		File classDir = new File(url.getFile());
		for (File file : classDir.listFiles()) {
			if (file.isDirectory()) {
				// 递归扫描子文件夹
				doScanner(scanPackage + "." + file.getName());
			} else {
				String clazzName = scanPackage + "." + file.getName().replace(".class", "");
				beanMapping.put(clazzName, null);
			}
		}
	}
}

运行演示

到此为止我们就实现了 mini-spring 的 1.0 版本。

1.0 版本具备了初步的功能,但是代码不够优雅,接下来我们在此基础上进行优化,采用常用的设计模式(工厂模式、单例模式、委派模式、策略模式)将 init() 方法中的代码进行封装,请看下篇。

参考

[1] 《Spring 5 核心原理与 30 个类手写实战》,谭勇德著。

标签:String,300,Spring,annotation,minispring,import,org,手写,example
From: https://www.cnblogs.com/myownswordsman/p/18315078/mini-spring

相关文章

  • Elastic Search基于Spring Boot实现复杂查询和对复杂查询结果的映射银行账户对象并获
    packagecom.alatus.search;importcom.alatus.search.config.MallElasticSearchConfig;importcom.alibaba.fastjson.JSON;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importlombok.ToString;importorg.elasticsearch.......
  • 【深度学习入门项目】多层感知器(MLP)实现手写数字识别
    多层感知器(MLP)实现手写数字识别导入必要的包获得软件包的版本信息下载并可视化数据查看一个batch的数据查看图片细节信息设置随机种子定义模型架构Buildmodel_1Buildmodel_2TraintheNetwork(30marks)Trainmodel_1Trainmodel_1Visualizethetrainingprocess......
  • 毕业设计&毕业项目:基于springboot+vue实现的在线音乐平台
    一、前言        在当今数字化时代,音乐已经成为人们生活中不可或缺的一部分。随着技术的飞速发展,构建一个用户友好、功能丰富的在线音乐平台成为了许多开发者和创业者的目标。本文将介绍如何使用SpringBoot作为后端框架,结合Vue.js作为前端框架,共同实现一个高效、可扩展的......
  • 计算机Java项目|基于SpringBoot的高校办公室行政事务管理系统
    作者主页:编程指南针作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与......
  • A144-基于SpringBoot的大学生心理健康咨询系统(源码+数据库+文档+包运行)
    项目简介这是一个基于SpringBoot框架开发的在线心理测评管理系统,主要分为两个角色:管理员和用户。系统提供了一系列功能,旨在方便管理员和用户进行相关操作。管理员角色功能登录:管理员可以通过登录功能进入系统。首页展示:展示系统的概要信息或重要通知。文章管理:管理系统内的......
  • 基于springboot+vue的治安管理系统
    博主主页:猫头鹰源码博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万+、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作​主要内容:毕业设计(Javaweb项目|小程序|Python|HTML|数据可视化|SSM|SpringBoot|Vue|Jsp|PHP......
  • 基于协同过滤推荐算法+springboot+vue的校园二手商城(前后端分离)
    博主主页:猫头鹰源码博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万+、专注Java技术领域和毕业设计项目实战主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询文末联系获取项目介绍: 本系统为原创项目,采用前后端分......
  • Spring Bean的生命周期函数
    ......
  • springsecurity使用:登录与校验
    首先是引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>默认方案:首次使用这个空项目的时候他会给你一个默认的账号账号名为user密码在控......
  • springcloud与dubbo分别基于什么实现的
    SpringCloud组件:服务注册与发现:Eureka:SpringCloud中的服务注册与发现组件,服务提供者将自身注册到Eureka中,服务消费者从Eureka中获取服务提供者的信息。负载均衡:Ribbon:客户端负载均衡组件,它可以在客户端实现负载均衡,通过扩展Eureka实现服务调用时的负载均衡。断路器:......