【Spring MVC】处理器映射器:AbstractHandlerMethodMapping源码分析

目录

一、继承体系

二、HandlerMapping

三、AbstractHandlerMapping

四、AbstractHandlerMethodMapping

4.1 成员属性

4.1.1 MappingRegistry内部类

4.2 AbstractHandlerMethodMapping的初始化

4.3 getHandlerInternal()方法:根据当前的请求url,获取对应的处理器HandlerMathod

五、RequestMappingInfoHandlerMapping

5.1 成员属性和构造方法

5.2 该类复写了一些方法

5.2.1 getMappingPathPatterns

5.2.2 getMatchingMapping

5.2.3 getMappingComparator

5.2.4 handleMatch,handleNoMatch

六、RequestMappingHandlerMapping

6.1 成员属性

6.2 主要方法

6.2.1 覆写了afterPropertiesSet()

6.2.2 isHandler()

6.2.3 getMappingForMethod()


我们现在最流行的就是使用注解实现Controller,那这就会涉及到AbstractHandlerMethodMapping,这个类在我们分析处理请求的源码中非常重要,所以这里单独拿出来分析。

一、继承体系

二、HandlerMapping

HandlerMapping是处理器映射器的顶层接口,只声明了1个方法–>getHandler–>调用getHandler实际上返回的是一个HandlerExecutionChain,这是典型的command的模式(命令模式)的使用,这个HandlerExecutionChain不但持有Handler本身,还包括了处理这个HTTP请求相关的拦截器,方法原型如下:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

三、AbstractHandlerMapping

实现HandlerMapping的抽象实现,模板方法模式,将一些共性的方法抽象成1个类。其实现了getHandler。如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 首先根据request获取handlerObject handler = getHandlerInternal(request);// 如果没有指定handler,就使用默认的if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}// 获取到了handler之后,再去获取拦截器,将两者封装到处理器执行链中返回return getHandlerExecutionChain(handler, request);
}

流程:

  1. 根据request获取对应的handler,该方法是1个抽象方法,由子类来实现,如下:

        

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
  1. 如果没有对应的handler,就是要默认的handler
  2. 如果没有默认的handler,返回null
  3. 如果获取到的handler是一个字符串,说明这个是Bean名,则通过名称取出对应的 handler bean
  4. 把handler 封装到HandlerExecutionChain中并加上拦截器。如下:

        

		protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {// 1. 获得HandlerExecutionChainHandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));// 2. 根据请求获得对应的PathString lookupPath = this.urlPathHelper.getLookupPathForRequest(request);// 3. 遍历adaptedInterceptorsfor (HandlerInterceptor interceptor : this.adaptedInterceptors) {// 3.1 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中if (interceptor instanceof MappedInterceptor) {MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {chain.addInterceptor(mappedInterceptor.getInterceptor());}}// 3.2 否则,直接加入到HandlerExecutionChainelse {chain.addInterceptor(interceptor);}}return chain;
}
  1. 获得HandlerExecutionChain
  2. 根据请求获得对应的Path
  3. 遍历adaptedInterceptors(拦截器)
    1. 如果是MappedInterceptor,并且匹配当前的path,则加入到HandlerExecutionChain中
    2. 否则,直接加入到HandlerExecutionChain

四、AbstractHandlerMethodMapping

AbstractHandlerMethodMapping是一个泛型类,其泛型参数T–>用来代表匹配handler的条件专门使用的一种类,这里的条件就不只是url了,还可以有很多其他条件,如request的类型,请求的参数,header等都可以作为匹配的HandlerMethod的条件。默认使用的是RequestMappingInfo,这个也是最常见的情况(只要是用注解实现Controller一般都是用RequestMappingInfo作为匹配条件类)。AbstractHandlerMethodMapping实现了InitializingBean接口。

4.1 成员属性

// scpoed 代理 bean的name的前缀。用来去除handler method的判断
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";// cors请求并且是options类型的请求并且请求头中含有Access-Control-Request-Method时返回的HandlerMethod
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();static {ALLOW_CORS_CONFIG.addAllowedOrigin("*");ALLOW_CORS_CONFIG.addAllowedMethod("*");ALLOW_CORS_CONFIG.addAllowedHeader("*");ALLOW_CORS_CONFIG.setAllowCredentials(true);
}// 如果为true,则在当前applicationContext和祖先applicationContext中获取所有的bean,如果为false,则在当前上下文获得所有的bean
private boolean detectHandlerMethodsInAncestorContexts = false;// 向MappingRegistry中的nameLookup进行注册时用来生成beanName,这里默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy
// 其规则为:类名里的大写字母组合+"#"+方法名.
private HandlerMethodMappingNamingStrategy<T> namingStrategy;// 用来存储各种映射关系
private final MappingRegistry mappingRegistry = new MappingRegistry();

