Spring MVC原理

Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。

完整的Spring MVC处理 流程如下:

SpringMVC接口解释

DispatcherServlet接口:

Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。

HandlerMapping接口:

能够完成客户请求到Controller映射。

Controller接口:

需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。

Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。

从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。

ViewResolver接口:

Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

手写Spring MVC

本次实现没有视图解析内容。主要包括,自动扫描class类、通过解析注解实现bean的实例化、bean之间的依赖注入、通过注解映射路径返回正确的处理方法。

Spring MVC框架主要依赖于Java的反射机制实现。实现原理与上面描述一致。

核心Servlet

工程名MySpringMVC

代码存放servlet包。

DispatcherServlet

package zqq.servlet;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.annotations.EnjoyService;
import zqq.controller.ZqqController; /**
* Servlet implementation class DispatcherServlet
*/
public class DispatcherServlet extends HttpServlet
{
private static final long serialVersionUID = 1L; // 扫描得到的类名集合
List<String> classNames = new ArrayList<String>(); // 存放所有Spring实例的Map
Map<String, Object> beans = new HashMap<String, Object>(); // 存放所有路径映射
Map<String, Object> handlerMap = new HashMap<String, Object>(); /**
* @see HttpServlet#HttpServlet()
*/
public DispatcherServlet()
{
} /**
* @see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException
{
// 1、扫描工程有多少class
doScanPackage("zqq");
// 打印所有class
for (String name : classNames)
{
System.out.println(name);
}
// 2、实例化
doInstance();
for (Map.Entry<String, Object> entry : beans.entrySet())
{
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 3、注入
doIoC(); // 4、请求映射
buildMapping(); for (Map.Entry<String, Object> entry : handlerMap.entrySet())
{
System.out.println(entry.getKey() + ":" + entry.getValue());
}
} /**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doPost(request, response);
} /**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// springmvc /zqq/query
String uri = request.getRequestURI(); String context = request.getContextPath(); // springmvc String path = uri.replace(context, ""); // /zqq/query // 获取映射对应的method
Method method = (Method) handlerMap.get(path); ZqqController instance = (ZqqController) beans.get("/" + path.split("/")[1]); Object args[] = this.hand(request, response, method); try
{
method.invoke(instance, args);
} catch (IllegalAccessException e)
{
e.printStackTrace();
} catch (IllegalArgumentException e)
{
e.printStackTrace();
} catch (InvocationTargetException e)
{
e.printStackTrace();
} } /**
* @author qqz
* @date 2018年7月12日 上午1:02:44 扫描当前路径下有多少个class类
* @param string
*/
private void doScanPackage(String basePackage)
{
// URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
String filepath = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/")).getFile();
try
{
filepath= java.net.URLDecoder.decode(filepath,"utf-8");
} catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
// String fileStr = url.getFile(); // 目录对象
File file = new File(filepath);
String[] filesStr = file.list(); // 递归处理路径basepackage下的类文件
for (String path : filesStr)
{
File filePath = new File(filepath + path);
if (filePath.isDirectory())
{
doScanPackage(basePackage + "." + path);
} else
{
// 得到class 全类名路径 zqq.controller.ZqqController
classNames.add(basePackage + "." + filePath.getName());
}
}
} /**
* @author qqz
* @date 2018年7月12日 上午1:11:04 TODO
*/
private void doInstance()
{
if (classNames.size() <= 0)
{
System.out.println("scan classes failed!");
} for (String className : classNames)
{
// 去掉.class后缀
String cn = className.replace(".class", ""); try
{
Class<?> clazz = Class.forName(cn);
// 处理带有EnjoyController注解的类
if (clazz.isAnnotationPresent(EnjoyController.class))
{
// 实例化对象
Object instance = clazz.newInstance();
EnjoyRequestMapping reqMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
String key = reqMapping.value();
beans.put(key, instance);
} else if (clazz.isAnnotationPresent(EnjoyService.class))
{
// 实例化对象
Object instance = clazz.newInstance();
EnjoyService service = clazz.getAnnotation(EnjoyService.class);
String key = service.value();
beans.put(key, instance);
} else
{
continue;
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}
} /**
* @author qqz 属性注入
* @date 2018年7月12日 上午1:21:10 TODO
*/
private void doIoC()
{
if (beans.entrySet().size() <= 0)
{
System.out.println("instance bean failed.");
return;
} for (Map.Entry<String, Object> entry : beans.entrySet())
{
Object instance = entry.getValue();
Class<?> clazz = instance.getClass(); if (clazz.isAnnotationPresent(EnjoyController.class))
{
// 获取类中所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields)
{
// 获取声明注入的属性
if (field.isAnnotationPresent(EnjoyAuthowired.class))
{
EnjoyAuthowired authowired = field.getAnnotation(EnjoyAuthowired.class);
// 获取注解EnjoyAutowired中命名的值
String value = authowired.value();
// 放开权限设置属性值
field.setAccessible(true);
try
{
field.set(instance, beans.get(value));
} catch (IllegalArgumentException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
} else
{
continue;
}
}
} }
} /**
* @author qqz
* @date 2018年7月12日 上午1:32:25 TODO
*/
private void buildMapping()
{
if (beans.entrySet().size() <= 0)
{
System.out.println("instance bean failed.");
return;
} for (Map.Entry<String, Object> entry : beans.entrySet())
{
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();
// 映射是在Controller层
if (clazz.isAnnotationPresent(EnjoyController.class))
{
// 获取类映射
EnjoyRequestMapping requestMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
String classPath = requestMapping.value(); // 获取方法上的映射
Method[] methods = clazz.getMethods(); for (Method method : methods)
{
if (method.isAnnotationPresent(EnjoyRequestMapping.class))
{
EnjoyRequestMapping requestMapping1 = method.getAnnotation(EnjoyRequestMapping.class); String methodPath = requestMapping1.value(); // 构建方法路径与方法的映射
handlerMap.put(classPath + methodPath, method); } else
{
continue;
}
} }
}
} /**
* @author qqz
* @date 2018年7月12日 上午1:59:48
* 方法参数注解解析
* @param request
* @param response
* @param method
* @return
*/
private static Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method)
{
// 拿到当前执行的方法有哪些参数
Class<?>[] paramClazzs = method.getParameterTypes(); // 根据参数的个数,new 一个参数的数组, 将方法里所有参数赋值到args来
Object[] args = new Object[paramClazzs.length]; int arg_i = 0;
int index = 0;
for (Class<?> paramClazz : paramClazzs)
{
if (ServletRequest.class.isAssignableFrom(paramClazz))
{
args[arg_i++] = request;
} if (ServletResponse.class.isAssignableFrom(paramClazz))
{
args[arg_i++] = response;
} // 从0-3判断有没有RequestParam注解,很明显paramClazz为0和1时,不是,当为2和3时为@RequestParam,需要
// 解析[@zqq.annotation.EnjoyRequestParam(value=name)]
Annotation[] paramAns = method.getParameterAnnotations()[index];
if (paramAns.length > 0)
{
for (Annotation paramAn : paramAns)
{
if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass()))
{
EnjoyRequestParam rp = (EnjoyRequestParam)paramAn;
//找到注解里的name和age
args[arg_i++] = request.getParameter(rp.value());
}
}
}
index ++;
}
return args;
}
}

