手写spring核心原理Version3

2023-05-16

上两篇博文手写spring核心原理Version1和手写spring核心原理Version2分别介绍了如何完成一个自动注入、以及如何用设计模式进行重构,接下来这篇将仿照SpringMVC对参数列表以及methodMapping进行重构。

重构MyDispatcherServlet

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.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class MyDispatcherServlet extends HttpServlet{

	private static final String LOCATION = "contextConfigLocation";
	public static final String SCAN_PACKAGE = "scanPackage";

	private Properties properties = new Properties();

	private List<String> classNames = new ArrayList<>();

	private Map<String,Object> iocContainer = new HashMap<>();

	private List<Handler> handlerMapping = new ArrayList<>();

	public MyDispatcherServlet(){ super(); }
	

	public void init(ServletConfig config) {

		//1、加载配置文件
		loadConfigurations(config.getInitParameter(LOCATION));

		//2、扫描所有相关的类
		scanPackages(properties.getProperty(SCAN_PACKAGE));
		
		//3、初始化所有相关类的实例,并保存到IOC容器中
		initInstances();
		
		//4、依赖注入
		autowireInstance();

		//5、构造HandlerMapping
		initHandlerMapping();
	}
	
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		this.doPost(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try{
			doDispatch(req,resp);
			
		}catch(Exception e){
			resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "").replaceAll(",\\s", "\r\n"));
		}
	}
	
	private void scanPackages(String packageName){
		URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));

		Stream.of(new File(url.getFile()).listFiles()).forEach(file -> {
			if (file.isDirectory()){
				scanPackages(packageName + "." + file.getName());
			}else {
				classNames.add(packageName + "." + file.getName().replace(".class", "").trim());
			}

		});
	}

	private void loadConfigurations(String location){
		try (InputStream input = this.getClass().getClassLoader().getResourceAsStream(location)){
			properties.load(input);
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	private void initInstances(){
		if(classNames.size() == 0){
			return;
		}
		
		try{
			for (String className : classNames) {
				Class<?> clazz = Class.forName(className);
				String beanName;

				if(clazz.isAnnotationPresent(GPController.class)){
					beanName = getBeanName(clazz,clazz.getAnnotation(GPController.class).value());
				}else if(clazz.isAnnotationPresent(GPService.class)){
					beanName = getBeanName(clazz,clazz.getAnnotation(GPService.class).value());
				}else {
					continue;
				}

				final Object instance = clazz.newInstance();
				iocContainer.put(beanName, instance);
				Stream.of(clazz.getInterfaces()).forEach(i -> iocContainer.put(i.getName(),instance));
			}
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}

	private void autowireInstance(){
		if(iocContainer.isEmpty()){
			return;
		}
		
		for (Entry<String, Object> entry : iocContainer.entrySet()) {
			Field [] fields = entry.getValue().getClass().getDeclaredFields();
			for (Field field : fields) {
				if(!field.isAnnotationPresent(GPAutowired.class)){
					continue;
				}
				
				String beanName = field.getAnnotation(GPAutowired.class).value().trim();
				if("".equals(beanName)){
					beanName = field.getType().getName();
				}

				field.setAccessible(true);
				try {
					field.set(entry.getValue(), iocContainer.get(beanName));
				} catch (Exception e) {
					e.printStackTrace();
					continue ;
				}
			}
		}
	}
	
	private void initHandlerMapping(){
		if(iocContainer.isEmpty()){
			return;
		}
		
		for (Entry<String, Object> entry : iocContainer.entrySet()) {
			Class<?> clazz = entry.getValue().getClass();
			if(!clazz.isAnnotationPresent(GPController.class)){
				continue;
			}
			
			String baseUrl = "";
			if(clazz.isAnnotationPresent(GPRequestMapping.class)){
				baseUrl = clazz.getAnnotation(GPRequestMapping.class).value();
			}
			
			Method [] methods = clazz.getMethods();
			for (Method method : methods) {
				if(!method.isAnnotationPresent(GPRequestMapping.class)){
					continue;
				}
				
				String regex =  baseUrl + method.getAnnotation(GPRequestMapping.class).value();
				Pattern pattern = Pattern.compile(regex);
				handlerMapping.add(new Handler(pattern,entry.getValue(),method));
			}
		}
		
	}
	
	private String getBeanName(Class<?> clazz,String name){
		if(!"".equals(name.trim())){
			return name;
		}

		char [] chars = clazz.getSimpleName().toCharArray();
		chars[0] += 32;
		return String.valueOf(chars);
	}
	
	private void doDispatch(HttpServletRequest req,HttpServletResponse resp) throws Exception{
		try{
			Handler handler = getHandler(req);
			
			if(handler == null){
				resp.getWriter().write("404 Not Found");
				return;
			}
			
			Class<?> [] paramTypes = handler.method.getParameterTypes();
			//保存所有需要自动赋值的参数值
			Object [] paramValues = new Object[paramTypes.length];
			
			
			Map<String,String[]> params = req.getParameterMap();
			for (Entry<String, String[]> param : params.entrySet()) {
				String value = Arrays.toString(param.getValue()).replaceAll("[\\[\\]]", "").replaceAll(",\\s", ",");
				
				if(!handler.paramIndexMapping.containsKey(param.getKey())){
					continue;
				}

				int index = handler.paramIndexMapping.get(param.getKey());
				paramValues[index] = convert(paramTypes[index],value);
			}
			
			
			//设置方法中的request和response对象
			int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
			paramValues[reqIndex] = req;

			int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
			paramValues[respIndex] = resp;

			handler.method.invoke(handler.controller, paramValues);
			
		}catch(Exception e){
			throw e;
		}
	}
	
	private Handler getHandler(HttpServletRequest req) {
		if(handlerMapping.isEmpty()){
			return null;
		}
		
		String url = req.getRequestURI().replace(req.getContextPath(), "");

		for (Handler handler : handlerMapping) {
			try{
				Matcher matcher = handler.pattern.matcher(url);
				if(!matcher.matches()){
					continue;
				}
				
				return handler;
			}catch(Exception e){
				throw e;
			}
		}
		return null;
	}

	//url传过来的参数都是String类型的,HTTP是基于字符串协议
	//只需要把String转换为任意类型就好
	private Object convert(Class<?> type,String value){
		if(Integer.class == type){
			return Integer.valueOf(value);
		}
		//如果还有double或者其他类型,继续加if
		//这时候,我们应该想到策略模式了
		//在这里暂时不实现
		return value;
	}

	/**
	 * Handler记录Controller中的RequestMapping和Method的对应关系
	 */
	private class Handler{
		protected Object controller;
		protected Method method;
		protected Pattern pattern;
		protected Map<String,Integer> paramIndexMapping;	//参数顺序
		
		protected Handler(Pattern pattern,Object controller,Method method){
			this.controller = controller;
			this.method = method;
			this.pattern = pattern;
			
			paramIndexMapping = new HashMap<>();
			putParamIndexMapping(method);
		}
		
		private void putParamIndexMapping(Method method){
			//提取方法中加了注解的参数
			Annotation [] [] pa = method.getParameterAnnotations();
			for (int i = 0; i < pa.length ; i ++) {
				for(Annotation a : pa[i]){
					if(a instanceof GPRequestParam){
						String paramName = ((GPRequestParam) a).value();
						if(!"".equals(paramName.trim())){
							paramIndexMapping.put(paramName, i);
						}
					}
				}
			}
			
			//提取方法中的request和response参数
			Class<?> [] paramsTypes = method.getParameterTypes();
			for (int i = 0; i < paramsTypes.length ; i ++) {
				Class<?> type = paramsTypes[i];
				if(type == HttpServletRequest.class ||
				   type == HttpServletResponse.class){
					paramIndexMapping.put(type.getName(),i);
				}
			}
		}
	}
}
  • 该demo源码参考自书籍《Spring 5核心原理与30个类手写实战》
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手写spring核心原理Version3 的相关文章

随机推荐

  • Debian 下 CUDA 生产环境配置笔记

    最近整了张 Tesla P4 xff0c 由于是半高卡 xff0c 索性就直接将其塞进了我的 NAS 里 xff0c 试图将原来用 onnx 跑在 CPU 上的模型迁移至 GPU 上 xff0c 遇到了些许问题 xff0c 在此记录下 本文
  • OpenWRT 扩容

    本文地址 xff1a blog lucien ink archives 535 官网原生的 overlay 只有 100M xff0c 不够用 本文只讨论新安装的情形 xff0c 已安装扩容的场景在本文不涉及 步骤 假设从官网下载的文件名为
  • PVE 下解决 iKuai 断流、重启问题

    本文地址 xff1a blog lucien ink archives 536 0 前言 懒得看过程可直接移步第 2 部分 之前入手了 N5105 43 i225 V xff0c 收到后装了 PVE 7 2 作为底层系统 xff0c 虚拟化
  • OpenWRT 安装 PassWall

    本文地址 xff1a blog lucien ink archives 537 访问 OpenWRT Download Server packges xff0c 找到自己的架构 xff0c 以 x86 64 为例 xff1a 在 etc o
  • ARM:系统移植2

    1 u boot启动程序 1 获取uboot源码渠道 1 xff09 u boot官方 xff1a https ftp denx de pub u boot 2 xff09 开发板厂家 3 xff09 芯片厂家 4 xff09 主管领导 x
  • ChatGPT 相关资料收集

    本文地址 xff1a blog lucien ink archives 538 本文用来收集各种和生成式模型相关的内容 xff0c 由于 ChatGPT 是其代表 xff0c 也是会被写入人类历史进程的一个名字 xff0c 所以便用 Cha
  • Debian配置apache2以及CA

    1 安装apache2 apt install y apache2 systemctl start apache2 启动服务 修改apache2配置文件 cd etc apache2 cp sites available default s
  • Debian之配置squid代理缓存

    1 安装服务 apt install squid 2 修改配置 vim etc squid squid conf set nu 显示行号 1390 转到第1390行 第1391行 http access allow localhost 修改
  • JAVA判断回文数的两种方法

    回文数是指正序 xff08 从左向右 xff09 和倒序 xff08 从右向左 xff09 读都是一样的整数 目录 方法一方法二 方法一 通过 61 61 String valueOf 方法把整数转换为字符串 xff0c 再用toCharA
  • java设置Access-Control-Allow-Origin允许多域名访问

    对于前后端分离的项目难免会遇到跨域的问题 xff0c 在设置跨域的问题中有许多需要注意的事情 xff0c 如本次将要将的设置Access Control Allow Origin使其允许多域名请求 1 设置允许多域名访问最简单的方法是使用通
  • Debian10配置静态ip

    查看网卡 xff1a ip addr 修改网卡网络地址配置 xff1a vim etc network interfaces 配置模板 xff1a auto span class token punctuation span 网卡名 spa
  • Docker(四)Image、Container

    一 Image镜像 Docker把应用程序及其依赖 xff0c 打包在image文件里面 只有通过这个文件 xff0c 才能生成Docker容器 image文件可以看作是容器的模板 Docker根据image文件生成容器的实例 同一个ima
  • 报错:ModuleNotFoundError: No module named ‘PIL‘

    运行代码的时候报错 xff0c 如图所示 xff1a 表示python中没有配置pillow库 纠正的步骤 xff1a window 43 R xff0c 并且在框中输入cmd 进入python所在的目录下 xff0c cd appdata
  • Window10 Excel复制粘贴卡死

    Windows10 Excel复制粘贴卡死 excel复制大量数据有时卡死 xff0c 关机重启也没用 网上找了好多方法没效果 xff0c 突然想到windows10有个云剪切板功能 xff0c 就想关了试试没想到解决了 xff0c 哈哈哈
  • (Latex)期刊论文里的数学字符怎么打出来的?

    xff08 Latex xff09 期刊论文里的数学字符怎么打出来的 导入包试一试 by 今天不飞了 最近边查文献边写文章 xff0c 看到别人文章公式里的变量那叫一个花里胡哨 xff0c 再看看自己的 不能忍 xff0c 我也要 于是搜集
  • 面试:ARM篇

    1 IIC I2C 1 由日本飞利浦公司研发的一种 串行半双工的总线 2 采用两根线 SCL 和 SDA 特点 1 硬件比较简单 比较节约资源的一种总线 2 主要用于两个芯片之间的通信 也可以是多主机多从机 但基本不用 3 传输速度一般在4
  • 工作日志——首次通过k8s Elasticsearch获取新建Pod的日志缓慢的原因

    使用k8s Elasticsearch查看pod日志的时候偶尔会遇到这样的情况 xff0c 在创建完容器并运行后去查看日志的时候总是加载不出来 xff0c 需要等待十几秒甚至一分钟才能加载 我 有幸 被分配来解决这个问题 xff0c 经过一
  • WSL2 安装 Ubuntu-20.04 子系统CUDA(Win10和Win11)

    1 安装WSL的CUDA驱动 驱动下载地址 xff1a https developer nvidia com cuda wsl 选好你自己的显卡类型 下载完成后直接默认安装就行 2 安装WSL2 xff08 使用Ubuntu 20 04版本
  • ROS MELODIC ARM64的一些源

    默认注释了源码镜像以提高 apt update 速度 xff0c 如有需要可自行取消注释 deb https mirrors tuna tsinghua edu cn ubuntu ports bionic main restricted
  • 手写spring核心原理Version3

    上两篇博文手写spring核心原理Version1和手写spring核心原理Version2分别介绍了如何完成一个自动注入 以及如何用设计模式进行重构 xff0c 接下来这篇将仿照SpringMVC对参数列表以及methodMapping进