从0手写实现SpringMVC框架
Posted 四阿哥胤禛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从0手写实现SpringMVC框架相关的知识,希望对你有一定的参考价值。
了解SpringMVC
MVC是什么?
MVC是模型(model)-- 视图(view)-- 控制器(controller)的缩写,它是一个设计模式
Spring是什么?
面向接口的编程思想,解决的是业务逻辑层和其他层的松耦合问题
Spring主要有两个功能为我们的业务对象管理提供了非常便捷的方法
DI(Dependency Injection,依赖注入)
AOP(Aspect Oriented Programming,面向切面编程)
框架优点
轻量级的容器框架,没有侵入性
使用IoC容器更加容易组合对象之间的关系,面向接口编程,降低耦合
Aop可以更加容易的进行功能扩展,遵循(Open Closed Principle,OCP)开闭原则
创建对象默认是单例的,不需要再使用单例模式进行处理
Spring MVC又是什么
Spring MVC是当前最优秀的MVC框架
Spring MVC优点
使用简单,学习成本低
很容易写出性能优秀的程序。单例
非常灵活,扩展性很强
Spring MVC运行流程
SpringMVC运行流程
假如我们在浏览器输入user.do的请求
我们应该都知道Spring MVC底层实质上是Servlet,对Servlet做了各种各样的封装,我们在使用SpringMVC开发时,应该都会注意到如果有web.xml,都需要在里面添加一个DispatcherServlet
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
请求进来之后,匹配url-pattern,最终进入到SpringMVC的DispatcherServlet ,DispatcherServlet 相当于一个中转站,只负责接收请求然后转发给SpringMVC的核心组件去处理,DispatcherServlet 并不对请求做其它处理;启动项目时,除了初始化Spring上下文,还需要初始化SpringMVC的上下文内容,从DispatcherServlet 类中,我们可以发现DispatcherServlet 中初始化springmvc九大组件,其中有一个叫HandlerMapping,我们可以近似的认为这是一个HashMap,里面存放的是我们的请求和Method的映射。当我们被转到HandlerMapping中,这时候就会去查找是否存在与我们请求对应的Method,如果存在的话,则由HandlerMapping;不存在的话,就会去找我们的配置文件中是否配置了默认处理器<mvc:default-servlet-handler />,如果配置了,则会去找目标资源;如果没有配置,则会在控制台报错
No mapping found for HTTP request with URI[/xx/xx] in DispatcherServlet
public class DispatcherServlet extends FrameworkServlet {
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
//用于处理上传请求,处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取 File
initMultipartResolver(context);
//SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源 或者主题的时候
initLocaleResolver(context);
//用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源
//如图片、CSS样式等,SpringMVC的主题也支持国际化
initThemeResolver(context);
//用来查找 Handler的
initHandlerMappings(context);
//从名字上看,这是一个适配器。Servlet需要的处理方法的结构是固定的,都是以request和response为参数的方法
//如何让固定的Servlet处理方法调用灵活的 Handler来进行处理呢?这就是HandlerAdapter要做的事情
initHandlerAdapters(context);
//这个组件是用来对异常情况进行处理
initHandlerExceptionResolvers(context);
//用来从request中获取viewName,在Handler处理完之哦户没有设置view和viewName的情况下
initRequestToViewNameTranslator(context);
//用来渲染页面的,也就是将程序返回的参数填入模板
initViewResolvers(context);
//用来管理 FlashMap的,FlashMap主要用在 redirect重定向中传递参数
initFlashMapManager(context);
}
}
如果找到了HandlerMapping,接下来就会去获取HandlerAdapter对象,当我们想对数据进行转化、数据格式化时,就是在HandlerAdapter对象里面处理的。
接下来调用拦截器的PreHandle方法,再调用目标Handler的目标方法得到ModelAndView对象,之后调用拦截器的PostHandler方法,判断是否存在异常,如果存在异常,则交给HandlerExceptionResolver组件处理异常,得到新的ModelAndView对象,由ViewResolver组件根据ModelAndView对象得到实际的View,然后渲染视图,调用拦截器的afterCompletetion方法
Spring MVC核心组件
SpringMVC中的Servlet一共有三个层:
HttpServletBean
直接继承自Java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的属性
FrameworkServlet
初始化了WebApplicationContext
DispatcherServlet
初始化自身的9个组件(上面已讲过)
设计自己的Spring MVC框架
读取配置
通过web.xml加载我们自己写的MyDispatcherServlet和读取文件
初始化
九大组件只需要实现基本的
加载配置文件
private void doLoadConfig(String initParameter) {
//将web.xml中的contextConfigLocation对应value值的文件加载到流
try (
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(initParameter);
){
properties.load(resourceAsStream);
} catch (Exception e) {
e.printStackTrace();
}
}
注解开发
(ElementType.TYPE)
(RetentionPolicy.RUNTIME)
public Controller {
/**
* controller别名
*/
String value() default "";
}
扫描用户配置包下的类
private void doScanner(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
Arrays.asList(dir.listFiles()).forEach(file -> {
if (file.isDirectory()) {
//递归读取package
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replace(".class", "");
classNames.add(className);
System.out.println("容器扫描到的类有:" + packageName + "." + file.getName());
}
});
}
通过反射机制实例化包下的类,并且放到ioc容器中(Map的键值对 beanName --- bean) beanName默认是首字母小写
private void doInstance() {
if (classNames.isEmpty()) {
return ;
}
classNames.forEach(className -> {
try {
//通过反射实例化
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Controller.class)) {
Controller annotation = clazz.getAnnotation(Controller.class);
String key = annotation.value();
if (!"".equals(key) && key != null) {
ioc.put(key, clazz.newInstance());
} else {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
}
} else if (clazz.isAnnotationPresent(Service.class)) {
Service annotation = clazz.getAnnotation(Service.class);
String key = annotation.value();
if (!"".equals(key) && key != null) {
ioc.put(key, clazz.newInstance());
} else {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
初始化HandlerMapping
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
try {
//存放controller的 url-method
Map<String, Object> url_method = new HashMap<>();
for(Entry<String, Object> entry : ioc.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
String baseUrl = "";
if (clazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for(Method method : methods){
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replaceAll("/+", "/");
handlerMapping.put(url, method);
url_method.put(url, clazz.newInstance());
System.out.println("HandlerMapping:" + url + "," + method);
}
}
ioc.putAll(url_method);
}
} catch (Exception e) {
// TODO: handle exception
}
}
运行
异常拦截
获取请求传入的参数并处理参数
通过初始化好的handlerMapping获取url对应的方法名,反射调用
//转发请求,根据url找到相应的method
public void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (handlerMapping.isEmpty()) {
return ;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
//拼接url并把多个 /替换成一个
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
resp.getWriter().write("404 NOT FOUND");
return ;
}
Method method = this.handlerMapping.get(url);
//获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
//获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
//保存参数值
Object[] paramValues = new Object[parameterTypes.length];
//方法的参数列表
for (int i = 0; i < parameterTypes.length; i++) {
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
paramValues[i] = req;
continue ;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = resp;
continue ;
}
if (requestParam.equals("String")) {
for (Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "");
paramValues[i] = value;
}
}
}
//利用反射机制来调用
try {
method.invoke(this.ioc.get(url), paramValues);
} catch (Exception e) {
e.printStackTrace();
}
}
https://github.com/biaotang/demo-mvc
点个“好看”支持一下鸭
点鸭点鸭点鸭
↓↓↓
以上是关于从0手写实现SpringMVC框架的主要内容,如果未能解决你的问题,请参考以下文章