摘要:学习编程技术的最快方式是读懂技术的原理,如果有这个条件的话,我们可以对技术的原理进行简单的实现,也编写一个简易版的该编程技术。在这篇文章中,我会先简单地介绍一下springmvc的原理,在理解原理的基础上,手写一个简易版的springmvc。如果不懂springmvc的小伙伴,也可以读该文章内容来理解springmvc的原理。
1.springmvc的原理
当客户端发送请求过来时,服务器需要对该请求进行响应。当服务器接收请求时,服务器首先会对发送请求过来的url地址进行解析,将解析出客户端发送过来的请求数据、请求资源、响应数据格式等信息,客户端会进行相应的响应(响应数据或响应视图)。在springmvc中,配置了前端控制器DispatcherServlet,这个前端控制器会根据请求的url地址解析出请求资源路径path,根据路径找出客户端访问的某个Controller类中的某个方法(每个方法都绑定了请求路径,如果是该请求路径,就执行该方法,并返回相应的响应数据)。
2.手写springmvc的思路
(0)初始化spring容器(这步不进行操作,如果要写的话,我们可以写一个spring(手写spring),springmvc之所以需要进行spring让容器的初始化,是因为springmvc的Controller需要交给spring管理)
(1)扫描包 doScan(AppConfig.class);扫描出所有的controller类
(2)实例化DispatcherServlet对象,doGet()方法中,解析handler()
(3)解析所有的controller中的方法, @RequestMapping注解
(4)内嵌tomcat服务器
(5)编写DispacherServlet前端控制器
(6)编写Controller(用于测试)
3.手写springmvc
(1)导入相应的依赖
<!-- tomcat依赖 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.38</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.38</version>
</dependency>
<!--Servlet坐标-->
<!-- <dependency>-->
<!-- <groupId>javax.servlet</groupId>-->
<!-- <artifactId>servlet-api</artifactId>-->
<!-- <version>2.5</version>-->
<!-- </dependency>-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1-b09</version>
</dependency>
<!-- 打印 日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>compile</scope>
</dependency>
(2)搭建项目结构
(3)编写javax.servlet.ServletContainerInitizerwe文件(内容写的应该是我们的程序中实现ServletContainerInitializer接口的全限定类名)(这是Servlet3.0的特性,程序会自动进行回调该类的onStartup()方法))
com.fs.util.MyWebApplicationInitializer
(4)编写MyWebApplicationIntializer类(implements ServletContainerInitializer)
package com.fs.util;
import com.fs.ann.Controller;
import com.fs.ann.ControllerScan;
import com.fs.ann.RequestMapping;
import com.fs.app.AppConfig;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Slf4j(topic = "e")
public class MyWebApplicationInitializer implements ServletContainerInitializer{
// implements ServletContainerInitializer
// key : controller类名小写,value : 实例化的controller对象
static Map<String,Object> controllerMap = new HashMap<>();
// 存储所有的方法和他对应的访问路径;
// key : method方法上的对应的访问路径@RequestMapping ,value : 对应的Method
static Map<String, Method> controllerMethodMap = new HashMap<>();
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
log.debug("执行了onStartup方法");
// 0.初始化spring容器
// AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// context.register(AppConfig.class);
// 1.扫描包 doScan(AppConfig.class);扫描出所有的controller类
doScan(AppConfig.class);
// 2.实例化DispatcherServlet对象,doGet()方法中,解析handler()
// DispatcherServlet servlet = new DispatcherServlet(context);
// 实例化MyDispatcherServlet对象
initServlet(servletContext);
// 解析所有的controller中的方法, @RequestMapping注解
parseHandler();
log.debug("controllerMap : " + controllerMap);
log.debug("controllerMethodMap : " + controllerMethodMap);
}
public void doScan(Class clazz) {
// 判断是否添加ControllerScan请求
if(clazz.isAnnotationPresent(ControllerScan.class)) {
// 获取需要扫描的包名
ControllerScan declaredAnnotation = (ControllerScan) clazz.getDeclaredAnnotation(ControllerScan.class);
String scanPackageName = declaredAnnotation.value();
// 扫描包名路径下的所有文件,查询出所有的controller,并存储到map到当中
scan(scanPackageName);
}
}
public void scan(String scanPagekeName){
// 项目的文件输出路径(相对路径)
String rootUrl = this.getClass().getClassLoader().getResource("").getPath();
// 将包名的.符号转换/ 定义一个方法 pointToSlash()
String pathName = pointToSlash(rootUrl + scanPagekeName);
// 遍历该路径(目录)下的所有目录和文件(递归)
File file = new File(pathName);
for (File listFile : file.listFiles()) {
// 判断是否目录
if (listFile.isDirectory()) {
// 如果当前文件是目录
scan(scanPagekeName + "/" + listFile.getName());
continue;
}
// 当前文件不是一个目录的情况下
String className = listFile.getName(); // 类名.class
// String classSimpleName = toSimpleClassName(className); // 类名小写
// 获取包名+".类名"
String packageName = scanPagekeName + "." + className.split("\\.")[0];
// 将所有的/符号转化成.
packageName = slashToPoint(packageName);
Class<?> aClass = null;
try {
aClass = Class.forName(packageName);
// 检测该类中是否有@Controller注解
if (aClass.isAnnotationPresent(Controller.class)) {
// 实例化对象
Controller declaredAnnotation = aClass.getDeclaredAnnotation(Controller.class);
String controllerName = declaredAnnotation.value();
if(controllerName.isEmpty()) {
// 如果是空字符串,则使用controller的默认命名方式
// 获取类名,类名小写
controllerName = controllerName + toSimpleClassName(className);
}
Object object = aClass.newInstance();
controllerMap.put(controllerName,object);
} else {
continue;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
public String pointToSlash(String path) {
path = path.replaceAll("\\.","/");
return path;
}
public String slashToPoint(String path) {
path = path.replaceAll("/","\\.");
return path;
}
// 类名小写(无后缀)
public static String toSimpleClassName(String className) {
className = className.split("\\.")[0];
char[] array = className.toCharArray();
array[0] = (char)(array[0] + 32);
return String.valueOf(array);
}
public void initServlet(ServletContext servletContext) {
// 实例化servlet对象
MyDispatcherServlet servlet = new MyDispatcherServlet();
// 交给tomcat服务器进行管理
ServletRegistration.Dynamic registration = servletContext.addServlet("app",servlet);
registration.setLoadOnStartup(1);
registration.addMapping("*.do");
}
public void parseHandler() {
Set<Map.Entry<String, Object>> entries = controllerMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
String key = entry.getKey();
Object obj = entry.getValue();
Class<?> aClass = obj.getClass();
// 获取所有的@RequestMapping方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
// 检查controller的方法上是否有@RequestMapping注解
if(method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String s = annotation.value();
controllerMethodMap.put(s,method);
}
}
}
}
}
(5)编写MyDispatcherServlet类(对应springmvc中的DispatherServlet前端控制器)
package com.fs.util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
@Slf4j(topic = "e")
public class MyDispatcherServlet extends HttpServlet {
@SneakyThrows
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求的路径
String servletPath = req.getServletPath();
// 2. 查询对应的方法
Method method = MyWebApplicationInitializer.controllerMethodMap.get(servletPath);
// 3. 查询对应的对象
String simpleName = method.getDeclaringClass().getSimpleName();
simpleName = MyWebApplicationInitializer.toSimpleClassName(simpleName);
Object object = MyWebApplicationInitializer.controllerMap.get(simpleName);
// 4. 查询对应方法上的参数
Class<?>[] parameterTypes = method.getParameterTypes();
// 由于parameterType数组中的值都是唯一的,所以可以用 == 来进行判断
// req.getClass() == parameterTypes[0]
if(parameterTypes.length == 0) {
log.debug("执行无参数的方法");
method.invoke(object);
} else if(parameterTypes.length == 1) {
log.debug("执行1个参数的方法");
if("HttpServletRequest".equals(parameterTypes[0].getSimpleName())) {
// 一个参数: HttpServletReqeust;
method.invoke(object,req);
} else {
// 一个参数: HttpServletResponse;
method.invoke(object,resp);
}
} else if(parameterTypes.length == 2){
log.debug("执行两个参数的方法");
if("HttpServletRequest".equals(parameterTypes[0].getSimpleName())) {
// 第一个参数: HttpServletReqeust;
method.invoke(object,req,resp);
} else {
// 第一个参数: HttpServletResponse;
method.invoke(object,resp,req);
}
}
}
}
(6)内嵌tomcat服务器
package com.fs.util;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
public class SpringApplication {
public static void main(String[] args) throws LifecycleException {
//记录以后静态文件存储的位置
String baseDir = Thread.currentThread().getContextClassLoader().getResource("public").getPath();
//实例化tomcat对象
Tomcat tomcat = new Tomcat();
//指定项目访问的路径项目名
//指定静态资源位置
tomcat.addWebapp("/bookstore",baseDir);
//设置端口号
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(8080);
connector.setThrowOnFailure(true);
//应用端口号
tomcat.getService().addConnector(connector);
tomcat.setConnector(connector);
//启动tomcat
tomcat.start();
//阻塞等待
tomcat.getServer().await();
}
}
(6)AppConfig类(配置类,给定需要扫描的包)
package com.fs.app;
import com.fs.ann.ControllerScan;
@ControllerScan("com.fs")
public class AppConfig {
}
(7)其它(自定义注解,Controller类,日志文件(logback日志))
package com.fs.ann;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
String value() default "";
}
package com.fs.ann;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerScan {
String value();
}
package com.fs.ann;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value();
}
package com.fs.web.controller;
import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j(topic = "e")
public class BController {
@RequestMapping("/bTest.do")
public void bTest() {
log.debug("BController类中的bTest()方法执行了");
}
}
package com.fs.web.controller;
import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j(topic = "e")
public class BController {
@RequestMapping("/bTest.do")
public void bTest() {
log.debug("BController类中的bTest()方法执行了");
}
}
package com.fs.web;
import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j(topic = "e")
public class AController {
@RequestMapping("/a.do")
public void aTest() {
log.debug("执行了AController的aTest()方法");
}
}
package com.fs.web;
import com.fs.ann.Controller;
import com.fs.ann.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@Slf4j(topic = "e")
public class IndexController {
// 两个参数
@RequestMapping("/index.do")
public void index(HttpServletRequest request, HttpServletResponse response) {
log.debug("执行了IndexController类中的index()方法");
}
// response参数
@RequestMapping("/login.do")
public void login(HttpServletResponse response) {
log.debug("login():欢迎登录");
}
// request参数
@RequestMapping("/register.do")
public void register(HttpServletRequest request) {
log.debug("register()执行了");
}
// 无参
@RequestMapping("/test.do")
public void test() {
log.debug("IndexController类中的test()方法执行了");
}
}
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %boldYellow(%msg%n)
</Pattern>
</encoder>
</appender>
<logger name="e" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<!-- <root level="DEBUG">-->
<!-- <appender-ref ref="STDOUT"/>-->
<!-- </root>-->
</configuration>