SpringMVC执行原理以及源码解析

SpringMVC执行原理

一. SpringMVC 执行过程如下图所示
在这里插入图片描述
执行过程如下:
图解:

①:前端控制器接受客户端浏览器的请求。

②:前端控制器调用HandlerMapping查找Handler,HandlderMapping返回一个执行链。

③:前端控制器调用HandlerAdapter。

④:HandlerAdapter执行Handler,并返回一个ModelAndView给前端控制器。

⑤:前端控制器调用视图解析器,将ModelAndView中的逻辑视图(加前缀加后缀)解析成真正的视图,并返回到View。

⑥:对得到的View进行视图渲染:将模型数据填充到request域中。

⑦:前端控制器响应请求,将View返回给客户端。

首先先清楚一点,如何通过传的/hello链接便能够找到自己对应的类并去执行它:在原生的Servlet中这样实现的

<servlet-name>default</servlet-name>
<servlet-class>cn.dglydrpy.study.smartmvc.controller.BizController</servlet-class>
<servlet-name>default</servlet-name>
<url-pattern>/hello</url-pattern>

通过在web.xml中配置servlet-mapping来映射该链接要执行的JAVA类中的某个方法,该类必须继承HttpServlet 类并且去实现doGet()和doPost()方法。过程繁琐复杂. 这种繁琐的工作由SpringMVC去解决,那SpringMVC是怎么解决映射关系的呢?

1.要想的第一点是,我们并没有指定要找的类,MVC是如何通过我们传入的链接找到的呢?

原理是:在web容器启动的时候,便将我们通过访问链接执行的类和方法全部加载到了容器中,这些方法上有和我们传入的链接一样的标识,这样便能定位到具体要执行的方法.代码如下:

public class ContextConfigListener implements ServletContextListener {

public HandlerMapping loadControlers(String xml) throws Exception {
	SAXReader reader = new SAXReader();
	InputStream in = ContextConfigListener.class.getClassLoader().getResourceAsStream(xml);
	Document doc = reader.read(in);
	in.close();
	Element root = doc.getRootElement();//beans
	List<Element> list=root.elements("bean"); 
	HandlerMapping mapping = new HandlerMapping();
	for(Element e:list) {
		String className=e.attributeValue("class");
		mapping.init(className);
	}
	return mapping;
}

public void contextInitialized(ServletContextEvent e)  {
	try {
    	ServletContext ctx = e.getServletContext();
    	HandlerMapping mapping = loadControlers("smartMVC.xml");
    	String path = ctx.getContextPath();
    	ctx.setAttribute("root", path); 
    	ctx.setAttribute("handlerMapping", mapping);
	}catch(Exception ex) {
		ex.printStackTrace();
		throw new RuntimeException(ex);
	}
}

public void contextDestroyed(ServletContextEvent arg0)  {}
}

ServletContextListener它能够监听 Web 应用的生命周期。当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理。里面有两个方法contextInitialized(初始化)和 contextDestroyed (销毁). Servlet容器启动执行初始化方法,这时候需要利用初始化方法将我们需要的Handler加载进去,调用loadControlers()方法,传入用户配置的smartMVC.xml 如下:

<bean class="cn.dglydrpy.study.smartmvc.controller.BizController"></bean>
<bean class="cn.dglydrpy.study.smartmvc.controller.EmpController"></bean>
<bean class="cn.dglydrpy.study.smartmvc.controller.UserController"></bean>
将我们的Controller类(Handler)配置到xml中(相当于@Controller注解),然后解析xml.到了这一步仅是通过解析xml,读取xml的字符串,然后运用反射能够实例化配置的Handler。那么真正执行的方法是怎么标识的呢,来看下一步

2. 如何标识并加载方法的呢?

HandlerMapping mapping = new HandlerMapping();
for(Element e:list) {
	String className=e.attributeValue("class");
	mapping.init(className);
}
return mapping;

上述代码 只是提取了xml的标签内容,真正执行反射和加载在上面这一小段代码中.我们需要将方法加载到HandelMapping映射器,使用了init()方法代码如下:

 public class HandlerMapping {
 
 private Map<String, RequestHandler> handlerMap = new HashMap<>();    
 public void init(String className) throws Exception{
	Class cls = Class.forName(className);
	Method[] methods = cls.getDeclaredMethods();
	Object controller = cls.newInstance();
	for(Method method:methods) {
		RequestMapping ann = method.getAnnotation(RequestMapping.class);
		if(ann!=null) {
			//找到注解上标注的路径
			String path=ann.value();
			//创建请求处理器对象,封装控制器对象和方法
			RequestHandler handler=new RequestHandler(method,controller);
			//请求路径和对应的“请求处理器”添加到map
			handlerMap.put(path, handler);
		}
	}
  }
}

为了方便利用java反射调用方法,我们把方法对象和配置的类(Handler)实例进行封装.

public class RequestHandler {

private Method method;
private Object controller;

public Method getMethod() {
	return method;
}
public void setMethod(Method method) {
	this.method = method;
}
public Object getController() {
	return controller;
}
public void setController(Object controller) {
	this.controller = controller;
}

public RequestHandler(Method method, Object controller) {
	this.method = method;
	this.controller = controller;
}
}

上述便是通过反射实例化类(都是java.lang.reflect中的方法,不懂可查看API文档,不再说明),然后获取方法数组并且循环方法,以此封装到Map集合中。这时候有个疑问,map集合中的value是封装的RequestHandlel类参数是方法和类,那key(path)是啥呢,便是RequestMapping注解上的路径(反射获取),注解代码如下:

  @Retention(RetentionPolicy.RUNTIME) //运行期
  @Target(ElementType.METHOD) //在方法上使用
      public @interface RequestMapping {
          public String value();//默认value属性
  }

不懂可查看JDK 1.8API文档

   @RequestMapping("/hello.do")
    public String hello(){
	      System.out.println("Hello.do");
	       return "hello";//返回视图名
     }

这时候就清晰明了了,注解的内容“/hello.do”作为key , 映射的value便是该注解的方法和类.这样便实现了一一映射关系。当我们启动web服务的时候,这些带有@Controller和@RequestMapping的类和方法便会加载到HandleMapping中,交给MVC来管理,初始化工作结束。

3. 之后的流程就简单了,每次访问链接,链接内容便是作为key,然后查找HandlerMapping中集合的key,如果在,便返回value(要执行的类和方法)。然后利用反射的invoke()方法动态代理去执行真正的方法,然后返回参数(ModelAndView).之后交给视图解析器,去渲染。有时间这些代码再写(因为很好理解了)。

注:很多文档都是写MVC的工作流程,困扰了我很久HandleMapping究竟是个啥,我便从GitHub上扒下了一个模拟MVC执行流程的代码。写下这篇文档希望能够帮助大家理解HandleMapping是怎么工作的.如何通过我们传入的链接便能执行要执行的方法,代码是在GitHub上找的模拟SpringMVC的执行。如果有需要的可以私聊我,我把代码发给你(免费)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值