首页 » Web前端 » 手写phpmvc框架技巧_从0开始手写一个 SpringMVC 框架向高手进阶

手写phpmvc框架技巧_从0开始手写一个 SpringMVC 框架向高手进阶

duote123 2024-11-14 0

扫一扫用手机浏览

文章目录 [+]

废话不多说,我们进入本日的正题,在Web运用程序设计中,MVC模式已经被广泛利用。
SpringMVC以DispatcherServlet为核心,卖力折衷和组织不同组件以完成要求处理并返回相应的事情,实现了MVC模式。
想要实现自己的SpringMVC框架,须要从以下几点入手:

一、理解SpringMVC运行流程及九大组件

手写phpmvc框架技巧_从0开始手写一个 SpringMVC 框架向高手进阶

二、梳理自己的SpringMVC的设计思路

手写phpmvc框架技巧_从0开始手写一个 SpringMVC 框架向高手进阶
(图片来自网络侵删)

三、实现自己的SpringMVC框架

一、理解SpringMVC运行流程及九大组件

1、SpringMVC的运行流程

⑴ 用户发送要求至前端掌握器DispatcherServlet

⑵ DispatcherServlet收到要求调用HandlerMapping处理器映射器。

⑶ 处理器映射器根据要求url找到详细的处理器,天生处理器工具及处理器拦截器(如果有则天生)一并返回给DispatcherServlet。

⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

⑸ 实行处理器(Controller,也叫后端掌握器)。

⑹ Controller实行完成返回ModelAndView

⑺ HandlerAdapter将controller实行结果ModelAndView返回给DispatcherServlet

⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器

⑼ ViewReslover解析后返回详细View

⑽ DispatcherServlet对View进行渲染视图(即将模型数据添补至视图中)。

⑾ DispatcherServlet响运用户。

从上面可以看出,DispatcherServlet有吸收要求,相应结果,转发等浸染。
有了DispatcherServlet之后,可以减少组件之间的耦合度。

2、SpringMVC的九大组件(ref:【SpringMVC】9大组件概览)

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);//其它组件都是用来干活的。
在干活的过程中难免会涌现问题,出问题后怎么办呢?//这就须要有一个专门的角色对非常情形进行处理,在SpringMVC中便是HandlerExceptionResolver。
initHandlerExceptionResolvers(context);//有的Handler处理完后并没有设置View也没有设置ViewName,这时就须要从request获取ViewName了,//如何从request中获取ViewName便是RequestToViewNameTranslator要做的事情了。
initRequestToViewNameTranslator(context);//ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。
//View是用来渲染页面的,也便是将程序返回的参数填入模板里,天生html(也可能是其它类型)文件。
initViewResolvers(context);//用来管理FlashMap的,FlashMap紧张用在redirect重定向中通报参数。
initFlashMapManager(context); }

二、梳理SpringMVC的设计思路

本文只实现自己的@Controller、@RequestMapping、@RequestParam表明起浸染,别的SpringMVC功能读者可以考试测验自己实现。

1、读取配置

从图中可以看出,SpringMVC实质上是一个Servlet,这个 Servlet 继续自 HttpServlet。
FrameworkServlet卖力初始化SpringMVC的容器,并将Spring容器设置为父容器。
由于本文只是实现SpringMVC,对付Spring容器不做过多讲解(有兴趣同学可以看看我另一篇文章:向spring大佬低头--大量源码流出解析)。

为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。
通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。

2、初始化阶段

在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

加载配置文件扫描用户配置包下面所有的类拿到扫描到的类,通过反射机制,实例化。
并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写初始化HandlerMapping,这里实在便是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

3、运行阶段

每一次要求将会调用doGet或doPost方法,以是统一运行阶段都放在doDispatch方法里处理,它会根据url要求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。
按顺序包括以下功能:

非常的拦截获取要求传入的参数并处理参数通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

三、实现自己的SpringMVC框架

工程文件及目录:

首先,新建一个maven项目,在pom.xml中导入以下依赖:

<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.liugh</groupId> <artifactId>liughMVC</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><java.version>1.8</java.version></properties><dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope></dependency> </dependencies></project>

