简介
为了更深入的了解 Spring 的实现原理和设计思想,一直打算出个系列文章,从零开始重新学习 Spring。在[Spring] 30个类手写 Spring Mini 版本系列(一)中,我们直接通过 Servlet API 初步实现了 Spring 的简易版。针对V1.1.0版,今天我们来做下优化,主要针对主要流程节点进行初步的方法封装,便于后续模块设计。
手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以加入『知识星球』获取长期知识分享服务。
正文
基本思路
web.xml
init-param
url-pattern
Annotation
init()
方法
IOC
容器初始化
准备工作
此处不再赘述,基于[Spring] 30个类手写 Spring Mini 版本系列(一)做优化。
注解和 Controller 复用,主要针对 XDispatchServlet 进行重构,主要是方法流程的编排,便于后续实现 Spring 中的设计
方法功能分割
设计流程分割
/*
* @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.v2.servlet;
import com.yido.mvcframework.annotation.*;
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.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
*
* 手写一个 请求转发器 DispatchServlet
* - 代码重构,流程清晰化
*
*
* @author Helios
* @date 2020/4/12 5:30 下午
*/
public class XDispatchServlet extends HttpServlet {
/**
* 配置
*/
private Properties contextConfig = new Properties();
//享元模式,缓存
private List<String> classNames = new ArrayList<String>();
//IoC容器,key默认是类名首字母小写,value就是对应的实例对象
private Map<String, Object> ioc = new HashMap<String, Object>();
//URL 和 Method 映射关系
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
/**
* 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 {
//【运行阶段】
//6. 委派:根据 URL 找到一个对应的 Method ,并通过 response 返回
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
//出现异常,返回堆栈信息
resp.getWriter().write("500 Exception, Detail: " + Arrays.toString(e.getStackTrace()));
}
}
/**
* 根据 URL 找到一个对应的 Method ,并通过 response 返回
*
* @param req
* @param resp
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
//1. 获取参数和请求路径
String url = req.getRequestURI();
String contextPath = req.getContextPath();
//替换请求上下文
url = url.replace(contextPath, "")
//替换多余 '/'
.replaceAll("/+", "/");
// 2. 获取请求处理器
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 Not Found!");
return;
}
Map<String, String[]> params = req.getParameterMap();
Method method = this.handlerMapping.get(url);
// 3. 解析参数
//3.1 获取形参列表
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] paramValues = new Object[parameterTypes.length];
boolean directReturn = true;
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if (parameterType == HttpServletRequest.class) {
paramValues[i] = req;
} else if (parameterType == HttpServletResponse.class) {
paramValues[i] = resp;
directReturn = false;
} else if (parameterType == String.class) {
//通过运行时状态去获取
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int j = 0; j < parameterAnnotations.length; j++) {
for (Annotation annotation : parameterAnnotations[j]) {
if (annotation instanceof XRequestParam) {
String paramName = ((XRequestParam) annotation).value();
if (!"".equals(paramName.trim())) {
paramValues[i] = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]", "")
.replaceAll("\\s+", ",");
}
}
}
}
}
}
//暂时硬编码
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//赋值实参列表
Object result = method.invoke(ioc.get(beanName), paramValues);
//方法入参没有 resp 时 & 方法返回值部不为 void 直接返回
if (!"void".equals(method.getReturnType().getName()) && directReturn) {
resp.getWriter().write(Arrays.toString(new Object[]{result}).replaceAll("\\[|\\]", ""));
}
}
/**
* 加载配置并缓存 Controller 实例和 初始化请求对应的 Method映射
*
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//【初始化阶段】
//====== 初始化加载 =====
//1. 读取配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2. 扫描
doScanner(contextConfig.getProperty("scanPackage"));
//====== IOC 容器 =====
//3. 初始化 IOC 容器,将扫描到的相关类实例化,保存到 IOC 容器
doInstance();
//AOP 生成新的代理对象
//====== DI =====
//4. 完成依赖注入
doAutoWired();
//====== MVC =====
//5. 初始化 HandlerMapping
doInitHandlerMapping();
System.out.println("XSpring MVC Framework has been initialed");
}
/**
* 读取配置文件
*
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation) {
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation)) {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
File rootDir = new File(url.getFile());
for (File file : rootDir.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) {
continue;
}
String clazzName = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(clazzName);
}
}
}
/**
* 完成依赖注入
*/
private void doAutoWired() {
if (ioc.isEmpty()) {
return;
}
Collection<Object> values = ioc.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 = toLowerFirstCase(field.getType().getSimpleName());
}
//注入依赖实例
field.setAccessible(true);
try {
field.set(value, ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
//Spring 实例平铺后只需要再次扫描进行一次 DI 操作即可解决依赖注入的问题,此处演示未涉及,暂不做此处理
}
/**
* 初始化 IOC 容器,将扫描到的相关类实例化,保存到 IOC 容器
*/
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
try {
for (String clazzName : classNames) {
if (!clazzName.contains(".")) {
continue;
}
Class<?> clazz = Class.forName(clazzName);
// 1. 处理 XController
if (clazz.isAnnotationPresent(XController.class)) {
ioc.put(toLowerFirstCase(clazz.getSimpleName()), clazz.newInstance());
// 2. 处理 XService
} else if (clazz.isAnnotationPresent(XService.class)) {
//2.1 在多个包下出现相同的类名,只能(自己)起一个全局唯一的名字
//自定义命名
XService service = clazz.getAnnotation(XService.class);
String beanName = service.value();
//2.2 默认的类名首字母小写
if ("".equals(beanName)) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
//2.3 如果是接口
//判断有多少个实现类,如果有多个重名实例,只能抛异常
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(toLowerFirstCase(i.getSimpleName()))) {
//不允许一个接口映射多个实例,默认取第一个
throw new Exception("The " + i.getName() + " is exists!!");
}
ioc.put(toLowerFirstCase(i.getSimpleName()), instance);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 首字母小写
*
* @param name
* @return
*/
private String toLowerFirstCase(String name) {
char[] chars = name.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
/**
* 初始化 HandlerMapping
*/
private void doInitHandlerMapping() {
if (classNames.isEmpty()) {
return;
}
try {
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(XController.class)) {
continue;
}
String baseUrl = "";
// 1. 处理 XController 中方法映射
if (clazz.isAnnotationPresent(XController.class)) {
// 1.1 解析请求路径前缀
if (clazz.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping requestMapping = clazz.getAnnotation(XRequestMapping.class);
baseUrl = requestMapping.value();
}
// 1.2 解析public方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(XRequestMapping.class)) {
XRequestMapping annotation = method.getAnnotation(XRequestMapping.class);
//替换多/为单/
String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
handlerMapping.put(url, method);
System.out.println("> Mapped--------->url: " + url + "," + method.getName());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* support:
* spring-v1
* spring-v2
*
* @param name
* @return
*/
@XRequestMapping("/v2/welcome")
public String welcome2(@XRequestParam(value = "name") String name) {
return helloService.welcome(name);
}
/**
* support:
* spring-v1
* spring-v2
*
* @param name
* @return
*/
@XRequestMapping("/v3/welcome")
public String welcome3(@XRequestParam(value = "name") String name, @XRequestParam("nick") String nick) {
return helloService.welcome(nick + " " + name);
}
为了便于区分,对接口添加版本标识
To do Continue...
扫码关注
架构探险之道
,回复『源码』,获取本文相关源码和资源链接
知识星球(扫码加入,获取珍贵笔记、视频、电子书的等资源)