spring系列6-springmvc的实现初探

目录

一. springmvc的初始化

1. 根IOC容器初始化

2. DispatcherServlet的IOC容器初始化

3. HandlerMapping类

4. DispatcherServlet的initHandlerMappings方法

二. springmvc的请求处理过程

1. 拦截器

2. 处理器

3. 视图解析器

三. springmvc Demo

参考资料


一. springmvc的初始化

1. 根IOC容器初始化

在web环境中使用ioc容器,需要将ioc容器的初始化与web服务器的启动集成在一起,关键在于web.xml配置文件中的监听器ContextLoaderListener:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
	<!-- web应用的初始化参数 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
	<!-- 监听web应用中ServletContext的创建与销毁 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- servlet配置... -->
</web-app>

如图1所示,随着web服务器的启动,根IOC容器(ROOT WAC)完成了初始化:

2. DispatcherServlet的IOC容器初始化

DispatcherServlet作为一种servlet,同样遵循以下的生命周期:

① 构造阶段(调用1次构造器):load-on-startup参数<0,第一次请求servlet时调用;load-on-startup参数≥0,web应用被servlet容器加载时调用

② 初始化阶段(调用1次init方法):在创建完实例后立即被调用,用于初始化当前servlet

③ 提供服务阶段(调用多次service方法):每次响应请求都会调用service方法

④ 销毁阶段(调用1次destroy方法):当前servlet所在的web应用被卸载前调用,用于释放该servlet所占用的资源

根据图2的DispatcherServlet的UML图,它从父类FrameworkServlet继承了属性:WebApplicationContext webApplicationContext,从祖先类HttpServletBean继承了init方法。DispatcherServlet的IOC容器初始化就是在servlet初始化阶段完成的。

DispatcherServlet的初始化流程:HttpServletBean的init方法 => FrameworkServlet的initServletBean方法 => FrameworkServlet的initWebApplicationContext方法 => DispatcherServlet的initStrategies方法。initWebApplicationContext方法完成了与图1相似的流程:通过反射创建IOC容器、wac记录servletContext、wac.refresh()、servletContext将wac记录到attribute。DispatcherServlet的initStrategies方法如下:

protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
    //重点关注initHandlerMappings方法
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

3. HandlerMapping类

在分析initHandlerMappings方法之前,先从图3了解一下什么是HandlerMapping。

ps:UML类图中不同线型表示的关系可以参考https://www.cnblogs.com/shindo/p/5579191.html

根据图3,得到如下信息:

① DispatcherServlet持有多个HandlerMapping;

② HandlerMapping有个方法getHandler(HttpServletRequest)返回HandlerExecutionChain,而HandlerExecutionChain有2个重要属性:Object handler、HandlerInterceptor[] interceptors。

书中在提到HandlerExecutionChain时,直接指出:handler属性就是业务代码中的controller。在目前主流应用场景中,handler属性应该是controller中标注@RequestMapping的方法。DispatcherServlet就是通过interceptors(拦截器链)和handler实现业务逻辑的

4. DispatcherServlet的initHandlerMappings方法

initHandlerMappings方法是为DispatcherServlet的handlerMappings属性赋值,而值的来源:在IOC容器中搜索HandlerMapping类型的bean,如果未搜到,则根据配置文件创建默认的BeanNameUrlHandlerMapping、RequestMappingHandlerMapping。无论是从IOC容器中搜索,还是创建指定的bean,都会触发如图4所示的调用链:

图4的ApplicationContextAwareProcessor是在ioc容器refresh阶段的prepareBeanFactory方法中默认设定的一种BPP处理器。图4执行链路的最后是AbstractHandlerMapping的initApplicationContext方法:

@Override
protected void initApplicationContext() throws BeansException {
	extendInterceptors(this.interceptors);
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}

其中detectMappedInterceptors方法的主要逻辑是:找出所有类型是MappedInterceptor的bean,并放入List<HandlerInterceptor> adaptedInterceptors中。

如图5所示,AbstractHandlerMapping的实现类分为两大支脉,一支是AbstractUrlHandlerMapping(基于类级别的Handler,过时用法)、另一支是AbstractHandlerMethodMappping(基于方法级别的Handler,对应目前主流的@Controller、@RequestMapping注解用法),分别以不同方式完成了处理器的初始化。

AbstractHandlerMethodMapping注册handler的过程较为复杂,不展开分析了,主要是3个新出现的类:

