前言:
前面的文章我们介绍过如何自己写一个MyBatis框架: 深入底层,仿MyBatis自己写框架
今天我们来写一个SpringMVC框架,相比于写MyBatis框架,SpringMVC要简单一些,只需要xml解析+反射就可以完成,不需要jdk动态代理。
废话不多说,直接开始搂干货。
要自己写框架,必须理解框架的底层原理和运行机制。那么我们首先来回顾SpringMVC的实现原理。
SpringMVC实现原理:
核心组件:
1.DispatcherServlet:前端控制器,是整个流程控制的核心,控制其他组件的执行,统一调度,降低组件之间的耦合性,相当于总指挥。
2.Handler:处理器,完成具体业务逻辑,相当于Servlet或Action。
3.HandlerMapping:DispatcherServlet接收到请求之后,通过HandlerMapping将不同的请求分发到不同的Handler。
4.HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要做一些拦截处理,可以来实现这个接口。
5.HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器设置)。
6.HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单数据的验证,数据类型的转换,将表单数据封装到JavaBean等等,这一系列的操作,都是由HandlerAdapter来完成,DispatcherServlet通过HandlerAdapter执行不同的Handler。
7.ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet。
8.ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析成物理视图,最终将渲染结果响应给客户端。
以上就是SpringMVC的核心组件。那么这些组件之间是如何进行交互的呢?
我们来看SpringMVC的实现流程:
1.客户端请求被DispatcherServlet(前端控制器)接收。
2.根据HandlerMapping映射到Handler。
3.生成Handler和HandlerInterceptor(如果有则生成)。
4.Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet。
5.DispatcherServlet通过HandlerAdapter调用Handler的方法做业务逻辑处理。
6.返回一个ModelAndView对象给DispatcherServlet。
7.DispatcherServlet将获取的ModelAndView对象传给ViewResolver视图解析器,将逻辑视图解析成物理视图View。
8.ViewResolver返回一个View给DispatcherServlet。
9.DispatcherServlet根据View进行视图渲染(将模型数据填充到视图中)。
10.DispatcherServlet将渲染后的视图响应给客户端。
通过以上的分析,大致可以将SpringMVC流程理解如下: 首先需要一个前置控制器DispatcherServlet,作为整个流程的核心,由它去调用其他组件,共同完成业务。主要组件有两个:一是Controller,调用其业务方法Method,执行业务逻辑。二是ViewResolver视图解析器,将业务方法的返回值解析为物理视图+模型数据,返回客户端。
我们自己写框架就按照这个思路来。
初始化工作: 1.根据Spring IOC的思路,需要将参与业务的对象全部创建并保存,供流程调用。所以首先我们需要创建Controller对象,HTTP请求是通过注解找到对应的Controller对象,所以我们需要将所有的Controller与其注解建立关联,很显然,使用key-value结构的Map集合来保存最合适不过了,这样就模拟了IOC容器。
2.Controller的Method也是通过注解与HTTP请求映射的,同样的,我们需要将所有的Method与其注解建立关联,HTTP直接通过注解的值找到对应的Method,这里也用Map集合保存。
3.实例化视图解析器。
初始化工作完成,接下来处理HTTP请求,业务流程如下: 1.DispatcherServlet接收请求,通过映射从IOC容器中获取对应的Controller对象。 2.根据映射获取Controller对象对应的Method。 3.调用Method,获取返回值。 4.将返回值传给视图解析器,返回物理视图。 5.完成页面跳转。
思路捋清楚了,接下来开始写代码,我们需要创建以下类: 1.MyDispatcherServlet:模拟DispatcherServlet。 2.MyController:模拟Controller注解。 3.MyRequestMapping:模拟RequestMapping注解。 4.MyViewResolver:模拟ViewResolver视图解析器。
创建MyDispatcherServlet,init方法完成初始化:
1.将Controller与注解进行关联,保存到iocContainer中。 哪些Controller是需要添加到iocContainer中的? 必须同时满足两点: 1.springmvc.xml中配置扫描的类。 2.类定义处添加了注解。 注意这两点必须同时满足。
代码思路: (1)解析springmvc.xml。 (2)获取component-scan标签配置的包下的所有类。 (3)判断若这些类添加了@MyController注解,则创建实例对象,并且保存到iocContainer。 (4)@MyRequestMapping的值为键,Controller对象为值。
2.将Controller中的Method与注解进行关联,保存到handlerMapping中。 代码思路: (1)遍历iocContainer中的Controller实例对象。 (2)遍历每一个Controller对象的Method。 (3)判断Method是否添加了@MyRequestMapping注解,若添加,则进行映射并保存。 (4)保存到handlerMapping中,@MyRequestMapping的值为键,Method为值。
3.实例化ViewResolver。
代码思路: (1)解析springmvc.xml。 (2)根据bean标签的class属性获取需要实例化的MyViewResolver。 (3)使用反射创建实例化对象,同时获取prefix和suffix属性,以及setter方法。 (4)使用反射调用setter方法给属性赋值,完成MyViewResolver的实例化。
doPost方法处理HTTP请求: 1.解析HTTP,分别得到Controller和Method对应的uri。 2.通过uri分别在iocContainer和handlerMapping中获取对应的Controller以及Method。 3.使用反射调用Method,执行业务方法,获取结果。 4.结果传给MyViewResolver进行解析,返回真正的物理视图(JSP页面)。 5.完成JSP的页面跳转。
代码:
1.创建MyController注解,作用目标为类。
package com.southwind.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
/** * 自定义Controller注解 * @author southwind * */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ''; }
2.创建MyRequestMapping注解,作用目标为类和方法。
package com.southwind.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;
/** * 自定义RequestMapping注解 * @author southwind * */ @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ''; }
3.创建MyDispatcherServlet,核心控制器,init完成初始化工作,doPost处理HTTP请求。
package com.southwind.servlet;
import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.HttpRetryException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map;
import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader;
import com.southwind.annotation.MyController; import com.southwind.annotation.MyRequestMapping; import com.southwind.view.MyViewResolver;
/** * DispatcherServlet * @author southwind * */ public class MyDispatcherServlet extends HttpServlet{
//模拟IOC容器,保存Controller实例对象 private Map iocContainer = new HashMap(); //保存handler映射 private Map handlerMapping = new HashMap(); //自定视图解析器 private MyViewResolver myViewResolver;
@Override public void init(ServletConfig config) throws ServletException { // TODO Auto-generated method stub //扫描Controller,创建实例对象,并存入iocContainer scanController(config); //初始化handler映射 initHandlerMapping(); //加载视图解析器 loadViewResolver(config); }
/** * 扫描Controller * @param config */ public void scanController(ServletConfig config){ SAXReader reader = new SAXReader(); try { //解析springmvc.xml String path = config.getServletContext().getRealPath('')+'\\WEB-INF\\classes\\'+config.getInitParameter('contextConfigLocation'); Document document = reader.read(path); Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element ele = (Element) iter.next(); if(ele.getName().equals('component-scan')){ String packageName = ele.attributeValue('base-package'); //获取base-package包下的所有类名 List list = getClassNames(packageName); for(String str:list){ Class clazz = Class.forName(str); //判断是否有MyController注解 if(clazz.isAnnotationPresent(MyController.class)){ //获取Controller中MyRequestMapping注解的value MyRequestMapping annotation = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class); String value = annotation.value().substring(1); //Controller实例对象存入iocContainer iocContainer.put(value, clazz.newInstance()); } } } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
/** * 获取包下的所有类名 * @param packageName * @return */ public List getClassNames(String packageName){ List classNameList = new ArrayList(); String packagePath = packageName.replace('.', '/'); ClassLoader loader = Thread.currentThread().getContextClassLoader(); URL url = loader.getResource(packagePath); if(url != null){ File file = new File(url.getPath()); File[] childFiles = file.listFiles(); for(File childFile : childFiles){ String className = packageName+'.'+childFile.getName().replace('.class', ''); classNameList.add(className); } } return classNameList; }
/** * 初始化handler映射 */ public void initHandlerMapping(){ for(String str:iocContainer.keySet()){ Class clazz = iocContainer.get(str).getClass(); Method[] methods = clazz.getMethods(); for (Method method : methods) { //判断方式是否添加MyRequestMapping注解 if(method.isAnnotationPresent(MyRequestMapping.class)){ //获取Method中MyRequestMapping注解的value MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class); String value = annotation.value().substring(1); //method存入methodMapping handlerMapping.put(value, method); } } } }
/** * 加载自定义视图解析器 * @param config */ public void loadViewResolver(ServletConfig config){ SAXReader reader = new SAXReader(); try { //解析springmvc.xml String path = config.getServletContext().getRealPath('')+'\\WEB-INF\\classes\\'+config.getInitParameter('contextConfigLocation'); Document document = reader.read(path); Element root = document.getRootElement(); Iterator iter = root.elementIterator(); while(iter.hasNext()){ Element ele = (Element) iter.next(); if(ele.getName().equals('bean')){ String className = ele.attributeValue('class'); Class clazz = Class.forName(className); Object obj = clazz.newInstance(); //获取setter方法 Method prefixMethod = clazz.getMethod('setPrefix', String.class); Method suffixMethod = clazz.getMethod('setSuffix', String.class); Iterator beanIter = ele.elementIterator(); //获取property值 Map propertyMap = new HashMap(); while(beanIter.hasNext()){ Element beanEle = (Element) beanIter.next(); String name = beanEle.attributeValue('name'); String value = beanEle.attributeValue('value'); propertyMap.put(name, value); } for(String str:propertyMap.keySet()){ //反射机制调用setter方法,完成赋值。 if(str.equals('prefix')){ prefixMethod.invoke(obj, propertyMap.get(str)); } if(str.equals('suffix')){ suffixMethod.invoke(obj, propertyMap.get(str)); } } myViewResolver = (MyViewResolver) obj; } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub this.doPost(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub //获取请求 String handlerUri = req.getRequestURI().split('/')[2]; //获取Controller实例 Object obj = iocContainer.get(handlerUri); String methodUri = req.getRequestURI().split('/')[3]; //获取业务方法 Method method = handlerMapping.get(methodUri); try { //反射机制调用业务方法 String value = (String) method.invoke(obj); //视图解析器将逻辑视图转换为物理视图 String result = myViewResolver.jspMapping(value); //页面跳转 req.getRequestDispatcher(result).forward(req, resp); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
4.创建视图解析器MyViewResolver。
package com.southwind.view;
/** * 自定义视图解析器 * @author southwind * */ public class MyViewResolver { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; }
public String jspMapping(String value){ return this.prefix+value+this.suffix; } }
5.创建TestController,处理业务请求。
package com.southwind.controller;
import com.southwind.annotation.MyController; import com.southwind.annotation.MyRequestMapping;
@MyController @MyRequestMapping(value = '/testController') public class TestController {
@MyRequestMapping(value = '/test') public String test(){ System.out.println('执行test相关业务'); return 'index'; }
}
6.测试。
跳转index.jsp,同时控制台打印业务日志,访问成功。
源码:
github
https://github.com/southwind9801/SpringMVCImitate.git
扫描下方二维码,加入Java大联盟
|