简介
使用过spring mvc的小伙伴都知道,mvc在使用的时候,我们只需要在controller上注解上@controller跟@requestMapping(“URL”),当我们访问对应的路径的时候,框架便会帮我们去映射到指定的controller里面的指定方法,那么这一切都是怎么做到的呢?还有我们所传递过去的参数,为什么通过request.getParam就能轻易地 拿到呢?大家都知道mvc的核心控制器DispacherServlet的基本运行流程,那么他的内部是怎么运行的呢,我们来做一下简单的实现,让我们能进一步的了解MVC。以助于我们今后的开发。
MVC的工作流程
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
手撕源码
首先创建一个springboot的项目
引入pom
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我们这边主要是简化注解形式的启动方式
自定义注解实现自己的controller,Service,RequestMapping和Autowired
自定义注解
首先是controller
@Target(ElementType.TYPE)//表示注解运行在哪里 这里表示只能注解再类上面
@Retention(RetentionPolicy.RUNTIME)//表示注解的(生命周期)哪来出现
public @interface LulfController {
}
然后是Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfService {
String value() default "";
}
然后是RequestMapping
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfRequestMapping {
String value();
}
然后是Autowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LulfAutowired {
String value() default "";
}
接口方法
定义等下调用的接口方法
public interface IHelloService {
String get(String name);
}
接口方法实现类
这边我们用到了自己的@LulfService注解
@LulfService
public class HelloServiceImpl implements IHelloService {
@Override
public String get(String name) {
return "hello , " + name;
}
}
定义controller
首先定义父类的controller
public class BaseController {
protected HttpServletRequest request;
protected HttpServletResponse response;
public void init(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
}
然后定义我们的测试controller
用上我们的自定义注解
@LulfController
@LulfRequestMapping("/lulf")
public class TestController extends BaseController {
@LulfAutowired
private IHelloService helloService;
@LulfRequestMapping("/index1.do")
public void index1() {
try {
response.getWriter().write("index" + helloService.get("lulf"));
} catch (IOException e) {
e.printStackTrace();
}
}
@LulfRequestMapping("/index2.do")
public void index2() {
try {
response.getWriter().write("index1" + helloService.get("lulf1"));
} catch (IOException e) {
e.printStackTrace();
}
}
@LulfRequestMapping("/index3.do")
public void index3() {
try {
response.getWriter().write("index2" + helloService.get("lulf2"));
} catch (IOException e) {
e.printStackTrace();
}
}
@LulfRequestMapping("/index.do")
public void index() {
try {
response.getWriter().write("index3" + helloService.get("lulf3"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
此时我们可以测试下
public class test {
public static void main(String[] args) {
// Class
Class clazz = TestController.class;
//判断这个类是否存在@LulfController注解
if (clazz.isAnnotationPresent(LulfController.class)) {
String path = "";
//判断clazz是否存在注解@LulfRequestMapping注解
if (clazz.isAnnotationPresent(LulfRequestMapping.class)) {
//取出注解的值 放入path
LulfRequestMapping reqAnno = (LulfRequestMapping) clazz.getAnnotation(LulfRequestMapping.class);
path = reqAnno.value().toString();
}
//拿到控制类所有公开方法遍历
Method[] ms = clazz.getMethods();
for (Method method : ms) {
//如果不存在该注解 就进入下一轮
if (!method.isAnnotationPresent(LulfRequestMapping.class)) {
continue;
}
System.out.println("方法" + method.getName() + ",映射的对外路径:"
+ path + method.getAnnotation(LulfRequestMapping.class).value().toString());
}
}
}
简单的说就是获取类,然后判断类是否含有某个注解,遍历方法,判断方法是否含有某个注解
DispacherServlet
这样我们就可以拿到指定的类里面的指定的一些注解的值,还可以做一系列的操作。好,那么现在我们需要想到的就是核心控制器DispacherServlet了。既然是servlet,我们先来看一下servlet的生命周期。Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
Servlet 通过调用 init () 方法进行初始化。
Servlet 调用 service() 方法来处理客户端的请求。
Servlet 通过调用 destroy() 方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
既然知道了servlet的生命周期,那就好办了,我们可以通过servlet的初始化,将指定包下的类都扫描起来,然后再重写service()方法去处理这些请求,不久可以了么?接下去我们试一试。
创建自己的DispacherServlet:我是再spring boot环境下去操作的。我们要配置好拦截路径,基准包并重写init(),service()方法
package com.example.mvc.servlet;
import com.example.mvc.annotation.LulfAutowired;
import com.example.mvc.annotation.LulfController;
import com.example.mvc.annotation.LulfRequestMapping;
import com.example.mvc.annotation.LulfService;
import com.example.mvc.controller.BaseController;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
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.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName LulfDispacherServlet
* @Description TODO
* @Autuor lulinfeng
* @Date 2020/10/12
* @Version 1.0
*/
@WebServlet(urlPatterns = {"*.do"}, loadOnStartup = 1, initParams = {@WebInitParam(name = "basePackage", value = "com.example")})
public class LulfDispacherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
//保存url和Method的对应关系
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
//保存扫描的所有的类名
private List<String> classNames = new ArrayList<String>();
//存放所扫描出来的类及其实例
private Map<String, Object> ioc = new HashMap<String, Object>();
public LulfDispacherServlet() {
super();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//访问地址http://localhost:8080/lulf/index.do
//这里拿到uri : /lulf/index.do
String uri = req.getRequestURI();
//从方法map里获取到映射到的方法实例 : public void com.example.demo.annotation.TestController.index()
//处理成相对路径
if (!this.handlerMapping.containsKey(uri)) {
resp.getWriter().write("404 Not Found!!!");
return;
}
Method method = this.handlerMapping.get(uri);
//通过反射拿到method所在class,拿到class之后还是拿到class的名称
//再调用toLowerFirstCase获得beanName
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
BaseController controller;
try {
//获取实例
controller = (BaseController) ioc.get(beanName);
//初始化该controller的请求与响应
//也就是我们的请求中参数怎么通过requset.getParam方法拿到的原因
System.out.println(req.getRequestURI());
controller.init(req, resp);
//然后调用该方法
method.invoke(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 如果类名本身是小写字母,确实会出问题
* 这个方法是private的,传值也是自己传,类也都遵循了驼峰命名法
* 默认传入的值,存在首字母小写的情况,也不可能出现非字母的情况
* 为了简化程序逻辑,不做其他判断了
**/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
//之所以加,是因为大小写字母的ASCII码相差32,
// 而且大写字母的ASCII码要小于小写字母的ASCII码
//在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
chars[0] += 32;
return String.valueOf(chars);
}
@Override
public void init(ServletConfig config) throws ServletException {
//获取基础扫描包: 这里设定为com.example
String basePackage = config.getInitParameter("basePackage");
//1 扫描包得到所有的class 并且注入ioc
doScanner(basePackage);
//2、初始化扫描到的类,并且将它们放入到ICO容器之中
doInstance();
//可参考DispacherServlet 的初始化流程
//可参考DispacherServlet#initStrategies(ApplicationContext context)
doAutowired();
//4、初始化HandlerMapping
initHandlerMapping();
}
/**
* 扫描相关类
**/
private void doScanner(String scanPackage) {
//scanPackage = com.example ,存储的是包路径
//转换为文件路径,实际上就是把.替换为/就OK了
//classpath
URL url = this.getClass().getClassLoader().getResource("" + scanPackage.replaceAll("\\.", "/"));
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) {
continue;
}
String className = (scanPackage + "." + file.getName().replace(".class", ""));
classNames.add(className);
}
}
}
private void doInstance() {
//初始化,为DI做准备
if (classNames.isEmpty()) {
return;
}
try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
//判断需要初始化的类,满足条件才让它初始化,即加了特定注解的类才初始化
//为了简化代码逻辑,主要体会设计思想,只举例 @Controller和@Service,
//如果类上打了@LulfController注解
if (clazz.isAnnotationPresent(LulfController.class)) {
Object instance = clazz.newInstance();
//Spring默认类名首字母小写
String beanName = toLowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, instance);
}
//如果类上打了@LulfController注解
else if (clazz.isAnnotationPresent(LulfService.class)) {
//1、自定义的beanName
LulfService service = clazz.getAnnotation(LulfService.class);
String beanName = service.value();
//2、默认类名首字母小写
if ("".equals(beanName.trim())) {
beanName = toLowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
//3、根据类型自动赋值,投机取巧的方式
for (Class<?> i : clazz.getInterfaces()) {
if (ioc.containsKey(i.getName())) {//接口若有多个实现
throw new Exception("The “" + i.getName() + "” is exists!!");
}
//把接口的类型直接当成key了
ioc.put(i.getName(), instance);
}
} else {
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 自动依赖注入DI
**/
private void doAutowired() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//Declared 所有的,特定的 字段,包括private/protected/default
//正常来说,普通的OOP编程只能拿到public的属性
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(LulfAutowired.class)) {
continue;
}
LulfAutowired autowired = field.getAnnotation(LulfAutowired.class);
//如果用户没有自定义beanName,默认就根据类型注
String beanName = autowired.value().trim();
if ("".equals(beanName)) {
//获得接口的类型,作为key待会拿这个key到ioc容器中去取值
beanName = field.getType().getName();
}
//如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值
//反射中叫做暴力访问,下面打开暴力访问的开关
field.setAccessible(true);
try {
//用反射机制,动态给字段赋值
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 初始化url和Method的一对一对应关系
**/
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(LulfController.class)) {
continue;
}
//保存写在类上面的@LulfRequestMapping("/lulf")
String baseUrl = "";
if (clazz.isAnnotationPresent(LulfRequestMapping.class)) {
LulfRequestMapping requestMapping = clazz.getAnnotation(LulfRequestMapping.class);
baseUrl = requestMapping.value();
}
//默认获取所有的public方法
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(LulfRequestMapping.class)) {
continue;
}
LulfRequestMapping requestMapping = method.getAnnotation(LulfRequestMapping.class);
//优化
// //demo///query
String url = ("/" + baseUrl + "/" + requestMapping.value())
.replaceAll("/+", "/");
handlerMapping.put(url, method);
System.out.println("Mapped :" + url + "," + method);
}
}
}
}
启动APP
@SpringBootApplication
@ServletComponentScan
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
}
工程目录如下: