SpringMvc-面试用

一、SpringMvc常用注解

1、修饰在类的

java 复制代码
@RestController
@RequestMapping("/test")

@RestController是什么?其实是一个复合注解

java 复制代码
@Controller    //其实就是@Component
@ResponseBody  //独立的注解
public @interface RestController {}

@RequestMapping 也可以认为是一个独立的注解

2、修饰在方法上

java 复制代码
@ResponseBody
@GetMapping("/add")
@PostMapping("/add")

@GetMapping 也是复合注解,@PostMapping也是一样,其实就是@RequestMapping

java 复制代码
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {}

@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {}

比较独立的注解一共就这三种,针对这三种注解都有对应的处理类

java 复制代码
1、@RequestMapping
RequestMappingHandlerMapping类处理,如果带有@RequestMapping就会将其转成Mapping

2、@Controller
RequestMappingHandlerMapping类处理,会将其转成Mapping

3、@ResponseBody       
RequestResponseBodyMethodProcessor类里面的,在返回结果时会解析方法是否有这个注解

一、启动流程

SpringMvc是spring的一个独立模块,就像AOP一样。在springmvc中把web框架和springioc融合在一起,是通过ContextLoaderListener监听servlet上下文的创建后来加载父容器完成的,然后通过配置一个servlet对象DispatcherServlet,在初始化DispatcherServlet时来加载具体子容器

启动的时候,我们重点关注下3面三个类,因为在处理请求的时候,重点也是这3个类。

1、DispatcherServlet

2、RequestMappingHandlerMapping

3、RequestMappingHandlerAdapter

RequestMappingHandlerMapping是什么?

RequestMappingHandlerMapping是HandlerMapping接口实现类,在IOC容器里面。他主要用到处理被@RequestMapping和@Controller修饰的类,解析这些类里面的每个方法,将这些方法封装成RequestMappingInfo对象,并且把把每个方法的url和RequestMappingInfo,存到mappingRegistry集合中去。mappingRegistry后面我们经常提到。

RequestMappingHandlerMapping是在DispatcherServlet初始化时候,从容器拿到所有实现HandlerMapping接口的bean,并保存到DispatcherServlet的属性里面,后续处理请求需要用到。

RequestMappingInfo又是什么?

RequestMappingInfo是对带有@RequestMapping注解的类或者方法的一个描述。在匹配url时候有很重要的作用。

@RequestMapping注解一共有六项配置,可以看出这些配置项是用于限定被注解的方法对象可以处理哪些类型的request请求。当spring启动过程中创建HandlerMapping对象时,会寻找所有被@Controller注解的类中被@RequestMapping注解的方法对象,然后解析方法对象的@RequestMapping注解,把解析结果封装成RequestMappingInfo对象,也就是说RequestMappingInfo对象是用来装载请求处理方法的配置信息的,每个请求处理方法对象都会对应一个RequestMappingInfo对象。

@RequestMapping注解一共有6个配置项,这6个配置项其实就是6种过滤器,限定了请求处理方法对象可处理的请求,通过这6种过滤条器把方法不能处理的请求都pass掉,RequestMappingInfo对象持有这6种过滤器

所以我们需要看下这三个类,怎么被加载导IOC容器里面去的,并且在初始化的时候,会给这三个类所对应的对象,做什么初始化的操作。

1、加载DispatcherServlet

DispatcherServlet是通过加载DispatcherServletConfiguration创建的,而至于DispatcherServletConfiguration是怎么被加载到IOC里面,可以理解成是通过Spring.factories,自定义的启动器都是通过这种方式。

java 复制代码
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {

	@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
	public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
		DispatcherServlet dispatcherServlet = new DispatcherServlet();
		return dispatcherServlet;
	}
}

2、加载RequestMappingHandlerAdapter和RequestMappingHandlerMapping

RequestMappingHandlerAdapter和RequestMappingHandlerMapping的加载,是通过WebMvcAutoConfiguration这个配置类来完成的。而EnableWebMvcConfiguration是通过WebMvcAutoConfigurationAdapter导进来的,最开始还是通过Spring.factories这种方式。

java 复制代码
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
	........
}

//通过@Import导入EnableWebMvcConfiguration

@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

	//RequestMappingHandlerAdapter作用,通过url请求对应的Controller中的方法,返回执行结果值
	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
		RequestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
		......
		return 
	}

	//通过解析HadlerMapping注解,创建Mapping和Handler的controller的关系,关注是注册到了mappingRegistry里面去。
	@Bean	
	public RequestMappingHandlerMapping requestMappingHandlerMapping(){
		RequestMappingHandlerMapping requestMappingHandlerMapping = createRequestMappingHandlerMapping();
		......
		return requestMappingHandlerMapping;
	}
}

3、实例化DispatcherServlet

4、实例化RequestMappingHandlerAdapter

5、实例化RequestMappingHandlerMapping

