版权声明:本篇博客大部分代码引用于公众号:java团长,我只是在作者基础上稍微修改一些内容,内容仅供学习与参考

前言:目前mvc框架经过大浪淘沙,由最初的struts1到struts2,到目前的主流框架SpringMvc,并逐渐区域占领市场主流稳定状态,由于其背后强大的Spring家族提供了一系列高可用的组件和服务,SpringMvc在短时间内肯定是无法被超越的。公司里开发的项目第一首先框架就是SpringMvc,在市场上如火如荼,甚嚣尘上。今天我们就来自己手动实现一个简历的阉割版SpringMvc,主要的目的就是体会SpringMvc的设计思路和理念,了解其幕后工作流程,当然需要注意的是本次实现的是简易版,代码不超过500行,所以本篇博客姑且只可领会设计思路,抛砖引玉,不可陷入细枝末节、吹毛求疵的死胡同里

本篇博客的目录

一:springMvc的使用方法

二:项目开发结构

三:简易版springmvc的实现

四:总结

一:springMvc的使用方法

1.1:开发基本过程

springmvc开发过程是典型的mvc模式,首先在xml里配置dispatcherServlet(假如没有用到springboot的话),然后再配置扫描包,注解驱动配置,在代码层面又用Controller上注解@RequestMapping映射请求,请求进入后,方法中用各种注解比如@pathvarible获得url请求参数,再到业务方法中的调用service,service再调用dao层,实现数据库的增删改查的业务性操作,之后再返回视图或者用@responseBoby修饰的返回数据,这已经成为一种典型的调用和开发模式!

1.2:基本原理

