Servlet工作原理
ApplicationContext
AppCtx
简介

ApplicationContext是一个很综合的类,其UML图如上图所示:
-
它是一个BeanFactory,拥有管理bean的能力
- 继承了ListableBeanFactory接口,拥有管理bean的能力,可以获得beanNames、判断某个beanName是否存在beanDefinition对象
- 继承了HierarchicalBeanFactory接口,拥有层级化的关系,可以获得父BeanFactory等功能
-
继承了ResourceLoader,拥有加载Resource的能力
- 继承了ResourcePatternResolver了,间接继承了ResourceLoader。这里的Resource可以是图片、文件、URL资源等等
-
继承了ApplicationEventPublisher,拥有发布事件的能力
通过ApplicaitonContext可以轻松实现发布-订阅模式,将Event发布给响应的Listener
-
继承了EnvironmentCapable,拥有获取系统环境变量的能力
-
继承了MessageSource接口,通过它可以事件国际化功能。
Bean管理功能
常用功能
通过ApplicationContext可以实现对bean的操作,常用的如下:
/**
获取对象
@param name
@return Object 一个以所给名字注册的bean的实例
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
/**
获取类型为requiredType的对象
如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
@param name bean注册名
@param requiredType 返回对象类型
@return Object 返回requiredType类型对象
*/
public static Object getBean(String name, Class requiredType) throws BeansException {
return applicationContext.getBean(name, requiredType);
}
/**
如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
@param name
*/
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
/**
判断以给定名字注册的bean定义是一个singleton还是一个prototype。
如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
@param name
@return boolean
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return applicationContext.isSingleton(name);
}
/**
@param name
@return Class 注册对象的类型
*/
public static Class getType(String name) throws NoSuchBeanDefinitionException {
return applicationContext.getType(name);
}
/**
如果给定的bean名字在bean定义中有别名,则返回这些别名
@param name
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return applicationContext.getAliases(name);
}
bean的加载过程
本节参考segmentfault,本节暂不深入
实例化+填充,这块的过程
Resource加载功能
Spring的Resource接口旨在成为一个更有能力的接口,用于抽象对低级资源的访问。
Resource主要指的就是文件,而文件可以视作一个InputStream来看待。
文件存储可以是:
实现类 | 说明 |
FileSystemResource | 通过文件系统获取资源 |
ClassPathResource | 通过类路径获取资源文件,打包之后的classes目录 |
URLResource | 通过URL地址获取资源 |
ServletContextResource | 相关Web应用程序根目录中的相对路径 |
ByteArrayResource | 字节数组的Resource实现类 |
InputStreamResource | 给定的输入流(InputStream)的Resource实现 |
| |
Resource的接口不多
public interface Resource extends InputStreamSource {
//判断资源是否存在,true表示存在
boolean exists();
//判断资源的内容是否可读
default boolean isReadable() {
return this.exists();
}
//判断当前Resource代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免资源泄露;该方法主要针对于InputStreamResource,实现类中只有它的返回结果为true,其他都为false。
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
//返回当前资源对应的URL。如果当前资源不能解析为一个URL则会抛出异常。如ByteArrayResource就不能解析为一个URL。
URL getURL() throws IOException;
//返回当前资源对应的URI。如果当前资源不能解析为一个URI则会抛出异常。
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
//返回当前资源内容的长度。
long contentLength() throws IOException;
//返回当前Resource代表的底层资源的最后修改时间。
long lastModified() throws IOException;
//根据资源的相对路径创建新资源。[默认不支持创建相对路径资源]
Resource createRelative(String var1) throws IOException;
//获取资源的文件名
@Nullable
String getFilename();
//返回当前资源底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)
String getDescription();
}
除此之外,继承了一个InputStreamSource的getInputStream
方法。该方法获取当前资源代表的输入流,除了InputStreamResource实现类以外,其它Resource实现类每次调用getInputStream()方法都将返回一个全新的InputStream。
如何加载资源这里也应该加一下
参考
Event发布功能
包括3个元素: publisher、event、listener
使用方式
@ComponentScan("com.wxx.official.event")
@Configuration
@EnableAsync
public class Test {
public static void main(String[] args) {
ApplicationEventPublisher publisher = new AnnotationConfigApplicationContext(Test.class);
publisher.publishEvent(new Event("注解事件"));
}
static class Event {
String name;
Event(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@Component
static class Listener {
@EventListener
@Async // 异步
public void listen(Event event) {
System.out.println("接收到事件:" + event);
System.out.println("处理事件");
}
}
listener可以通过@Order来实行顺序串联
参考
环境变量获取功能
它其实代表了当前Spring容器的运行环境,比如JDK环境,系统环境。每个环境都有自己的配置数据,如System.getProperties()可以拿到JDK环境数据,System.getenv()可以拿到系统变量,ServletContext.getInitParameter()可以拿到Servlet环境配置数据。Spring抽象了一个Environment来表示Spring应用程序环境配置,整合了各种各样的外部环境,并且提供统一访问的方法。
Environment接口继承了PropertyResolver,而Environment接口自身主要提供了对Profile的操作,PropertyResolver接口主要提供了对当前运行环境中属性的操作,如果我们去查看它的一些方法的实现可以发现,对属性的操作大都依赖于PropertySource。
参考
ApplicationContext的不同类型
参考
ServletContext
-
定义
这里加一段ServletContext的概念
ServletContext 对象由服务器进行创建,一个项目只有一个对象。不管在项目的任意位置进行获取得到的都是同一个对象,那么不同用户发起的请求获取到的也就是同一个对象了,该对象由用户共同拥有。
Request 解决了一次请求内的数据共享问题,
session 解决了用户不同请求的数据共享问题,
ServletContext解决了不同的用户的数据共享的问题
-
使用
通过 this.getServletContext();
得到ServletContext对象。你可以把它想象成一张表,这个和Session非常相似:每一行就是一个属性,如下:
- 添加属性:
setAttribute(String name, Object obj);
- 得到值:
getAttribute(String name),这个方法返回Object
- 删除属性:
removeAttribute(String name)
SpringMVC流程
基本流程
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
组件
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
方法
映射关系
-
@Controller
-
@RequestMapping
- GetMapping、PostMapping、PutMapping、DeleteMapping、PatchMapping
RequestMapping中有几个属性,可以对映射进行定义
属性 | 描述 |
value | 指定请求的实际地址 |
method | 指定请求的method类型 |
cosumes | 指定处理请求的Content-Type,例如application/json, text/html; |
produces | 指定返回的内容类型,仅当request请求头中的(Accept类型中包含该指定类型才返回 |
params | 指定request中必须包含某些参数值是,才让该方法处理 |
headers | 指定request中必须包含某些指定的header值,才能让该方法处理请求 |
comsumes示例:
@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
方法仅处理request Content-Type为“application/json”类型的请求。
produces示例:
@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}
方法仅处理request请求中Accept头中包含了"application/json"的请求,同时暗示了返回的内容类型为application/json;
params示例:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
仅处理请求中包含了名为“myParam”,值为“myValue”的请求;
headers示例:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
仅处理request的header中包含了指定“Refer”请求头和对应值为“http://www.ifeng.com/
”的请求;
处理方法
注解 | 描述 |
@RequestBody | 请求body体 |
@ResponseBody | 返回body体 |
@ResponseStatus | Http返回状态码 |
@PathVariable | URL路径变量 |
@RequestParam | query参数 |
@RequestHeader | HTTP的请求头 |
HttpEntity | 请求头部和消息体 |
ResonponseEntity | HttpEntity+返回状态吗 |
RequestEntity | Method + URI + HttpEntity +Type |
参考
-
HttpEntity、RequestEntity、ResponseEntity
spring Web使用类HttpEntity包装一个HTTP请求或者响应的以下内容 : 头部和消息体。概念上来讲,可以简单理解成这样 :
HttpEntity = n headers + 1 body
Spring Web针对请求和响应两种情况分别继承HttpEntity提供了实现类 :RequestEntity与ResponseEntity
RequestEntity = HttpEntity + HttpMethod + URI + Type(消息体类型)
ResponseEntity = HttpEntity + (HttpStatus/Intgeger类型) 状态码
-
示例
RestTemplate
// 用于表示请求消息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> entity = new HttpEntity<String>(helloWorld, headers);
URI location = template.postForLocation("https://example.com", entity);
// 用于接收响应消息
HttpEntity<String> entity = template.getForEntity("https://example.com", String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
controller
@RequestMapping("/handle")
public HttpEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new HttpEntity<String>("Hello World", responseHeaders);
}
类型转换
-
概念
Converter与Formatter都是做类型转换用的
Converter接口只有一个convert,从S转成T
public interface Converter<S, T> {
T convert(S var1);
}
Formatter接口有2个,一个print,一个parse,都是对象与String之间的接口
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
public interface Printer<T> {
String print(T var1, Locale var2);
}
public interface Parser<T> {
T parse(String var1, Locale var2) throws ParseException;
}
Converter是一般工具,可以将一种类型转换成另一种类型。例如,将String转换成Date,或者将Long转换成Date。Converter既可以用在web层,也可以用在其它层中。
Formatter只能将String转成成另一种java类型。例如,将String转换成Date,但它不能将Long转换成Date。所以,Formatter更适用于web层。
参考
-
使用
Formatter与Converter的使用相同:
- 实现响应的Formatter与Converter接口
- 通过@Component自动将其添加到SpringBoot容器,这样就会自动生效。
-
示例
Formatter
@Component
public class DateFormatter implements Formatter<Date> {
@Override
public Date parse(String text, Locale locale) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
System.out.println("parse input text: " + text);
System.out.println("parse date: " + dateFormat.parse(text));
return dateFormat.parse(text);
}
@Override
public String print(Date object, Locale locale) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
System.out.println("print method." + object);
return dateFormat.format(object);
}
}
Converter
@Component
public class StudentConvert implements Converter<String ,Student> {
@Override
public Student convert(String text) {
if (NumberUtils.isParsable(text)) {
Student s = new Student();
s.setAge(Integer.parseInt(text));
return s;
}
return new Student();
}
}
参考
定义校验
参考1
基本使用
-
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注意这个依赖,有时有Jarkata的依赖,那个加了也没用
-
验证规则注解
public class UserDto{
@NotNull(message="age不能为空")
private Integer age;
}
注解主要有:
注解 | 校验功能 |
@AssertFalse | 必须是false |
@AssertTrue | 必须是true |
@DecimalMax | 小于等于给定的值 |
@DecimalMin | 大于等于给定的值 |
@Digits | 可设定最大整数位数和最大小数位数 |
@Email | 校验是否符合Email格式 |
@Future | 必须是将来的时间 |
@FutureOrPresent | 当前或将来时间 |
@Max | 最大值 |
@Min | 最小值 |
@Negative | 负数(不包括0) |
@NegativeOrZero | 负数或0 |
@NotBlank | 不为null并且包含至少一个非空白字符 |
@NotEmpty | 不为null并且不为空 |
@NotNull | 不为null |
@Null | 为null |
@Past | 必须是过去的时间 |
@PastOrPresent | 必须是过去的时间,包含现在 |
@Pattern | 必须满足正则表达式 |
@PositiveOrZero | 正数或0 |
@Size | 校验容器的元素个数 |
@Valid与@Validated的差异
参考1、参考2
-
@Valid
所在包javax.validation
级联验证时,可以增加@Valid
public class CreateMembershipRemarkInDto {
@NotBlank
private String remark;
@NotBlank
private String fooStr;
@NotNull
@Valid // 增加 @Valid 注解
private CreateBarInDto bar;
public static class CreateBarInDto {
@NotBlank
private String barStr;
}
}
不支持分组
-
@Validated
所在包org.springframework.validation.annotation,@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。@Validated提供了分组验证的功能
定义分组接口:
public interface Create { }
public interface Update { }
注解规则时,注明分组:
public class UserDto {
**//在Update分组时,判断不能为空**
@NotEmpty(groups={Update.class})
private String id;
@NotNull(message="age不能为空")
private Integer age;
//name字段不为空,且长度在3-8之间
@NotEmpty
@Size(min=3,max=8)
private String name;
}
对于age与name,不分配groups,默认每次都要进行验证。
Controller上进行验证时,注明分组:
@Controller
public class UserController {
@PostMapping("/user")
//不需验证ID
public String addUser(@Validated({Create.class}) UserDto userDto) {
...
}
}
这样,验证时,采用@Validated会更好一些。
BindingResult
除了能直接跑出异常,也可以通过BindingResult,将验证的结果引导Controller内部去使用:
如下:
public String addUser(@Validated UserDto userDto, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if(!fieldErrors.isEmpty()){
return fieldErrors.get(0).getDefaultMessage();
}
return "OK";
}
这样在Controller内部可以对验证的结果进行自定义的输出
Exception
以上处理比较麻烦,使用全局异常处理会更好一些。
当我们写了@validated
注解,不写BindingResult
的时候,Spring 就会抛出异常。由此,可以写一个全局异常处理类来统一处理这种校验异常,从而免去重复组织异常信息的代码。
全局异常处理类只需要在类上标注@RestControllerAdvice
,并在处理相应异常的方法上使用@ExceptionHandler
注解,写明处理哪个异常即可。
@RestControllerAdvice
public class GlobalControllerAdvice {
private static final String BAD_REQUEST_MSG = "客户端请求参数错误";
// <1> 处理 form data方式调用接口校验失败抛出的异常
@ExceptionHandler(BindException.class)
public ResultInfo bindExceptionHandler(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <2> 处理 json 请求体调用接口校验失败抛出的异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultInfo methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <3> 处理单个参数校验失败抛出的异常
@ExceptionHandler(ConstraintViolationException.class)
public ResultInfo constraintViolationExceptionHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
List<String> collect = constraintViolations.stream()
.map(o -> o.getMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
}
视图渲染原理
源码在:org.springframework:spring-webmvc下
结构
参考
ViewResolver
viewResolver的接口只有一个函数:resolveViewName(),根据viewName解析得到View
public interface ViewResolver {
/**
* Resolve the given view by name.
...
* ViewResolvers that support internationalization should respect this.
* @return the View object, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
常见的ViewResolver:
视图解析器 | 说明 |
AbstractCachingViewResolver | 一个抽象视图,继承该类可以让视图解析器具有缓存功能 |
ContentNegotiatingViewResolver | 用于解析基于Content-Type或Accept的视图 |
UrlBasedViewResolver | 一个简单的视图解析器,不做任何匹配,需要视图名和实际视图文件名相同 |
FreeMarkerViewResolver | 也是UrlBasedViewResolver的子类,用于FreeMarker视图技术 |
ResourceBundleViewResolver | 使用properties配置文件的视图解析器,默认配置文件是类路径下的views.properties |
InternalResourceViewResolver | UrlBasedViewResolver的一个子类,支持Servlet容器的内部类型(JSP、Servlet、以及JSTL等),可以使用setViewClass(..)指定具体的视图类型 |
View
view的接口有2个:getContentType与render
public interface View {
...
/**
* Return the content type of the view, if predetermined.
* Can be used to check the view's content type upfront,
*/
@Nullable
default String getContentType() {
return null;
}
/**
* Render the view given the specified model.
*/
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
常见的View:
我们挑几个常用的了解下
视图 | 说明 |
InternalResourceView | 将 JSP 或其他资源封装成一个视图,一般 JSP 页面用该视图类 |
JstlView | 继承自InternalResourceView,如果 JSP 页面使用了 JSTL 标签,则需要使用该视图类 |
AbstractPdfView | PDF视图的抽象超类 |
AbstractXlsView | 传统XLS格式的Excel文档视图的便捷超类,与Apache POI 3.5及更高版本兼容。 |
AbstractXlsxView | Office 2007 XLSX格式的Excel文档视图的便捷超类,兼容Apache POI 3.5及更高版本。 |
MappingJackson2JsonView | 将模型数据 通过 Jackson 开源框架的 ObjectMapper 以 JSON 方式输出,继承自AbstractJackson2View |
MappingJackson2XmlView | 将模型数据 通过 Jackson 开源框架的 ObjectMapper 以 xml 方式输出,集成自AbstractJackson2View |
流程
流程都是在DispatchServlet中完成,过程包括:
-
initStrategies
通过initViewResolvers(context),将所有的ViewResolver加载到ApplicationContext中
-
doDispatch
-
请求来到之后,经过doService()进入到doDispatch中进行真正的处理:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
处理完成之后,通过处理调到结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
在其中进行render操作
render中,通过view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
得到view。这里依次调用viewResolver,找到第一个view返回。
然后调用view.render(mv.getModelInternal(), request, response)
进行渲染
使用
-
Jackson2View的使用
可以通过注入@JsonComponent注入给MappingJackson2JsonView使用的序列化与反序列化器,从而控制Model到View的渲染过程。
序列化:
@JsonComponent
public class MoneySerializer extends StdSerializer<Money> {
protected MoneySerializer() {
super(Money.class);
}
@Override
public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumber(money.getAmount());
}
}
反序列化:
@JsonComponent
public class MoneyDeserializer extends StdDeserializer<Money> {
protected MoneyDeserializer() {
super(Money.class);
}
@Override
public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Money.of(CurrencyUnit.of("CNY"), p.getDecimalValue());
}
}
Interceptor
SpringBoot中Interceptor与Nest也类似,实现响应接口,然后通过WebConfigure进行配置即可,源码也是在DispatchServlet中完成的
实现接口
实现HandlerInterceptor
接口,该接口有3个函数如下:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
preHandle在方法调用前,postHandle与afterCompletion在方法调用后(它们2者的区别在于一个是在view render前,一个在view render后)。
另外还有一个专门用于异步的接口AsyncHandlerInterceptor
,它有专门的后处理接口:
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
}
}
专门的意思是postHandle与afterCompletion不会生效。
配置使用
SpringBoot中使用
-
创建一个@Configuration的WebMvcConfigurer配置类
这个配置类中,不能带@EnableWebMvc,除非自己想完全控制MVC的配置。
-
通过addInterceptors(),将自定义的Interceptor加入
源码解析
源码也是在DispatcherServlet中的,在initStrategies中通过initHandlerAdapters,注册了所有的handler,保存在handlerAdapters中。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
请求来时,在doDispatch进行调用
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 这里调用Pre
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 这里判断是异步,就退出。这里进入到后边的finally中
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 这里调用Post
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 前面看到在这里进行了view的渲染,结束后调用了triggerAfterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// 这里是异步调用的后处理,由此它不会调用Post与AfterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
示例
这里借用极客时间的示例:
public class PerformanceInteceptor implements HandlerInterceptor {
private ThreadLocal<StopWatch> stopWatch = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
StopWatch sw = new StopWatch();
stopWatch.set(sw);
sw.start();
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
stopWatch.get().stop();
stopWatch.get().start();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
StopWatch sw = stopWatch.get();
sw.stop();
String method = handler.getClass().getSimpleName();
if (handler instanceof HandlerMethod) {
String beanType = ((HandlerMethod) handler).getBeanType().getName();
String methodName = ((HandlerMethod) handler).getMethod().getName();
method = beanType + "." + methodName;
}
log.info("{};{};{};{};{}ms;{}ms;{}ms", request.getRequestURI(), method,
response.getStatus(), ex == null ? "-" : ex.getClass().getSimpleName(),
sw.getTotalTimeMillis(), sw.getTotalTimeMillis() - sw.getLastTaskTimeMillis(),
sw.getLastTaskTimeMillis());
stopWatch.remove();
}
}
@SpringBootApplication
@EnableJpaRepositories
@EnableCaching
public class WaiterServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(WaiterServiceApplication.class, args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PerformanceInteceptor())
.addPathPatterns("/coffee/**").addPathPatterns("/order/**");
}
...
}