RequestMappingHandlerMapping间接实现了InitializingBean,而InitializingBean就只有afterPropertiesSet方法,处理逻辑大部分都是afterPropertiesSet里面,看下afterPropertiesSet的逻辑,拿到所有的Bean类型,判断是否是Handler

java 复制代码
@Override
public void afterPropertiesSet() {
	//1、拿到所有的BeanName,判断是否是Handler
    for (String beanName : getCandidateBeanNames()) {  
        processCandidateBean(beanName);
    }
    handlerMethodsInitialized(getHandlerMethods());
}

protected void processCandidateBean(String beanName) {
	//获取Bean的类型,判断这个Bean是不是一个Handler,判断Handler的标准也很简单是否包含Controller和RequestMapping注解,如果符合就把里面的Method转成RequestMappingInfo对象,并保存到mappingRegistry里面去
	Class<?> beanType = obtainApplicationContext().getType(beanName);
	if (beanType != null && isHandler(beanType)) {
		detectHandlerMethods(beanName);
	}
}

detectHandlerMethods的处理逻辑

java 复制代码
protected void detectHandlerMethods(Object handler) {
	Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		
		//把带有@Controller和@RequestMapping注解的类,转成Map,key=类中的某个Method,value是这个方法所封装的RequestMappingInfo对象
		Map<Method, RequestMappingInfo> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					return getMappingForMethod(method, userType);
				});
		
		methods.forEach((method, mapping) -> {
			.....注册到mappingRegistry里面去,后面处理请求的时候,会通过url从里面找到对应的Method
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

//注册到mappingRegistry里面去
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}

二、调用

先说下调用过程中的流程

java 复制代码
1. 用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。
2. DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。
3. 处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet
4. DispatcherServlet会根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor)    --注,处理器适配器有多个
5. 处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller
6. Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)
7. HandlerAdaptor直接将ModelAndView交给DispatcherServlet ,至此,业务处理完毕
8. 业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet调用ViewResolver,将ModelAndView中的视图名称封装为视图对象
9. ViewResolver将封装好的视图 (View) 对象返回给 DIspatcherServlet
10. DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)
11. 前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。

1、initStrategies方法

第一次调用时会初始化DispatcherServlet类的9个属性,或者说是9个组件,这些属性其实可以把他理解成处理器,属性的赋值主要是通过调用initStrategies方法完成,为什么要提前给这些属性赋值,因为在处理请求的时候,要用到这些处理器,而且每个请求的用到的处理都是这些。

java 复制代码
public class DispatcherServlet extends FrameworkServlet {
	//比较重要的2个组件
	private List<HandlerMapping> handlerMappings;
	private List<HandlerAdapter> handlerAdapters;
	//次要的7个组件
	private MultipartResolver multipartResolver;
	private LocaleResolver localeResolver;
	private ThemeResolver themeResolver;
	private List<HandlerExceptionResolver> handlerExceptionResolvers;
	private RequestToViewNameTranslator viewNameTranslator;
	private FlashMapManager flashMapManager;

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	protected void initStrategies(ApplicationContext context) {
		//从IOC容器中拿到所有HandlerMapping实现类,存到DispatcherServlet的属性handlerMappings里面去
		initHandlerMappings(context);
		//拿到IOC容器中拿到所有HandlerAdapter实现类,存到DispatcherServlet的属性handlerAdapters里面去
		initHandlerAdapters(context);
		
		//下面也是一样,通过IOC获取不同类型Bean存到DispatcherServlet对应的属性里面去
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
}

Spring MVC 九大内置处理器

java 复制代码
1、HandlerMapping
根据request找到相应的处理器,这里说的处理器是指HandlerMethod。因为HandlerMethod有两中形式,一种是基于类的Handler,另一种是基于Method的Handler。

2、HandlerAdapter
HandlerAdapter负责调用Handler的适配器。如果把HandlerMethod当做工具的话,那么HandlerAdapter就相当于干活的工人,人通过工具完成一系列的事情。

3、HanderExceptionResolver
对异常的处理

4、ViewResolver
用来将String类型的视图名和Locale解析为View类型的视图

5、RequestToViewNameTranslator
有的Handler(Controller)处理完后没有设置返回类型,比如是Void方法,这是需要从request中获取的viewName

6、LocaleResolver
从request中解析出Locale。Locale表示一个区域,比如zh-cn,对于不同的区域的用户,显示不同的结果,这就是i18你(SpringMVC中有具体的拦截器LocaleChangelnterceptor)

7、ThemeResolver
主题解析,这种类似于我们手机的主题

8、MultipartResolver
处理上传请求,将普通的request封装成MultipartHttpServletRequest

9、FlashMapManager
用于管理FlashMap,FlashMap用于在redirect重定向中传递参数

2、doDispatch方法

在调用完initStrategies方法后,接着调用DispatcherServlet的doDispatch方法,这里才是开始处理request请求。处理时主要会通过HandlerMapping拿到符合request的HandlerMethod和HandlerInterceptor,HandlerMethod和HandlerInterceptor会被包装成HandlerExecutionChain。

接着在拿到HandlerAdapter,在通过HandlerAdapter调用HandlerMethod方法,也就是我们Controller里面的方法,HandlerAdapter再把方法返回的内容封装成ModelAndView对象返回出去

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//通过request判断是否是multipart类型,multipart就是上传的类型
	//通过multipartResolver将其封装成MultipartHttpServletRequest对象
	processedRequest = checkMultipart(request);

