共计 17593 个字符,预计需要花费 44 分钟才能阅读完成。
前言
应用 SpringBoot 进行 web 开发时,控制器类由 @RestController 注解润饰,通常 @RestController 注解与 @RequestMapping 配合应用,被润饰的类用于解决由 DispatcherServlet 散发下来的 web 申请。那么当一个 web 申请达到时,DispatcherServlet 是如何将申请下发给对应的控制器解决呢。该篇文章将联合 SpringMVC 源码,对在散发申请过程中起重要作用的类 RequestMappingHandlerMapping 进行学习。
SpringBoot 版本:2.4.1
注释
一. DispatcherServlet 散发申请
当一个 web 申请到来时,DispatcherServlet 负责接管申请并响应后果。DispatcherServlet 首先须要找到以后申请对应的 Handler(处理器)来解决申请,流程如下图所示。
HandlerMapping 称为处理器映射器,是一个接口,定义 web 申请和 Handler 之间的映射。DispatcherServlet 中有一个成员变量叫做 handlerMappings,是一个 HandlerMapping 的汇合,当申请到来时,DispatcherServlet 遍历 handlerMappings 中的每一个 HandlerMapping 以获取对应的 handler。上述步骤产生在 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);
// 依据申请获取 Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {noHandlerFound(processedRequest, response);
return;
}
......
}
catch (Exception ex) {......}
catch (Throwable err) {......}
......
}
catch (Exception ex) {......}
catch (Throwable err) {......}
finally {......}
}
Handler 的获取由 DispatcherServlet 的 getHandler() 办法实现,上面再看一下 getHandler() 具体做了什么事件。
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;
}
前文已知 handlerMappings 是 HandlerMapping 的汇合,因而 getHandler() 次要实现遍历每一个 HandlerMapping 并依据申请获取对应的 Handler。仅看源码不够直观,当初通过打断点的形式理论看一下 handlerMappings 里的内容。
察看 handlerMappings 的内容能够发现,handlerMappings 中加载了 ApplicationContext 中的所有 HandlerMapping,例如 BeanNameUrlHandlerMapping,将 url 与名称以 / 结尾的 bean 建设了映射关系,再例如本文重点探讨的 RequestMappingHandlerMapping,可能将 @Controller 注解润饰的类中的 @RequestMapping 注解的内容解析成 RequestMappingInfo 数据结构。每一种 HandlerMapping 都有本人相应的实现,来实现通过申请获取 Handler 的性能。
大节:DispatcherServlet 散发申请次要是通过遍历 HandlerMapping 的汇合并将申请传递给 HandlerMapping 以获取对应的 Handler。
二. RequestMappingHandlerMapping 初始化
首先通过类图认识一下 RequestMappingHandlerMapping。
由类图可知,RequestMappingHandlerMapping 的父类 AbstractHandlerMethodMapping 实现了 InitializingBean 接口,RequestMappingHandlerMapping 和 AbstractHandlerMethodMapping 均实现了 afterPropertiesSet() 办法,该办法会在 bean 属性实现初始化后被调用。这里先剖析 RequestMappingHandlerMapping 实现的 afterPropertiesSet() 办法。
public void afterPropertiesSet() {this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
if (getPatternParser() != null) {this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}
// 调用 AbstractHandlerMethodMapping 的 afterPropertiesSet() 办法
super.afterPropertiesSet();}
在 RequestMappingHandlerMapping 的 afterPropertiesSet() 办法中调用了 AbstractHandlerMethodMapping 的 afterPropertiesSet() 办法,上面再剖析 AbstractHandlerMethodMapping 的 afterPropertiesSet() 办法。
public void afterPropertiesSet() {initHandlerMethods();
}
protected void initHandlerMethods() {
// 获取容器中所有 bean 的名称
for (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 依据 bean 名称解决 bean
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 先获取容器,而后依据 bean 名称获取 bean 的 Class 对象
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean'" + beanName + "'", ex);
}
}
// 判断 bean 是否是由 @Controller 注解或者 @RequestMapping 注解润饰的对象
if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);
}
}
在 AbstractHandlerMethodMapping 的 afterPropertiesSet() 办法中,调用了 initHandlerMethods() 办法,该办法次要是从容器中将所有 bean 获取进去(这里是获取的所有 bean 的名称),而后又在 processCandidateBean() 办法中判断每个 bean 是否是由 @Controller 注解或者 @RequestMapping 注解润饰的对象,如果是则判断该 bean 为一个处理器,则须要在 detectHandlerMethods() 办法中查找出该处理器的处理器办法。detectHandlerMethods() 办法是一个重要办法,并且浏览起来有一点绕,上面具体看一下这个办法做的事件。
protected void detectHandlerMethods(Object handler) {
// 获取 handler 的 Class 对象
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 获取 handler 的实在 Class 对象(假若 handler 是 cglib 代理生成的子类,则获取原始类的 Class 对象)Class<?> userType = ClassUtils.getUserClass(handlerType);
// 调用 getMappingForMethod() 获取 method 和 RequestMappingInfo 的 map 汇合
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {// 在 selectMethods() 办法中理论调用的是 getMappingForMethod() 办法
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);
// 将 handler,method 和 RequestMappingInfo 缓存,并建设映射关系
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
detectHandlerMethods() 中首先是获取 handler 的实在 Class 对象,而后应用 MethodIntrospector.selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) 办法将 handler 的办法解析成 <Method, RequestMappingInfo> 的 map 汇合。metadataLookup 是一个回调函数,metadataLookup 的具体应用稍后再剖析,当初再看一下 MethodIntrospector.selectMethods() 的具体实现。
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {final Map<Method, T> methodMap = new LinkedHashMap<>();
Set<Class<?>> handlerTypes = new LinkedHashSet<>();
Class<?> specificHandlerType = null;
// 判断给定对象是否是 JDK 动静代理生成对象
// 如果不是,(如果是 CGLIB 动静代理生成对象)则获取其原始类的 Class 对象,并增加到 Class 的 Set 汇合中
if (!Proxy.isProxyClass(targetType)) {specificHandlerType = ClassUtils.getUserClass(targetType);
handlerTypes.add(specificHandlerType);
}
// 获取给定对象和给定对象父类实现的所有接口的 Class 对象,并增加到 Class 的 Set 汇合中
handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));
for (Class<?> currentHandlerType : handlerTypes) {final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
// 获取实在办法(如果办法是接口的办法则依据 Class 对象找到实在实现的办法)Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// 执行回调函数 metadataLookup
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
// 依据实在办法获取其桥接办法,但如果实在办法不是桥接办法则返回其自身
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
// 将实在办法与其回调函数执行后果寄存到 map 中
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
在 MethodIntrospector.selectMethods() 中有一个 Class 对象的 Set 汇合,外面寄存了给定对象的 Class 对象以及给定对象实现的接口的 Class 对象(如果给定对象有父类,则还包含父类实现的接口的 Class 对象),而后遍历 Set 汇合,并应用 ReflectionUtils.doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) 解决 Set 汇合中的每一个 Class 对象。mc 是一个回调函数,mc 的具体应用稍后剖析,最初再来看一下 ReflectionUtils.doWithMethods() 的具体应用。
public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {
// 获取给定对象的所有申明办法
Method[] methods = getDeclaredMethods(clazz, false);
for (Method method : methods) {
// 对 method 依据传入的 MethodFilter 进行过滤,满足指定的条件的 method 才执行回调办法
if (mf != null && !mf.matches(method)) {continue;}
try {
// 对满足条件的 method 执行回调办法
mc.doWith(method);
}
catch (IllegalAccessException ex) {throw new IllegalStateException("Not allowed to access method'" + method.getName() + "':" + ex);
}
}
// 递归对给定对象的父对象执行雷同操作
if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) {doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {for (Class<?> superIfc : clazz.getInterfaces()) {doWithMethods(superIfc, mc, mf);
}
}
}
ReflectionUtils.doWithMethods() 中做的事件很简略,先将给定的 Class 对象的所有申明办法获取进去,而后针对每一个申明办法用给定的 MethodFilter 进行过滤,再将过滤后的申明办法传入回到函数 mc 并执行,(当初往前推)回调函数 mc 中理论就是将申明办法传入回调函数 metadataLookup 并执行,而后将申明办法和 metadataLookup 执行失去的后果存入 map 汇合,回调函数 metadataLookup 中理论就是将申明办法传入 getMappingForMethod() 办法,在 getMappingForMethod() 中会将申明办法和 handler 上的 @RequestMapping 注解信息解析成 RequestMappingInfo 并返回。
前文可知 MethodIntrospector.selectMethods() 中调用 ReflectionUtils.doWithMethods() 时传入的 MethodFilter 为 ReflectionUtils.USER_DECLARED_METHODS,ReflectionUtils.USER_DECLARED_METHODS 示意如果办法即不是桥接办法也不是合成办法时则匹配胜利,此时调用 matches() 返回 true。
阐明一下 getMappingForMethod() 办法,该办法是 AbstractHandlerMethodMapping 申明的形象办法,RequestMappingHandlerMapping 对其的实现如下。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 将 method 上的 @RequestMapping 注解内容解析为 RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 将类上的 @RequestMapping 注解内容解析为 RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 将 method 和类上的 @RequestMethod 注解解析成的 RequestMappingInfo 组合
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
RequestMappingHandlerMapping 在 getMappingForMethod() 中先后别离获取办法和类的 @RequestMapping 注解解析成的 RequestMappingInfo 并进行组合。@RequestMapping 注解信息的解析产生在 createRequestMappingInfo() 办法中,其实现如下。
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 查找 @RequestMapping 注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
// 可能查找到 @RequestMapping 注解,解析 @RequestMapping 的信息
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {builder.customCondition(customCondition);
}
return builder.options(this.config).build();}
最初回到 detectHandlerMethods() 办法,该办法中执行完 MethodIntrospector.selectMethods() 后会失去 method 和 RequestMappingInfo 的 map 汇合,而后遍历 map 汇合并调用 registerHandlerMethod() 办法将 handler,method 和 RequestMappingInfo 缓存,并建设映射关系。registerHandlerMethod() 办法中会调用 mappingRegistry 的 register() 办法,mappingRegistry 是 AbstractHandlerMethodMapping 的一个外部类对象,register() 办法次要是将 handler,method 和 RequestMappingInfo 写入 mappingRegistry 的 pathLookUp,nameLookUp,corsLookUp 和 registry 数据结构中。相干源码如下所示。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();
try {
// 获取 HandlerMethod 的实例,HandlerMethod 对 handler method 进行了一层封装,其持有 handler 的对象,并且能够不便获取办法入参和出参
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 在 registry 中依据 RequestMappingInfo 获取曾经缓存的 cachedHandlerMethod,如果 cachedHandlerMethod 不为空且不等于 handlerMethod,则报错
validateMethodMapping(handlerMethod, mapping);
// 依据 RequestMappingInfo 获取 path 的 Set 汇合,并建设 path 和 RequestMappingInfo 的映射关系:Map<String, List<RequestMappingInfo>> pathLookUp
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
// 拼接 name
// 规定:handler 的 Class 对象名称取大写字母 + # + 办法名
name = getNamingStrategy().getName(handlerMethod, mapping);
// 建设 name 与 handlerMethod 的映射关系:Map<String, List<HandlerMethod>> nameLookup
addMappingName(name, handlerMethod);
}
// 获取跨域配置对象
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
if (config != null) {config.validateAllowCredentials();
// 建设 handlerMethod 和跨域配置对象的映射关系:Map<HandlerMethod, CorsConfiguration> corsLookup
this.corsLookup.put(handlerMethod, config);
}
// 建设 RequestMappingInfo 与 MappingRegistration 的映射关系:Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>>
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
}
finally {this.readWriteLock.writeLock().unlock();}
}
MappingRegistration 将传入的 RequestMappingInfo,获取的 handlerMethod,取得的 directPaths 和拼接的 name 做了一层封装。
static class MappingRegistration<T> {
private final T mapping;
private final HandlerMethod handlerMethod;
private final Set<String> directPaths;
@Nullable
private final String mappingName;
public MappingRegistration(T mapping, HandlerMethod handlerMethod,
@Nullable Set<String> directPaths, @Nullable String mappingName) {Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;
this.handlerMethod = handlerMethod;
this.directPaths = (directPaths != null ? directPaths : Collections.emptySet());
this.mappingName = mappingName;
}
public T getMapping() {return this.mapping;}
public HandlerMethod getHandlerMethod() {return this.handlerMethod;}
public Set<String> getDirectPaths() {return this.directPaths;}
@Nullable
public String getMappingName() {return this.mappingName;}
}
大节:RequestMappingHandlerMapping 初始化时会先获取容器中所有被 @Controller 注解或 @RequestMapping 注解润饰的类的对象(handler 对象),而后遍历这些对象和其父对象的所有办法,将这些办法的 @RequestMapping 注解信息(如果有)解析成 RequestMappingInfo,最初将 handler 对象,handler 办法和 RequestMappingInfo 退出缓存并建设映射关系。
三. RequestMappingHandlerMapping 获取 handler
回顾上文,一大节中提到,web 申请来到 DispatcherServlet 之后,会先遍历 HandlerMapping 的汇合,而后将申请传入 HandlerMapping 并获取 handler。再贴出源码如下所示。
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;
}
实际上,RequestMappingHandlerMapping 获取 handler 是产生在其父类 AbstractHandlerMapping 的 getHandler() 办法中,源码如下。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 依据 request 获取 handler,理论获取到的 handler 是一个 HandlerMethod 对象
Object handler = getHandlerInternal(request);
if (handler == null) {handler = getDefaultHandler();
}
if (handler == null) {return null;}
if (handler instanceof String) {String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 依据 request 和 handler 创立 HandlerExecutionChain 对象,该对象还会蕴含和 request 匹配的拦截器
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {logger.trace("Mapped to" + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {logger.debug("Mapped to" + executionChain.getHandler());
}
// 如果 handler 有跨域配置,则更新 HandlerExecutionChain 对象使得其能够进行跨域解决
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandler() 要害的操作就是在 getHandlerInternal() 办法中依据 request 获取到了 handler 对象(理论是一个 HandlerMethod 对象),RequestMappingHandlerMapping 的 getHandlerInternal() 会调用父类 AbstractHandlerMethodMapping 的 getHandlerInternal() 办法,当初看一下其做了什么事件。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 依据 request 获取申请门路
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
// 依据申请门路和 request 获取最匹配的 HandlerMethod 对象
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();
// 通过缓存 pathLookup 获取申请门路映射的 RequestMappingInfo 汇合
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 汇合中的每一个 RequestMappingInfo 均会和 request 进行匹配,匹配上的话就创立一个 Match 对象并退出 Match 对象汇合
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
//Match 汇合不为空则从 Match 汇合中找到最匹配的 Match 对象,并返回该 Match 对象的 HandlerMethod 对象
if (!matches.isEmpty()) {Match bestMatch = matches.get(0);
if (matches.size() > 1) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {logger.trace(matches.size() + "matching mappings:" + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException("Ambiguous handler methods mapped for'" + uri + "': {" + m1 + "," + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
大节:RequestMappingHandlerMapping 获取 handler 理论就是依据 request 在映射缓存中寻找最匹配的 HandlerMethod 对象并封装成 HandlerExecutionChain。
总结
RequestMappingHandlerMapping 次要用于 @Controller 注解和 @RequestMapping 注解联合应用的场景,可能将咱们编写的控制器信息缓存并在申请到来时依据申请信息找到最合适的控制器来解决申请。最初,学习 SpringMVC 源码,通过打断点察看外部数据结构的形式往往可能更直观的帮忙咱们了解,值得尝试。
第一次写博客,集体能力无限,有许多中央不够深刻,也有许多中央了解存在偏差,敬请大家批评指正。