4.1.1 MappingRegistry内部类

这里有必要说明一下MappingRegistry类,它是AbstractHandlerMethodMapping的内部类,其成员属性如下:

class MappingRegistry {private final Map<T, MappingRegistration<T>> registry = new HashMap<T, MappingRegistration<T>>();// 保存着匹配条件(也就是RequestMappingInfo)和HandlerMethod的对应关系private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>();// 保存着url与匹配条件(也就是RequestMappingInfo)的对应关系,当然这里的url是pattren式的,可以使用通配符。// 由于RequestMappingInfo可以同时使用多种不同的匹配方式而不只是url一种,所以反过来说同一个url就可能有多个RequestMappingInfo与之对应// 这里的RequestMappingInfo其实就是在@RequestMapping中注释的内容private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>();// 这个Map是spring mvc 4 新增的,保存着name(Controller中处理请求的方法名)与HandlerMethod的对应关系,这个name是从HandlerMethodMappingNamingStrategy的实现类从// HandlerMethod中解析处理的,默认使用的是RequestMappingInfoHandlerMethodMappingNamingStrategy,解析规则是:// 类名里的大写字母组合+"#"+方法名。这个在正常的匹配过程不需要使用,它主要用在MvcUriComponentsBuilder里,可以根据name获取相应的urlprivate final Map<String, List<HandlerMethod>> nameLookup =new ConcurrentHashMap<String, List<HandlerMethod>>();private final Map<HandlerMethod, CorsConfiguration> corsLookup =new ConcurrentHashMap<HandlerMethod, CorsConfiguration>();private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();...
}

注意:带有通配符的路径匹配不在urlLookup属性参数中,只存在了registry。直接匹配所有的参数路径中,才会两者都存。

4.2 AbstractHandlerMethodMapping的初始化

由于AbstractHandlerMethodMapping实现了InitializingBean,因此在其初始化过程中,会调用afterPropertiesSet方法,如下:

public void afterPropertiesSet() {initHandlerMethods();
}

这里我们就可以看出,在初始化AbstractHandlerMethodMapping类的时候,就自动调用了initHandlerMethods方法。这个方法其实就帮我们提前建立起了urlmethod之间的映射关系。在后面处理请求的时候可以根据url直接找到要处理该请求的method。

protected void initHandlerMethods() {if (logger.isDebugEnabled()) {logger.debug("Looking for request mappings in application context: " + getApplicationContext());}// 获取ApplicationContext中的所有bean的nameString[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :getApplicationContext().getBeanNamesForType(Object.class));// 遍历所有的bean namefor (String beanName : beanNames) {// 如果bean nanme 不是 scopedTarget开头的,则获得其类型if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {Class<?> beanType = null;try {// 获取bean的类型beanType = getApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);}}// 如果beanType不为空,并且是一个处理器,则进行处理if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}}// 所有的处理器方法都初始化完成后,调用子类的方法handlerMethodsInitialized(getHandlerMethods());
}

