为了提高自己对springmvc框架的理解,决定模仿springmvc手写一个简单demo
1.创建servlet项目
新建一个maven项目,选中webapp项目的结构
2.下载完成后添加servlet依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
3.新增自定义servlet类
package com.txxzctest.springmvcdemo;
import javax.servlet.http.HttpServlet;
/**
* @author Huang
*/
public class MyDispatcherServlet extends HttpServlet{
}
.
4.删掉原有web.xml内容,修改为自己的servlet内容
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.txxzctest.springmvcdemo.MyDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
5.自定义servlet中重写init、doGet、doPost方法
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("启动成功!");
super.init(config);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get方法测试成功!");
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post方法测试成功!");
super.doPost(req, resp);
}
6.tomcat启动服务
看到当前servlet项目启动成功
7.新增三个自定义的注解
NewController(自定义Controller)
package com.txxzctest.springmvcdemo.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NewController {
/**
* 表示给controller注册别名
* @return
*/
String value() default "";
}
NewRequestMapping(自定义RequestMapping)
package com.txxzctest.springmvcdemo.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NewRequestMapping {
/**
* 表示给requestmapping注册别名
* @return
*/
String value() default "";
}
8.扫描包下所有类,并加入到类集合里
首先需要找到相关包下的class文件,web.xml里设置包名
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.txxzctest.springmvcdemo.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>packName</param-name>
<param-value>com.txxzctest.springmvcdemo</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>`
修改servlet中启动的逻辑,让其启动时扫描包下所有class文件
//类集合
private List<String> CLASSNAME = new ArrayList<>();
//包名
private String packName;
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("启动成功!");
//获取包位置
packName = config.getInitParameter("packName");
//扫描包下所有class文件
doScan(packName);
}
private void doScan(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if(file.isDirectory()){
//递归读取包
doScan(packageName+"."+file.getName());
}else{
String className =packageName +"." +file.getName().replace(".class", "");
//加入到集合中
CLASSNAME.add(className);
}
}
}
9.装载自定义controller到自定义ioc中
//ioc容器
private Map<String, Object> IOC = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("启动成功!");
//获取包位置
packName = config.getInitParameter("packName");
//扫描包下所有class文件
doScan(packName);
//装载实例到ioc
doInstant();
}
private void doInstant() {
if (CLASSNAME.isEmpty()) {
return;
}
for (String className : CLASSNAME) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(NewController.class)) {
IOC.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
} else {
continue;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
/**
* 把字符串的首字母小写
*
* @param name
* @return
*/
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
10.初始化请求路由处理器
//url-controller map方便后面访问时使用反射
private Map<String, Object> controllerMap = new HashMap<>();
//url-method map方便后面访问时使用反射
private Map<String, Method> urlMap = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("启动成功!");
//获取包位置
packName = config.getInitParameter("packName");
//扫描包下所有class文件
doScan(packName);
//装载实例到ioc
doInstant();
//初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
}
private void initHandlerMapping() {
if (IOC.isEmpty()) {
return;
}
for (String className : IOC.keySet()) {
try {
Class<? extends Object> clazz = IOC.get(className).getClass();
//不是controller组件跳过
if (!clazz.isAnnotationPresent(NewController.class)) {
continue;
}
//获取controller 基本路径
String controllerUrl = "";
if(clazz.isAnnotationPresent(NewRequestMapping.class)) {
NewRequestMapping annotation = clazz.getAnnotation(NewRequestMapping.class);
controllerUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(NewRequestMapping.class)) {
continue;
}
String methodUrl = method.getAnnotation(NewRequestMapping.class).value();
String url = (controllerUrl + "/" + methodUrl).replaceAll("/+", "/");
urlMap.put(url, method);
controllerMap.put(url, clazz.newInstance());
System.out.println(url + "," + method);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
11.重写get和post方法,根据请求路由通过反射执行controller下的对应方法。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispath(req, resp);
}
private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (urlMap.isEmpty()) {
resp.getWriter().write("404 NOT FOUND!");
return;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
//拼接url并把多个/替换成一个
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.urlMap.containsKey(url)) {
resp.getWriter().write("404 NOT FOUND!");
return;
}
Method method = this.urlMap.get(url);
//获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
//获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
//保存参数值
Object[] paramValues = new Object[parameterTypes.length];
//方法的参数列表
for (int i = 0; i < parameterTypes.length; i++) {
//根据参数名称,做某些处理
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
//参数类型已明确,这边强转类型
paramValues[i] = req;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = resp;
continue;
}
}
//利用反射机制来调用
try {
method.invoke(this.controllerMap.get(url), paramValues);//obj是method所对应的实例 在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
编写测试用例
package com.txxzctest.springmvcdemo.controller;
import com.txxzctest.springmvcdemo.annotation.NewController;
import com.txxzctest.springmvcdemo.annotation.NewRequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@NewController
@NewRequestMapping("/test")
public class TestController {
@NewRequestMapping("/method")
public void test(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().write("test success");
} catch (IOException e) {
e.printStackTrace();
}
}
}
访问该路由,测试成功