首先是xml配置,springmvc需要读取xml里面的配置,然后放在简单的一个集合数据模型里(Map),再然后就是装载用户的配置,扫描基准包,获取用户添加的注解,获取注解信息和配置的参数,再用spring的IOC初始化配置的Bean实例,自动注入类实例(比如contoller的依赖service、dao层mappeer等,等容器启动起来,如果有请求进来,就会按照路径映射对应的controller,再选择方法进行处理完成一系列操作后返回给客户端。

二:项目开发结构

2.1:项目基本结构

这是一个简易版的springmvc,其中包含了基本的使用注解自己实现,和后台的默认控制器DispatcherServlet,还有解析xml的工具,和默认xml配置资源,我们使用的serlvet是3.1版本,采用了对基本servlet进行了二次封装,以下是基本的代码:

2.2:具体的开发过程示例

  1. @MyRequestMapping(name = "/orderApi")
  2. @MyController(name = "order")
  3. public class OrderController {
  4.  
  5. @MyQualifer(name = "orderServiceImpl")
  6. private OrderServiceImpl orderServiceImPl;
  7.  
  8. @MyRequestMapping(name = "/create")
  9. public void create() {
  10. orderServiceImPl.createOrder();
  11. }
  12. }
  1. @MyRepository(name = "orderDao")
    public class OrderDao {
  2.  
  3. public void insert() {
    System.out.println("数据库中插入一条记录");
    }
    }
  1. public interface OrderServiceEx {
  2.  
  3. public void createOrder();
    }
  1. @MyService(name = "orderServiceImpl")
    public class OrderServiceImpl implements OrderServiceEx {
  2.  
  3. @MyQualifer(name = "orderDao")
    private OrderDao orderDao;
  4.  
  5. public void createOrder() {
  6.  
  7. }
    }

三:简易版springmvc的实现

3.1:读取xml配置

解析xml采用的是dom4j的1.6.1版本,目的是模仿springmvc解析用户的配置文件,读取用户配置的扫描包,以下是xmlUtil的源码:

  1. public class XmlUtil {
  2.  
  3. public static String getNodeValue(String nodeName, String xmlPath) {
  4. try {
  5. // 将字符串转为xml
  6. Document document = DocumentHelper.parseText(xmlPath);
  7. // 查找节点
  8. Element element = (Element) document.selectSingleNode(nodeName);
  9. if (element != null) {
  10. return element.getStringValue();
  11. }
  12. } catch (DocumentException e) {
  13. e.printStackTrace();
  14. }
  15. return "";
  16. }
  17. }

3.2:定义注解

3.2.1:模拟Reposity注解

作用就是告诉spring容器将依赖实例化注入到需要的调用类里面,其中用了name这个属性,就是让spring根据名字去寻找对应的bean进行依赖注入。并标识其运行在类上面(无法放在方法上)

  1. @Documented
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface MyRepository {
  5. public String name();
  6. }

3.2.2:模拟controller注解

作用就是告诉spring容器这是一个控制器,并交给spring去实例化,可以运行在类和方法上

  1. @Documented
  2. @Target({ElementType.TYPE,ElementType.METHOD})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface MyController {
  5. public String name();
  6. }

3.2.3:模拟Quanlifer注解

作用于成员变量上,主要的作用是让spring根据配置的bean的名字进行注入:

  1. @Documented
  2. @Target(ElementType.FIELD)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface MyQualifer {
  5. public String name();
  6. }

3.2.4:模拟@service注解

作用于类或者接口上,主要的作用就是标识一个类为service层,并交给spring去初始化!

  1. @Documented
  2. @Target(ElementType.Type)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface MyService {
  5. public String name();
  6. }

3.2.5:MyDispatcherServlet

模拟DispatcherServlet,主要的作用就是作为请求控制器,处理客户端请求,继承自HttpServlet,我使用的版本是3.1.0,因此可以用注解来取得一些参数:

  1. @WebServlet(name = "dispatherServlet", urlPatterns = "/", loadOnStartup = 1)
  2. public class MyDispatcherServlet extends HttpServlet {
  3.  
  4. /**
  5. * 扫描的基准包
  6. */
  7. private String basePackage = "";
  8. /**
  9. * 包名
  10. */
  11. private List<String> packageNames = new ArrayList<>();
  12. /**
  13. * 注解名和类实例
  14. */
  15. private Map<String, Object> instanceMap = new HashMap<String, Object>();
  16. /**
  17. * 包名和注解名
  18. */
  19. private Map<String, String> nameMap = new HashMap<>();
  20. /**
  21. * url和方法
  22. */
  23. private Map<String, Method> urlMethodMap = new HashMap<>();
  24. /**
  25. * Method和包名
  26. */
  27. private Map<Method, String> methodPackageMap = new HashMap<>();
  28. @Override
  29. public void init(ServletConfig config) {
  30. try {
  31. getBasePackageFromConfigXml();
  32. scanBasePackage(basePackage);
  33. instance(packageNames);
  34. springIoc();
  35. handleUrlMethodMap();
  36. } catch (ClassNotFoundException e) {
  37. e.printStackTrace();
  38. } catch (IllegalAccessException e) {
  39. e.printStackTrace();
  40. } catch (InstantiationException e) {
  41. e.printStackTrace();
  42. }
  43. }

这里是一个流程处理类,第一步从xml里面获取用户配置的信息,这里的主要作用就是扫描出配置的包名,第二步就是扫描基准包的路径然后放在一个List中,保存起来。第三步:根据扫描出来的包,获取包里面的类class,然后利用反射获取类上的class信息(不包含方法),然后把注解名和对应的类实例保存在map中,再把包名和注解的参数(这里主要是controller、service等在类上的)保存起来。第四步:模拟spring的IOC注入实例的过程,遍历第三步中的注解名和类实例map,然后利用反射实例化字段!第五步:遍历整个包下的类,然后获取类中的所有方法,再取得MyRequestMapping的注解上的配置的参数,把它存在urlMethodMap中,这里的作用就是把类上的MyRequestingMapping的配置的参数和方法上的注解配置的参数组装起来!最终在dopost方法中处理:

3.2.6:getBasePackFromConfig方法

整个方法的目的就是解析xml,获取基准包,这是我们继续往下的基础,因为只有获取包路径,我们才能扫描到注解,才能知道自己要开发的类在哪个目录

  1. /**
  2. * 从xml中获取basePackage
  3. * @return
  4. */
  5. private void getBasePackageFromConfigXml() {
  6. final String basePackageName = XmlUtil.getNodeValue("scan-package-path", "springmvcConfig.xml");
  7. basePackage=basePackageName;
  8. }

3.2.7:scanBasePackage

扫描基础包:这是一个递归的方法,主要是解析路径,获取目录下的文件,然后添加文件名到packageNames这个list,保存了我们扫描的路径下的所有文件,接下来就是循环遍历这个list,取出其中的class再进一步的处理

  1. /**
  2. * 扫描基础包,保存类路径到list中
  3. * @param basePackage
  4. */
  5. private void scanBasePackage(String basePackage) {
  6. final URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
  7. final File basePackageFile = new File(url.getPath());
  8. System.out.println("扫描到的文件是:" + basePackageFile.getName());
  9. final File[] files = basePackageFile.listFiles();
  10. for (File file : files) {
  11. if (file.isDirectory()) {
  12. scanBasePackage(basePackage + "." + file.getName());
  13. } else if (file.isFile()) {
  14. packageNames.add(basePackage + "." + file.getName().split("\\.")[0]);
  15. }
  16. }
  17. }

3.2.8:instance(packageNames)

该方法的主要目的就是遍历包下面的类,扫描类上面的注解,主要是@MyController、@MyService、@MyRepository,然后利用反射获取类上的注解,取得注解上配置的参数(就是name上的值,在下面的实例中,比如:instanceMap将会存放("order",OrderController.class),nameMap将会存放("test.java.com.wyq","order")

  1. /**
  2. * 初始化
  3. *
  4. * @param packageNames
  5. */
  6. private void instance(List<String> packageNames) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  7.  
  8. if (packageNames.size() < 1) {
  9. return;
  10. }
  11. for (String packageName : packageNames) {
  12. Class<?> fileClass = Class.forName(packageName);
  13. if (fileClass.isAnnotationPresent(MyController.class)) {
  14. MyController myController = fileClass.getAnnotation(MyController.class);
  15. final String controllerName = myController.name();
  16. instanceMap.put(controllerName, fileClass.newInstance());
  17. nameMap.put(packageName, controllerName);
  18. System.out.println("Controller:" + packageName + "name" + controllerName);
  19. } else if (fileClass.isAnnotationPresent(MyService.class)) {
  20. final MyService service = fileClass.getAnnotation(MyService.class);
  21. final String serviceName = service.name();
  22. instanceMap.put(serviceName, fileClass.newInstance());
  23. nameMap.put(packageName, serviceName);
  24. } else if (fileClass.isAnnotationPresent(MyRepository.class)) {
  25. final MyRepository repository = fileClass.getAnnotation(MyRepository.class);
  26. final String repositoryName = repository.name();
  27. instanceMap.put(repositoryName, fileClass.newInstance());
  28. nameMap.put(packageName, repositoryName);
  29. }
  30. }
  31. }

3.2.9:springIoc

springIoc的注入:该方法的目的主要是遍历instanceMap,找到有@MyController、@MyService、@MyRepository中的成员字段上有@Myqualifer的注解,(注:Myqualifer的作用类似于@AutoWired自动注入)然后获取这个注解,利用反射机制,实例化这个成员变量。

  1. /**
  2. * springIOC的注入
  3. *
  4. * @throws IllegalAccessException
  5. */
  6. private void springIoc() throws IllegalAccessException {
  7. for (Map.Entry<String, Object> annotationNameAndInstance : instanceMap.entrySet()) {
  8. final Field[] fields = annotationNameAndInstance.getValue().getClass().getDeclaredFields();
  9. for (Field field : fields) {
  10. if (field.isAnnotationPresent(MyQualifer.class)) {
  11. final String myQualiferName = field.getAnnotation(MyQualifer.class).name();
  12. field.setAccessible(true);
  13. field.set(annotationNameAndInstance.getValue(), instanceMap.get(myQualiferName));
  14. }
  15. }
  16. }
  17. }

3.3.0:handleUrlMethodMap

这个方法的主要作用就是获取@MyRequestMapping中配置的url链接,然后将他们封装到一个methodPackageMap的map中,其首先是取类上面的@MyRequestMapping配置的url,然后再取方法上的@MyRequestMapping配置的url,拼接在一起,作为键,映射其对应的方法,这样当请求进来时,就可以直接根据url匹配到对应的处理的方法。向下面的实例:methodPackageMap将会存放的数据是("/orderApi/create",create(method)),而methodPackageMap将会存放的是(create,"com.wyq")

  1. /**
  2. * 处理请求的url和method
  3. * @throws ClassNotFoundException
  4. */
  5. private void handleUrlMethodMap() throws ClassNotFoundException {
  6.  
  7. if (packageNames.size() < 1) {
  8. return;
  9. }
  10.  
  11. for (String packageName : packageNames) {
  12.  
  13. final Class<?> fileClass = Class.forName(packageName);
  14.  
  15. final Method[] methods = fileClass.getMethods();
  16.  
  17. final StringBuffer baseUrl = new StringBuffer();
  18.  
  19. if (fileClass.isAnnotationPresent(MyRequestMapping.class)) {
  20.  
  21. MyRequestMapping myRequest = (MyRequestMapping) fileClass.getAnnotation(MyRequestMapping.class);
  22. baseUrl.append(myRequest.name());
  23. }
  24.  
  25. for (Method method : methods) {
  26. if (method.isAnnotationPresent(MyRequestMapping.class)) {
  27. final MyRequestMapping myrequest = method.getAnnotation(MyRequestMapping.class);
  28. baseUrl.append(myrequest.name());
  29.  
  30. urlMethodMap.put(baseUrl.toString(), method);
  31. methodPackageMap.put(method, packageName);
  32. }
  33. }
  34. }
  35. }

3.3.1:doPost方法

这个方法的主要的思路就是把上面组装的map数据然后再反向解析,首先就是获取请求的url,然后获取contextPath(项目的地址),比如请求http://172.123.344.122:8080/order/create,那么最终的path就是order/create,再根据请求的url获取对应的处理方法,这里会找到create方法,再根据方法名找到类所在的包路径,再根据包路径获取Controller的名字,再根据Controller的名字获取具体的Controller(用户写的),再利用反射机制调用具体的Controller里的方法,这样从请求进入到解析处理就完成了!

  1. /**
  2. * 具体的处理请求的方法
  3. * @param req
  4. * @param resp
  5. * @throws ServletException
  6. * @throws IOException
  7. */
  8. @Override
  9. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  10.  
  11. final String requestURI = req.getRequestURI();
  12. final String contextPath = req.getContextPath();
  13. final String path = requestURI.replaceAll(contextPath, "");
  14. //根据请求的路径反向获取方法
  15. final Method method = urlMethodMap.get(path);
  16. if (null != method) {
  17. //获取包名
  18. final String packageName = methodPackageMap.get(method);
  19. //根据包名获取到Controller的名字
  20. final String controllerName = nameMap.get(packageName);
  21. //根据controller的名字得到控制器
  22. Object orderController = instanceMap.get(controllerName);
  23. try {
  24. method.setAccessible(true);
  25. method.invoke(orderController);
  26. } catch (IllegalAccessException e) {
  27. e.printStackTrace();
  28. } catch (InvocationTargetException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }

四:总结

本篇博客主要是讲解了简易版SpringMvc的实现,其中主要包括了基本注解定义,还有springmv的具体处理模式,当然真实的springmvc实现很复杂,这里只是冰山一角的简单实现一个功能,不过举一反三,基本思路我们要思考,虽然没有搭建一个完成版springmvc的能力,不过从最简单的实现开始,一步步的走,对我们的编程能力有推波助澜的功效!加油

手动实现一个简易版SpringMvc的更多相关文章

  1. 【react】---手动封装一个简易版的redux

    export let createStore = (reducer)=>{ //定义默认的state let state; //定义默认的action let actionTypes = &qu ...

  2. 【react】---手动封装一个简易版的redux---【巷子】

    export let createStore = (reducer)=>{ //定义默认的state let state = {}; //定义默认的action let actionTypes ...

  3. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  4. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  5. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

  6. .NET CORE学习笔记系列(2)——依赖注入[4]: 创建一个简易版的DI框架[上篇]

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  7. 如何实现一个简易版的 Spring - 如何实现 Setter 注入

    前言 之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了...相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离 ...

  8. 如何实现一个简易版的 Spring - 如何实现 Constructor 注入

    前言 本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功 ...

  9. 如何实现一个简易版的 Spring - 如何实现 @Component 注解

    前言 前面两篇文章(如何实现一个简易版的 Spring - 如何实现 Setter 注入.如何实现一个简易版的 Spring - 如何实现 Constructor 注入)介绍的都是基于 XML 配置文 ...

随机推荐

  1. C++11 TypeList 妙用

    源码展示: #include <iostream> using namespace std; template <typename ... Args> struct typel ...

  2. 微信小程序之注释出现的问题(.json不能注释)

    js的注释一般是双斜杠// 或者是/**/这样的快注释 .json是配置文件,其内容必须符合json格式内部不允许有注释. JSON有两种数据结构: 名称/值对的集合:key : value样式: 值 ...

  3. 如何区别cookie和token?---测试cookie和token接口时先看。

    cookie 是什么? cookie--------------在浏览器中的长相?火狐浏览器 ----------------------------------------------------- ...

  4. JAVA 面试须知

    本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺. 1. Java中的原始数据类型都有哪些, ...

  5. Linux内核设计笔记7——中断

    中断与中断处理 何为中断? 一种由设备发向处理器的电信号 中断不与处理器时钟同步,随时可以发生,内核随时可能因为中断到来而被打断. 每一个中断都有唯一一个数字标志,称之为中断线(IRQ) 异常是由软件 ...

  6. [原创]Docker学习记录: Shipyard+Swarm+Consul+Service Discover 搭建教程

    网上乱七八糟的资料实在是太多了, 乱, 特别乱, 而看书呢, 我读了2本书, 一本叫做<>, 另一本叫做<< Docker进阶与实战>> 在 服务发现这块讲的又不清 ...

  7. Rightmost Digit(最后一位数字)

    Description Given a positive integer N, you should output the most right digit of N^N.    Input The ...

  8. windows编程了解

    文章:浅谈Windows API编程 (这个经典)

  9. freefcw/hustoj Install Guide

    First of all, this version hustoj is a skin and improved for https://code.google.com/p/hustoj/. So t ...

  10. lintcode-179-更新二进制位

    179-更新二进制位 给出两个32位的整数N和M,以及两个二进制位的位置i和j.写一个方法来使得N中的第i到j位等于M(M会是N中从第i为开始到第j位的子串) 注意事项 In the function ...