流程:

  1. 获得ApplicationContext中的所有bean的name
  2. 遍历
    1. 如果bean name 不是 scopedTarget开头的,则获得其类型
    2. 如果该bean是一个handler(处理器),则调用detectHandlerMethods()对其进行注册。detectHandlerMethods()方法源码如下:

        

		protected void detectHandlerMethods(final Object handler) {// 获得handler的类型Class<?> handlerType = (handler instanceof String ?getApplicationContext().getType((String) handler) : handler.getClass());// 如果是cglib代理的子对象类型,则返回父类型,否则直接返回传入的类型final Class<?> userType = ClassUtils.getUserClass(handlerType);// 获取当前bean里所有符合Handler要求的Method(也就是获取当前Controller中所有的用来处理请求的方法)Map<Method, T> methods = MethodIntrospector.selectMethods(userType,new MethodIntrospector.MetadataLookup<T>() {@Overridepublic T inspect(Method method) {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}}});// 将符合要求的methods注册到mappingRegistry中,也就是保存到mappingRegistry的3个map中for (Map.Entry<Method, T> entry : methods.entrySet()) {Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);T mapping = entry.getValue();registerHandlerMethod(handler, invocableMethod, mapping);}}
  1. 获得handler的类型,如果是cglib代理的子对象类型,则返回父类型,否则直接返回传入的类型。
  2. 获取当前bean里所有符合Handler要求的Method,其中会回调getMappingForMethod方法,该方法是个抽象方法,由子类实现。
  3. 将符合要求的methods注册,代码如下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {// 将method注册到mappingRegistry中this.mappingRegistry.register(mapping, handler, method);
}

MappingRegistry#register()将url和handler之间的映射关系进行了注册,实现如下:

public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {// 根据传入的handler和method创建HandlerMethod处理器类型的对象HandlerMethod handlerMethod = createHandlerMethod(handler, method);// 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常assertUniqueMethodMapping(handlerMethod, mapping);if (logger.isInfoEnabled()) {logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);}// 添加到mappingLookup中this.mappingLookup.put(mapping, handlerMethod);// 添加到urlLookupList<String> directUrls = getDirectUrls(mapping);for (String url : directUrls) {this.urlLookup.add(url, mapping);}// 添加到nameLookupString name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}// 实例化CorsConfigurationCorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));}finally {this.readWriteLock.writeLock().unlock();}
}

步骤:

  1. 创建HandlerMethod,代码如下:

        

protected HandlerMethod createHandlerMethod(Object handler, Method method) {HandlerMethod handlerMethod;// 如果handler是String类型,那么就是beanNameif (handler instanceof String) {String beanName = (String) handler;// 根据beanName获取对应的beanhandlerMethod = new HandlerMethod(beanName,getApplicationContext().getAutowireCapableBeanFactory(), method);}else {handlerMethod = new HandlerMethod(handler, method);}return handlerMethod;
}
  1. 检查是否在mappingLookup已经存在,如果存在而且和现在传入的不同则抛出异常。代码如下:
private void assertUniqueMethodMapping(HandlerMethod newHandlerMethod, T mapping) {// 检查是否有重复的映射HandlerMethod handlerMethod = this.mappingLookup.get(mapping);if (handlerMethod != null && !handlerMethod.equals(newHandlerMethod)) {throw new IllegalStateException("Ambiguous mapping. Cannot map '" + newHandlerMethod.getBean() + "' method \n" +newHandlerMethod + "\nto " + mapping + ": There is already '" +handlerMethod.getBean() + "' bean method\n" + handlerMethod + " mapped.");}
}
  1. 添加到mappingLookup中。
  2. 添加到urlLookup,其中getDirectUrls–>获得mapping的Path,如果不含有*或者含有?的话,则添加到结果集中。代码如下:
private List<String> getDirectUrls(T mapping) {List<String> urls = new ArrayList<String>(1);for (String path : getMappingPathPatterns(mapping)) {if (!getPathMatcher().isPattern(path)) {urls.add(path);}}return urls;
}

AntPathMatcher#isPattern,如下:

public boolean isPattern(String path) {return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
  1. 添加到nameLookup。
  2. 实例化CorsConfiguration,如果不为null,则添加到corsLookup。此处默认返回null,由子类复写。
  3. 添加到registry中。

handlerMethodsInitialized()是模板方法,空实现。

4.3 getHandlerInternal()方法:根据当前的请求url,获取对应的处理器HandlerMathod

getHandlerInternal()是一个很重要的方法,它的实现如下(删去多余代码):

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 1.利用request截取用于匹配的url有效路径(获取当前的请求路径)String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 2. 使用lookupHandlerMethod方法通过lookupPath和request找对应的HandlerMethodHandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// 3. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethodreturn (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 
}
  1. 利用request对象截取用于匹配的url有效路径。
  2. 使用lookupHandlerMethod方法通过lookupPath和request找HandlerMethod。代码如下:

        

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {// match是内部类,用于保存匹配条件和HandlerMethodList<Match> matches = new ArrayList<Match>();// 根据请求路径lookupPath获取到匹配条件List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {// 将匹配到的条件添加到matchesaddMatchingMappings(directPathMatches, matches, request);}// 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matchesif (matches.isEmpty()) {// No choice but to go through all mappings...addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}// 对matches进行排序,并取第一个作为bestMatch。如果前面两个排序相同则抛出异常if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));Collections.sort(matches, comparator);if (logger.isTraceEnabled()) {logger.trace("Found " + matches.size() + " matching mapping(s) for [" +lookupPath + "] : " + matches);}Match bestMatch = matches.get(0);if (matches.size() > 1) {if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}// 如果matches有多个匹配的,则将第2个和第一个进行比较,看顺序是否一样,如果是一样的话,则抛出异常Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");}}// 在返回前做一些处理,handleMatch方法的默认实现是将lookupPath设置到request的属性,将更多的参数设置到了request,主要是为了以后使用时方便handleMatch(bestMatch.mapping, lookupPath, request);// 返回匹配的HandlerMethodreturn bestMatch.handlerMethod;}else {// 如果没有匹配的,则调用handleNoMatch方法,子类RequestMappingInfoHandlerMapping进行了重写return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}

Match内部类讲解:

  1. 根据lookupPath获取到匹配条件,将匹配到的条件添加到matches
  2. 如果不能直接使用lookupPath得到匹配条件,则将所有匹配条件加入到matches
  3. 如果matches非空
    1. 对matches进行排序,并取第一个作为bestMatch,如果前面两个排序相同则抛出异常
    2. 在返回前做一些处理。默认实现是将lookupPath设置到request的属性,子类RequestMappingInfoHandlerMapping进行了重写,将更多的参数设置到了request。主要是为了以后使用时方便
  4. 否则,调用handleNoMatch,默认返回null。

  1. 如果可以找到handlerMethod则调用createWithResolvedBean方法创建新的HandlerMethod。代码如下:
// 该方法用于创建一个HandlerMethod对象
// 此时handlerMethod中只有匹配条件,还没有handler处理器,这个方法就是要将处理器和匹配条件绑定在一起,创建HandlerMethod对象
public HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);
}

