文章目录
自己实现SpringMVC 底层机制[一]
搭建SpringMVC 底层机制开发环境
-
创建Maven 项目
-
对Myspringmvc 进行配置: 修改my-springmvc\pom.xml , 将1.7修改成1.8.
<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>
- 对my-springmvc 进行配置: 创建main 和test 相关的源码目录和资源目录和测试目录
- 引入需要的基本的jar 包, 修改my-springmvc\pom.xml
<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本身是有servlet的jar, 到时直接使用tomcat本身的servlet-api.jar,防止版本冲突
-->
<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>
自己实现SpringMVC 底层机制【核心分发控制器+ Controller 和Service 注入容器+ 对象自动装配+ 控制器方法获取参数+ 视图解析+ 返回JSON 格式数据】
实现任务阶段1- 开发myDispatcherServlet
编写myDispatcherServlet 充当原生的DispatcherServlet(即核心控制器)
- 创建src\main\java\com\myspringmvc\servlet\myDispatcherServlet.java
public class myDispatcherServlet extends HttpServlet {
@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);
}
}
- 创建src\main\resources\myspringmvc.xml, 充当原生的applicationContext-mvc.xml 文件
(就是spring 的容器配置文件, 比如指定要扫描哪些包下的类), 先创建个空的文件
-
修改src\main\webapp\WEB-INF\web.xml, 完成myDispatcherServlet 的配置
<!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> <!--配置myDispatcherServlet, 作为我们自己的前端控制器--> <servlet> <servlet-name>myDispatcherServlet</servlet-name> <servlet-class>com.myspringmvc.servlet.myDispatcherServlet</servlet-class> <!--给myDispatcherServlet配置参数,指定要操作的spring容器配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:myspringmvc.xml</param-value> </init-param> <!--myDispatcherServlet在tomcat启动时,就自动加载--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>myDispatcherServlet</servlet-name> <!--因为myDispatcherServlet作为前端控制器,所以需要拦截所有请求,url-pattern配置 /--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
配置Tomcat, 完成测试
修改my-springmvc\src\main\java\com\servlet\myDispatcherServlet.java
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()被调用");
}
}
启动Tomcat,完成测试.
实现任务阶段2- 完成客户端/浏览器可以请求控制层
创建自己的Controller 和自定义注解
创建my-springmvc\src\main\java\com\controller\MonsterController.java
public class MonsterController {
public void listMonsters(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>妖怪列表</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建自定义注解,my-springmvc\src\main\java\com\myspringmvc\annotation\Controller.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
创建自定义注解,my-springmvc\src\main\java\com\myspringmvc\annotation\RequestMapping.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
配置myspringmvc.xml
在该文件指定,我们的springmvc 要扫描的包
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<component-scan base-package="com.controller"></component-scan>
</beans>
编写XMLParser 工具类,可以解析myspringmvc.xml,得到要扫描的包
创建my-springmvc\src\main\java\com\myspringmvc\xml\XMLPaser.java
//XMLParser 用于解析spring配置文件
public class XMLParser {
public static String getBasePackage(String xmlFile) {
SAXReader saxReader = new SAXReader();
//通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]
InputStream inputStream = XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到xmlFile文档
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 "";
}
}
创建my-springmvc\src\test\java\com\test\mySpringMvcTest.java 完成测试
public class mySpringMvcTest {
@Test
public void readXML() {
String basePackage = XMLPaser.getbasePackage("myspringmvc.xml");
System.out.println("basePackage= " + basePackage);
}
}
开发myWebApplicationContext,充当Spring 容器-得到扫描类的全路径列表
即把指定的目录包括子目录下的java 类的全路径扫描到集合中,比如ArrayList。
扫描后的classFullPathList=
[com.controller.MonsterController,
com.service.impl.MonsterServiceImpl,
com.service.MonsterService]
创建my-springmvc\src\main\java\com\myspringmvc\context\myWebApplicationContext.java
public class myWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList<>();
// 初始化自定义的spring 容器, 目标就是把要@Controller 等等初始化到容器中
public void init() {
//这里是写的固定的spring容器配置文件=>做活
//String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackage => com.controller,com.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
//测试
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
}
//创建方法,完成对包的扫描
//@param pack 表示要扫描的包,比如"com.controller"
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.controller" => url 是 D:\my-springmvc\target\my-springmvc\WEB-INF\classes\com\controller
//细节说明: 1. 不要直接使用Junit测试, 否则 url null
// 2. 启动tomcat来测试
URL url =
this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
//System.out.println("url=" + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
} else {
//说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath =
pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
修改com\myspringmvc\servlet\MyWebApplicationContext.java , 增加
@Override
public void init(ServletConfig config) throws ServletException {
MyWebApplicationContext myWebApplicationContext = new myWebApplicationContext();
myWebApplicationContext.init();
}
启动Tomcat 完成测试,看看扫描是否成功. 需要使用Tomcat 启动方式完成测试,直接用Junit 测试URL 是null
完善myWebApplicationContext,充当Spring 容器-实例化对象到容器中。
将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service…), 反射注入到ioc 容器。
扫描后的ioc={monsterController=com.controller.MonsterController@690aac1e}。
修改com\myspringmvc\context\MyWebApplicationContext.java
public class MyWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList<>();
//ioc 用于存放反射后的bean 对象.
private ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
// 初始化自定义的spring 容器, 目标就是把要@Controller 等等初始化到容器中
public void init() {
//这里是写的固定的spring容器配置文件.?=>做活
//String basePackage = XMLParser.getBasePackage("Myspringmvc.xml");
String basePackage =
XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackage => com.controller,com.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ico容器
executeInstance();
System.out.println("扫描后的 ioc容器= " + ioc);
//完成注入的bean对象,的属性的装配
executeAutoWired();
System.out.println("装配后 ioc容器= " + ioc);
}
//创建方法,完成对包的扫描
/**
* @param pack 表示要扫描的包,比如"com.controller"
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.controller" => url 是 D:\My-springmvc\target\My-springmvc\WEB-INF\classes\com\controller
//细节说明: 1. 不要直接使用Junit测试, 否则 url null
// 2. 启动tomcat来吃测试
URL url = this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
//System.out.println("url=" + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
} else {
//说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath =
pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
完成测试(提示: 启动tomcat 来加载MyDispatcherServlet 的方式测试)
完成请求URL 和控制器方法的映射关系
示意图分析
-将配置的@RequestMapping 的url 和对应的控制器-方法映射关系保存到集合中
如图
handlerList= [MyHandler{url='/monster/list',
controller=com.controller.MonsterController@714dab1, method=public void
com.controller.MonsterController.listMonsters(javax.servlet.http.HttpServletReques
t,javax.servlet.http.HttpServletResponse)}]
创建src\main\java\com\myspringmvc\handler\MyHandler.java
//MyHandler 对象记录请求的 url 和 控制器方法映射关系
public class MyHandler {
private String url;
private Object controller;
private Method method;
public MyHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "MyHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
修改src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java
public class MyDispatcherServlet extends HttpServlet {
//存放url 和控制器-方法的映射关系
private ArrayList<MyHandler> handlerList =
new ArrayList<>();
private MyWebApplicationContext myWebApplicationContext;
@Override
public void init(ServletConfig config) throws ServletException {
myWebApplicationContext = new MyWebApplicationContext();
myWebApplicationContext.init();
//调用initHandlerMapping()
搞定完成控制器层url---> Controller ---> 方法的映射关系
initHandlerMapping();
//测试(启动Tomcat 装载MyDispatcherServlet 对象方式来测试)
System.out.println("handlerList= " + handlerList);
}
@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()被调用");
}
/**
* 1. 完成控制器层url---> Controller ---> 方法的映射关系(该关系封装到 MyHandler 对象)
* 2. 并放入到handlerList 集合中
* 3. 后面我们可以通过handlerList 结合找到某个url 请求对应的控制器的方法(!!!)
*/
private void initHandlerMapping() {
if(myWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for(Map.Entry<String,Object> entry:
myWebApplicationContext.ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if(clazz.isAnnotationPresent(Controller.class)) {
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
String url = requestMappingAnnotation.value();
handlerList.add(new MyHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
}
完成测试(启动Tomcat , 加载MyDispatcherServlet 方式), 注意看后台输出。
完成MyDispatcherServlet 分发请求到对应控制器方法
当用户发出请求,根据用户请求url 找到对应的控制器-方法, 并反射调用。
如果用户请求的路径不存在,返回404。
修改com\myspringmvc\servlet\MyDispatcherServlet.java
/**
* 1. MyDispatcherServlet充当原生DispatcherServlet
* 2. 本质是一个Servlet, 继承HttpServlet
*/
public class MyDispatcherServlet extends HttpServlet {
//定义属性 handlerList , 保存MyHandler[url和控制器方法的映射]
private List<MyHandler> handlerList =
new ArrayList<>();
//定义属性myWebApplicationContext,自己的spring容器
MyWebApplicationContextmyWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取到web.xml中的 contextConfigLocation
/*
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:myspringmvc.xml</param-value>
</init-param>
*/
String configLocation =
servletConfig.getInitParameter("contextConfigLocation");
//创建自己的spring容器
myWebApplicationContext =
new MyWebApplicationContext(configLocation);
myWebApplicationContext.init();
//调用 initHandlerMapping , 完成url和控制器方法的映射
initHandlerMapping();
//输出handlerList
System.out.println("handlerList初始化的结果= " + handlerList);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println("--MyDispatcherServlet--doPost---");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
//编写方法,完成url 和 控制器方法的映射
private void initHandlerMapping() {
if (myWebApplicationContext.ioc.isEmpty()) {
//判断当前的ioc容器是否为null
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
//java基础 map的遍历
for (Map.Entry<String, Object> entry :myWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object的clazz对象
Class<?> clazz = entry.getValue().getClass();
//如果注入的Bean是Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//取出它的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
//遍历方法
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有@RequestMapping
if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping值->就是映射路径
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
//这里可以把工程路径+url
//getServletContext().getContextPath()
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
//创建MyHandler对象->就是一个映射关系
MyHandlermyHandler = new MyHandler(url, entry.getValue(), declaredMethod);
//放入到handlerList
handlerList.add(myHandler);
}
}
}
}
}
}
完成测试(启动Tomcat)。
注意
因为getRequestURL返回的是主机后的所有内容,为了和handlermapping存放的URL一致,需要把tomcat配置的application context修改成 /,
否则匹配不上,因为按照原来的得到的是my_springmvc/monster/list.
增加方法和控制器
创建my-springmvc\src\main\java\com\controller\OrderController.java
@Controller
public class OrderController {
@RequestMapping(value = "/order/list")
public void listOrder(HttpServletRequest request, HttpServletResponse response) {
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>订单列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/order/add")
public void addOrder(HttpServletRequest request, HttpServletResponse response) {
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>添加订单...</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}