RequestMappingInfo:负责提取、转化@RequestMapping注解的配置信息

MappingRegistry:注册中心,多个map维护多种映射关系

HandlerMethod:对@RequestMapping标注的方法的包装(属性method是@RequestMapping标注的方法、属性bean是controller类实例)

tips:注册中心采用了非公平的可重入读写锁ReentrantReadWriteLock,在注册register、注销unregister时使用写锁,而在服务运行过程中使用读锁。读写锁的互斥性保证了注册或注销时没有请求能进入处理流程;锁的可重入性保证不阻塞请求,只有处理完所有请求才能获得写锁进行注销。

二. springmvc的请求处理过程

DispatchServlet完成初始化后,可以处理http请求了。当http请求到达时,沿着Servlet类的service方法 => ... => DispatchServlet类的doDispatch方法,doDispatch方法的主要流程如下:

1. 拦截器

从上图流程和注释可以看出,对于任意http请求,普通HandlerInterceptor会直接生效,而MappedInterceptor会匹配路径决定是否拦截处理请求。

2. 处理器

上图重点分析处理器是方法级别的情况(即采用@RequestMapping注解)。列举了3种情况的HandlerMethodReturnValueHandler:

(1)异步servlet请求

参考博客1:什么是servlet异步请求 ,注意:返回Callable这种方式不复用线程,生产环境不推荐使用这种方式,应该使用线程池代替 → 返回值采用WebAsyncTask包装Callable。

参考博客2:tomcat如何实现异步servlet,ps:异步servlet请求处理过程会进入doDispatch方法2次,执行2次拦截链的preHandle,执行1次拦截链的postHandle。

从返回值处理器CallableMethodReturnValueHandler中可以看到:
① 启动异步逻辑:startCallableProcessing(...)

② 子线程完成业务逻辑后 或 异常情况,执行:setConcurrentResultAndDispatch(...),该方法存在类似参考博客2的AsyncContext.complete()的逻辑,执行ActionCode.ASYNC_COMPLETE分支

(2)@ResponseBody

该注解有专门的处理器,将controller的方法的返回对象按指定格式写到response中,通常用来返回JSON数据或者是XML

(3)ModelAndView

spring集成了第三方视图方案,可以返回多种视图格式

3. 视图解析器

在业务逻辑new ModelAndView时,设置的是viewName,视图解析器用于从viewName得到view

三. springmvc Demo

(1)dispatchServlet的ioc容器配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config />
	<context:component-scan base-package="com.webtest"/>

	<!-- 将viewName作为beanName,在ioc容器中搜索得到view对象 -->
	<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>

	<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<props>
				<prop key="/outdated">outdatedController</prop>
			</props>
		</property>
	</bean>

	<bean id="outdatedController" class="com.webtest.controller.OutdatedController"/>

	<bean id="requestMappingHandlerMapping"
		  class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
		<property name="interceptors">
			<list>
				<bean class="com.webtest.MyInterceptor"></bean>
			</list>
		</property>
	</bean>

	<bean id="myInterceptor" class="com.webtest.MyInterceptor"></bean>
</beans>

注意:敲黑板!!!由于上述配置中使用了context:annotation-config、context:component-scan标签,demo启动时会报错:

信息: Initializing Spring DispatcherServlet 'dispatcher'
ERROR DispatcherServlet Context initialization failed
 org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context]
Offending resource: ServletContext resource [/WEB-INF/dispatcher-servlet.xml]

	at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
	at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:119)
	at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:111)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:281)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1388)

参考博客:spring系列1-基于源码搭建普通工程&web工程的五-2-(5)引入其它spring模块的问题

(2)类级别的处理器:

package com.webtest.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class OutdatedController extends AbstractController {

	@Override
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return new ModelAndView("showJsp", "testcontroller", "hello world!");
	}
}

(3)方法级别的处理器:

package com.webtest.controller;

import com.webtest.view.MyExcelView;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;
import org.springframework.web.servlet.ModelAndView;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Callable;

@Controller
@RequestMapping("/test")
public class TestController {

	@RequestMapping("/abc")
	@ResponseBody
	public String abc() {
		return "hello abc!";
	}

