day05-SpringMVC底层机制简单实现-01
SpringMVC底层机制简单实现-01
主要完成:核心分发控制器+Controller和Service注入容器+对象自动装配+控制器方法获取参数+视图解析+返回JSON格式数据
1.搭建开发环境
创建 Maven 项目,File-New-Project-Maven
将 pom.xml 文件中的编译版本改为1.8
在 src 目录下创建以下目录:
java 代码放在 java 目录下,相关的资源文件放在 resource 目录下,对 maven 的 web 项目而言,resource 就是类路径。前端页面放在 webapp 下,该目录对应之前的 web 目录。test/java 目录用于存放测试文件,测试需要的资源文件放在 test/resource 目录下。
在 pom.xml 中引入基本的 jar 包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency> <!--引入原生servlet依赖的jar包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
1.scope表示引入jar包的作用范围,
2.provided表示项目在打包放到生产环境时,不需要打上servlet-api.jar
3.因为 tomcat本身就有该jar包,使用tomcat的即可,防止版本冲突
-->
<scope>provided</scope>
</dependency> <!--引入dom4j,用于解析xml-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency> <!--引入常用的工具类jar包,该jar含有很多常用的类-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
2.任务1-开发MyDispatcherServlet
说明:编写 MyDispatcherServlet,充当原生的 DispatcherServlet(即核心控制器)
2.1分析
2.2代码实现
创建 src/main/java/com/li/myspringmvc/servlet/MyDispatcherServlet.java,充当原生的前端控制器。
package com.li.myspringmvc.servlet; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* @author 李
* @version 1.0
* 1.MyDispatcherServlet 充当原生的 DispatcherServlet,它的本质就是一个Servlet
* 因此继承 HttpServlet
*/
public class MyDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyDispatcherServlet doGet() 被调用..");
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyDispatcherServlet doPost() 被调用..");
}
}
创建 src/main/resources/myspringmvc.xml,充当原生的 applicationContext-mvc.xml(即 spring 容器配置文件)
配置 src/main/webapp/WEB-INF/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>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>MyDispatcherServlet</servlet-name>
<servlet-class>com.li.myspringmvc.servlet.MyDispatcherServlet</servlet-class>
<!--给前端控制器指定配置参数,指定要操作的spring容器文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:myspringmvc.xml</param-value>
</init-param>
<!--要求该对象在tomcat启动时就自动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyDispatcherServlet</servlet-name>
<!--作为前端控制器,拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping> </web-app>
配置 Tomcat,进行测试
浏览器访问
http://localhost:8080/li_springmvc/aaa
3.任务2-实现客户端/浏览器可以请求控制层
3.1分析
任务2的总目标是:
实现自己的 @Controller 注解和 @RequestMapping 注解,当浏览器访问指定的 URL 时,由前端控制器,找到 Controller 的某个方法,然后通过 tomcat 将数据返回给浏览器。
3.2代码实现
步骤一:两个注解和测试Controller
(1)Controller 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* 该注解用于标识一个控制器组件
* 1.@Target(ElementType.TYPE) 指定自定义注解可修饰的类型
* 2.@Retention(RetentionPolicy.RUNTIME) 作用范围,RUNTIME使得可以通过反射获取自定义注解
* 3.@Documented 在生成文档时,可以看到自定义注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
(2)RequestMapping 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* RequestMapping 注解用于指定控制器-方法的映射路径
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
(3)用于测试的控制器 MonsterController.java
package com.li.controller;
import com.li.myspringmvc.annotation.Controller;
import com.li.myspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MonsterController {
//编写方法,可以列出妖怪列表
//springmvc支持原生的servlet api,为了看到底层机制,这里直接放入两个参数
@RequestMapping(value = "/list/monster")
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
//设置编码
response.setContentType("text/html;charset=utf-8");
//获取writer,返回提示信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.print("<h1>妖怪列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
步骤二:配置容器文件 springmvc.xml,指定扫描的包
指定扫描的包是为了之后使用注解获取需要反射的类
如果需要添加新的扫描包,在base-package添加包路径,用逗号表示间隔。
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!--指定要扫描的包及其子包的java类-->
<component-scan base-package="com.li.controller,com.li.service"/>
</beans>
步骤三:编写 XMLParse 工具类,用于解析 springmvc.xml,得到要扫描的包
package com.li.myspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @author 李
* @version 1.0
* XMLParse用于解析spring配置文件
*/
public class XMLParse {
public static String getBasePackage(String xmlFile) {
SAXReader saxReader = new SAXReader();
//maven的类路径是在target/li-springmvc/WEB-INF/classes/目录下
//通过类的加载路径-->获取到spring配置文件[对应的资源流]
InputStream inputStream =
XMLParse.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到配置文件的文档
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement = rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
步骤四:开发MyWebApplicationContext,充当原生Spring容器,得到扫描类的全路径列表
即把指定目录包括子目录下的 java 类的全路径扫描到 ArrayList 集合中,以便之后反射。是否需要反射,还要取决于类中是否添加了@Controller注解
(1)MyWebApplicationContext.java 实现自定义的 spring 容器,目前先完成扫描工作
package com.li.myspringmvc.context;
import com.li.myspringmvc.xml.XMLParse;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* @author 李
* @version 1.0
* MyWebApplicationContext 是我们自定义的spring容器
*/
public class MyWebApplicationContext {
//属性classFullPathList用于保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//该方法完成对自己的 spring容器的初始化
public void init() {
//返回的是我们在容器文件中配置的base-package的value
String basePackage = XMLParse.getBasePackage("myspringmvc.xml");
//这时你的 basePackage是像 com.li.controller,com.li.service 这样子的
//通过逗号进行分割包
String[] basePackages = basePackage.split(",");
if (basePackages.length > 0) {
//遍历这些包
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的路径classFullPathList=" + classFullPathList);
}
/**
* 该方法完成对包的扫描
* @param pack 表示要扫描的包,如 "com.li.controller"
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
// (1)通过类的加载器,得到指定包的工作路径[绝对路径]
// (2)然后用斜杠代替点=>如 com.li.controller=>com/li/controller
URL url =
this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
// url=file:/D:/IDEA-workspace/li-springmvc/target/li-springmvc
// /WEB-INF/classes/com/li/controller/
//System.out.println("url=" + url);
//根据得到的路径,对其进行扫描,把类的全路径保存到 classFullPathList属性中
String path = url.getFile();
System.out.println("path=" + path);
//在io中,把目录也视为一个文件
File dir = new File(path);
//遍历 dir目录,因为可能会有[多个文件/子目录]
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
//如果是目录,需要递归扫描
//pack加上下一级的目录名继续下一层的扫描
scanPackage(pack + "." + file.getName());
} else {
//这时得到的文件可能是.class文件,也可能是其他文件
//就算是class文件,还需要考虑是否要注入到容器的问题
//目前先把所有文件的全路径都保存到集合中,后面注入对象到spring容器时再考虑过滤
String classFullPath =
pack + "." + file.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
(2)通过 MyDispatcherServlet 前端控制器来调用并初始化 spring 容器
//添加init方法,用于初始化spring容器
@Override
public void init() throws ServletException {
MyWebApplicationContext myWebApplicationContext = new MyWebApplicationContext();
myWebApplicationContext.init();
}
(3)启动tomcat,后台成功获取到了路径,测试成功。
tomcat启动--加载了MyDispatcherServlet--通过该Servlet的init()生命周期方法初始化自定义的 spring 容器,同时调用自定义 spring 容器的 init 方法去扫描包
步骤五:完善MyWebApplicationContext(自定义 spring 容器),实例化对象到容器中
将扫描到的类,在满足添加了注解的情况下,通过反射注入到 ioc 容器
day05-SpringMVC底层机制简单实现-01的更多相关文章
- tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了
简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...
- SpringMVC视图机制详解[附带源码分析]
目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门bl ...
- 深入源码分析SpringMVC底层原理(二)
原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...
- SpringMVC异常处理机制
SpringMVC异常处理机制 springMVC会将所有在doDispatch方法中的异常捕获,然后处理.无法处理的异常会抛出给容器处理. 在doDispatch()中调用processDispat ...
- [转]STL 容器一些底层机制
1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...
- C++ STL容器底层机制
1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...
- 探索C++的底层机制
探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...
- 设计模式:与SpringMVC底层息息相关的适配器模式
目录 前言 适配器模式 1.定义 2.UML类图 3.实战例子 4.总结 SpringMVC底层的适配器模式 参考 前言 适配器模式是最为普遍的设计模式之一,它不仅广泛应用于代码开发,在日常生活里也很 ...
- php-浅谈php底层机制
php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...
- SpringMVC异常处理机制详解[附带源码分析]
目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...
随机推荐
- Vue3组件间传值
12种方式 1. 父组件 ./father.vue 点击查看代码 <template> <h1>father:</h1> <h3>子组件传过来的:{{ ...
- Go语言核心36讲14
在前几期文章中,我们分了几次,把Go语言自身提供的,所有集合类的数据类型都讲了一遍,额外还讲了标准库的container包中的几个类型. 在几乎所有主流的编程语言中,集合类的数据类型都是最常用和最重要 ...
- Day20:继承详解
继承的理解 继承:对类进行抽象化:也就是将存在的类构造成新的类: 比如说学生是一个类,老师是一个类,那么我们可以将学生类和老师类收纳进人这个类:那么学生和老师则为子类(派生类).人为父类(基类):子类 ...
- MySQL DATE_SUB查询工龄大于35的员工信息
#(11) 查询工龄大于或等于35年的员工信息.SELECT * FROM emp e WHERE e.HIREDATE<=DATE_SUB(SYSDATE(),INTERVAL 35 YEAR ...
- 在实际应用中联合体union的妙用
关键字union,又称为联合体.共用体,联合体的声明和结构体类似,但是它的行为方式又和结构体不同,这里的行为方式主要指的是其在内存中的体现,结构体中的成员每一个占据不同的内存空间,而联合体中的所有成员 ...
- 论文翻译:2022_DNS_1th:Multi-scale temporal frequency convolutional network with axial attention for speech enhancement
论文地址:带轴向注意的多尺度时域频率卷积网络语音增强 论文代码:https://github.com/echocatzh/MTFAA-Net 引用:Zhang G, Yu L, Wang C, et ...
- 【Java】【数据库】索引为何使查询变得更快?--B+树
排序数据的二分查找 二分查找的时间复杂度是\(O(log_2n)\),明显快于暴力搜索. 索引 建立索引的数据,就是通过事先排好顺序,在查找时可以应用二分查找来提高查询效率. 所以索引应该尽可能建立在 ...
- 深入理解 MySQL 的事务隔离级别和 MVCC 机制
前言 我们都知道 MySQL 实现了 SQL 标准中的四个隔离级别,但是具体是如何实现的可能还一知半解,本篇博客将会从代码层面讲解隔离级别的实现方式,下面进入正题. 事务 考虑这样一个场景:博主向硝子 ...
- Elasticsearch提示low disk watermark [85%] exceeded on [UTyrLH40Q9uIzHzX-yMFXg][Sonofelice][/Users/baid...
mac本地启动es之后发现运行一段时间一分钟就能打印好几条info日志: [2018-03-13T10:15:42,497][INFO ][o.e.c.r.a.DiskThresholdMonitor ...
- 运维、监控、AIOps的几个重要观点
监控是整个运维乃至整个产品生命周期中最重要的一环,通过配置合理的告警机制,采集准确的监控指标,来提前或者尽早发现问题,解决问题,进而保证产品的稳定,提升用户的体验.『分布式实验室』特约记者艾尔斯兰(下 ...