接着,我们在WEB-INF下创建一个web.xml,如下配置:

<?xml version=\"大众1.0\公众 encoding=\"大众UTF-8\"大众?><web-app xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\公众xmlns=\公众http://java.sun.com/xml/ns/javaee\"大众 xmlns:web=\公众http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\公众xsi:schemaLocation=\"大众http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"大众version=\"大众3.0\"大众><servlet><servlet-name>MySpringMVC</servlet-name><servlet-class>com.liugh.servlet.MyDispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>application.properties</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>MySpringMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

application.properties文件中只是配置要扫描的包到SpringMVC容器中。

scanPackage=com.liugh.core

创建自己的Controller表明,它只能标注在类上面:

package com.liugh.annotation;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;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyController { / 表示给controller注册别名 @return / String value() default \"大众\公众;}

RequestMapping表明,可以在类和方法上:

package com.liugh.annotation;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;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestMapping { / 表示访问该方法的url @return / String value() default \公众\"大众;}

RequestParam表明,只能表明在参数上

package com.liugh.annotation;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;@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestParam { / 表示参数的别名,必填 @return / String value();}

然后创建MyDispatcherServlet这个类,去继续HttpServlet,重写init方法、doGet、doPost方法,以及加上我们第二步剖析时要实现的功能:

package com.liugh.servlet;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.net.URL;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.Properties;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;public class MyDispatcherServlet extends HttpServlet{ private Properties properties = new Properties();private List<String> classNames = new ArrayList<>();private Map<String, Object> ioc = new HashMap<>();private Map<String, Method> handlerMapping = new HashMap<>();private Map<String, Object> controllerMap =new HashMap<>();@Overridepublic void init(ServletConfig config) throws ServletException {//1.加载配置文件doLoadConfig(config.getInitParameter(\公众contextConfigLocation\"大众));//2.初始化所有干系联的类,扫描用户设定的包下面所有的类doScanner(properties.getProperty(\"大众scanPackage\"大众));//3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean) beanName默认是首字母小写doInstance();//4.初始化HandlerMapping(将url和method对应上)initHandlerMapping();}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try {//处理要求doDispatch(req,resp);} catch (Exception e) {resp.getWriter().write(\"大众500!! Server Exception\"大众);}}private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { if(handlerMapping.isEmpty()){return;}String url =req.getRequestURI();String contextPath = req.getContextPath();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(\"大众\\[|\\]\公众, \"大众\公众).replaceAll(\公众,\\s\"大众, \"大众,\"大众); paramValues[i]=value; } } } //利用反射机制来调用try {method.invoke(this.controllerMap.get(url), paramValues);//第一个参数是method所对应的实例 在ioc容器中} catch (Exception e) {e.printStackTrace();}}private void doLoadConfig(String location){//把web.xml中的contextConfigLocation对应value值的文件加载到流里面InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location); try {//用Properties文件加载文件里的内容properties.load(resourceAsStream);} catch (IOException e) {e.printStackTrace();}finally {//关流if(null!=resourceAsStream){try {resourceAsStream.close();} catch (IOException e) {e.printStackTrace();}}}}private void doScanner(String packageName) {//把所有的.更换成/URL url =this.getClass().getClassLoader().getResource(\"大众/\"大众+packageName.replaceAll(\"大众\\.\"大众, \"大众/\公众));File dir = new File(url.getFile());for (File file : dir.listFiles()) { if(file.isDirectory()){//递归读取包doScanner(packageName+\公众.\"大众+file.getName());}else{String className =packageName +\"大众.\"大众 +file.getName().replace(\"大众.class\"大众, \"大众\"大众);classNames.add(className);}}}private void doInstance() {if (classNames.isEmpty()) {return;}for (String className : classNames) {try {//把类搞出来,反射来实例化(只有加@MyController须要实例化)Class<?> clazz =Class.forName(className); if(clazz.isAnnotationPresent(MyController.class)){ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());}else{continue;}} catch (Exception e) {e.printStackTrace();continue;}}}private void initHandlerMapping(){if(ioc.isEmpty()){return;}try {for (Entry<String, Object> entry: ioc.entrySet()) {Class<? extends Object> clazz = entry.getValue().getClass(); if(!clazz.isAnnotationPresent(MyController.class)){continue;}//拼url时,是controller头的url拼上方法上的urlString baseUrl =\公众\公众;if(clazz.isAnnotationPresent(MyRequestMapping.class)){MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);baseUrl=annotation.value();}Method[] methods = clazz.getMethods();for (Method method : methods) { if(!method.isAnnotationPresent(MyRequestMapping.class)){continue;}MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);String url = annotation.value();url =(baseUrl+\"大众/\公众+url).replaceAll(\"大众/+\"大众, \"大众/\"大众);handlerMapping.put(url,method);controllerMap.put(url,clazz.newInstance());System.out.println(url+\"大众,\"大众+method);}}} catch (Exception e) {e.printStackTrace();}}/ 把字符串的首字母小写 @param name @return /private String toLowerFirstWord(String name){ char[] charArray = name.toCharArray();charArray[0] += 32;return String.valueOf(charArray);}}

这里我们就开拓完了自己的SpringMVC,现在我们测试一下:

package com.liugh.core.controller;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;import com.liugh.annotation.MyRequestParam;@MyController@MyRequestMapping(\"大众/test\"大众)public class TestController { @MyRequestMapping(\"大众/doTest\"大众) public void test1(HttpServletRequest request, HttpServletResponse response, @MyRequestParam(\"大众param\"大众) String param){ System.out.println(param); try { response.getWriter().write( \公众doTest method success! param:\"大众+param); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping(\"大众/doTest2\"大众) public void test2(HttpServletRequest request, HttpServletResponse response){ try { response.getWriter().println(\"大众doTest2 method success!\"大众); } catch (IOException e) { e.printStackTrace(); } }}

访问http://localhost:8080/liughMVC/test/doTest?param=liugh如下:

访问一个不存在的试试:

到这里我们就大功告成了!

我自己在腾讯教室上也有讲过一堂手写springMVC的直播分享,有感兴趣的可以来看看

https://pan.baidu.com/s/17v3syshIGQWjCHL0yi73Cg

1,3分钟读懂Spring核心源码;

2,SpringMVC与Spring框架关系;

3,SpringMVC的所有表明定义实战;

4,手写SpringMVC框架实战;

5,Tomcat加载进行测试实战;

6,互动答疑。

如果感兴趣的话可以来找我获取其他的资料 想学习提升自己的私信我【JAVA架构】获取往期Java高等架构资料、源码、条记、视频。
Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技能 记得转发下哦

来自同事James的一篇文章

标签:

相关文章

今日头条算法如何实现个化推荐与精准传播

信息传播方式发生了翻天覆地的变化。今日头条作为国内领先的信息分发平台,凭借其强大的算法推荐系统,吸引了海量用户。今日头条的算法究竟...

Web前端 2025-01-31 阅读1 评论0

今日头条算法关闭之谜内容分发新格局

今日头条作为一款备受瞩目的新闻资讯平台,凭借其独特的算法推荐机制,吸引了大量用户。近期有关今日头条算法关闭的消息引发了广泛关注。本...

Web前端 2025-01-31 阅读1 评论0

今日头条算法智能推荐背后的科技魅力

信息爆炸的时代已经到来。人们每天在互联网上接触到海量的信息,如何从中筛选出有价值的内容,成为了人们关注的焦点。今日头条作为一款智能...

Web前端 2025-01-31 阅读1 评论0

今日头条算法专利申请个化推荐的秘密武器

信息爆炸的时代已经来临。在众多信息中,如何快速找到自己感兴趣的内容成为了一个难题。今日头条作为中国领先的资讯平台,凭借其独特的算法...

Web前端 2025-01-31 阅读1 评论0

今日头条算法机器推荐模式的秘密与挑战

大数据、人工智能等新兴技术的应用已经渗透到我们生活的方方面面。在信息爆炸的时代,人们获取信息的渠道越来越丰富,如何在海量信息中找到...

Web前端 2025-01-31 阅读1 评论0