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<String,Object> iocContainer = new HashMap<String,Object>();
//保存handler映射
private Map<String,Method> handlerMapping = new HashMap<String,Method>();
//自定视图解析器
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<String> 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<String> getClassNames(String packageName){
List<String> classNameList = new ArrayList<String>();
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<String,String> propertyMap = new HashMap<String,String>();
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