一、前言
只实现了主线流程,因为看源码主要还是需要掌握其核心流程做了什么,所以我也希望通过这种方式来进行源码阅读后的记录和总结。工程我已经搭建好并测试过了,注释也写得比较详细,感兴趣的小伙伴可以关注私聊我给你们发工程地址~
二、实现思路
- 1、创建Maven工程
- 2、创建控制层、业务层代码
- 3、准备自定义注解和SpringMVC核心配置文件springmvc.xml
- 4、准备前端控制器DispatcherServlet,并在web.xml文件中声明自定义的前端控制器
- 5、创建Spring容器,通过DOM4J解析springmvc的XML文件
- 6、扫描springmvc中的控制器以及service类并实例化对象放入容器中【iocMap】
- 7、实现容器中对象的注入,比如将Service对象注入至Controller
- 8、建立请求映射地址与控制器以及方法之间的映射关系【MyHandler对象存储】
- 9、接收用户请求并进行分发操作【DispatcherServlet.doDispatcher()】
- 10、Controller方法调用以及不同类型的响应数据处理
三、代码结构
四、上干货~
1、创建Maven工程
在pom文件中引入一些必要的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--有梦想的肥宅-->
<groupId>com.zhbf.springmvc</groupId>
<artifactId>springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<!--设置maven编译的属性-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--导入servlet依赖-->
<!--PS:SpringMvc底层还是依赖于servlet,所以需要导入这个包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope><!--编译和测试阶段使用-->
</dependency>
<!--apache.commons工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!--lombok包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<!--jackson【json转换工具包】-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<!--XML解析包-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件,用于把方法中的参数从arg[0],argp[1]转换成对应的参数名-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 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.
2、创建控制层、业务层代码
这里就不一一粘贴代码了,涉及到的代码如下:
3、准备自定义注解和SpringMVC核心配置文件springmvc.xml
springmvc.xml
自定义注解
/**
* 自定义注解【AutoWired】
* -- @Retention: 注解保留策略
* -- @Target: 注解作用域
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(value = RetentionPolicy.RUNTIME)//运行时保留,可以通过反射读取
public @interface AutoWired {
}
/**
* 自定义注解【Controller】
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
@Target(ElementType.TYPE)//能在类、接口(包含注解类型)和枚举类型上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
String value() default "";
}
/**
* 自定义注解【RequestMapping】
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
@Target({ElementType.TYPE, ElementType.METHOD})//能在类、接口、方法和枚举类型上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
}
/**
* 自定义注解【ResponseBody】
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
@Target({ElementType.TYPE, ElementType.METHOD})//能在类、接口、方法和枚举类型上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}
/**
* 自定义注解【ResponseBody】
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
@Target(ElementType.TYPE)//能在类、接口(包含注解类型)和枚举类型上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String value();
}
- 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.
4、准备前端控制器DispatcherServlet,并在web.xml文件中声明自定义的前端控制器
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>有梦想的肥宅手写SpringMVC测试</display-name>
<!--1、配置前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>com.zhbf.springmvc.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--2、配置web应用程序启动时加载servlet
(1)load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
(2)它的值必须是一个整数,表示servlet被加载的先后顺序。
(3)如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
(4)如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--3、将请求映射到对应的Servlet,“/”表示映射所有请求-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 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.
DispatcherServlet
/**
* 自定义前端控制器
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
public class DispatcherServlet extends HttpServlet {
//自定义的SpringMvc容器
private WebApplicationContext webApplicationContext;
//创建集合:用于存放自定义映射关系对象,处理请求直接从该集合中进行匹配
List<MyHandler> handList = new ArrayList<>();
/**
* 初始化方法【主线流程】
*
* @throws ServletException
*/
@Override
public void init() throws ServletException {
//1、加载初始化参数【读取web.xml中配置的参数contextConfigLocation对应的值】
String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");
//2、创建Springmvc容器
webApplicationContext = new WebApplicationContext(contextConfigLocation);
//3、进行初始化操作
try {
webApplicationContext.init();
} catch (Exception e) {
System.out.println("初始化自定义IOC容器异常:" + e.getMessage());
e.printStackTrace();
}
//4、初始化请求映射关系
initHandlerAdapter();
}
/**
* 初始化请求映射关系
*/
private void initHandlerAdapter() {
//1、轮询IOC容器集合
for (Map.Entry<String, Object> entry : webApplicationContext.iocMap.entrySet()) {
//2、获取bean的class类型
Class<?> clazz = entry.getValue().getClass();
//3、判断是否为Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//3.1 获取Controller的RequestMapping
String controllerMapping = "";
if (clazz.isAnnotationPresent(RequestMapping.class)) {
controllerMapping = clazz.getAnnotation(RequestMapping.class).value();
}
//4、获取bean中所有的方法,为这些方法建立映射关系
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//5、如果含有注解RequestMapping则建立映射关系
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String url = controllerMapping + requestMapping.value();//获取注解中的值
//6、建立RequestMapping地址与控制器方法的映射关系,保存到MyHandler对象中
MyHandler myHandler = new MyHandler(url, entry.getValue(), method);
//7、映射关系存入集合中
handList.add(myHandler);
}
}
}
}
}
/**
* Get请求处理
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//PS:doGet内部调用doPost主要是为了统一处理方便
this.doPost(request, response);
}
/**
* Post请求处理
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//进行请求分发处理
this.doDispatcher(request, response);
}
/**
* 进行请求分发处理
*/
public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
//1、根据用户的请求地址找到对应的自定义映射关系对象【模拟HandlerMapping】
MyHandler myHandler = getHandler(req);
//2、获取方法返回对象
Object result = null;
if (myHandler == null) {
resp.getWriter().print("<h1>404 NOT FOUND!</h1>");
} else {
//2.1 获取目标方法【模拟HandlerAdapter】
try {
result = myHandler.getMethod().invoke(myHandler.getController());
} catch (Exception e) {
e.printStackTrace();
}
//2.2 设置响应内容
if (result instanceof String) {//跳转JSP
String viewName = (String) result;
//判断返回的路径 forward:/test.jsp
if (viewName.contains(":")) {
String viewType = viewName.split(":")[0];
String viewPage = viewName.split(":")[1];
if (viewType.equals("forward")) {//转发
req.getRequestDispatcher(viewPage).forward(req, resp);
} else {//重定向
resp.sendRedirect(viewPage);
}
} else {//默认转发
req.getRequestDispatcher(viewName).forward(req, resp);
}
} else {//返回JSON格式数据
Method method = myHandler.getMethod();
if (method.isAnnotationPresent(ResponseBody.class)) {
//将返回值转换成 json格式数据
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
resp.setContentType("text/html;charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.print(json);
writer.flush();
writer.close();
}
}
}
}
/***
* 获取请求对应的handler
* @param req
* @return
*/
public MyHandler getHandler(HttpServletRequest req) {
// 1、读取请求的URI
String requestURI = req.getRequestURI();
//2、从容器的Handle取出URL和用户的请求地址进行匹配,找到满足条件的Handler
for (MyHandler myHandler : handList) {
if (myHandler.getUrl().equals(requestURI)) {
return myHandler;
}
}
return null;
}
}
- 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.
5、创建Spring容器,通过DOM4J解析springmvc的XML文件
/**
* 应用上下文【Spring的IOC容器】
*
* @author 有梦想的肥宅
* @date 2021/08/24
*/
public class WebApplicationContext {
//定义容器配置的路径:classpath:springmvc.xml
String contextConfigLocation;
//定义List:用于存放bean的类路径【用于反射创建对象】
List<String> classNameList = new ArrayList<String>();
//定义IOC容器:key存放bean的名字,value存放bean实例
public Map<String, Object> iocMap = new ConcurrentHashMap<>();
/**
* 无参构造方法
*/
public WebApplicationContext() {
}
/**
* 有参构造方法
*
* @param contextConfigLocation
*/
public WebApplicationContext(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* 初始化Spring容器
*/
public void init() throws Exception {
//1、解析springmvc.xml配置文件【com.zhbf.business.*】
String pack = XmlPaser.getbasePackage(contextConfigLocation.split(":")[1]);
String[] packs = pack.split(",");
//2、进行包扫描
for (String pa : packs) {
excuteScanPackage(pa);
}
//3、实例化容器中bean
executeInstance();
//4、bean自动注入
executeAutoWired();
}
/**
* 扫描包
*
* @author 有梦想的肥宅
*/
private void excuteScanPackage(String pack) {
//1、把包路径转换为文件目录 com.zhbf.business ==> com/zhbf/business
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
String path = url.getFile();
//2、通过IO流读取文件并解析
File dir = new File(path);
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
//若当前为文件目录,则递归继续进行扫描
excuteScanPackage(pack + "." + f.getName());
} else {
//若当前为文件,则获取全路径 UserController.class ==> com.zhbf.business.controller.UserController
String className = pack + "." + f.getName().replaceAll(".class", "");
//3、存放bean的类路径【用于反射创建对象】
classNameList.add(className);
}
}
}
/**
* 实例化容器中的bean
*
* @author 有梦想的肥宅
*/
private void executeInstance() throws Exception {
// 1、循环存放bean的类路径的集合【用于反射创建对象】
for (String className : classNameList) {
//2、获取Class对象
Class<?> clazz = Class.forName(className);
//3、根据注解判断是Controller还是Service
if (clazz.isAnnotationPresent(Controller.class)) {
//Controller
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
iocMap.put(beanName, clazz.newInstance());
} else if (clazz.isAnnotationPresent(Service.class)) {
//Service【如果是Service则读取@Service注解中设置的BeanName】
Service serviceAn = clazz.getAnnotation(Service.class);
String beanName = !StringUtils.isEmpty(serviceAn.value()) ? serviceAn.value() : clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
iocMap.put(beanName, clazz.newInstance());
}
}
}
/**
* 进行自动注入操作
*
* @author 有梦想的肥宅
*/
private void executeAutoWired() throws IllegalAccessException {
//1、从容器中取出bean并判断bean中是否有属性上使用了@AutoWired注解,如果使用了,就需要进行自动注入操作
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
//2、获取容器中的bean
Object bean = entry.getValue();
//3、获取bean中的属性
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutoWired.class)) {
//4、获取属性值
String beanName = field.getName();
field.setAccessible(true);//取消检查机制
//5、在bean中设置iocMap.get(beanName)获得的对象
field.set(bean, iocMap.get(beanName));
}
}
}
}
}
- 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.
6、扫描springmvc中的控制器以及service类并实例化对象放入容器中【iocMap】
详见WebApplicationContext.excuteScanPackage(String pack)方法。
7、实现容器中对象的注入,比如将Service对象注入至Controller
详见WebApplicationContext.executeAutoWired()方法。
8、建立请求映射地址与控制器以及方法之间的映射关系【MyHandler对象存储】
详见DispatcherServlet.initHandlerAdapter()和DispatcherServlet.getHandler(HttpServletRequest req)方法。
9、接收用户请求并进行分发操作【DispatcherServlet.doDispatcher()】
详见DispatcherServlet.doDispatcher()方法。
10、Controller方法调用以及不同类型的响应数据处理
详见DispatcherServlet.doDispatcher()方法中2.2注释下的代码实现。
五、调用测试
PS:上面所放出的代码不是所有的代码,不过最主线核心的代码已经放在上面了,感兴趣的小伙伴可以自己尝试着补充未写在博客上的代码,或者联系我获取所有源码~共同探讨进步,共勉!🤞🤞🤞