【Spring】用300行代码实现spring1.0版本(详细注释)

通过手写来实现SpringMvc三层模型,spring的Ioc控制反转,Di依赖注入的功能,主要分为配置阶段,初始化阶段和运行阶段这三个阶段。

配置阶段
  1. 配置web.xml,设定init-param和param-name , 设置url-parttern,方法的过滤路径为 /*。

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
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<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>Archetype Created Web Application</display-name>

<servlet>
<servlet-name>gpmvc</servlet-name>
<servlet-class>com.alan.mvcframework.v2.servlet.MyDispatchServlet</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>gpmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
  1. 创建 application.properties ,配置包扫描的路径。
1
scanPackage=com.alan.demo
  1. 创建自己的注解文件。
1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired {
String value() default "";
}
1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController {
String value() default "";
}
1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
String value() default "";
}
1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
String value() default "";
}
1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
String value() default "";
}
  1. 引入 servlet-api 的依赖jar包。
1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.api.version}</version>
<scope>provided</scope>
</dependency>
初始化阶段

创建一个类继承 HttpServlet,重写 doGet ,doPost ,init 方法 。

  1. 获取包扫描的配置信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void doLoadConfig(String contextConfigLocation) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
//存入一个property里面
contextConfig.load(is);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
  1. 扫描包下的所有类,将类名全路径名称存入数组中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void doScanner(String scanPackage) {
//获取classLoader下面该路径的url,由/拼接
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
//获取File文件
File classPath = new File(url.getFile());
//循环文件路径,获取下面的子文件(文件架)
for (File file : classPath.listFiles()) {
//如果是文件夹,递归
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
//如果是文件,判断是否是class文件
if (!file.getName().endsWith(".class")) {
continue;
}
//去掉后缀,将文件的全路径名存入数组
classNames.add(scanPackage + "." + file.getName().replace(".class", ""));
}
}
}
  1. 扫描到的类实例化。
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
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
try {
//循环扫描的类名称
for (String className : classNames) {
//反射获取Class
Class<?> clazz = Class.forName(className);
//如果注解是controller
if (clazz.isAnnotationPresent(MyController.class)) {
//获取实例
Object instance = clazz.newInstance();
//获取类名首字母小写
String beanName = toLowerFirstCase(clazz.getSimpleName());
//将类名作为key,类实例做为value存入map(ioc容器)
ioc.put(beanName, instance);
//注解是service
} else if (clazz.isAnnotationPresent(MyService.class)) {
//获取注解内的名称
String beanName = clazz.getAnnotation(MyService.class).value();
//注解内没有名称,类名为clazz的首字母小写类名
if ("".equals(beanName.trim())) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
//获取内的实例
Object instance = clazz.newInstance();
//将类名作为key,类实例做为value存入map(ioc容器)
ioc.put(beanName, instance);
//循环获取class的所有接口
for (Class<?> i : clazz.getInterfaces()) {
//如果接口有多个实现类,报异常
if (ioc.containsKey(i.getName())) {
throw new Exception("The " + i.getName() + " is exists!!");
}
//否则将接口作为key,实例作为value存入map
ioc.put(i.getName(), instance);
}
//没有注解,跳出,下一个
} else {
continue;
}
}

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

private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
  1. 将扫描到的类的所有属性进行依赖注入。
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
private void doAutowired() {
if (ioc.isEmpty()) {
return;
}
//循环ioc容器
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//获取value的所有的 private/protected/default/public 修饰字段
for (Field field : entry.getValue().getClass().getDeclaredFields()) {
//没有MyAutowired的注解不管
if (!field.isAnnotationPresent(MyAutowired.class)) {
continue;
}

MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value().trim();
//如果注解内没有value,beanName就为字段的类型
if ("".equals(beanName.trim())) {
beanName = field.getType().getName();
}

//暴力访问
field.setAccessible(true);

try {
//将这个字段注入到这个类中
field.set(entry.getValue(), ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
  1. 获取类的方法和方法的url参数,存入handlerMapping中。
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
private void doInitHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
//循环ioc容器
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//获取value(Object)的class
Class<?> clazz = entry.getValue().getClass();
//如果这个类没有controller注解,不管它
if (!clazz.isAnnotationPresent(MyController.class)) {
continue;
}

//获取controller上面的url
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = requestMapping.value();
}

//获取class的所有方法
for (Method method : clazz.getMethods()) {
//没有MyRequestMapping注解的方法不管
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
//将url作为key,方法作为value存入handlerMapping中
handlerMapping.put(url, method);

System.out.println("Map :" + url + "," + method);
}
}
}
运行阶段
  1. 根据 请求url 从 handlerMapping 获取方法,2. 获取请求参数,3. 调用请求。
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
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 not found!!");
return;
}

Map<String, String[]> params = req.getParameterMap();
//根据url获取方法
Method method = this.handlerMapping.get(url);

//获取形参列表
Class<?>[] parameterTypes = method.getParameterTypes();
//获取参数类型
Object[] paramValues = new Object[parameterTypes.length];

for (int i = 0; i < parameterTypes.length; i++) {
Class paramterType = parameterTypes[i];
if (paramterType == HttpServletRequest.class) {
paramValues[i] = req;
} else if (paramterType == HttpServletResponse.class) {
paramValues[i] = resp;
} else if (paramterType == String.class) {
//通过运行时的状态去拿到你
Annotation[][] pa = method.getParameterAnnotations();
for (int j = 0; j < pa.length; j++) {
for (Annotation a : pa[i]) {
if (a instanceof MyRequestParam) {
String paramName = ((MyRequestParam) a).value();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]", "")
.replaceAll("\\s+", ",");
paramValues[i] = value;
}
}
}
}

}
}

String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//反射调用方法
method.invoke(ioc.get(beanName), paramValues);
}

最后来结合整体思路来看看总体代码:

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import com.alan.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.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class MyDispatchServlet extends HttpServlet {
private Properties contextConfig = new Properties();
//存扫描的类
private List<String> classNames = new ArrayList<String>();
//IOC容器
private Map<String, Object> ioc = new HashMap<String, Object>();
//handlerMapping存url和method
private Map<String, Method> handlerMapping = new HashMap<String, Method>();

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

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

//委派,根据url去找到一个对应的Method,然后通过response返回
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception,Detail :" + Arrays.toString(e.getStackTrace()));
}
}

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 not found!!");
return;
}

Map<String, String[]> params = req.getParameterMap();
//根据url获取方法
Method method = this.handlerMapping.get(url);

//获取形参列表
Class<?>[] parameterTypes = method.getParameterTypes();
//获取参数类型
Object[] paramValues = new Object[parameterTypes.length];

for (int i = 0; i < parameterTypes.length; i++) {
Class paramterType = parameterTypes[i];
if (paramterType == HttpServletRequest.class) {
paramValues[i] = req;
} else if (paramterType == HttpServletResponse.class) {
paramValues[i] = resp;
} else if (paramterType == String.class) {
//通过运行时的状态去拿到你
Annotation[][] pa = method.getParameterAnnotations();
for (int j = 0; j < pa.length; j++) {
for (Annotation a : pa[i]) {
if (a instanceof MyRequestParam) {
String paramName = ((MyRequestParam) a).value();
if (!"".equals(paramName.trim())) {
String value = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]", "")
.replaceAll("\\s+", ",");
paramValues[i] = value;
}
}
}
}

}
}

String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//反射调用方法
method.invoke(ioc.get(beanName), paramValues);
}


@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件,获取web.xml里面init-param的param-name,作为参数传递
doLoadConfig(config.getInitParameter("contextConfigLocation"));

// 扫描所有的类,传入配置的扫描包路径
doScanner(contextConfig.getProperty("scanPackage"));

// 将类的实例存入IOC容器
doInstance();

//DI 依赖注入
doAutowired();

//初始化handlerMapping
doInitHandlerMapping();
}

private void doInitHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
//循环ioc容器
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//获取value(Object)的class
Class<?> clazz = entry.getValue().getClass();
//如果这个类没有controller注解,不管它
if (!clazz.isAnnotationPresent(MyController.class)) {
continue;
}

//获取controller上面的url
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = requestMapping.value();
}

//获取class的所有方法
for (Method method : clazz.getMethods()) {
//没有MyRequestMapping注解的方法不管
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
//将url作为key,方法作为value存入handlerMapping中
handlerMapping.put(url, method);

System.out.println("Map :" + url + "," + method);
}
}
}

private void doAutowired() {
if (ioc.isEmpty()) {
return;
}
//循环ioc容器
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//获取value的所有的 private/protected/default/public 修饰字段
for (Field field : entry.getValue().getClass().getDeclaredFields()) {
//没有MyAutowired的注解不管
if (!field.isAnnotationPresent(MyAutowired.class)) {
continue;
}

MyAutowired autowired = field.getAnnotation(MyAutowired.class);
String beanName = autowired.value().trim();
//如果注解内没有value,beanName就为字段的类型
if ("".equals(beanName.trim())) {
beanName = field.getType().getName();
}

//暴力访问
field.setAccessible(true);

try {
//将这个字段注入到这个类中
field.set(entry.getValue(), ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

private void doInstance() {
if (classNames.isEmpty()) {
return;
}
try {
//循环扫描的类名称
for (String className : classNames) {
//反射获取Class
Class<?> clazz = Class.forName(className);
//如果注解是controller
if (clazz.isAnnotationPresent(MyController.class)) {
//获取实例
Object instance = clazz.newInstance();
//获取类名首字母小写
String beanName = toLowerFirstCase(clazz.getSimpleName());
//将类名作为key,类实例做为value存入map(ioc容器)
ioc.put(beanName, instance);
//注解是service
} else if (clazz.isAnnotationPresent(MyService.class)) {
//获取注解内的名称
String beanName = clazz.getAnnotation(MyService.class).value();
//注解内没有名称,类名为clazz的首字母小写类名
if ("".equals(beanName.trim())) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
//获取内的实例
Object instance = clazz.newInstance();
//将类名作为key,类实例做为value存入map(ioc容器)
ioc.put(beanName, instance);
//循环获取class的所有接口
for (Class<?> i : clazz.getInterfaces()) {
//如果接口有多个实现类,报异常
if (ioc.containsKey(i.getName())) {
throw new Exception("The " + i.getName() + " is exists!!");
}
//否则将接口作为key,实例作为value存入map
ioc.put(i.getName(), instance);
}
//没有注解,跳出,下一个
} else {
continue;
}
}

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

private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}

private void doScanner(String scanPackage) {
//获取classLoader下面该路径的url,由/拼接
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
//获取File文件
File classPath = new File(url.getFile());
//循环文件路径,获取下面的子文件(文件架)
for (File file : classPath.listFiles()) {
//如果是文件夹,递归
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
//如果是文件,判断是否是class文件
if (!file.getName().endsWith(".class")) {
continue;
}
//去掉后缀,将文件的全路径名存入数组
classNames.add(scanPackage + "." + file.getName().replace(".class", ""));
}
}
}

private void doLoadConfig(String contextConfigLocation) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
//存入一个property里面
contextConfig.load(is);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}


The End.