五、RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping–> 继承自AbstractHandlerMethodMapping。

5.1 成员属性和构造方法

// 对OPTIONS请求的处理时用到
private static final Method HTTP_OPTIONS_HANDLE_METHOD;static {try {HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");}catch (NoSuchMethodException ex) {// Should never happenthrow new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);}
}// 构造方法
protected RequestMappingInfoHandlerMapping() {setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
}

5.2 该类复写了一些方法

5.2.1 getMappingPathPatterns

protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {return info.getPatternsCondition().getPatterns();
}

该方法是在hander注册的时候调用,如下:

5.2.2 getMatchingMapping

检查给定的RequestMappingInfo是否匹配当前的请求,返回RequestMappingInfo,代码如下:

protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {return info.getMatchingCondition(request);
}

5.2.3 getMappingComparator

返回1个比较RequestMappingInfo的Comparator,在有多个Handler匹配当前请求时用到。代码如下:

protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {return new Comparator<RequestMappingInfo>() {@Overridepublic int compare(RequestMappingInfo info1, RequestMappingInfo info2) {return info1.compareTo(info2, request);}};
}

5.2.4 handleMatchhandleNoMatch

比较简单,这里就不再贴出

六、RequestMappingHandlerMapping

RequestMappingHandlerMapping–> 继承自RequestMappingInfoHandlerMapping。根据在实现Controller接口或者被@Controller注解的类中的在类和方法上声明的@RequestMapping,创建一个 RequestMappingInfo。

6.1 成员属性

// 是否使用后缀匹配(.*)当对请求进行模式匹配时,如果可用时,则/users 对/users.*也匹配.默认是true.
private boolean useSuffixPatternMatch = true;// 是否后缀匹配应该只对ContentNegotiationManager中注册的扩展符匹配时生效.这一般建议减少歧义和避免问题比如当.出现在路径的情况下
private boolean useRegisteredSuffixPatternMatch = false;// 是否有无斜杠都匹配,如果启用的化,则/users 也匹配 /users/.默认是true
private boolean useTrailingSlashMatch = true;// 内容协商
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();// 这里使用的是EmbeddedValueResolver
private StringValueResolver embeddedValueResolver;// RequestMappingInfo的Builder类,用来创建RequestMappingInfo的
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

6.2 主要方法

6.2.1 覆写了afterPropertiesSet()

public void afterPropertiesSet() {this.config = new RequestMappingInfo.BuilderConfiguration();this.config.setUrlPathHelper(getUrlPathHelper());this.config.setPathMatcher(getPathMatcher());this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);this.config.setContentNegotiationManager(getContentNegotiationManager());super.afterPropertiesSet();
}

6.2.2 isHandler()

protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

调用链如下:

6.2.3 getMappingForMethod()

