areschao's Studio.

手写简易的springmvc

字数统计: 1.4k阅读时长: 7 min
2018/12/18 Share

先上一张图了解一下springmvc流程

img

了解了框架的原理之后,学习框架就非常的简单

本文主要实现了简易的ioc 注解主要实现了 @Autowired @Controller @RequestMapping @Service

@RequestParam,只做简单的实现

项目结构

1545094576418

注解实现

@ChaoAutowired

1
2
3
4
5
6
@Target({ElementType.FIELD})//用于修饰在成员变量上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoAutowired {
String value() default "";
}

@ChaoController

1
2
3
4
5
6
@Target({ElementType.TYPE})//用于修饰在类上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoController {
String value() default "";
}

@ChaoRequestMapping

1
2
3
4
5
6
@Target({ElementType.METHOD,ElementType.TYPE})//用于修饰在成员变量,类上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoRequestMapping {
String value() default "";
}

@ChaoRequestParam 与 @ChaoService 与之前的都相同 这里不需要重复了

MainController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ChaoController("mainController")
@ChaoRequestMapping("/main")
public class MainController {

@ChaoAutowired("mainService")
private MainService mainService;

@ChaoRequestMapping("/query")
public void query(HttpServletRequest request, HttpServletResponse response,@ChaoRequestParam("name") String name, @ChaoRequestParam("age") String age) {
try {
PrintWriter printWriter = response.getWriter();
String result = mainService.query(name, age);
printWriter.write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}

这里没有实现@Reponsebody 所以使用了 printwrite 实现的效果是相同的

MainService

1
2
3
4
5
public interface MainService {

String query(String name,String age);

}

MainServiceImpl

1
2
3
4
5
6
@ChaoService("mainService")
public class MainServiceImpl implements MainService {
public String query(String name, String age) {
return "name===>"+name+";,age===>"+age;
}
}

这里只是简单的接收两个参数,注意都是String类型的

因为后面实现参数的匹配没有进行数据类型的转换所以都是String 类型的才能够接收

否则会出现参数类型不匹配的错误

DispatcherServlet

我们知道Springmvc就是对servlet的封装,而最主要的就是这个DispatcherServlet

ioc的实现就是一个 map 我下面的实现很清楚,而且有依赖注入,也就是DI

上代码

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
public class DispatcherServlet extends HttpServlet {

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

private Map<String, Object> beans = new ConcurrentHashMap<>();

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

//这里声明一个controller bean的map 是为了反射调用方法的时候能够获取实例,key 与方法的url相同
private Map<String, Object> controllerHandlerMap = new HashMap<>();

public void init() throws ServletException {
//把所有的bean扫描出来
scanPackage("com.chao");

//根据全类名 进行实例化
doInstance();

//进行注入
doDI();

bulidUrlMapping();
}

private void scanPackage(String packages) {
String urlPath = "/" + packages.replaceAll("\\.", "/");
URL url = this.getClass().getClassLoader().getResource(urlPath.replace("classes","target"));
File file = new File(url.getFile());
String[] files = file.list();
for (String path : files) {
File scanFile = new File(url.getFile() + path);
if (scanFile.isDirectory()) {
scanPackage(packages+"."+path);
} else {
classNames.add(packages + "." + scanFile.getName());
}
}
}

//进行bean的实例化
private void doInstance() {
if (classNames.size() <= 0) {
System.out.println("包扫描失败");
return;
}

classNames.forEach(className -> {
try {
Class<?> clazz = Class.forName(className.replace(".class", ""));

if (clazz.isAnnotationPresent(ChaoController.class)) {
Object instance = clazz.newInstance(); //创建控制类
ChaoRequestMapping chaoRequestMapping = clazz.getAnnotation(ChaoRequestMapping.class);
String value = chaoRequestMapping.value(); //这里可以进行判断 如果为空则用类名首字母小写,这里没判断只写思路
beans.put(value, instance); //放到ioc容器
} else if (clazz.isAnnotationPresent(ChaoService.class)) {
Object instance = clazz.newInstance(); //创建控制类
ChaoService chaoService = clazz.getAnnotation(ChaoService.class);
beans.put(chaoService.value(), instance); //放到ioc容器
}


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

//把service注入到控制层
private void doDI() {
if (beans.entrySet().size() <= 0) {
System.out.println("无实例化的类");
}

for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();

if (clazz.isAnnotationPresent(ChaoController.class)) {
Field[] fields = clazz.getDeclaredFields(); //getDeclaredFields私有属性也能够获取
for (Field field : fields) {
if (field.isAnnotationPresent(ChaoAutowired.class)) {
ChaoAutowired chaoAutowired = field.getAnnotation(ChaoAutowired.class);
String value = chaoAutowired.value();
field.setAccessible(true); //打开私有属性的权限
try {
field.set(instance, beans.get(value));//给autowired 的属性赋值
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}

private void bulidUrlMapping() {
if (beans.entrySet().size() <= 0) {
System.out.println("无实例化的类");
}
for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();
if (clazz.isAnnotationPresent(ChaoController.class)) {
ChaoRequestMapping chaoRequestMapping = clazz.getAnnotation(ChaoRequestMapping.class);
String classUrl = chaoRequestMapping.value();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(ChaoRequestMapping.class)) {
ChaoRequestMapping methodAnnotation = method.getAnnotation(ChaoRequestMapping.class);
String methodUrl = methodAnnotation.value();
String url = classUrl + methodUrl;
handlerMap.put(url, method); //这里的路径是需要处理的 可能是/main query 少/ 这里暂不进行处理
controllerHandlerMap.put(url, instance);
}
}
}
}
}

@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 {
//获取请求路径
String uri = req.getRequestURI(); // /myspringmvc/main/query

if (!handlerMap.containsKey(uri)){
resp.getWriter().write("404 NOT FOUND");
return;
}

//得到项目根路径
String contextPath = req.getContextPath(); // /myspringmvc

String urlPath = uri.replace(contextPath, ""); // /main/query

Method method = (Method) handlerMap.get(urlPath);

Object[] params = hand(req, resp, method);

try {
method.invoke(controllerHandlerMap.get(urlPath), params);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}

private Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method) {
//拿到当前待执行的方法有哪些参数类型
Class<?>[] parameterTypes = method.getParameterTypes();

Object[] args = new Object[parameterTypes.length];

int args_i = 0;
int index = 0;

for (Class<?> parameterType : parameterTypes) {
if (ServletRequest.class.isAssignableFrom(parameterType)) {
args[args_i++] = request;
}
if (ServletRequest.class.isAssignableFrom(parameterType)) {
args[args_i++] = response;
}

Annotation[] paramAns = method.getParameterAnnotations()[index];

if (paramAns.length > 0) {
for (Annotation paramAn : paramAns) {
if (ChaoRequestParam.class.isAssignableFrom(paramAn.getClass())) {
ChaoRequestParam requestParam = (ChaoRequestParam) paramAn;
args[args_i++] = request.getParameter(requestParam.value());
}
}
}
index++;
}
return args;
}
}

重要地点我都进行了注释,这样看未免不方便大家可以去下载我的源码

另外还有最重要的web.xml

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.chao.servlet.DispatcherServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

源码地址

另外还有我的博客地址

博客地址

CATALOG
  1. 1. 先上一张图了解一下springmvc流程
  • 项目结构
  • 注解实现
    1. @ChaoAutowired
    2. @ChaoController
    3. @ChaoRequestMapping
    4. MainController
    5. MainService
    6. MainServiceImpl
    7. DispatcherServlet
    8. web.xml