	//通过request的请求url,通过RequestMappingHandlerMapping,找到对应的HandlerMethod和HandlerInterceptor拦截器
	//并将HandlerMethod和HandlerInterceptor封装成HandlerExecutionChain
	//后面在调用HandlerExecutionChain的时候,会先调用拦截器的PreHandle方法,在调用具体的Controller方法,在调用拦截器的PostHandle方法
	mappedHandler = getHandler(processedRequest);
	
	if (mappedHandler == null) {
		//如果获取不到,则根据配置抛出异常或返回404错误
		noHandlerFound(processedRequest, response);
		return;
	}

	//获得当前handler对应的HandlerAdapter对象,Adapter就是请求对应handler方法,并返回视图
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

	//调用HandlerExecutionChain里面的拦截器PreHandle方法
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}
	
	//通过Adapter调用handler方法,并返回视图
	ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	//如果需要异步处理,直接返回
	if (asyncManager.isConcurrentHandlingStarted()) {
		return;
	}

	//当view为空时,根据request设置默认的view
	applyDefaultViewName(processedRequest, mv);
	
	//调用HandlerExecutionChain里面的拦截器PreHandle方法
	mappedHandler.applyPostHandle(processedRequest, response, mv);

	//处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

doDispatch() 主要执行流程

java 复制代码
1、获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器集合)
2、获得当前handler对应的HandlerAdapter对象

3、执行Interceptor拦截器的preHandler
4、执行Controller里面的Method方法
5、执行响应的interceptor的postHandler方法

6、处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion

挑几个上面重要的方法看下:getHandler、getHandlerAdapter、ha.handle

2.1 getHandler(processedRequest)

先看getHandler,该方法时拿到DispatcherServlet在initStrategies时,从IOC容器里面获取到的所有HandlerMapping的实现类,有多个,这里主要看RequestMappingHandlerMapping,在调用RequestMappingHandlerMapping的getHandler方法,该方法主要是,通过url和RequestMappingInfo匹配,返回RequestMappingInfo里面的HandlerMethod。

java 复制代码
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}
//通过request匹配正确的HandlerMethod
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	//从request拿到Url
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	
	this.mappingRegistry.acquireReadLock();//获取锁
	try {
		//通过Url匹配对应的HandlerMethod
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();//释放锁
	}
}


@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();

	//前面我们说了在初始化RequestMappingHandlerMapping对象的时候,会解析所有带有@RequestMapping注解的类,存到这个mappingRegistry里面去,key是url,value=RequestMappingInfo
	//URL进行精准匹配RequestMappingInfo
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);//如果匹配上了,就封装成Match存到matches里面去。Match是什么?
	}

	if (matches.isEmpty()) {
		//没有精确匹配上,则执行模糊匹配,模糊匹配是处理@RequestMapping的url中配了"xxx/{name}"
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Match bestMatch = matches.get(0);//默认拿第一个
		....
		//返回匹配好的方法,也就是HandlerMethod
		return bestMatch.handlerMethod;
	} else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

2.2 getHandlerAdapter(mappedHandler.getHandler())

获得当前handler对应的HandlerAdapter对象

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

DispatcherServlet在initStrategies时,从IOC容器里面获取到的所有HandlerAdapter的实现类,也是会拿到很多实现类,这里也包含我们要用到RequestMappingHandlerAdapter

java 复制代码
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	//拿到多个handlerAdapters,返回其中一个,过滤也没什么逻辑,都是返回第一个Adapters
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
}

2.3 ha.handle(processedRequest, response, mappedHandler.getHandler())

通过Adapter调用handler方法,并返回视图

ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

java 复制代码
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	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);

	//封装model和view的容器
	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();
		invocableMethod = invocableMethod.wrapConcurrentResult(result);
	}

	//调用Controller中的具体方法并处理返回值
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	if (asyncManager.isConcurrentHandlingStarted()) {
		return null;
	}

	return getModelAndView(mavContainer, modelFactory, webRequest);
}
相关推荐
测试老哥3 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
ThisIsClark5 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
测试19987 小时前
外包干了2年,技术退步明显....
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
Aphasia3117 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试
GISer_Jing9 小时前
2025年前端面试热门题目——HTML|CSS|Javascript|TS知识
前端·javascript·面试·html
上海运维Q先生10 小时前
面试题整理14----kube-proxy有什么作用
运维·面试·kubernetes
开发者每周简报11 小时前
求职市场变化
人工智能·面试·职场和发展
贵州晓智信息科技15 小时前
如何优化求职简历从模板选择到面试准备
面试·职场和发展
百罹鸟15 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
古木20191 天前
前端面试宝典
前端·面试·职场和发展