使用在类和方法上声明的@RequestMapping来创建RequestMappingInfo。代码如下:

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {// 1. 根据Method上的@RequestMapping创建RequestMappingInfoRequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {// 2. 根据类上的@RequestMapping创建RequestMappingInfoRequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {// 3. 合并info = typeInfo.combine(info);}}return info;
}

createRequestMappingInfo(),如下:

private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {// 获取@RequestMapping 注解RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);// 此处返回的都是nullRequestCondition<?> condition = (element instanceof Class ?getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
  1. 获取@RequestMapping 注解
  2. 获得RequestCondition,此处返回的都是null
  1. 如果requestMapping等于null,则返回null,否则根据RequestMapping创建RequestMappingInfo。代码如下:
protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, RequestCondition<?> customCondition) {
return RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name()).customCondition(customCondition).options(this.config).build();
}

相关文章:【Spring MVC】Spring MVC框架的介绍及其使用方法

                  【Spring MVC】Spring MVC的执行流程与源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2808293.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Java基于物联网技术的智慧工地云管理平台源码 依托丰富的设备接口标准库,快速接入工地现场各类型设备

目录 风险感知全面化 项目进度清晰化 环境监测实时化 人员管理高效化 工地数字化 数据网络化 管理智慧化 智慧工地平台整体架构 1个可扩展监管平台 2个应用端 3方数据融合 N个智能设备 智慧工地的远程监管&#xff0c;是工地负责人掌握施工现场情况的必要手段&…

12 - grace数据处理 - 泄露误差改正 - 区域核函数法

grace数据处理 - 泄露误差改正 - 区域核函数法 *0* 引言*1* 实现过程*2* 实现的主要方法0 引言 高斯滤波又称为高斯平滑,其本质是一种加权平均方法,球面某点的信号可由其它点加权平均得到,可实现抑制高阶噪声的目的。既然是一种平滑方法,对研究区边缘数据平滑时容易产生数据…

✅技术社区项目—JWT身份验证

通用的JWT鉴权方案 JWT鉴权流程 基本流程分三步: ● 用户登录成功之后&#xff0c;后端将生成的jwt返回给前端&#xff0c;然后前端将其保存在本地缓存; ● 之后前端与后端的交互时&#xff0c;都将iwt放在请求头中&#xff0c;比如可以将其放在Http的身份认证的请求头 Author…

【编译原理】第六章课后习题(王原生第三版)

前言 课本&#xff1a; 编译原理&#xff08;第三版&#xff09;[王生原、董渊…等编著]习题&#xff1a; 主要习题内容是第一章到第八章&#xff0c;具体内容如下表 章节内容链接第一章课后部分选择题https://blog.csdn.net/Zchengjisihan/article/details/136243955第二章课…

C++ //练习 8.4 编写函数,以读模式打开一个文件,将其内容读入到一个string的vector中,将每一行作为一个独立的元素存于vector中。

C Primer&#xff08;第5版&#xff09; 练习 8.4 练习 8.4 编写函数&#xff0c;以读模式打开一个文件&#xff0c;将其内容读入到一个string的vector中&#xff0c;将每一行作为一个独立的元素存于vector中。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09…

数据结构知识点总结-线性表(1)-线性表的定义、基本操作、顺序表表示

线性表 定义 线性表是具有相同数据类型的N&#xff08;N>0&#xff09;个元素的有限序列&#xff0c;其中N为表长&#xff0c;当N0时线性表是一张空表。 线性表的逻辑特征&#xff1a;每个非空的线性表都有一个表头元素和表尾元素&#xff0c;中间的每个元素有且仅有一个直…

第九章 shell编程之awk

目录 1.1. 概念 1.2. 工作流程 1.2.1. 如图&#xff1a; 1.2.2. 流程&#xff1a; 1.3. awk命令的基本语法 1.3.1. 格式&#xff1a; 1.3.2. BEGIN模式与END模式 1.3.3. awk的输出 1.4. awk程序执行方式 1.4.1. 通过命令行执行awk程序 1.4.2. awk命令调用脚本执行 …

用Python Matplotlib画图导致paper中含有Type-3字体,如何解决?

用Python Matplotlib画图导致paper中含有Type-3字体&#xff0c;如何解决&#xff1f; 在提交ACM或者IEEE论文之前&#xff0c;都会有格式的检查&#xff0c;格式的其中一个要求是paper中不能含有Type-3的字体。因为Type-1和True Type字体都是矢量字体&#xff0c;而Type-3并不…

STL常用容器(vector容器)---C++

