简介
为了更深入的了解 Spring 的实现原理和设计思想,一直打算出个系列文章,从零开始重新学习 Spring。有兴趣的小伙伴可以持续关注更新。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuer.github.io/NoteBooks/ |
正文
基本思路
web.xml
init-param
url-pattern
Annotation
init()
方法IOC
容器初始化准备工作
pom.xml
sevlet-api
依赖jetty
插件定义 Controller 和 Service
定义配置
src/main/resources/application.properties
scanPackage=com.yido.demo
<?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>
/*
* @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();
}
}
.
To do Continue…
.