springmvc原理
准备工作
1、新建gitee代码库
gitee地址:https://gitee.com/study.liu/Lspring/tree/develop/
仓库地址https://gitee.com/study.liu/Lspring.git
2、IDE工具通过git克隆到本地,切换develop分支
项目构建过程不详述、包结构如下:
springmvc原理
流程图
实现要点
1、包扫描
2、类的实例化
3、依赖注入|完成装配
4、url和Controller中的method的关系映射
Talk is cheap. Show me the code. ---- Linus Torvalds
代码
@Autowired
package ai.ls.springmvc.annotation;
import java.lang.annotation.*;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.annotation
* @Author: ls
* @CreateTime: 2020-12-29 15:18
* @Description: 模拟@Autowired
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
String value() default "";
}
@Controller
package ai.ls.springmvc.annotation;
import java.lang.annotation.*;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.annotation
* @Author: ls
* @CreateTime: 2020-12-29 15:15
* @Description: 模拟@Controller
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
String value() default "";
}
@RequestMapping
package ai.ls.springmvc.annotation;
import java.lang.annotation.*;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.annotation
* @Author: ls
* @CreateTime: 2020-12-29 15:17
* @Description: 模拟@RequestMapping
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
String value() default "";
}
@Service
package ai.ls.springmvc.annotation;
import java.lang.annotation.*;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.annotation
* @Author: ls
* @CreateTime: 2020-12-29 15:19
* @Description: 模拟@Service
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
String value() default "";
}
DispatcherServlet
package ai.ls.springmvc.servlet;
import ai.ls.springmvc.annotation.Autowired;
import ai.ls.springmvc.annotation.Controller;
import ai.ls.springmvc.annotation.RequestMapping;
import ai.ls.springmvc.annotation.Service;
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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
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;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.servlet
* @Author: ls
* @CreateTime: 2020-12-29 16:56
* @Description: 模拟DispatcherServlet
*/
public class DispatcherServlet extends HttpServlet {
List<String> clazzNames = new ArrayList<>();
Map<String, Object> beans = new HashMap<>();
Map<String, Object> handlerMethod = new HashMap<>();
public DispatcherServlet() {
}
@Override
public void init() throws ServletException {
//1、包扫描
scanPackage("ai.ls");
//2、实例化类
classInstance();
//3.依赖注入/自动装配
inject();
//4、url和Controller中method的关系映射
handlerMapping();
}
private void handlerMapping() {
if (beans.isEmpty()) {
System.out.println("没有实例化的类");
return;
}
for (Map.Entry<String, Object> entry : beans.entrySet()) {
//从集合中获取类实例
Object instance = entry.getValue();
if (instance.getClass().isAnnotationPresent(Controller.class)) {
RequestMapping requestMapping = instance.getClass().getAnnotation(RequestMapping.class);
//获取RequestMapping中定义的路径值
String path = requestMapping.value();
Method[] methods = instance.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
String value = methodMapping.value();
//类上的路径+方法上路径
handlerMethod.put(path + value, method);
}
}
}
}
}
private void inject() {
if (beans.isEmpty()) {
System.out.println("没有实例化的类");
return;
}
for (Map.Entry<String, Object> entry : beans.entrySet()) {
//从集合中获取类实例
Object instance = entry.getValue();
//获取类中的所有成员属性
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
//判断成员属性上有无Autowired注解
if (field.isAnnotationPresent(Autowired.class)) {
Autowired autowired = field.getAnnotation(Autowired.class);
String value = autowired.value();
field.setAccessible(true);
try {
//完成依赖注入
field.set(instance,beans.get(value));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
private void classInstance() {
if (clazzNames.isEmpty()) {
System.out.println("没有扫描到类");
return;
}
for (String name : clazzNames) {
String realName = name.replace(".class", "");
try {
Class<?> aClass = Class.forName(realName);
if (aClass.isAnnotationPresent(Controller.class)) {
Controller controller = aClass.getAnnotation(Controller.class);
//完成Controller的实例化
Object instance = aClass.newInstance();
RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
String mappingValue = requestMapping.value();
//类的映射路径和类的实例化对象进行绑定
beans.put(mappingValue, instance);
}
if (aClass.isAnnotationPresent(Service.class)) {
Service service = aClass.getAnnotation(Service.class);
//完成Service的实例化
Object instance = aClass.newInstance();
String mappingValue = service.value();
//类的映射路径和类的实例化对象进行绑定
beans.put(mappingValue, instance);
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
private void scanPackage(String basePackage) {
String path = basePackage.replaceAll("\\.", "/");
//相对路径fileName前不加/
URL url = getClass().getClassLoader().getResource(path);
String filePath = url.getFile();
//目录递归扫描
File[] files = new File(filePath).listFiles();
for (File file : files) {
//判断是不是目录
if (file.isDirectory()) {
scanPackage(basePackage + "." + file.getName());
} else {
clazzNames.add(basePackage + "." + file.getName());
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
resp.getWriter().append("served at: ").append(req.getContextPath());
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
String path = uri.replace(contextPath, "");
Method method = (Method)handlerMethod.get(path);
Object instance = beans.get("/" + path.split("/")[1]);
try {
Object object = method.invoke(instance, null);
System.out.println(object.toString());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
TestService
package ai.ls.springmvc.service;
import ai.ls.springmvc.annotation.Service;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.service
* @Author: ls
* @CreateTime: 2020-12-29 15:38
* @Description: test service
*/
@Service("/testService")
public class TestService {
public String test() {
return "TestService.test()";
}
}
TestController
package ai.ls.springmvc.controller;
import ai.ls.springmvc.annotation.Autowired;
import ai.ls.springmvc.annotation.Controller;
import ai.ls.springmvc.annotation.RequestMapping;
import ai.ls.springmvc.service.TestService;
/**
* @BelongsProject: Lspring
* @BelongsPackage: ai.ls.springmvc.controller
* @Author: ls
* @CreateTime: 2020-12-29 15:36
* @Description: 测试入口
*/
@Controller
@RequestMapping("/test")
public class TestController {
@Autowired("/testService")
TestService testService;
@RequestMapping("/index")
public String index() {
return "test springMVC:" + testService.test();
}
}
web.xml
<?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>ai.ls.springmvc.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
说明
本文参考云析擎天老司机
本文旨在模拟springmvc核心重点原理,实现过程不够优雅、但简单易懂
文章有错漏希望给与指出、共同探讨
项目源码gitee地址在前文已贴出,感兴趣的大佬可以在develop分支随意玩耍,提交内容小弟酌情合并到master