注解定义

代码存放的包annotations中。包括如下几个

EnjoyAuthowired.java

EnjoyController.java

EnjoyRequestMapping.java

EnjoyRequestParam.java

EnjoyService.java

属性注解EnjoyAuthowired.java

package zqq.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EnjoyAuthowired
{
String value() default "";
}

Controller注解EnjoyController.java

package zqq.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnjoyController
{
String value() default "";
}

映射注解EnjoyRequestMapping.java

package zqq.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface EnjoyRequestMapping
{
String value() default "";
}

参数注解EnjoyRequestParam.java

package zqq.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface EnjoyRequestParam
{
String value() default "";
}

Service注解EnjoyService.java


package zqq.annotations; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnjoyService
{
String value() default "";
}

控制层

代码存放controller包。

/**
* ZqqController.java
*/
package zqq.controller; import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.service.ZqqService; @EnjoyController
@EnjoyRequestMapping("/zqq")
public class ZqqController
{
@EnjoyAuthowired("ZqqServiceImpl")
private ZqqService zqqService; @EnjoyRequestMapping("/query")
public void query(HttpServletRequest req, HttpServletResponse resp, @EnjoyRequestParam("name") String name,
@EnjoyRequestParam("age") String age)
{
PrintWriter pw;
try
{
pw = resp.getWriter();
String result = zqqService.query(name, age);
pw.write(result);
} catch (IOException e)
{
e.printStackTrace();
} }
}

Service层

代码存放service包

/**
* ZqqService.java
*/
package zqq.service;
public interface ZqqService
{
String query(String name,String age);
}

