跟我一起造轮子 手写springmvc
原创地址:https://www.cnblogs.com/xrog/p/9820168.html
作为java程序员,项目中使用到的主流框架多多少少和spring有关联,在面试的过程难免会问一些spring springmvc spring boot的东西,比如设计模式的使用、 怎么实现springioc 怎么实现springmvc诸如此类的问题,今天我们就来探寻spring mvc的实现,然后自己实现一个简单的spring mvc
一. 了解spring mvc的基本运行流程
ps: 网上一大堆关于springmvc的详细讲解,在这里就不累赘了
小结:spring mvc的核心是DispatcherServlet,DispatcherServlet继承于HttpServlet,可以说spring mvc是基于Servlet的一个实现,DispatcherServlet负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。
二. 梳理简单SpringMVC的设计思路
1. 初始化容器
1.1 读取配置文件
1.1.1.加载配置文件信息到DispatcherServlet
1.2 根据配置扫描包、初始化容器和组件
1.2.1.根据配置信息递归扫描包
1.2.2.把包下的类实例化 并且扫描注解
1.2.3.根据类的方法和注解,初始化HandlerMapping
2. 处理业务请求
2.1 处理请求业务
2.2.1 首先拿到请求URI
2.2.2 根据URI,在HandlerMapping中查找和URI对应的Handler
2.2.3 根据Handler里面的method中的参数名称和http中的请求参数匹配,填充method参数,反射调用
三. 没时间解释了,快上车
ps :环境基于maven idea tomat(端口8080) servlet
1.搭建一个基本web项目,并导入idea配置servlet 和javassist pom依赖 如下
创建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0
pom依赖
<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.adminkk</groupId>
<artifactId>adminkk-mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>war</packaging> <name>adminkk-mvc</name>
<url>http://maven.apache.org</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency> <!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency> <!-- asm -->
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency> <!-- javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.23.1-GA</version>
</dependency> </dependencies>
</project>
2.创建mvc的注解 Controller RequestMapping 和统一异常处理类、方法参数工具类ParameterNameUtils
package com.adminkk.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller { public String value() default ""; public String description() default "";
}
package com.adminkk.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping { public String value() default ""; public String method() default ""; public String description() default "";
}
package com.adminkk.exception; public final class MvcException extends RuntimeException{ public MvcException() {
super();
} public MvcException(String message) {
super(message);
} }
package com.adminkk.tools; import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo; import java.lang.reflect.Method; public final class ParameterNameUtils { public final static String[] getParameterNamesByJavassist(final Class<?> clazz, final Method method) { ClassPool pool = ClassPool.getDefault();
try {
CtClass ctClass = pool.get(clazz.getName());
CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName()); // 使用javassist的反射方法的参数名
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
.getAttribute(LocalVariableAttribute.tag);
if (attr != null) { String[] rtv = new String[ctMethod.getParameterTypes().length];
int len = ctMethod.getParameterTypes().length;
// 非静态的成员函数的第一个参数是this
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
for (int i = 0; i < len; i++) {
rtv[i] = attr.variableName(i + pos);
}
return rtv;
}
} catch (NotFoundException e) {
System.out.println("获取异常"+ e.getMessage());
}
return new String[0];
} }
3.创建 HandlerMapping类 主要是两个方法 doInit初始化 doService处理请求 相关代码如下
package com.adminkk.handler; import com.adminkk.scan.FileScaner;
import com.adminkk.scan.Scaner;
import com.adminkk.scan.XmlScaner;
import com.adminkk.tools.ParameterNameUtils; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; public final class HandlerMapping { private static final Map<String,Handler> handlerMapping = new HashMap<String, Handler>(); private static final List<Scaner> scaners = new ArrayList<>(2);
static {
scaners.add(new XmlScaner());
scaners.add(new FileScaner());
} public static void scanPackage(String scanUrl) throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException { for (Scaner scaner : scaners) {
scaner.doScane(scanUrl);
} } public static void doInit(String scanUrl) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
scanPackage(scanUrl);
} public static void doService(HttpServletRequest request, HttpServletResponse response) { String requestURI = request.getRequestURI();
System.out.println("请求地址是="+ requestURI);
Handler handler = handlerMapping.get(requestURI);
if(handler == null){
System.out.println("请求地址是="+ requestURI+" 没有配置改路径");
return;
}
Method method = handler.getMethod();
Object instance = handler.getInstance();
response.setCharacterEncoding("UTF-8");
//response.setContentType("application/json; charset=utf-8");
PrintWriter writer = null; try { //这里是简单的解析 可以像springmvc那样解析处理
Map<String, String[]> parameterMap = request.getParameterMap();
String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method);
Object[] parameter = new Object[parameters.length];
if(parameters != null && parameters.length > 0){
for (int i = 0; i < parameters.length; i++) {
final String simpleName = parameters[i];
StringBuilder parameterSb = new StringBuilder();
final String[] parameterStr = parameterMap.get(simpleName);
if(parameterStr != null){
for (int j = 0; j < parameterStr.length; j++) {
parameterSb.append(parameterStr[j]);
}
}
parameter[i] = parameterSb.toString();
}
} writer = response.getWriter();
String result = (String) method.invoke(instance,parameter);
writer.print(result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("请求地址是="+ requestURI+" 执行异常");
writer.print("业务执行异常");
}finally {
writer.flush();
writer.close();
}
} public static Handler addHandlerMapping(String url,Handler handler) {
return handlerMapping.put(url,handler);
} public static Handler getHandlerMapping(String url) {
return handlerMapping.get(url);
} }
扫描包
package com.adminkk.scan; public interface Scaner { void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException;
}
package com.adminkk.scan; import com.adminkk.exception.MvcException;
import com.adminkk.factory.BeanPostProcessor;
import com.adminkk.factory.MvcBeanPostProcessor;
import com.adminkk.factory.ServiceBeanPostProcessor;
import com.adminkk.handler.HandlerMapping;
import javassist.ClassClassPath;
import javassist.ClassPool; import java.io.File;
import java.util.ArrayList;
import java.util.List; public final class FileScaner implements Scaner{ public FileScaner() {
} public static final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
static {
beanPostProcessorList.add(new MvcBeanPostProcessor());
beanPostProcessorList.add(new ServiceBeanPostProcessor());
} @Override
public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
if(scanUrl == null || scanUrl.length() == 0){
throw new MvcException("容器基础扫描路径为空,请检查参数配置");
}
String baseUrl = HandlerMapping.class.getResource("/").getPath();
String codeUrl = scanUrl.replaceAll("\\.", "/");
String path = baseUrl + codeUrl;
File file = new File(path);
if(file == null || !file.exists()){
throw new MvcException("找不到对应扫描路径,请检查参数配置");
}
recursionRedFile(scanUrl,file);
} //递归读取文件
private void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException { if(!file.exists()){
return;
} //读取java文件
if(file.isFile()){ String beanName = scanUrl.replaceAll(".class","");
Class<?> forName = Class.forName(beanName);
//放到Javassist容器里面
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(forName);
pool.insertClassPath(classPath);
if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){
return;
}
Object newInstance = forName.newInstance(); //前置执行
for (int i = 0; i < beanPostProcessorList.size() ; i++) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName);
} //后置执行
for (int i = beanPostProcessorList.size()-1; i > 0 ; i--) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessAfterInitialization(newInstance,beanName);
}
return;
} //文件夹下面的文件都递归处理
if(file.isDirectory()){
File[] files = file.listFiles();
if(files != null && files.length >0){
for (int i = 0; i < files.length; i++) {
File targetFile = files[i];
recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile);
}
}
} }
}
package com.adminkk.scan; public final class XmlScaner implements Scaner{ public XmlScaner() {
} @Override
public void doScane(String scanUrl) {
//可自行扩展
} }
扫描bean
package com.adminkk.factory; import com.adminkk.exception.MvcException; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException; Object postProcessAfterInitialization(Object object, String beanName) throws MvcException;
}
package com.adminkk.factory; import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;
import com.adminkk.exception.MvcException;
import com.adminkk.handler.Handler;
import com.adminkk.handler.HandlerMapping; import java.lang.reflect.Method; public class MvcBeanPostProcessor implements BeanPostProcessor{ //扫描Controller业务
@Override
public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException { Class<?> objectClass = object.getClass();
if(objectClass.getAnnotation(Controller.class) != null){
RequestMapping calssRequestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);
StringBuilder urlSb = new StringBuilder();
if(calssRequestMappingAnnotation != null){
urlSb.append(calssRequestMappingAnnotation.value());
}
Method[] methods = objectClass.getMethods();
if(methods != null && methods.length > 0 ){
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
if(methodAnnotation != null){
String methodValue = methodAnnotation.value();
String url = new StringBuilder().append(urlSb).append(methodValue).toString();
Handler handler = HandlerMapping.getHandlerMapping(url);
if(handler == null){
handler = new Handler();
handler.setMethod(method);
handler.setInstance(object);
HandlerMapping.addHandlerMapping(url,handler);
}else {
throw new MvcException("请求路径"+ url + "已经存在容器中");
}
}
} }
} return object;
} @Override
public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
return null;
}
}
package com.adminkk.factory; import com.adminkk.exception.MvcException; public class ServiceBeanPostProcessor implements BeanPostProcessor { @Override
public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {
//可自行扩展
return null;
} @Override
public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
//可自行扩展
return null;
}
}
5.创建 DispatcherServlet
package com.adminkk.servlet; import com.adminkk.handler.HandlerMapping; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})
public final class DispatcherServlet extends HttpServlet { public static final String BASE_SCAN_URL = "com.adminkk"; //初始化容器
@Override
public void init() throws ServletException {
doInit();
} //处理业务请求
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doService(req,resp);
} private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
try {
HandlerMapping.doService(req,resp);
}catch (Exception e){
e.printStackTrace();
throw new ServletException(e.getMessage());
}
} private void doInit() throws ServletException {
try {
HandlerMapping.doInit(this.BASE_SCAN_URL);
}catch (Exception e){
e.printStackTrace();
throw new ServletException(e.getMessage());
} }
}
好了,目前为止我们就写好了简版的springmvc 下面开始测试
package com.adminkk.controller; import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping; @Controller
@RequestMapping("/mvc")
public class MvcController { @RequestMapping("/index")
public String index(){
return "adminkk-mvc system is running";
} @RequestMapping("/arg")
public String parameter(String argOne, String argTwo){
return "argOne = " + argOne + " argTwo = " + argTwo;
}
}
访问地址 http://localhost:8080/mvc/index
访问地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo
总结:整体实现简单的springmvc,设计上还可以扩展更多,难点在于method 获取方法上的参数名称,由于jdk1.8以前是不支持的,需要借用第三方工具 比如 asm javassist黑科技工具包来帮助实现,spring-core使用的是LocalVariableTableParameterNameDiscoverer底层是调用asm,我们这里使用的是javassist。延用这套思路还可以和spring项目结合,写一个 基于spring的springmvc项目
源代码 : https://gitee.com/chenchenche/mvc
写博客不容易,希望大家多多提建议
下一篇预告 跟我一起造轮子 手写分布式IM系统(上)
跟我一起造轮子 手写springmvc的更多相关文章
- 《四 spring源码》手写springmvc
手写SpringMVC思路 1.web.xml加载 为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...
- Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写(全部用POSIX C实现)
Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写实现了日志.原子操作.哈希字典.红黑树.动态库加载.线程.锁操作.配置文件.os适配层.事件驱动.工作队列.RPC.IPC ...
- (二)springMvc原理和手写springMvc框架
我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)Sp ...
- 手写SpringMVC 框架
手写SpringMVC框架 细嗅蔷薇 心有猛虎 背景:Spring 想必大家都听说过,可能现在更多流行的是Spring Boot 和Spring Cloud 框架:但是SpringMVC 作为一款实现 ...
- 手写SpringMVC框架(三)-------具体方法的实现
续接前文 手写SpringMVC框架(二)结构开发设计 本节我们来开始具体方法的代码实现. doLoadConfig()方法的开发 思路:我们需要将contextConfigLocation路径读取过 ...
- 手写SpringMVC框架(二)-------结构开发设计
续接前文, 手写SpringMVC框架(一)项目搭建 本节我们来开始手写SpringMVC框架的第二阶段:结构开发设计. 新建一个空的springmvc.properties, 里面写我们要扫描的包名 ...
- 纯手写SpringMVC到SpringBoot框架项目实战
引言 Spring Boot其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 通过这种方式,springboot ...
- 手写SpringMVC框架(一)-------项目搭建
SpringMVC处理请求的大致流程: 我们来开始着手手写一个SpringMVC框架. 新建一个springMVC项目,流程参见 SpringMVC框架搭建流程 引入servlet相关的jar包: & ...
- 看看一个老程序员如何手写SpringMVC!
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...
随机推荐
- ACM训练联盟周赛(第一场)
B:Zeratul与Xor 题目描述 Xor(按位异或),对应C++中的“^”运算符. Zeratul给出了一个数列A[n](n≤105),要做q(q≤105)组动作,这些动作包括: 1 a:数列中 ...
- 俩层判断,判断button是否可以点击
描述如下: 当被保人数超过三个人并且input是必填项的时候button是disable为false的 代码如下: //起保日期 $('.pickerfour').on('tap', function ...
- luogu P3157 [CQOI2011]动态逆序对(CDQ分治)
题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序 ...
- Unity C# 设计模式(一)单例模式
动机(Motivation): 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性.以及良好的效率 意图: 保证一个类仅有一个实例,并提供一 ...
- CSU1608: Particle Collider(后缀数组)
Description In the deep universe, there is a beautiful planet named as CS on which scientists have d ...
- 英语音乐---一、Scarborough Fair
英语音乐---一.Scarborough Fair 一.总结 一句话总结:斯卡布罗集市 <斯卡布罗集市>诉说了一个缠绵凄美的爱情故事:一个参军的男青年远离自己相爱的姑娘在战争中不幸遇难,但 ...
- 洛谷P3355 骑士共存问题
题目描述 在一个 n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示.棋盘上某些方格设置了障碍,骑士不得进入 对于给定的 n*n 个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置 ...
- Win7+MSVC2010+PCL1.7.2
我的配置环境是Win7+MSVC2010+PCL1.7.2. 网上关于点云库配置的文章已经很多,这里不做过多的重复,这里只讲一下自己在配置过程中遇到的问题及一些注意事项. K1: 在用Cmake编译时 ...
- express随笔
Express 是node 第三方框架,框架的意义就在于能大大简化程序地开发.看一下Express是怎么简化node程序开发的. 1,用Express写一个hello world 程序,我们来体验一下 ...
- Scrapy中将数据保存至数据库
一.在settings.py文件中配置数据库连接参数 # 数据库连接参数 DB_HOST = '192.168.183.1' DB_PORT = 3306 DB_USER = 'root' DB_PA ...