STL常用容器目录 2.vector容器2.1 vector基本概念2.2 vector构造函数2.3 vector赋值操作2.4 vector容量和大小2.5 vector插入和删除2.6 vector数据存取2.7 vector互换容器2.7.1 vector互换容器收缩内存空间 2.8 vector预留空间 2.vector容器 2.1 vector基本概念 功能&#xf…

文献阅读:Large Language Models are Null-Shot Learners

文献阅读&#xff1a;Large Language Models are Null-Shot Learners 1. 文章简介2. 方法介绍3. 实验考察 & 结论 1. 基础实验 1. 实验设计2. 实验结果 2. 消融实验 1. 小模型上的有效性2. ∅CoT Prompting3. 位置影响4. 组成内容 4. 总结 & 思考 文献链接&#xff1…

计算机网络:思科实验【3-集线器与交换机的区别、交换机的自学习算法】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;Cisco Packet Tracer实验 本文对应的实验报告源文件请关注微信公众号程序员刘同学&#xff0c;回复思科获取下载链接。 实验目的实验环境实验内容集线器与交换机的区别交换机的自学习算法…

Cubase学习:Cubase 12常用快捷键

按键盘上的上下箭头就可以让选中的音符向上或向下移动 数字0键: 停止 Ctrl+数字 0 键: 新建视图层 Alt+数字0 键: 重新设置视图层 小数点键: 播放指针回零点 数字1 键: 左定位指针 数字 2 键: 右定位指针 数字3 键--数字9键: 分别控制 3--9 的7个定位标志 Alt+数字1 键--数字9键…

自定义神经网络四之编写自定义神经网络

文章目录 前言神经网络组件代码整体的项目结构Tensor张量Layers层NeuralNet神经网络Loss损失函数Optim优化器data数据处理train训练 神经网络解决实际问题实际问题训练和推理代码 总结 前言 自定义神经网络一之Tensor和神经网络 自定义神经网络二之模型训练推理 自定义神经网络…

【Android】View 与 ViewGroup

View 是 Android 所有控件的基类&#xff0c;我们平常所用的 TextView 和 ImageView 都是继承自 View 的&#xff0c;源码如下&#xff1a; public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {... }public class ImageView extends View {.…

【Java程序设计】【C00297】基于Springboot的养老院管理系统(有论文)

基于Springboot的养老院管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的养老院管理系统设计与实现&#xff0c;本系统有管理员以及用户二种角色&#xff1b; 系统整体功能有&#xff1a;老人管理、字典表…

第八章 堆

第八章 堆 文章目录 第八章 堆0. 前情概述1. 堆(Heap)的核心概述1.1 堆的内存细分 2. 设置堆内存大小与OOM2.1 对空间大小的设置2.2 OutOfMemory举例 3. 年轻代与老年代4. 图解对象分配过程5. Minor GC、Major GC与Full GC5.1 最简单的分代式GC策略的触发条件 6. 堆空间分代思想…

【黑马程序员】3、TypeScript常用类型_黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

课程地址&#xff1a;【黑马程序员前端TypeScript教程&#xff0c;TypeScript零基础入门到实战全套教程】 https://www.bilibili.com/video/BV14Z4y1u7pi/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 3、TypeScript常用类型 3.1 类型注解 …

SAM轻量化的终点竟然是RepViT + SAM

本文首发&#xff1a;AIWalker&#xff0c;欢迎关注~~ 殊途同归&#xff01;SAM轻量化的终点竟然是RepViT SAM&#xff0c;移动端速度可达38.7fps。 对于 2023 年的计算机视觉领域来说&#xff0c;「分割一切」&#xff08;Segment Anything Model&#xff09;是备受关注的一项…

安装 Ubuntu 22.04.3 和 docker

文章目录 一、安装 Ubuntu 22.04.31. 简介2. 下载地址3. 系统安装4. 系统配置 二、安装 Docker1. 安装 docker2. 安装 docker compose3. 配置 docker 一、安装 Ubuntu 22.04.3 1. 简介 Ubuntu 22.04.3 是Linux操作系统的一个版本。LTS 版本支持周期到2032年。 系统要求双核 C…

了解 JavaScript 中的重放攻击和复现攻击

在网络安全领域&#xff0c;重放攻击&#xff08;Replay Attack&#xff09;和复现攻击&#xff08;Playback Attack&#xff09;是一些可能导致安全漏洞的攻击形式。这两种攻击类型涉及在通信过程中再次发送已经捕获的数据&#xff0c;以达到欺骗系统的目的。本文将介绍 JavaS…