[Spring] 30个类手写 Spring Mini 版本系列(一)
简介java
为了更深刻的了解 Spring 的实现原理和设计思想,一直打算出个系列文章,从零开始从新学习 Spring。有兴趣的小伙伴能够持续关注更新。ios

手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的连接,如须要连接和更多资源,能够关注其余博客发布地址。git
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
我的博客 | https://yiyuer.github.io/NoteBooks/ |
正文github
基本思路web
配置阶段spring
配置
web.xml
编程DispatchSevletapi
设定
init-param
浏览器contextConfigLocation = classpath:application.xml缓存
设定
url-pattern
/*
定义
Annotation
@Controller
@Service
@Autowried
@RequestMapping
初始化阶段
调用
init()
方法加载配置文件
IOC
容器初始化Map
扫描相关的类
Scan-package="com.yido"
建立实例化并保存至容器
经过反射机制将类实例化放入 IOC 容器
进行 DI
扫描 IOC 容器中的实例,给没有赋值的属性自动赋值
初始化 HandlerMapping
将 URL 和 Method 创建一对一的映射关系
运行阶段
调用 doPost() / doGet()
Web 容器调用 doPost() / doGet() ,得到 request / response 对象
匹配 HandlerMapping
从 request 对象中获取用户输入的 url , 找到对应的 Method
反射调用 method.invoke()
利用反射调用方法并返回结果
返回结果
利用 response.getWriter().write(), 将返回结果输出到浏览器
V1 版本
准备工做
pom.xml
sevlet-api
依赖jetty
插件
V1.0.0 版本
注解定义

定义 Controller 和 Service

定义配置
src/main/resources/application.properties
scanPackage=com.yido.demo
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/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Sun Web Application</display-name>
<!--定义核心调度 servlet -->
<servlet>
<servlet-name>mvc-servlet</servlet-name>
<servlet-class>com.yido.mvcframework.v1.servlet.XDispatchServlet</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>mvc-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!--直接经过 servlet api 使用-->
<servlet>
<servlet-name>sun-servlet</servlet-name>
<servlet-class>com.yido.simple.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sun-servlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
定义 DispatchServlet
/*
* @ProjectName: 编程学习
* @Copyright: 2019 HangZhou Ashe Dev, Ltd. All Right Reserved.
* @address: https://yiyuery.github.io/NoteBooks/
* @date: 2020/4/12 5:30 下午
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package com.yido.mvcframework.v1.servlet;
import com.yido.mvcframework.annotation.XAutowired;
import com.yido.mvcframework.annotation.XController;
import com.yido.mvcframework.annotation.XRequestMapping;
import com.yido.mvcframework.annotation.XService;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* <p>
* 手写一个 请求转发器 DispatchServlet
* </p>
*
* @author Helios
* @date 2020/4/12 5:30 下午
*/
public class XDispatchServlet extends HttpServlet {
/**
* key: 请求路由
* value:
* - 对应 Controller 实例
* - Method 方法
*/
private Map<String, Object> mapping = new HashMap<String, Object>();
/**
* Get 请求处理转发
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
/**
* Post请求处理转发
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
//出现异常,返回堆栈信息
resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
}
}
/**
* 统一转发处理全部请求数据
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, InvocationTargetException, IllegalAccessException {
//1. 获取参数和请求路径
String url = req.getRequestURI();
String contextPath = req.getContextPath();
//替换请求上下文
url = url.replace(contextPath, "")
//替换多余 '/'
.replaceAll("/+", "/");
// 2. 获取请求处理器
/**
* {@link XDispatchServlet#init(ServletConfig)}
* 从缓存的 requestMapping 中加载指定包路径下的请求路径对应的 Controller 处理器
* - 配置 web.xml
* - 初始化时 加载配置文件
*/
if (!this.mapping.containsKey(url)) {
resp.getWriter().write("404 Not Found!");
return;
}
Method method = (Method) this.mapping.get(url);
// 3. 解析参数并经过反射执行方法
Map<String, String[]> parameterMap = req.getParameterMap();
Object controller = this.mapping.get(method.getDeclaringClass().getName());
method.invoke(controller, new Object[]{req, resp, parameterMap.get("name")[0]});
}
/**
* 加载配置并缓存 Controller 实例和 初始化请求对应的 Method映射
*
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
InputStream is = null;
try {
//1. 读取参数
Properties configContext = new Properties();
is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
configContext.load(is);
//2. IOC 构建容器, 扫描全部实例和请求方法映射
doIoc(configContext.getProperty("scanPackage"));
//3. DI 依赖注入
doInjection();
} catch (Exception e){
e.printStackTrace();
} finally {
if (is != null) {
try{
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("XSpring MVC Framework has been initialed");
}
/**
* 依赖注入
*/
private void doInjection() {
Collection<Object> values = mapping.values();
for (Object value : values) {
if (null == value) {
continue;
}
Class clazz = value.getClass();
if (clazz.isAnnotationPresent(XController.class)) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(XAutowired.class)) {
continue;
}
XAutowired autowired = field.getAnnotation(XAutowired.class);
String beanName = autowired.value();
if ("".equals(beanName)) {
beanName = field.getType().getName();
}
//注入依赖实例
field.setAccessible(true);
try {
field.set(value, mapping.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* IOC 加载全部请求处理方法映射和实例
*/
private void doIoc(String scanPackage) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
doScanTask(scanPackage);
Map<String, Object> cacheMap = new HashMap<String, Object>();
for (String clazzName : mapping.keySet()) {
if (!clazzName.contains(".")) {
continue;
}
Class<?> clazz = Class.forName(clazzName);
// 1. 处理 XController
String baseUrl = "";
if (clazz.isAnnotationPresent(XController.class)) {
cacheMap.put(clazzName, clazz.newInstance());
// 1.1 解析请求路径前缀
if (clazz.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping requestMapping = clazz.getAnnotation(XRequestMapping.class);
baseUrl = requestMapping.value();
}
// 1.2 解析方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping annotation = method.getAnnotation(XRequestMapping.class);
String url = baseUrl + annotation.value();
cacheMap.put(url, method);
System.out.println("> Mapped--------->url: " + url + "," + method.getName());
}
}
// 2. 处理 XService
} else if (clazz.isAnnotationPresent(XService.class)) {
XService service = clazz.getAnnotation(XService.class);
String beanName = service.value();
if ("".equals(beanName)) {
beanName = clazzName.getClass().getName();
}
Object instance = clazz.newInstance();
cacheMap.put(beanName, instance);
for (Class<?> i : clazz.getInterfaces()) {
cacheMap.put(i.getName(),instance);
}
}
}
if (!cacheMap.isEmpty()) {
this.mapping.putAll(cacheMap);
}
}
/**
* 扫描指定包路径下全部需实例类
* @param scanPackage
*/
private void doScanTask(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
File rootDir = new File(url.getFile());
for (File file : rootDir.listFiles()) {
if (file.isDirectory()) {
doScanTask(scanPackage+"."+file.getName());
}else{
if (!file.getName().endsWith(".class")) {
continue;
}
String clazzName = scanPackage + "." + file.getName().replace(".class", "");
mapping.put(clazzName, null);
}
}
}
}
效果演示
/**
* 返回欢迎信息
* support:
* spring-v1
* @param name
*/
@XRequestMapping("/v1/welcome")
public void welcome(HttpServletRequest req, HttpServletResponse resp, @XRequestParam(value = "name") String name) {
String result = helloService.welcome(name);
try {
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}

小结
XDispatchServlet 职责不够单一
流程方法混乱,不清晰
XDispatchServlet 须要进行重构
没有自动解析注入 Request 、Response 对象
Spring 思想没有体现,没有 ApplicationContext、BeanDefinition、BeanDefinitionReader,没有解决循环依赖问题
没有 Aop 逻辑
To do Continue…
更多
扫码关注
架构探险之道
,回复『源码』,获取本文相关源码和资源连接

知识星球(扫码加入获取历史源码和文章资源连接)

本文分享自微信公众号 - 架构探险之道(zacsnz1314)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。