关键词:
SpringMVC 底层机制的简易实现
项目基础
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--原生 Servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--作用范围-->
<!--provided:表示该项目在打包放在生产环境的时候不用带上 servlet-api.jar 包-->
<!--目的: 防止和 tomcat 中该jar冲突-->
<scope>provided</scope>
</dependency>
<!--引入 xml 解析工具-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--引入常用工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
配置 xml 文件
mySpringMVC.xml 文件:用来指定扫描包路径
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<component-scan base-package="com.al_tair"></component-scan>
</beans>
web.xml 文件
- 指定要操作的 spring 容器配置文件名
- 配置自动加载
- 路径全部访问中央控制器
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>MyDispatcherServlet</servlet-name>
<servlet-class>com.al_tair.servlet.MyDispatcherServlet</servlet-class>
<!--指定要操作的 spring 容器配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mySpringMVC.xml</param-value>
</init-param>
<!--自动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
开发指南
开发步骤
MyDispatcherServlet 本质就是一个 Servlet ,那么我们就知道他的基本特性,刚启动服务的时候,我们会进行初始化,然后每进行 http 请求,就会访问 Service 方法(这里交给 Servet 的父类来实现,我们只需要实现 doPost 和 doGet 的方法)
1.初始化数据
@Override
public void init(ServletConfig config) throws ServletException
// 需要将 config 传给父类,才可以获取工程路径
super.init(config);
// 上述 web.xml 文件中指定了该参数为 Spring 容器的文件名 mySpringMVC.xml
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
// 分成两步骤进行理解
// 1.通过 xml 解析获取到该 mySpringMVC.xml 的包路径
// 2.将该路径进行扫描,生成 ioc 容器
ioc = new SpringIocApplicationContext(XmlParser.getBasePackage(contextConfigLocation.split(":")[1]));
// 初始化映射器
initHandlerMapper();
-
XmlParser 解析器
public class XmlParser public static String getBasePackage(String fileName) // 解析器 SAXReader saxReader = new SAXReader(); // 获取到资源路径 InputStream in = XmlParser.class.getClassLoader().getResourceAsStream(fileName); String basePackage = null; try Document read = saxReader.read(in); // 获取到 component-scan 元素 Element element = read.getRootElement().element("component-scan"); // 扫描包路径 basePackage = element.attribute("base-package").getText(); catch (Exception e) e.printStackTrace(); return basePackage;
-
初始化 ioc 容器(我主要介绍实现过程)
- 获取扫描包的全路径
- 通过类加载器获取到对应包的资源路径
- 扫描资源路径,将 java 文件生成的 .class 文件路径保存到 List 集合中
- 遍历每个 java 文件路径,并进行类加载,通过判断是否有注解(MyController 、MyService、MyMapper、MyComponent)
- 然后通过注解上的值来获取对应 id(默认方法名首字母小写),并划分单例还是多例(用于判断是否当下就创建对象并放入 ioc 容器中)
- ioc 容器用于存储 id <=> BeanDefinition对象(属性有 scope、Class对象)
- singletonObject 单例池用于存储 id <=> 单例对象
-
初始化映射器
private void initHandlerMapper() // 默认所有映射都是非懒加载的单例池中 ConcurrentHashMap<String, Object> singletonObject = ioc.getSingletonObject(); if (singletonObject.isEmpty()) return; else for (Map.Entry<String, Object> entry : singletonObject.entrySet()) // 拿到加载的类对应的 Class 类 Class<?> aClass = entry.getValue().getClass(); // 判断是否是控制类 if (aClass.isAnnotationPresent(MyController.class)) // 1.获取类注解值 String classValue = ""; // 2.获取类上路径地址 if (aClass.isAnnotationPresent(MyRequestMapping.class)) MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class); classValue = annotation.value(); // 3.获取到控制类中的所有方法 Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) if (declaredMethod.isAnnotationPresent(MyRequestMapping.class)) MyRequestMapping MyRequestMappingAnnotation = declaredMethod.getAnnotation(MyRequestMapping.class); // getServletContext().getContextPath() 获取工程路径 String url = getServletContext().getContextPath() + classValue + MyRequestMappingAnnotation.value(); // 创建 HandlerMapper 对象(用于存储 访问的 url、controller 对象、对应的方法) HandlerMapper handlerMapper = new HandlerMapper(url, entry.getValue(), declaredMethod); // HandlerList 集合用来保存每个能够访问到的对应类的对应方法信息 HandlerList.add(handlerMapper);
2.中央控制器 - 分发请求
刚开始讲到了中央控制器本质就是 Servlet,每次请求都会访问 Service,然后分发到对应的请求类型比如 get、post等等
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
// 防止乱码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
// 分发请求
executeDispatch(req, resp);
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
this.doGet(req, resp);
分发请求(同时也会接收返回的结果,这个后续讲解)
<!-- 用于获取请求参数名(默认为 args0、args1等)-->
<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>
private void executeDispatch(HttpServletRequest req, HttpServletResponse resp)
HandlerMapper handler = getHandler(req);
// 判断路径是否匹配成功
if (handler == null)
try
resp.getWriter().print("<h1>404 not found</h1>");
catch (IOException e)
log.print(e.getMessage());
else
// 1.获取请求的方法
Method method = handler.getMethod();
// 2.获取请求方法的参数类型列表
Class<?>[] parameterTypes = method.getParameterTypes();
// 用于匹配对应方法的参数,可以自动化装配
Object[] params = new Object[parameterTypes.length];
// 3.遍历每个参数,并填充数据
for (int i = 0; i < parameterTypes.length; i++)
Class<?> parameterType = parameterTypes[i];
// 4.获取类型的简单名称,就是类名(原来名称全包名)
String simpleName = parameterType.getSimpleName();
if ("HttpServletRequest".equals(simpleName))
params[i] = req;
else if ("HttpServletResponse".equals(simpleName))
params[i] = resp;
// 5.传入的参数的 key - value
Map<String, String[]> parameterMap = req.getParameterMap();
// 6.对应方法参数上的信息
Parameter[] parameters = method.getParameters();
// 7.遍历方法的每个参数并根据参数名或者注解值来填入数据
for (int i = 0; i < params.length; i++)
if(params[i] == null)
boolean isMyRequestParam = parameters[i].isAnnotationPresent(MyRequestParam.class);
// 参数名
String paramName = "";
if(isMyRequestParam)
paramName = parameters[i].getDeclaredAnnotation(MyRequestParam.class).value();
else
// 注意获取参数名必须添加脚本文件在 pom.xml 中
paramName = parameters[i].getName();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet())
if(paramName.equals(entry.getKey()))
// 根据类型来判断是否是数组或者集合等等
if("List".equals(parameterTypes[i].getSimpleName()))
params[i] = Arrays.asList(entry.getValue());
else if(parameterTypes[i].getSimpleName().contains("[]"))
params[i] = entry.getValue();
else
params[i] = entry.getValue()[0];
break;
try
// 匹配成功,通过反射来调用对应的类(返回结果暂时就是默认 String 类型)
Object result = method.invoke(handler.getController(), params);
// 重定向或者请求转发(视图解析操作)
viewParser(req,resp,method,result);
catch (Exception e)
log.print(e.getMessage());
3.开发者角度
开发控制层、服务层、Dao层,用于接口的开发等等…
4.视图解析器
主要用于方法调用完之后接下来的操作(重定向、请求转发、或者返回 json 格式数据等等操作)
<!--引入jackson 使用他的工具类可以进行json操作 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
public void viewParser(HttpServletRequest request,HttpServletResponse response,Method method,Object result)
if(result instanceof String)
// 返回结果: forward:/200_ok.jsp(例子)
String res = (String)result;
if(res.contains(":"))
String[] split = res.split(":");
if("forward".equals(split[0]))
try
request.getRequestDispatcher(split[1]).forward(request,response);
catch (Exception e)
System.out.println("请求转发异常" + e.getMessage());
else if("redirect".equals(split[0]))
try
// 重定向需要获取工程路径
response.sendRedirect(getServletContext().getContextPath() + split[1]);
catch (IOException e)
System.out.println("重定向异常" + e.getMessage());
// 默认请求转发
else
try
request.getRequestDispatcher(res).forward(request,response);
catch (Exception e) 查看详情
手动实现一个简易版springmvc(代码片段)
...经过大浪淘沙,由最初的struts1到struts2,到目前的主流框架SpringMvc,并逐渐区域占领市场主流稳定状态,由于其背后强大的Spring家族提供了一系列高可用的组件和服务,SpringMvc在短时间内肯定是无法被超越的。公司里开发的项目第... 查看详情
synchronized底层实现及优化机制(代码片段)
synchronized底层实现及优化机制前言首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。jdk1.6之前... 查看详情
synchronized底层实现及优化机制(代码片段)
synchronized底层实现及优化机制前言首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。jdk1.6之前... 查看详情
aqs:java中悲观锁的底层实现机制(代码片段)
介绍AQSAQS(AbstractQueuedSynchronizer)是Java并发包中,实现各种同步组件的基础。比如各种锁:ReentrantLock、ReadWriteLock、StampedLock各种线程同步工具类:CountDownLatch、CyclicBarrier、Semaphore线程池中的WorkerLock接口的实现基本都是通过聚合... 查看详情
springmvc--springmvc异常处理机制(代码片段)
1.SpringMVC异常处理机制1.1异常处理的思路1.2异常处理两种方式使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器1.3简单异常处理器SimpleMappingExceptionResolvSpr... 查看详情
用go语言撸一个简易版的区块链(代码片段)
...你了解区块链内部的大概运行机制时没有问题的。比特币底层区块链的代码非常复杂,但是我们可以从中梳理几个核心的概念,然后对应进行简单的实现。通过这些简易版本的实现我们可以以小窥大。下面我们先来梳理... 查看详情
用go语言撸一个简易版的区块链(代码片段)
...你了解区块链内部的大概运行机制时没有问题的。比特币底层区块链的代码非常复杂,但是我们可以从中梳理几个核心的概念,然后对应进行简单的实现。通过这些简易版本的实现我们可以以小窥大。下面我们先来梳理... 查看详情
java并发编程:java并发机制的底层实现原理(代码片段)
1、前言Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。2、volatile应用2.1、简介在多线... 查看详情
java并发编程:java并发机制的底层实现原理(代码片段)
1、前言Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。2、volatile应用2.1、简介在多线... 查看详情
[linux环境]基于thrift模拟游戏的简易匹配机制(代码片段)
第二天: 利用生产者消费者模型实现傻瓜版匹配机制(不按段位和匹配时间)前文回顾: [linux环境]基于thrift模拟游戏的简易匹配机制(一)_☆迷茫狗子的秘密基地☆-CSDN博客thrift官方文档ApacheThrift-Homehttps://thrift.apache.org/我的Git仓库文... 查看详情
springmvc之异常处理机制(代码片段)
HandlerExceptionResolver接口该接口是SpringMVC处理异常的祖接口。下面我们看该类的注释:接口由对象实现,这些对象可以解决在处理程序映射或执行期间抛出的异常,在典型情况下是错误视图。实现者通常在应用程序上下文中... 查看详情
day03-自己实现mybatis底层机制-02(代码片段)
自己实现Mybatis底层机制-027.任务阶段4&5阶段4任务:开发Mapper接口和Mapper.xml阶段5任务:开发和Mapper接口相映射的MapperBean(1)Mapper接口packagecom.li.mapper;importcom.li.entity.Monster;/***@author李*@version1.0*MonsterMapper:声明对数据库的crud方... 查看详情
java集合韩顺平老师底层对比分析(代码片段)
...现子类是双列集合,存放key和value这样的数据 2对比和底层机制1ArrayList和VectorArrayList的底层操作机制源码分析Vector对比ArrayList和LinkedListLinkedList的底层操作机制ArrayList和LinkedLis 查看详情
springmvc异常处理机制,(代码片段)
首先异常处理属于SpringMVC模块,而不属于springcore,有这种意识能帮助我们更好的理解springframework。SpringMVC统一异常处理机制,一共有两种,第一种实现HandlerExceptionResolver接口、第二种在异常处理类上添加@Control... 查看详情
day02-自己实现mybatis底层机制-01(代码片段)
自己实现Mybatis底层机制-01主要实现:封装SqlSession到执行器+Mapper接口和Mapper.xml+MapperBean+动态代理Mapper的方法1.Mybatis整体架构分析对上图的解读:1)mybatis的核心配置文件 mybatis-config.xml:进行全局配置,全局只能有一个这样的... 查看详情
栈的简易实现(代码片段)
栈的简易实现简单实现栈的结构;底层结构采用链表的节点;packagecom.xiaozhi.day04stack;importjava.util.Iterator;/***@authorby@CSDN小智RE0*@date2021-12-26*/publicclassMyStack<T>implementsIterable<T>classListNode//数据域;publicTdata;//下一个节点;pu... 查看详情
spring系列之手写一个springmvc(代码片段)
...AOP和配置文件解析等功能。这一节我们来实现一个自己的springMvc。关于MVC/SpringMVCspringMvc是一个基于mvc模式的 查看详情