Service实现类存放service/impl

/**
* ZqqServiceImpl.java
*/
package zqq.service.impl; import zqq.annotations.EnjoyService;
import zqq.service.ZqqService; @EnjoyService("ZqqServiceImpl")
public class ZqqServiceImpl implements ZqqService
{ /*
* (non-Javadoc)
*
* @see zqq.service.ZqqService#query(java.lang.String, java.lang.String)
*/
@Override
public String query(String name, String age)
{
return "{name:" + name + ",age:" + age + "}";
} }

web层

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>DispatcherServlet</servlet-name>
<display-name>DispatcherServlet</display-name>
<description></description>
<servlet-class>zqq.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

使用

部署后访问localhost:8080/MySpringMVC/zqq/query?name=zqq&age=18

可以在页面上看到请求中的name和age。

项目码云路径

MySpringMVC

参考资料:

SpringMVC框架介绍

【Spring】手写Spring MVC的更多相关文章

  1. 一个老程序员是如何手写Spring MVC的

    人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...

  2. 手写Spring MVC

    闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...

  3. Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)

    前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...

  4. 我是这样手写 Spring 的(麻雀虽小五脏俱全)

    人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...

  5. 手写 Spring

    手写 Spring 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背下来 1)配置阶段 配置 web.xml: XDispatchServlet 设定 init ...

  6. 手写Spring框架,加深对Spring工作机制的理解!

    在我们的日常工作中,经常会用到Spring.Spring Boot.Spring Cloud.Struts.Mybatis.Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越 ...

  7. 手写Spring+demo+思路

    我在学习Spring的时候,感觉Spring是很难的,通过学习后,发现Spring没有那么难,只有你去学习了,你才会发现,你才会进步 1.手写Spring思路: 分为配置.初始化.运行三个阶段如下图 ...

  8. 我是这样手写Spring的,麻雀虽小五脏俱全

    人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...

  9. 手写spring

    体系结构 Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入.下面的部分对在 Spring 框架中所有可用的 ...

随机推荐

  1. nginx代理天地图做缓存解决跨域问题

    作为一个GISer开发者,天地图是经常在项目中以底图的形式出现,其加载地址如: 天地图矢量:http://t{0-6}.tianditu.com/DataServer?T=vec_w&x={x ...

  2. mysql安装出现问题(The service already exists)

    1.管理员身份运行cmd(系统win10) 2.输入命令cd /d F:\mysql-5.7.19-win32\bin(此为mysql要安装的目录) 3.输入安装命令mysqld install 出现 ...

  3. ffmpeg相关函数整理

    1.av_read_frame() 该函数用于读取具体的音/视频帧数据,从流中读取数据帧到 AVPacket,AVPacket保存仍然是未解码的数据 int av_read_frame(AVForma ...

  4. SQL SERVER-查询爆破sa密码的主机

    drop table if exists #sql create table #sql ( Logdatae ), processinfo ), [text] varchar(max) ) go IN ...

  5. InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised.解决办法

    最近使用requests进行get请求的时候,控制台输出如下错误. InsecureRequestWarning: Unverified HTTPS request is being made. Ad ...

  6. 虚拟机 与 host主机,无法ping通的问题

    这个写的比较简单,先做以下记录 centos虚拟机安装到别的电脑上,因为linux中的程序需要向外有网络互通,所以需要重新设置ip 通过 ifconfig eth4 192.168.0.20  bro ...

  7. Python 位操作运算符

    & 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 (a & b) 输出结果 12 ,二进制解释: 0000 1100 | 按位或运算符:只要对应的二 ...

  8. 爬虫系列---scrapy全栈数据爬取框架(Crawlspider)

    一 简介 crawlspider 是Spider的一个子类,除了继承spider的功能特性外,还派生了自己更加强大的功能. LinkExtractors链接提取器,Rule规则解析器. 二 强大的链接 ...

  9. OpenResty:通过 Lua 扩展 NGINX 实现的可伸缩的 Web 平台

    关于 http://openresty.org/cn/about.html 这个开源 Web 平台主要由章亦春(agentzh)维护.在 2011 年之前曾由淘宝网赞助,在后来的 2012 ~ 201 ...

  10. rtsp 流媒体服务器,播放器

    https://github.com/EasyDSS/EasyPlayer-RTSP-Android EasyPlayer EasyPlayer RTSP Android 播放器是由紫鲸团队开发和维护 ...