首页 > 编程语言 > 详细

Spring 二、300行代码手写初体验Spring V1.0版本

时间:2020-04-05 22:52:49      阅读:68      评论:0      收藏:0      [点我收藏+]

?周末无事,借鉴Tom老师思路,手撕一个简版的Spring,大致思路如图。
技术分享图片
?首先按照spring的设计,把前提条件如对应注解、controller、service提前建好。

一、配置阶段、准备前置条件

  1. 新建web项目,创建自己的Spring注解如 @ShenController、@ShenRequestMapping
  2. 创建Service、Controller,使用自己的Spring注解 
  3. 配置文件简单指定需要扫描的包,并在web.xml里加以配置

二、实现spring IOC、DI、Url Mapping、Dispatcher逻辑

?核心代码如下,整个小项目可在此处查看 码云demo

package com.bigshen.mvc.spring.servlet;

import com.bigshen.mvc.spring.annonation.*;

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.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @Author eddy.shen
 * @Date 2020/4/5 15:52
 * @Desc dispatcher类模拟Spring处理流程
 **/
public class ShenDispatcherServlet extends HttpServlet {

	// 配置文件properties,内部配置了类的扫描路径
	private Properties properties = new Properties();
	// 扫描路径下的所有class
	private List<String> classList = new ArrayList<>();
	// ioc容器管理bean
	private Map<String, Object> iocMap = new HashMap<>();
	// url对应映射关系
	private Map<String, HandlerMapping> urlMap = new HashMap<>();

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doDispatcher(req, resp);
	}

	// 分发匹配对应url
	private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {

		String rootPath = req.getContextPath();
		// 请求url的路径,去掉项目前缀
		String path = req.getRequestURI().replace(rootPath, "");
		if (null == urlMap.get(path)) {
			throw new RuntimeException("404 not found!");
		}

		HandlerMapping handler = urlMap.get(path);

		Method method = handler.getMethod();
		Object instance = handler.getInstance();

		Class[] paramTypes = handler.getParameterTypes();
		// 参数值数组
		Object[] paramValues = new Object[paramTypes.length];
		// 本次请求传入参数
		Map<String, String[]> requestParamMap = req.getParameterMap();

		for (Map.Entry<String, Integer> entry : handler.getParameterIndexMap().entrySet()) {

			String paramName = entry.getKey();
			Integer index = entry.getValue();

			if (paramName.equals(HttpServletRequest.class.getName())) {
				// 为方法中的HttpServletRequest类型赋值
				paramValues[index] = req;
				continue;
			}

			if (paramName.equals(HttpServletResponse.class.getName())) {
				// 为方法中的HttpServletResponse类型赋值
				paramValues[index] = resp;
				continue;
			}

			if (requestParamMap.containsKey(paramName)) {
				// 为方法中使用spring注解的参数赋值
				String[] value = requestParamMap.get(paramName);
				// 根据参数类型class将值转换为对应类型
				paramValues[index] = convert(paramTypes[index], value);
			}

		}

		try {
			//反射执行url所请求的对应method
		   Object result = method.invoke(instance, paramValues);
			if(result == null || result instanceof Void){ return; }
			resp.getWriter().write(result.toString());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private Object convert(Class paramType, String[] value) {

		String paramValue = Arrays.toString(value).replaceAll("\\[|\\]","")
				.replaceAll("\\s",",");

		if (paramType.equals(Integer.class)) {
			return Integer.valueOf(paramValue);
		}
		if (paramType.equals(Double.class)) {
			return Double.valueOf(paramValue);
		}
		// .... 简单方式转换,待补充
		return paramValue;
	}


	@Override
	public void init(ServletConfig config) throws ServletException {

		// 1.加载配置文件
		doLoadConfig(config.getInitParameter("contextConfigLocation"));

		// 2.根据配置中指定的扫描路径进行扫描
		doScanner(properties.getProperty("scanPackage"));

		// 3.匹配的类放入ioc容器
		doIOC();

		//TODO doAOP() 在依赖注入前通过字节码重组生成新的代理类,进行依赖注入

		// 4.将ioc容器中需要DI依赖注入的进行注入
		doDI();

		// 5.扫描controller类的method,将url与method绑定映射
		initHandlerMapping();

		System.out.println("Spring framework is init.");

	}


	// 加载配置文件,转为properties
	private void doLoadConfig(String scanPackage) {

		// 读取web.xml中配置的properties文件
		InputStream is = this.getClass().getClassLoader().getResourceAsStream(scanPackage);
		try {
			// 转为properties
			properties.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	// 根据配置中指定的扫描路径进行扫描
	private void doScanner(String scanPackage) {

		// 将scanPackage扫描路径中的 . 转为 /
		String packagePath = scanPackage.replaceAll("\\.", "/");
		URL url = this.getClass().getClassLoader().getResource("/" + packagePath);
		File file = new File(url.getFile());

		if (file.isDirectory()) {
			// 如果是路径,遍历子路径,递归查询
			File[] childFile = file.listFiles();
			for (File child : childFile) {

				if (child.isFile() && child.getName().endsWith(".class")) {
					// 是文件且是class文件,先统计出来
					String className = child.getName();
					String classPath = scanPackage + "." + className.replace(".class", "");
					classList.add(classPath);
				} else if (child.isDirectory()) {
					// 文件夹、递归
					doScanner(scanPackage + "." + child.getName());
				}

			}
		}



	}

	// 遍历扫描到的类,如果符合Spring注解则交由IOC容器管理
	private void doIOC() {

		if (classList.isEmpty()) {return;}

		for (String clazzName : classList) {
			try {
				Class clazz = Class.forName(clazzName);
				if (clazz.isAnnotationPresent(ShenController.class)) {
					// 如果类上有Controller类型注解
					// 以类全名为key,将类对象创建出来放入 ioc容器
					iocMap.put(clazz.getName(), clazz.newInstance());
				} else if (clazz.isAnnotationPresent(ShenService.class)) {
					// 如果类上有Service类型注解
					ShenService service = (ShenService) clazz.getAnnotation(ShenService.class);
					String value = service.value();
					Boolean isEmpty = null == value || "".equals(value.trim()) ? true : false;
					Boolean isInterface = clazz.getInterfaces().length > 0 ? true : false;
					// ioc中key有几种情况
					if (!isEmpty) {
						// 1. 注解中value有值
						Object exist = iocMap.get(value);
						if (null != exist) {
							throw new RuntimeException("IOC容器中存在重复的声明Bean【】" + value);
						}
						iocMap.put(value, clazz.newInstance());
					} else if (!isInterface) {
						// 2. 注解中value无值,且类没有实现接口
						iocMap.put(clazz.getName(), clazz.newInstance());
					} else {
						// 3. 注解中value无值,类实现了接口
						Class[] interfaces = clazz.getInterfaces();
						Object instance = clazz.newInstance();
						for (Class element : interfaces) {
							// 为每个接口类型绑定同样的对象实例
							Object exist = iocMap.get(element.getName());
							if (null != exist) {
								throw new RuntimeException("IOC容器中存在重复的声明Bean【】" + value);
							}
							iocMap.put(element.getName(), instance);
						}
					}

				}

			} catch (ClassNotFoundException e) {
					e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			}
		}

	}

	// 遍历IOC容器中的对象的属性,如果使用了注入的注解,对属性进行依赖注入
	private void doDI() {

		if (iocMap.isEmpty()) {return;}

		for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

			String name = entry.getKey();
			Object instance = entry.getValue();
			Class instanceClazz = instance.getClass();
			Field[] fields = instanceClazz.getDeclaredFields();

			for (Field field : fields) {

				if (field.isAnnotationPresent(ShenAutowired.class)) {
					// ioc容器中的属性上有spring注入注解
					field.setAccessible(true);
					Class fieldClazz = field.getType();
					try {
						field.set(instance, iocMap.get(fieldClazz.getName()));
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}

			}

		}

	}

	// 将controller中使用注解的method提取出,将URL与ioc容器中对象绑定,在接收到request请求后进行url匹配
	private void initHandlerMapping() {

		if (iocMap.isEmpty()) {return;}

		for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

			String name = entry.getKey();
			Object instance = entry.getValue();
			Class instanceClazz = instance.getClass();

			String rootPath = "";
			// 提取controller配置的url根路径
			if (instanceClazz.isAnnotationPresent(ShenController.class) && instanceClazz.isAnnotationPresent(ShenRequestMapping.class)) {
				ShenRequestMapping controllerMapping = (ShenRequestMapping) instanceClazz.getAnnotation(ShenRequestMapping.class);
				rootPath = controllerMapping.value();
			}

			Method[] methods = instanceClazz.getDeclaredMethods();
			for (Method method :methods) {
				// 为每个method提取独立的url,并记录映射关系
				if (method.isAnnotationPresent(ShenRequestMapping.class)) {
					ShenRequestMapping methodMapping = method.getAnnotation(ShenRequestMapping.class);
					String methodUrl = rootPath + methodMapping.value();
					urlMap.put(methodUrl, new HandlerMapping(methodUrl, instance, method));
				}

			}



		}

	}

	class HandlerMapping {

		private String methodUrl;
		private Object instance;
		private Method method;
		// 方法类型
		private Class[] parameterTypes;
		// 参数值、参数位置对应关系
		private Map<String, Integer> parameterIndexMap = new HashMap<>();

		public HandlerMapping(String methodUrl, Object instance, Method method) {
			this.methodUrl = methodUrl;
			this.instance = instance;
			this.method = method;
			// 在映射关系中,根据method提取方法入参及顺序
			initMethodParameters();
		}

		// 提取method对应的参数映射关系
		private void initMethodParameters() {

			// 方法参数类型
			parameterTypes = method.getParameterTypes();

			// 根据参数注解找对应入参
			// 方法参数的注解类型, 一个方法有多个参数,每个参数可以有多个注解,所有是个二维数组
			Annotation[][] paramAnnotations = method.getParameterAnnotations();
			for(int i = 0; i < paramAnnotations.length; i ++) {
				Annotation[] paramAnnotationArray = paramAnnotations[i];
				for (Annotation annotation : paramAnnotationArray) {
					if (annotation instanceof ShenRequestParam) {
						ShenRequestParam requestParam = (ShenRequestParam) annotation;
						String paramName = requestParam.value();
						if (null != paramName && !"".equals(paramName.trim())) {
							parameterIndexMap.put(paramName, i);
						}
					}
				}
			}

			// 固定的HttpServletRequest、HttpServletResponse
			for(int i = 0 ; i < parameterTypes.length; i++) {
				Class paramType = parameterTypes[i];
				if (paramType.equals(HttpServletRequest.class)) {
					parameterIndexMap.put(HttpServletRequest.class.getName(), i);
					continue;
				}
				if (paramType.equals(HttpServletResponse.class)) {
					parameterIndexMap.put(HttpServletResponse.class.getName(), i);
					continue;
				}
			}

		}

		public String getMethodUrl() {
			return methodUrl;
		}

		public Object getInstance() {
			return instance;
		}

		public Method getMethod() {
			return method;
		}

		public Class[] getParameterTypes() {
			return parameterTypes;
		}

		public Map<String, Integer> getParameterIndexMap() {
			return parameterIndexMap;
		}
	}

}

Spring 二、300行代码手写初体验Spring V1.0版本

原文:https://www.cnblogs.com/Qkxh320/p/spring_02.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!