SpringMVC源码逻辑解析
url访问servlet流程主要有两个阶段:构建url和method的映射关系阶段、映射访问Controller方法阶段;在spring中前者是在bean初始化阶段完成,后置在用户发起请求后完成。
构建url和method的映射关系
//WebMvcConfigurationSupport#requestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
//省略....
return mapping;
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
//省略....
return mapping;
}
在spring的mvc模块中有个@Configuration配置类:DelegatingWebMvcConfiguration,他(父类)注册了MVC用到的核心组件:RequestMappingHandlerMapping和RequestMappingHandlerAdapter等等,前者就是构建url和method的映射关系的bean,后者是回调执行Controller方法的bean,这里只看RequestMappingHandlerMapping。
RequestMappingHandlerMapping实现了InitializingBean接口,在初始化时会执行afterPropertiesSet方法,这里就是构建url和method的映射关系的地方:
//AbstractHandlerMethodMapping#afterPropertiesSet
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
//RequestMappingHandlerMapping#isHandler
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
我们可以看到上述代码的逻辑是循环spring bean名称列表,根据class对象判断是否存在Controller.class或RequestMapping.class,之后进一步执行detectHandlerMethods(beanName)
//AbstractHandlerMethodMapping#detectHandlerMethods
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//创建RequestMappingInfo对象,对应Map的T
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//创建urlLookup和mappingLookup列表
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
//RequestMappingHandlerMapping#getMappingForMethod
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//获取method上的RequestMapping注解信息
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//获取class上的RequestMapping注解信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//拼接两端url信息
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).build().combine(info);
}
}
return info;
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
这里是构建url映射关系的核心类,先获取class对象,根据class遍历method列表,每个method执行一次getMappingForMethod方法,getMappingForMethod方法会获取class和method上的RequestMapping注解值,并拼接成一个对象,循环完后形了Map对象methods;之后,再循环methods,根据key(Method)和value(RequestMappingInfo),循环执行registerHandlerMethod方法
//AbstractHandlerMethodMapping#registerHandlerMethod
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
//mapping为RequestMappingInfo,handler为类名,method为方法对象
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
这里就很好明白了,根据mapping(RequestMappingInfo),handler(类名),method(方法对象)三个参数构建两个map:urlLookup<url, RequestMappingInfo>和mappingLookup<RequestMappingInfo, HandlerMethod>。之后的servlet请求就是依据请求url从这两个map中获取到HandlerMethod对象的。
url请求DispatcherServlet
DispatcherServlet就是springMVC中构建的通用servlet处理器,我们通过发起请求来了解DispatcherServlet的init方法和service方法的处理逻辑。
先看DispatcherServlet的initStrategies方法,这是servlet的的init方法最后执行的逻辑(顺着继承链往上可以找到init方法)
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//完成初始化工作,从IOC容器中获取MVC需要用到的九大组件
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
此处的初始化逻辑是从IOC容器中或创建MVC要用到的九大组件,详细逻辑参考下图:
初始化完成后,直接找到DispatcherServlet的doDispatch(),这是请求跳转方法的核心方法(顺着继承链往上可以找到service方法)
//DispatcherServlet#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);
//根据request从RequestMappingHandlerMapping中找到HandlerMethod,将HandlerMethod封装成
//HandlerExecutionChain,之后将adaptedInterceptors拦截器对象,加入HandlerExecutionChain中
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//根据HandlerMethod获取RequestMappingHandlerAdapter对象
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;
}
}
//Interceptor接口对象前置方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//通过RequestMappingHandlerAdapter对象回调中的方法HandlerMethod
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//Interceptor接口对象处理方法
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、getHandlerAdapter、ha.handle中,通过这三个步骤,就完成了@RequestMapping方法的回调,并将结果写入response中。下面从getHandler开始看起:
//DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//循环handlerMappings,返回非null的HandlerExecutionChain对象(一般指RequestMappingHandlerMapping)
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
//AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取handlerMethod对象
Object handler = this.getHandlerInternal(request);
if (handler == null) {
handler = this.getDefaultHandler();
}
if (handler == null) {
return null;
} else {
if (handler instanceof String) {
String handlerName = (String)handler;
handler = this.obtainApplicationContext().getBean(handlerName);
}
//将handlerMethod和request封装成HandlerExecutionChain
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
//省略...
return executionChain;
}
}
//AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//从request中截取请求url
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
HandlerMethod var4;
try {
//根据lookupPath从RequestMappingHandlerMapping中的urlLookup和mappingLookup获取到HandlerMethod
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;
} finally {
this.mappingRegistry.releaseReadLock();
}
return var4;
}
//AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//将HandlerMethod封装成HandlerExecutionChain
HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
Iterator var5 = this.adaptedInterceptors.iterator();
//将adaptedInterceptors拦截对象写入到chain中
while(var5.hasNext()) {
HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandler的工作是返回一个HandlerExecutionChain对象,通过循环handlerMappings列表,获取非null的Mapping映射对象返回chain,这个对象一般是RequestMappingHandlerMapping,我们继续跟进找到对应的方法getHandlerInternal,发现他是通过获取resquest中的url,来匹配RequestMappingHandlerMapping中的urlLookup和mappingLookup获取到HandlerMethod对象,拿到HandlerMethod后,经过getHandlerExecutionChain的处理将其封装成HandlerExecutionChain后写入adaptedInterceptors拦截器列表后返回HandlerExecutionChain对象。(总结:RequestMappingHandlerMapping用于使请求url成功匹配到要回调的Controller方法)
拿到HandlerExecutionChain对象后,来看getHandlerAdapter的逻辑:
//DispatcherServlet#getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
//返回能支持匹配HandlerMethod对象的
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
逻辑和之前的getHandler类似,从handlerAdapters列表中返回一个支持匹配HandlerMethod的对象,这个对象一般是RequestMappingHandlerAdapter
拿到HandlerExecutionChain和RequestMappingHandlerAdapter后,就要用RequestMappingHandlerAdapter来回调HandlerMethod的对象了,来看ha.handle的逻辑:
//.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return this.handleInternal(request, response, (HandlerMethod)handler);
}
//RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
this.checkRequest(request);
ModelAndView mav;
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
//解析request请求参数,调用handlerMethod
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
//RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try {
//创建数据绑定工厂
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
//ServletInvocableHandlerMethod是处理调用handlerMethod的真正对象
ServletInvocableHandlerMethod invocableMethod = this.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();
//视图写入request参数
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
//省略...
//进一步处理
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if (!asyncManager.isConcurrentHandlingStarted()) {
ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
return var15;
}
result = null;
} finally {
webRequest.requestCompleted();
}
return (ModelAndView)result;
}
//ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//进一步回调
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
//处理回调视图
if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
this.disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//根据返回值和类型及其注解获取HandlerMethodReturnValueHandler对象,完成处理返回值的工作,包括是否返回视图还是数据
this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception var6) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatErrorForReturnValue(returnValue), var6);
}
throw var6;
}
}
//InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//处理request获取参数列表
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
//回调方法
return this.doInvoke(args);
}
//InvocableHandlerMethod#getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//获取request中的参数列表
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
//通过providedArgs获取参数值,一般是null
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//通过HandlerMethodArgumentResolverComposite对象处理参数
//主要是通过参数及参数上的注解获取对应的HandlerMethodArgumentResolver对象,之后调用
//resolveArgument方法,方法中创建了WebDataBinder对象获取spring中ConversionService对象
//通过当前参数类型和目标参数类型的对应关系匹配ConversionService中的类型转换列表,来获取类型转换对象
//之后调用类型转换对象的convert方法完成类型的转换
//(ConversionService中的类型转换列表可以认为是spring内部的N多个实现了Converter<>接口的对象集合)
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (this.logger.isDebugEnabled()) {
String exMsg = var10.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
this.logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw var10;
}
}
}
return args;
}
}
//InvocableHandlerMethod#doInvoke
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(this.getBridgedMethod());
//回调Controller方法
return this.getBridgedMethod().invoke(this.getBean(), args);
}
ha.handle的逻辑比较的复杂,主要逻辑涉及参数类型的转换问题和是否返回视图数据对象。在RequestMappingHandlerAdapter组件中,经过层层调用,组织了一个真正的处理对象ServletInvocableHandlerMethod,并将初始化阶段创建的参数解析对象、返回值解析对象、数据绑定对象写入其中;使用参数解析对象通过数据绑定对象获取spring中ConversionService对象,借助ConversionService完成类型转换工作,之后将参数组织成数组回调Controller方法;之后获取回调值和返回类型,根据返回值解析对象获取对应的HandlerMethodReturnValueHandler对象,通过执行handleReturnValue方法完成返回值和视图的处理。(总结:RequestMappingHandlerAdapter主要用于处理参数,回调的Controller方法,及之后的返回视图数据问题)
整个DispatcherServlet的处理逻辑如下图所示:
总结
- 在spring实例化内部对象RequestMappingHandlerMapping.class时,会执行InitalizBean接口方法进行bean的class列表遍历,判断bean是否包含**@Controller.class和@RequestMapping.class**注解。
- 不包含则跳过,包含则进一步获取bean的全部Method列表进行遍历,若方法有**@RequestMapping.class注解则尝试获取类上的@RequestMapping.class**注解,将两段注解值进行拼接获取请求url和Method的对象关系,之后对获取的请求信息进行缓存,主要组成一个MappingRegistry对象存储所有的映射关系,MappingRegistry对象主要有两个重要的Map对象 :urlLookup(Map<url, RequestMappingInfo>)和mappingLookup(RequestMappingInfo, HandlerMethod)。(HandlerMethod包含方法的参数信息)
- 这样在后面的servlet请求中就可以根据requset中的url地址对MappingRegistry进行匹配:url ->匹配到urlLookup信息获取RequestMappingInfo对象,又可以根据RequestMappingInfo->匹配mappingLookup获取HandlerMethod对象。(具体逻辑在方法org.springframework.web.servlet.DispatcherServlet#getHandler中)。
- 之后根据HandlerMethod获取对应的HandlerAdapter对象(默认是RequestMappingHandlerAdapter可解析**@RequestMapping.class**),使用适配器对象去反射回调HandlerMethod中的method(回调之前会使用HandlerMethod包含的方法参数匹配request中的参数列表返回一个参数数组)。
本文地址:https://blog.csdn.net/baidu_38304104/article/details/107390895
上一篇: PHP下ereg实现匹配ip的正则
下一篇: 一直被问如何搞流量!?写下了这篇血泪文
推荐阅读
-
深入源码解析Python中的对象与类型
-
从vue源码解析Vue.set()和this.$set()
-
【spring-boot 源码解析】spring-boot 依赖管理
-
深入理解react-router@4.0 使用和源码解析
-
springboot源码怎么看(springboot源码深度解析)
-
java源码解析(java新手代码大全)
-
stringbuffer截取字符串的下标(解析springmvc工作流程)
-
Springboot源码 TargetSource解析
-
Springboot源码 AbstractAdvisorAutoProxyCreator解析
-
【spring-boot 源码解析】spring-boot 依赖管理梳理图