【Spring】手写Spring MVC
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。
项目码云路径
参考资料:
SpringMVC框架介绍
【Spring】手写Spring MVC的更多相关文章
- 一个老程序员是如何手写Spring MVC的
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...
- 手写Spring MVC
闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
- 我是这样手写 Spring 的(麻雀虽小五脏俱全)
人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...
- 手写 Spring
手写 Spring 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背下来 1)配置阶段 配置 web.xml: XDispatchServlet 设定 init ...
- 手写Spring框架,加深对Spring工作机制的理解!
在我们的日常工作中,经常会用到Spring.Spring Boot.Spring Cloud.Struts.Mybatis.Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越 ...
- 手写Spring+demo+思路
我在学习Spring的时候,感觉Spring是很难的,通过学习后,发现Spring没有那么难,只有你去学习了,你才会发现,你才会进步 1.手写Spring思路: 分为配置.初始化.运行三个阶段如下图 ...
- 我是这样手写Spring的,麻雀虽小五脏俱全
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...
- 手写spring
体系结构 Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入.下面的部分对在 Spring 框架中所有可用的 ...
随机推荐
- Vs2017 xaramin mac build agent部署后记
换了四种黑苹果,最终成功了 步骤: 1.升级vs2017, 2.安装XCODE 8.3 3.安装vs2017 for mac 企业版 4 ...
- 大湾区联动:广州深圳助力东莞.NET俱乐部首次线下活动
新年伊始,经过一个寒冬考验后的.NET社区热情不减,长沙.南京.合肥.东莞先后建立以微信为主要平台的线上.NET社区.并相继开始筹划和组织各地区的首次线下活动.东莞作为粤港澳大湾区的腹地,制造业基地, ...
- DVWA 黑客攻防演练(二)暴力破解 Brute Froce
暴力破解,简称"爆破".不要以为没人会对一些小站爆破.实现上我以前用 wordpress 搭建一个博客开始就有人对我的站点进行爆破.这是装了 WordfenceWAF 插件后的统计 ...
- MySQL 处理海量数据时的一些优化查询速度方法
查询速度慢的原因 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O 吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存不足 5.网络速度慢 6 ...
- Linux & Windows 环境下 Redis 安装与基本配置
索引: 目录索引 参看代码 GitHub: redis.txt 一.Linux (DeepinOS) 环境 .安装Redis服务 sudo apt-get install redis-server . ...
- 【公众号系列】超详细SAP HANA JOB全解析
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[公众号系列]超详细SAP HANA JOB全解 ...
- 宋宝华:关于Ftrace的一个完整案例【转】
Ftrace简介 Ftrace是Linux进行代码级实践分析最有效的工具之一,比如我们进行一个系统调用,出来的时间过长,我们想知道时间花哪里去了,利用Ftrace就可以追踪到一级级的时间分布. Ftr ...
- webstorm 的 .后缀名-tab快捷键
if (key) {}//key.if tab if (!key) {}//key.else tab if (key != null) {}//key.notnull tab if (typeof k ...
- 英语口语练习系列-C15-心情不好
单词 1. artist [ˈɑ:tɪst] n. 艺术家 a great artist 一名伟大的艺术家 a Chinese artist 一名中国艺术家 2. beef [bi:f] n. 牛肉 ...
- java程序启动 环境属性的获取
System.getProperties().list(System.out); 如果要获取某一个属性,例如常见的“操作系统” 则 System.getProperty("os.name& ...