	@RequestMapping(value = "/async1", produces = "text/plain;charset=UTF-8")
	@ResponseBody
	public Callable<String> callable() {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		StringBuilder sb = new StringBuilder();
		sb.append("主线程开始:").append(dateFormat.format(new Date()));
		System.out.println(sb.toString());
		Callable<String> result = () -> {
			Thread.sleep(5 * 1000);
			String s = "子线程执行:" + dateFormat.format(new Date());
			System.out.println(s);
			return sb.append("\n").append(s).toString();
		};
		System.out.println("主线程退出......");
		return result;
	}

	@RequestMapping("/async2")
	@ResponseBody
	public WebAsyncTask<String> webAsyncTask() {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		StringBuilder sb = new StringBuilder();
		sb.append("主线程开始:").append(dateFormat.format(new Date()));
		System.out.println(sb.toString());
		Callable<String> result = () -> {
			Thread.sleep(5 * 1000);
			String s = "子线程执行:" + dateFormat.format(new Date());
			System.out.println(s);
			return sb.append("\n").append(s).toString();
		};
		WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(10000, result);
		System.out.println("主线程退出......");
		return webAsyncTask;
	}

	@RequestMapping("/getExcel")
	public ModelAndView getExcel() {
		return new ModelAndView(new MyExcelView(), new HashMap<>(0));
	}

	@RequestMapping("/getExcel2")
	public ModelAndView getExcel2() {
		return new ModelAndView("myExcelView", new HashMap<>(0));
	}
}

自定义的excel视图:

package com.webtest.view;

import com.webtest.ExcelUtil;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractXlsView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Component
public class MyExcelView extends AbstractXlsView {

	@Override
	protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
		Set<String> names = new HashSet<>();
		names.add("age");
		names.add("name");
		List<Person> people = new ArrayList<>();
		people.add(new Person(13, "Lilei"));
		people.add(new Person(17, "Hanmeimei"));
		ExcelUtil.setSheet(workbook, people, Person.class, names);
	}

	static class Person {
		private int age;
		private String name;

		Person(int age, String name) {
			this.age = age;
			this.name = name;
		}
	}
}
package com.webtest;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;

public class ExcelUtil {

    private static String SUFFIX = ".xls";

    /**
     * 将data的录入到文件的sheet中,每调用一次新增一个sheet
     *
     * @param workbook        文件
     * @param allowFieldNames 允许导入文件的字段名称
     */
    public static <T> void setSheet(Workbook workbook, List<T> data, Class<T> clazz, Set<String> allowFieldNames) {
		Sheet sheet = workbook.createSheet();
        Field[] fields = clazz.getDeclaredFields();
        setTableHeader(sheet, fields, allowFieldNames);
        setTableData(sheet, data, fields, allowFieldNames);
    }

    /**
     * 设置表头
     */
    private static void setTableHeader(Sheet sheet, Field[] fields, Set<String> allowFieldNames) {
        if (fields == null) {
            return;
        }
        Row row = sheet.createRow(0);
        int count = 0;
        for (Field f : fields) {
            if (allowFieldNames.contains(f.getName())) {
                sheet.setColumnWidth(count, f.getName().length() * 256 * 2);
                Cell cell = row.createCell(count);
                cell.setCellValue(f.getName());
                count++;
            }
        }
    }

    /**
     * 设置表数据
     */
    private static <T> void setTableData(Sheet sheet, List<T> data, Field[] fields, Set<String> allowFieldNames) {
        if (CollectionUtils.isEmpty(data) || fields == null) {
            return;
        }
        for (int i = 0; i < data.size(); i++) {
            Row row = sheet.createRow(i + 1);
            int count = 0;
            for (Field f : fields) {
                if (allowFieldNames.contains(f.getName())) {
                    f.setAccessible(true);
                    Object fieldValue = null;
                    try {
                        fieldValue = f.get(data.get(i));
                    } catch (IllegalAccessException e) {
						throw new RuntimeException(e);
                    }
                    Class fieldClazz = f.getType();
                    String strValue = fieldClazz.equals(Date.class) ?
                            DateFormat.getDateInstance().format(fieldValue) : String.valueOf(fieldValue);
                    row.createCell(count).setCellValue(strValue);
                    count++;
                }
            }
        }
    }
}

(4)自定义拦截器:

package com.webtest;

import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("myInterceptor pre handle....");
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
						   @Nullable ModelAndView modelAndView) throws Exception {
		System.out.println("myInterceptor post handle....");
	}
}

参考资料

《spring技术内幕》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值