King's Studio

手写SpringMVC

字数统计: 1.4k阅读时长: 7 min
2019/07/13 Share

SpringMVC是一款基于MVC架构模式的轻量级Web框架,其目的是将Web开发模块化,对整体架构进行解耦,简化Web开发流程。SpringMVC基于请求驱动,即使用请求-响应模型。由于SpringMVC遵循MVC架构规范,因此分层开发数据模型层(Model)、响应视图层(View)和控制层(Controller),可以让开发者设计出结构规整的Web层。

概念

SpringMVC中的核心DispatcherServlet继承了HttpServlet,并且重写了Java中HttpServlet的方法,用于处理get/post请求,其中重写了init方法用来解析web.xml的配置,解析SpringMVC的xml配置,如扫描包的路径,然后根据这个路径扫描整个项目,确定controller的位置,利用@RequestMapping注解拦截请求,并调用controller的处理方法,返回需要的值。

模拟DispatcherServlet

DispatcherServlet重写HttpServlet的init方法,首先利用getInitParameter方法获取到xml文件中的位置,然后进行xml文件的配置解析,利用dom4j的api。首先引入dom4j,在pom.xml文件中添加依赖。

1
2
3
4
5
6
7
<!--dom4j解析xml文件依赖引入-->
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

SpringMVC.xml文件放在resources文件夹下,xml文件解析的步骤:

  • 创建SAXReader对象
  • 利用read方法读取xml文件
  • 获取xml文件的根节点
  • 利用根节点拿到子节点
  • 获取子节点中的元素
  • 拿到了扫描项目的入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package com.jinqi.servlet;

import com.alibaba.fastjson.JSON;
import com.jinqi.annotation.Controller;
import com.jinqi.annotation.RequestMapping;
import com.jinqi.annotation.ResponseBody;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
* 编写前端控制器
*/
public class TestDispatcherServlet extends HttpServlet {

private static String CLASS_PATH = TestDispatcherServlet.class.getResource("/").getPath();

private static String SCAN_PATH = "";

private static String BASE_PATH;

private static Map<String,Method> map = 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 {
try{
// localhost:8080/user/getUser.do
String requestURI = req.getRequestURI();
//拿到controller中的方法
Method method = map.get(requestURI);
if (method!=null){
//如果方法不为空,获取参数,通过invoke调用该方法
Class<?> clazz = method.getDeclaringClass();
Object o = clazz.newInstance();
//形参
Parameter[] parameters = method.getParameters();
//定义实参数据
Object[] objects = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
String paraName = parameters[i].getName();
Class paraType = parameters[i].getType();
//如果参数类型是HttpServletRequest
if (paraType==HttpServletRequest.class){
objects[i] = req;
}else if (paraType==HttpServletResponse.class){
//如果参数类型是HttpServletResponse
objects[i] = resp;
}else if (paraType==String.class){
String parameter = req.getParameter(paraName);
objects[i] = parameter;
}else {
//如果参数是对象
Object o1 = paraType.newInstance();
for (Field field : paraType.getDeclaredFields()) {
field.setAccessible(true);
String name = field.getName();
String parameter = req.getParameter(name);
field.set(o1,parameter);
}
objects[i] = o1;
}
}
//利用invoke方法调用实体类中的具体返回的方法
Object invoke = method.invoke(o, objects);
if (method.getAnnotation(ResponseBody.class)!=null){
resp.getWriter().write(JSON.toJSONString(invoke));
}else {
//如果返回值是String类型,返回页面
if (method.getReturnType()==String.class) {
req.getRequestDispatcher("/"+(String) invoke).forward(req,resp);
}
}
}else {
resp.setStatus(404);
}
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 重写init方法
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {

try{
String mvcLocation = config.getInitParameter("testContextConfigLocation");
CLASS_PATH = CLASS_PATH.replaceAll("%20"," ");
//获取xml文件的对象
File xmlFile = new File(CLASS_PATH+mvcLocation);
//利用dom4j解析xml文件
SAXReader saxReader = new SAXReader();
Document xmlDocument = saxReader.read(xmlFile);
Element rootElement = xmlDocument.getRootElement();
Element packageScan = rootElement.element("packageScan");
Attribute aPackage = packageScan.attribute("package");
SCAN_PATH = aPackage.getValue();
File file = new File(CLASS_PATH+SCAN_PATH);
BASE_PATH = file.getPath();
scanPackage(file);

}catch (Exception e){
e.printStackTrace();
}
}

/**
* 封装扫描包方法
* @param file
*/
public void scanPackage(File file){
try {
if(file.isDirectory()){
for (File file1 : file.listFiles()) {
scanPackage(file1);
}
}else {
String fileName = file.getName();
if (fileName.substring(fileName.lastIndexOf(".")).equals(".class")){
String filePath = file.getPath();
//处理扫描到的文件路径
filePath = filePath.replace(BASE_PATH,"");
filePath = SCAN_PATH + filePath;
String classPath = filePath.replaceAll("\\\\",".");
String className = classPath.substring(0,classPath.lastIndexOf("."));
Class<?> clazz = Class.forName(className);
//拿到controller包中的类,判断是否含有Controller注解
if (clazz.isAnnotationPresent(Controller.class)){
RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
String url_path = "";
if (classRequestMapping!=null){
url_path = classRequestMapping.value();
}
for (Method method : clazz.getDeclaredMethods()) {
String methodPath = "";
RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
if (methodRequestMapping!=null){
methodPath = methodRequestMapping.value();
map.put(url_path+methodPath,method);
System.out.println(url_path+methodPath+"被映射到了"+clazz.getName()+"的"+method.getName()+"方法上");
}
}
}
//com/jinqi/servlet/TestDispatcherServlet
//Class<?> aClass = Class.forName("com.jinqi.servlet.TestDispatcherServlet");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}

编写controller进行测试

通常我们写Spring项目时,会在controller类上加上两个注解:@Controller和@RequestMapping。表明这是controller控制器和拦截的请求路径,因此我们也手写这两个注解用于测试。

1
2
3
4
5
6
7
8
9
10
11
12
package com.jinqi.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default " ";
}
1
2
3
4
5
6
7
8
9
10
11
package com.jinqi.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

controller类我们写getUser方法并传入HttpServletRequest、HttpServletResponse对象,以及姓名和User实体类对象,并将其打印,看能否获得正确的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.jinqi.controller;

import com.jinqi.annotation.Controller;
import com.jinqi.annotation.RequestMapping;
import com.jinqi.annotation.ResponseBody;
import com.jinqi.entity.UserEntity;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/user")
public class UserController {

@RequestMapping("/getUser.do")
@ResponseBody
public Object getUser(HttpServletRequest request, HttpServletResponse response, String name, UserEntity userEntity){

System.out.println(request);
System.out.println(response);
System.out.println(name);
System.out.println(userEntity);
System.out.println("getUser");
return userEntity;
}

@RequestMapping("/index.do")
public String index(){
return "index.html";
}
}

原文作者:金奇

原文链接:https://www.rossontheway.com/2019/07/13/手写SpringMVC/

发表日期:July 13th 2019, 12:00:00 am

更新日期:July 13th 2019, 9:51:24 am

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可,除特别声明外,转载请注明出处!

CATALOG
  1. 1. 概念
    1. 1.1. 模拟DispatcherServlet
    2. 1.2. 编写controller进行测试