简介
Spring MVC 基于 Servlet,提供核心控制器DispatcherServlet
,结构松散,以至于能适应各种灵活的需求
初始化
IoC初始化
ServletContextListener
接口可以在web容器的初始化和结束期中执行一定的逻辑。所以,通过下面这个类,可以在ServletContextListener
初始化之前初始化Ioc,也可以在结束期完成对Ioc容器的销毁
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}//初始化
public void contextDestroyed(ServletContextEvent event) {
//关闭应用上下文
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
//清除属性
}//销毁
}
映射请求上下文 初始化
这个是由DispatcherServlet初始化的
FrameworkServlet
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
//上面都是日志相关的,其实不用看,但是我们可以知道,原来日志可以这么用,所以我打算好好了解一下日志,因为平时里做的日志确实不是很多,而且很难把这些日志利用起来
//原来启动时间是这里来的
long startTime = System.currentTimeMillis();
try {
//初始化操作
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
//下面都是日志,没啥意思,我直接删了
}
/**
* 初始化和注册WebApplicationContext为这个Servlet.
* 这个是能够被子类重写的,所以用的是protected方法
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//判断是否已经被初始化
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//如果Ioc容器没有刷新,那么就刷新父容器的上下文
//如果父容器为空
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//下面逻辑判断好奇怪,如果是我写会判断是否初始化,是否有web Ioc容器,但是它这样的写法,怎么说,还行,毕竟操作的是一个对象,就是可读性不好
//如果没有被初始化
if (wac == null) {
// 查找是否有存在web Ioc容器
wac = findWebApplicationContext();
}
//如果没有初始化,而且没有找到存在的ioc容器
if (wac == null) {
//则我自己创建一个
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 如果没有执行过onRefresh方法,就执行,注意一下,spring在这个版本加了锁,因为是发现并发的问题了
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// 作为Servlet的上下文属性发布Ioc容器
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
DispatcherServlet
onRefresh会跳转到这些方法执行,这个将初始化mvc的各个组件,是一个值得十分关注的方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
MultipartResolver
文件解析器,支持服务器上传文件的功能,不过现在公司一般用阿里云的OSS来完成文件服务器
LocaleResovler
国际化解析器,提供不同语言不同的返回语言结果,这个一般交给前端处理,写多套前端,比写后端,然后配置语言要方便的多,而且前端很多UI都支持国际化
ThemeResolver
主题解析器,也是交给前端,不用管
HandlerMapping
非常重要,它会包装用户提供一个控制器的方法和对它的一些拦截器,通过调用它就能运行控制器
handlerAdaptr
处理器适配器,因为处理器会在不同的上下文中运行,所以spring会找到合适的适配器,然后运行处理器服务方法,比如对于控制器的SimpleControllerHandlerAdapter
,对于普通请求的HttpRequestHandlerAdapter
HandlerExceptionResolver
全局异常处理器,而且可以指定前端跳转到指定页面
RequestToViewNameTranlator
可以在控制器中返回一个视图的名称,通过它可以找到实际的视图,如果没有就根据url寻找,我猜是静态资源解析
ViewResolver
视图解析器,解析名称,定位实际视图
实际上DispatcherServlet
会通过DispatcherServlet.properties
注解配置方式初始化
由于Servlet3以后支持了注解配置,所以spring在3以后也做了支持,只需要我们继承一个AbstractAnnotationConfigDispatcherSerletInitializer
然后实现它定义的方法,为什么MVC可以做到我们继承一个类,就能代替它原有的支持呢,而且我们在MVC开发的时候,也遇到了好多这样的情况,都是继承一个类,然后就能实现对应的方法
因为Servlet3容许动态加载Servlet,所以spring提供了SpringSerletContainerInitializer
,它继承了Serlet提供的接口,这样利用Servlet实现了动态配置加载
SpringServletContainerInitializer
spring的抽象类层级是不止一层的
- getRootConfigClasses 获取Ioc容器的java配置类,用以装载各类的bean
- getServletConfigClasses 获取各类MVC的URI控制器的配置关系类,用以生成web请求的上下文
- getServletMappings 定义DispatchServlet拦截的请求
开发流程补充
在这里记录一下,我平时开发没有注意到的地方
解析的大体流程,用Controller标注一个类,而且只能用这个标注,restController一样,我们使用后者,然后spring会扫描这些配置,然后结合@XXXMapping
,它的作用是把uri和方法绑定在一起,然后mvc结合配置的拦截器,组成了多个拦截器和一个控制器的形式,放到一个HandlerMapping中。当请求来服务器的时候,首先通过请求的信息找到对应的HandlerMapping,然后找到拦截器和处理的函数
spring mvc竟然还提供了@SessionAttibute来直接获取session中的值,所以,我在想,能不能自定义一个注解,把token里的信息取出来,如果能把这个加到我的权限框架中,我觉得会很好用
@RequestAttribute
我们可以在request里面设置属性,它一次请求的作用内有效,其实本质就是同一个request对象,我们可以通过拦截器和web上下文设置额外的参数
多个拦截器的执行顺序
多个拦截器是根据责任链的顺序执行的,但是如果其中有一个preHandler返回false,那么整个链就直接停止了
视图
对于我们返回的对象,spring可以做任意的处理,可以把我们的对象转换为excel,pdf还有我们常见的json,这些就是视图,并不仅仅是指jsp页面
视图实现了ViewResoler
的接口,而且是支持国际化的,因为这个接口的唯一方法resolveViewName
有Locale参数
举个例子,我们一开始在spring中返回一个字符串,spring就能把他对应成html,是通过InternalResourceViewResolver
来实现的
导出excel
我们用视图来实现这个功能,spring推荐我们使用AbstractXlsView来实现(我发现框架的抽象类都是这个前缀)这个用到了ModelView
spring提供的这个我觉得有一点不好,因为他限制了只能用poi,他的方法入参应该是一个文件流,也就是说,我们方法参数的接受范围要尽可能的大,这样,我们的函数功能才会更加强大,但是有一点,如果你的方法里出现了大量的关于参数的判断,那么是完全没有必要的
文件上传
这里我记录一些spring的设计思想,它的MultipartRequest
类,它继承了HttpServletRequest
然后扩充了它对文件操作的方法,利用这种继承,使一个类的功能得到了增强
数据转换和格式化
首先,当一个请求到达了DispatchServlet
的时候,需要找到对应的HandlerMapping,然后通过HandlerMapping找到对应的HandlerAdapter
处理器,处理器在调用控制器(就是我们定义的controller)之前,会先获取HTTP发送过来的信息然后将其转变为控制器的各种参数,这就是我们能通过注解获取参数的方法,一句话概括的话,就是spring处理了我们的http请求。
spring用HttpMessageConverter
消息转换器实现了消息的装换,但这只是一个比较简单的转换,所以spring提供了转换器和格式化器,然后就会对这些参数进行验证,只是我个人习惯自己写验证,因为一般涉及到增的地方,就会设计到改,如果用框架,需要写的代码比较多,而且有些验证逻辑很复杂,需要自定义复杂的类,所以我一般是让对象自己完成验证的
然后,处理我们的业务逻辑,最后在返回结果的时候,spring会用HttpMessageConverter
进行转换,比如我们的@ResponeBody
Http请求的参数会给HttpMessageConverter然后会给转换器(转换器又分为普通转换器Converter和集合转换器GenericConverter)和格式化器(Formatter),然后就变成了我们的java对象,接下来,spring会对它进行验证。然后就是调用我们编写的业务逻辑,接下来,又把交回了HttpMessageConverter
然后进行比如Json之类的转换最后返回给我们的客户端
所以这个能够完成json和对象的双向转换,而且这些转换器是需要注册的,如果我们自己的写的话,这个类型转换器确实是很有必要的,我整理了一下spring boot的注册,因为spring的太繁琐了, 我还发现spring boot采用的方式是继承一个类,然后里面有一个参数,我们只要操作这个参数就能完成各种配置和注册
源码分析
不过事到如今,我觉得一个方法,含boolen参数也是合理的,因为对于用户来说,只要我们bool参数的变量名起的比较好,那么是完全没有问题的,而且idea会帮助我们把参数名显示出来,这是非常好的,所以有些代码规则在编译器日新月异的改变中,代码规范是可能会发生改变的,又比如现在重构一个很长的类,很长的方法,也是很方便的
mvc执行流程图
我根据这个图的顺序来自己分析一下源码,如果有不对的地方,希望大家指出来
从一个http请求进来出发
DispatchServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {//这个就是我们在写servlet时常见的哪些参数
logRequest(request);//spring自己也有为请求参数做日志的方法
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;//属性快照
if (WebUtils.isIncludeRequest(request)) {
//这个方法应该是把request里面的属性放到了自己的map里面
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
//上面我们可以看到,spring在request的属性里面没少放东西
try {
doDispatch(request, response);
}//这个可以不处理异常,我当时竟然没有试过不写这个会怎么样,这个其实就相当于,我执行一个方法,然后无论方法执行如何我都要保证后面的代码被执行
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
logRequest
private void logRequest(HttpServletRequest request) {
LogFormatUtils.traceDebug(logger, traceOn -> {
String params;
//如何判断上传的是否为文件类型
if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {
params = "multipart";
}
else if (isEnableLoggingRequestDetails()) {//如果容许答应请求参数细节
//我发现这个默认是false,怎么开启?
//我看到参数的注释上说可以通过debug日志级别开启
//不得不说,一次请求的debug日志数量就超出了我的想象
params = request.getParameterMap().entrySet().stream()
.map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
.collect(Collectors.joining(", "));
//map怎么用Java8的流,用entrySet(),从形式上看就是一个List
//collect提供了用,拼接字符串的方法,这个说实话挺常见的,所以你也要学会使用这个方法
}
else {
params = (request.getParameterMap().isEmpty() ? "" : "masked");
}
String queryString = request.getQueryString();
String queryClause = (StringUtils.hasLength(queryString) ? "?" + queryString : "");
String dispatchType = (!DispatcherType.REQUEST.equals(request.getDispatcherType()) ?
"\"" + request.getDispatcherType() + "\" dispatch for " : "");
String message = (dispatchType + request.getMethod() + " \"" + getRequestUri(request) +
queryClause + "\", parameters={" + params + "}");
if (traceOn) {
List<String> values = Collections.list(request.getHeaderNames());
String headers = values.size() > 0 ? "masked" : "";
if (isEnableLoggingRequestDetails()) {
headers = values.stream().map(name -> name + ":" + Collections.list(request.getHeaders(name)))
.collect(Collectors.joining(", "));//从日志上来看,就是打印了header的信息
}
return message + ", headers={" + headers + "} in DispatcherServlet '" + getServletName() + "'";
}
else {
return message;
}
});
}
又有了一个想法,我可以设置如果发生线上异常情况,就报警,报警的内容要尽可能详细一点,然后只在上线的项目要通过报警,所以要设置环境参数,然后报警的内容,要尽可能的让我快速定位到问题,并且解决它
spring boot 设置日志级别
logging:
level:
root: debug
记得要加上那个root,不然会出现报错,无法把字符串转换成枚举类
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;//这个是spring无论如何都要初始化的
Exception dispatchException = null;//转发异常
try {
processedRequest = checkMultipart(request);//检测是否为文件上传
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);//选择handler
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 = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {//这里特殊处理了get和head方法
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.实际上执行的是这个处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {//目前的handler开始了吗
return;
}
//这个明显就是上面那个if不成立
applyDefaultViewName(processedRequest, mv);
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);
}
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()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {//controller的url映射都放在了这个里面
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;//明显这个处理逻辑就是,如果我找到一个匹配的就返回
}
}
}
return null;
}
我发现这个handlerMapping竟然是一个接口,但是实现类特别多,我不知道具体用的是哪一个,怎么办?我目前知道最好的办法就是debug打断点去看了,不得不说,我发现依托于idea强大的debug能力,能让我发现好多东西,因为他显示的值都是实际项目的值,依托于这个值,我可以很容易的猜到这个属性是干什么的,看来以后得经常用了,而且我觉得尽量用,说不定能发现好多意想不到的收获呢
HandlerMapping
spring确实初始化了不少,真的不止一个接口实现类,不过我最后确定是这个SimpleUrlHandlerMapping
,再次赞叹idea的debug强大
SimpleUrlHandlerMapping
下面这个类我删除了很多无用的代码,就是对分析代码没什么用的
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
//这个urlMap直接给人一种通透的感觉,这里就是把url和某个对象结合起来,然后根据这个url找到对应的Object来执行具体的操作,同时我还注意到,这是一个LinkedHashMap,说明是想保证一定顺序的
private final Map<String, Object> urlMap = new LinkedHashMap<>();
//我这里产生了一些疑惑的地方,如果我们的url是restFul风格,spring怎么来找呢?
//不过自己分析源码还是很有意思的,当然这都建立在别人告诉我入口在哪里,不然我肯定不知道怎么找,然后就是我用过这个,可以根据自己平时的使用去猜测
/**
* Register all handlers specified in the URL map for the corresponding paths.
* 注册全部的处理器
*/
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.trace("No patterns in " + formatMappingName());
}
else {
urlMap.forEach((url, handler) -> {
// Prepend with slash if not already present.
if (!url.startsWith("/")) {//你看这里,明确的告诉我们,前面的url是可以不加/的,但是spring肯定会给你补上这个
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {//而且会帮助我们去除字符串首尾的空格
handler = ((String) handler).trim();
}
registerHandler(url, handler);//注册
});
logMappings();//打印日志的方法
}
}
}
registerHandler
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
// 如果通过名称引用单例,则优先解析处理程序,这个是字符串,可能是视图解析优先吧
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {//如果你不小心定义了重复的urlPath,spring是会帮助你处理的
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {//这种会被当成Root来处理
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {//通配符号
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {//这个应该就是一般的
this.handlerMap.put(urlPath, resolvedHandler);
if (getPatternParser() != null) {
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);//这个方法来了,spring是怎么处理url的
}
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
parse
public PathPattern parse(String pathPattern) throws PatternParseException {
Assert.notNull(pathPattern, "Path pattern must not be null");
this.pathPatternData = pathPattern.toCharArray();
this.pathPatternLength = this.pathPatternData.length;
this.headPE = null;
this.currentPE = null;
this.capturedVariableNames = null;
this.pathElementStart = -1;
this.pos = 0;
this.resetPathElementState();
for(; this.pos < this.pathPatternLength; ++this.pos) {//这是逐个遍历,有点惊讶,不过好像确实没有什么好的办法
char ch = this.pathPatternData[this.pos];
char separator = this.parser.getPathOptions().separator();//分割符号
if (ch == separator) {//如果你是分割符号
if (this.pathElementStart != -1) {
this.pushPathElement(this.createPathElement());
}
if (this.peekDoubleWildcard()) {
this.pushPathElement(new WildcardTheRestPathElement(this.pos, separator));
this.pos += 2;
} else {
this.pushPathElement(new SeparatorPathElement(this.pos, separator));
}
} else {
if (this.pathElementStart == -1) {
this.pathElementStart = this.pos;
}
if (ch == '?') {
++this.singleCharWildcardCount;
} else if (ch == '{') {//这个地方应该就是restFul风格的解析
if (this.insideVariableCapture) {
throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_NESTED_CAPTURE, new Object[0]);
}
this.insideVariableCapture = true;
this.variableCaptureStart = this.pos;
} else if (ch == '}') {
if (!this.insideVariableCapture) {
throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.MISSING_OPEN_CAPTURE, new Object[0]);
}
this.insideVariableCapture = false;
if (this.isCaptureTheRestVariable && this.pos + 1 < this.pathPatternLength) {
throw new PatternParseException(this.pos + 1, this.pathPatternData, PatternMessage.NO_MORE_DATA_EXPECTED_AFTER_CAPTURE_THE_REST, new Object[0]);
}
++this.variableCaptureCount;
} else if (ch == ':') {//这个我好像没有遇到过
if (this.insideVariableCapture && !this.isCaptureTheRestVariable) {
this.skipCaptureRegex();
this.insideVariableCapture = false;
++this.variableCaptureCount;
}
} else if (ch == '*') {//这个应该是通配符号
if (this.insideVariableCapture && this.variableCaptureStart == this.pos - 1) {
this.isCaptureTheRestVariable = true;
}
this.wildcard = true;
}
if (this.insideVariableCapture) {
if (this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) == this.pos && !Character.isJavaIdentifierStart(ch)) {
throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
}
if (this.pos > this.variableCaptureStart + 1 + (this.isCaptureTheRestVariable ? 1 : 0) && !Character.isJavaIdentifierPart(ch) && ch != '-') {
throw new PatternParseException(this.pos, this.pathPatternData, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR, new Object[]{Character.toString(ch)});
}
}
}
}
if (this.pathElementStart != -1) {
this.pushPathElement(this.createPathElement());
}
return new PathPattern(pathPattern, this.parser, this.headPE);
}
说实话,上面这个把我劝退了,很难,就像是算法题一样,不过,还有一点,就是我不知道这个handler是什么时候生成的,从代码里面,我只能看到string类型的hander生成,怎么办,我试着把端点放在了那个类的属性上,好像不行,所以我在下面补充一些我学习到的debug技巧
debug
步出:向上指的蓝色箭头。如果我们点了步入,发现它执行了我们不想要的代码,那么我们就可以点击这个步出,而且不是直接关掉再重新调试一遍
运行到关标处:蓝色箭头指着一个关标,我们完全没有必要一步一步的点着它去执行
计算表达式的值:我发现这个其实可以执行java的代码,所以很多时候,我们不需要,为了debug专门的计算某些值
最好的用的是绿色箭头,它可以直接走到下一个断点
点击debug的两个红点,选择添加,可以发现idea有那种被动的debug机制,可以给字段和异常打上断点,这个时候我就发现了,如果我们想看某个字段的变化,可以在这里用,然后就是如果我们触发了某个异常,但是不知道异常触发的原因,我们也可以通过debug的方法查看,比如最头疼的空指针异常,而且这个是可以支持多线程调试的
说实话,这个处理的结果和我想象的不太一样,那就先继续分析其他的流程了,不过我至少知道了,如果是外部的项目,我们可以通过Ctrl+Alt+F7来决定,到底是哪些类用了这个类的方法
getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {//判断方法同上
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
SimpleServletHandlerAdapter
public class SimpleServletHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
//原来handler竟然是一个Servlet,不过想想确实很合理,看来经过spring的层层包装,最后还是我们的Servlet执行了最后的方法
return null;
}
@Override
@SuppressWarnings("deprecation")
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
下面这个是doDispatch
方法,这个就执行了我们定义的前置处理器,而我们定义的早就在mapperChain这条处理责任链里面
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;//如果你判断假的话,一个!把这个变成真,然后停止方法的执行
}
//不过不得不说的是,这个!还是有点饶人的
AbstractHandlerMethodAdapter
这个好像就做了一个参数类的转换然后就丢给具体的实现类了,只有一个实现类
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
RequestMappingHandlerAdapter
这个类还是很复杂的,所以我只弄了断点跑过来的这个方法
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
//完成了session的判断,不过现在已经都是jwt,session很少见了
if (this.synchronizeOnSession) {//注意到这里还有并发的处理
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
//我调试的时候,执行的是这个方法
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);//设置参数
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//设置返回类型
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//上面对请求过来的参数做了相当多的处理,看名字能猜出一些,但是我不是很敢写这些注释,不过也不是什么很重要的方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);//设置404,500,200这种状态码
if (returnValue == null) {//如果你返回了一个null,spring也会帮你处理
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);//我猜这是把对象处理成json的方法
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
InvocableHandlerMethod
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);//方法参数
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return this.doInvoke(args);
}
InvocableHandlerMethod
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = this.getBridgedMethod();//终于走到真正的执行方法了
try {
return KotlinDetector.isSuspendingFunction(method) ? CoroutinesUtils.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
} catch (IllegalArgumentException var5) {
this.assertTargetBean(method, this.getBean(), args);
String text = var5.getMessage() != null ? var5.getMessage() : "Illegal argument";
throw new IllegalStateException(this.formatInvokeError(text, args), var5);
} catch (InvocationTargetException var6) {
Throwable targetException = var6.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException)targetException;
} else if (targetException instanceof Error) {
throw (Error)targetException;
} else if (targetException instanceof Exception) {
throw (Exception)targetException;
} else {
throw new IllegalStateException(this.formatInvokeError("Invocation failure", args), targetException);
}
}
}
!现在方法执行完成,开始处理返回结果,我们只关注到json对象的生成,然后一直交给servlet方法
HandlerMethodReturnValueHandlerComposite
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} else {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
RequestResponseBodyMethodProcessor
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
AbstractMessageConverterMethodProcessor
你看下面那个方法名,用MessageConverters来实现了,这样就和我们之前学的结合起来了
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class<?> valueType;
Type targetType;
if (value instanceof CharSequence) {//字符类型,字符串也是,因为它实现了这个接口,所以我们定义的类,不要乱实现这个,根据我的经验,这个应该是处理视图的
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes;
try {
acceptableTypes = getAcceptableMediaTypes(request);
}
catch (HttpMediaTypeNotAcceptableException ex) {
int series = outputMessage.getServletResponse().getStatus() / 100;
if (body == null || series == 4 || series == 5) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring error response content (if any). " + ex);
}
return;
}
throw ex;
}
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
//你会发现上面的方法在根据http协议定义的接受类型来决定自己要做什么事情,当然这些是通过debug看出来的
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;//这里选择了Json类型然后break了
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
//没想到spring在决定用哪个类型处理器的时候,用的是for循环,我也是在for了几次之后,发现用的处理器实际上是MappingJackson2CborHttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
}
}
下面的比较重复,今天看的太多了,而且返回其实不很重要,这个环节是很少会出现错误的。
其他
其他的地方,书上讲的不太全,而且不是很常用,我打算从网上找一些常用的东西,然后补充一下,其他的部分,我会在之后看更深入的书籍来补充自己的知识
如何获取request
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
通过上面这个方法,我们就可以在AOP中获取对应的参数,然后做我们想做的处理,我发现,可以把用户的id,用户的常用信息设置在request的属性里面,然后我们可以通过Controller的相关注解获取