Spring RequestMappingHandlerMapping详解

文章目录

  • 前言
  • 一、AbstractHandlerMethodMapping
    • 1.1 mappingRegistry
    • 1.2 MappingRegistry 注册表
    • 1.3 getHandlerInternal
    • 1.4 lookupHandlerMethod
  • 二、RequestMappingInfoHandlerMapping
  • 三、RequestMappingHandlerMapping
  • 总结


前言

RequestMappingHandlerMapping是Spring MVC中的一个请求映射处理器,它负责将HTTP请求映射到特定的@RequestMapping注解的方法上。允许你使用简单的注解(如@GetMapping@PostMapping@RequestMapping等)来定义请求路径和HTTP方法。
工作机制
* 当Spring MVC的DispatcherServlet接收到一个HTTP请求时,它会查找一个合适的HandlerMapping来处理这个请求。
* RequestMappingHandlerMapping会检查它的映射注册表,该注册表包含了所有使用@RequestMapping注解的方法的映射信息。
* 如果找到了一个匹配的映射,那么RequestMappingHandlerMapping会返回一个HandlerExecutionChain,它包含了要执行的处理器(通常是HandlerMethod,代表一个@Controller中的方法)和任何与之关联的拦截器。
与其他组件的关系
* RequestMappingHandlerMappingHandlerAdapter(如RequestMappingHandlerAdapter)紧密合作。一旦RequestMappingHandlerMapping找到了一个匹配的处理器,它会将这个处理器传递给HandlerAdapter,后者负责调用处理器并执行相应的逻辑。
* RequestMappingHandlerMapping还可能与HandlerInterceptor一起使用,以在请求处理过程中添加额外的逻辑(如日志记录、身份验证等)。

在这里插入图片描述
由类图可知,RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,RequestMappingHandlerMapping和AbstractHandlerMethodMapping均实现了afterPropertiesSet() 方法,该方法会在bean属性完成初始化后被调用。

一、AbstractHandlerMethodMapping

AbstractHandlerMethodMapping,实现 InitializingBean 接口,继承 AbstractHandlerMapping 抽象类,以 Method 方法 作为 Handler 处理器 的 HandlerMapping 抽象类,提供 Mapping 的初始化、注册等通用的骨架方法。

那么具体是什么呢?AbstractHandlerMethodMapping 定义为了 泛型,交给子类做决定。例如,子类 RequestMappingInfoHandlerMapping 使用 RequestMappingInfo 类作为 泛型,也就是我们在上面注解模块看到的 @RequestMapping 等注解信息。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	/**
	 * 是否只扫描可访问的 HandlerMethod 们
	 */
	private boolean detectHandlerMethodsInAncestorContexts = false;
	/**
	 * Mapping 命名策略
	 */
	@Nullable
	private HandlerMethodMappingNamingStrategy<T> namingStrategy;
	/**
     * Mapping 注册表
     */
	private final MappingRegistry mappingRegistry = new MappingRegistry();
}

1.1 mappingRegistry

HandlerMethodMappingNamingStrategy 接口,HandlerMethod 的 Mapping 的名字生成策略接口。

@FunctionalInterface
public interface HandlerMethodMappingNamingStrategy<T> {
	/**
	 * 根据 HandlerMethod 获取名称,就是为对应的 Mappring 对象生成一个名称,便于获取
	 */
	String getName(HandlerMethod handlerMethod, T mapping);
}

public class RequestMappingInfoHandlerMethodMappingNamingStrategy implements HandlerMethodMappingNamingStrategy<RequestMappingInfo> {

	/** Separator between the type and method-level parts of a HandlerMethod mapping name. */
	public static final String SEPARATOR = "#";

	@Override
	public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
		// 情况一,mapping 名字非空,则使用 mapping 的名字
		if (mapping.getName() != null) {
			return mapping.getName();
		}
		// 情况二,使用类名大写 + "#" + 方法名
		StringBuilder sb = new StringBuilder();
		String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
		for (int i = 0; i < simpleTypeName.length(); i++) {
			if (Character.isUpperCase(simpleTypeName.charAt(i))) {
				sb.append(simpleTypeName.charAt(i));
			}
		}
		sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
		return sb.toString();
	}

}

情况一,如果 Mapping 已经配置名字,则直接返回。例如,@RequestMapping(name = “login”, value = “user/login”) 注解的方法,它对应的 Mapping 的名字就是 login
情况二,如果 Mapping 未配置名字,则使用使用类名大写 + “#” + 方法名。例如,@RequestMapping(value = “user/login”) 注解的方法,假设它所在的类为 UserController ,对应的方法名为 login ,则它对应的 Mapping 的名字就是 USERCONTROLLER#login

1.2 MappingRegistry 注册表

class MappingRegistry {
    /**
     * 注册表
     *
     * Key: Mapping
     * Value:{@link MappingRegistration}(Mapping + HandlerMethod)
     */
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
    /**
     * 注册表2
     *
     * Key:Mapping
     * Value:{@link HandlerMethod}
     */
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
    /**
     * 直接 URL 的映射
     *
     * Key:直接 URL(就是固定死的路径,而非多个)
     * Value:Mapping 数组
     */
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
    /**
     * Mapping 的名字与 HandlerMethod 的映射
     *
     * Key:Mapping 的名字
     * Value:HandlerMethod 数组
     */
    private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
    /**
     * 读写锁
     */
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    public void register(T mapping, Object handler, Method method) {
	    // <1> 获得写锁
	    this.readWriteLock.writeLock().lock();
	    try {
	        // <2.1> 创建 HandlerMethod 对象
	        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
	        // <2.2> 校验当前 mapping 是否存在对应的 HandlerMethod 对象,如果已存在但不是当前的 handlerMethod 对象则抛出异常
	        assertUniqueMethodMapping(handlerMethod, mapping);
	        // <2.3> 将 mapping 与 handlerMethod 的映射关系保存至 this.mappingLookup
	        this.mappingLookup.put(mapping, handlerMethod);
	
	        // <3.1> 获得 mapping 对应的普通 URL 数组
	        List<String> directUrls = getDirectUrls(mapping);
	        // <3.2> 将 url 和 mapping 的映射关系保存至 this.urlLookup
	        for (String url : directUrls) {
	            this.urlLookup.add(url, mapping);
	        }
	
	        // <4> 初始化 nameLookup
	        String name = null;
	        if (getNamingStrategy() != null) {
	            // <4.1> 获得 Mapping 的名字
	            name = getNamingStrategy().getName(handlerMethod, mapping);
	            // <4.2> 将 mapping 的名字与 HandlerMethod 的映射关系保存至 this.nameLookup
	            addMappingName(name, handlerMethod);
	        }
	
	        // <5> 初始化 CorsConfiguration 配置对象
	        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
	        if (corsConfig != null) {
	            this.corsLookup.put(handlerMethod, corsConfig);
	        }
	        // <6> 创建 MappingRegistration 对象
	        // 并与 mapping 映射添加到 registry 注册表中
	        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	    }
	    finally {
	        // <7> 释放写锁
	        this.readWriteLock.writeLock().unlock();
	    }
	}

}

1.3 getHandlerInternal

通过 AbstractHandlerMapping 的 getHandler(HttpServletRequest request) 方法获取 HandlerExecutionChain 处理器执行链时,需要调用 getHandlerInternal 抽象方法获取处理器,这个方法由子类去实现,就到这里了。

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // <1> 获得请求的路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // <2> 获得读锁
    this.mappingRegistry.acquireReadLock();
    try {
        // <3> 获得 HandlerMethod 对象
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        // <4> 进一步,获得一个新的 HandlerMethod 对象
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        // <5> 释放读锁
        this.mappingRegistry.releaseReadLock();
    }
}

1.4 lookupHandlerMethod

获得请求对应的 HandlerMethod 处理器对象

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    // <1> Match 数组,存储匹配上当前请求的结果(Mapping + HandlerMethod)
    List<Match> matches = new ArrayList<>();
    // <1.1> 优先,基于直接 URL (就是固定死的路径,而非多个)的 Mapping 们,进行匹配
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    // <1.2> 其次,扫描注册表的 Mapping 们,进行匹配
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    // <2> 如果匹配到,则获取最佳匹配的 Match 结果的 `HandlerMethod`属性
    if (!matches.isEmpty()) {
        // <2.1> 创建 MatchComparator 对象,排序 matches 结果,排序器
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        // <2.2> 获得首个 Match 对象,也就是最匹配的
        Match bestMatch = matches.get(0);
        // <2.3> 处理存在多个 Match 对象的情况!!
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            // 比较 bestMatch 和 secondBestMatch ,如果相等,说明有问题,抛出 IllegalStateException 异常
            // 因为,两个优先级一样高,说明无法判断谁更优先
            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);
        // <2.4> 处理首个 Match 对象
        handleMatch(bestMatch.mapping, lookupPath, request);
        // <2.5> 返回首个 Match 对象的 handlerMethod 属性
        return bestMatch.handlerMethod;
    }
    // <3> 如果匹配不到,则处理不匹配的情况
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

二、RequestMappingInfoHandlerMapping

当 Spring MVC 收到一个 HTTP 请求时,会通过 RequestMappingInfoHandlerMapping 来查找与请求匹配的 HandlerMethod,然后执行该方法来处理请求。
RequestMappingInfo
org.springframework.web.servlet.mvc.method.RequestMappingInfo,实现 RequestCondition 接口,每个方法的定义的请求信息,也就是 @RequestMapping 等注解的信息

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {

    @Nullable
    private final String name;
    //请求路径条件(url合并,筛选url,择优url)
    private final PatternsRequestCondition patternsCondition;
    //请求方法条件
    private final RequestMethodsRequestCondition methodsCondition;
    //请求参数条件
    private final ParamsRequestCondition paramsCondition;
    //请求头条件
    private final HeadersRequestCondition headersCondition;
    //请求content-type条件
    private final ConsumesRequestCondition consumesCondition;
    //请求accept条件
    private final ProducesRequestCondition producesCondition;
    //自定义请求条件
    private final RequestConditionHolder customConditionHolder;
}

getMatchingCondition
getMatchingCondition(HttpServletRequest request)方法,从当前 RequestMappingInfo 获得匹配的条件。如果匹配,则基于其匹配的条件,创建新的 RequestMappingInfo 对象,如果不匹配,则返回 null ,代码如下:

@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    // 匹配 methodsCondition、paramsCondition、headersCondition、consumesCondition、producesCondition
    // 如果任一为空,则返回 null ,表示匹配失败
    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    if (methods == null) {
        return null;
    }
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    if (params == null) {
        return null;
    }
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    if (headers == null) {
        return null;
    }
    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    if (consumes == null) {
        return null;
    }
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
    if (produces == null) {
        return null;
    }
    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
    if (patterns == null) {
        return null;
    }
    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }
    /*
     * 创建匹配的 RequestMappingInfo 对象
     * 一个 methodsCondition 可以配置 GET、POST、DELETE 等等条件,
     * 但是实际就匹配一个请求类型,此时 methods 只代表其匹配的那个。
     */
    return new RequestMappingInfo(this.name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}

三、RequestMappingHandlerMapping

RequestMappingHandlerMapping实现 MatchableHandlerMapping、EmbeddedValueResolverAware 接口,继承 RequestMappingInfoHandlerMapping 抽象类,基于@RequestMapping 注解来构建 RequestMappingInfo 对象。
类的属性定义

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
		implements MatchableHandlerMapping, EmbeddedValueResolverAware {
	private boolean useSuffixPatternMatch = true;
	private boolean useRegisteredSuffixPatternMatch = false;
	private boolean useTrailingSlashMatch = true;

	private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

	@Nullable
	private StringValueResolver embeddedValueResolver;
	//RequestMappingInfo 的构建器
	private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

afterPropertiesSet
因父类 AbstractHandlerMethodMapping 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:

@Override
public void afterPropertiesSet() {
    // 构建 RequestMappingInfo.BuilderConfiguration 对象
    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();
}

isHandler
是否还记得 AbstractHandlerMethodMapping 的这个抽象方法?在它的 processCandidateBean 方法中,扫描 Spring 中所有 Bean 时会调用,判断是否需要扫描这个 Bean 中的方法,有 @Controller 或者 @RequestMapping 的注解的类才需要进行扫描,方法如下:

@Override
protected boolean isHandler(Class<?> beanType) {
    // 判断是否有 @Controller 或者 @RequestMapping 的注解
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

getMappingForMethod
AbstractHandlerMethodMapping 的 detectHandlerMethods 方法中调用该方法,用于获取 Method 方法对应的 Mapping 对象,方法如下:

@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    //1、基于方法上的 @RequestMapping 注解,创建 RequestMappingInfo 对象
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // 2、 基于类上的 @RequestMapping 注解,合并进去
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
        // 3、 如果有前缀,则设置到 info 中
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}

createRequestMappingInfo
基于方法上的 @RequestMapping 注解,创建 RequestMappingInfo 对象

@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    // <1> 获得 @RequestMapping 注解
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    // <2> 获得自定义的条件。目前都是空方法,可以无视
    RequestCondition<?> condition = (element instanceof Class ? 
                                     getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    // <3> 基于 @RequestMapping 注解,创建 RequestMappingInfo 对象
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
    // 创建 RequestMappingInfo.Builder 对象,设置对应属性
    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);
    }
    // 创建 RequestMappingInfo 对象
    return builder.options(this.config).build();
}

总结

将RequestMappingHandlerMapping注入到 Spring 上下文时,会进行一些初始化工作,扫描 @Controller 或者 @RequestMapping 注解标注的 Bean 对象,会将带有 @RequestMapping 注解(包括其子注解)解析成 RequestMappingInfo 对象。接下来,会将 RequestMappingInfo、该方法对象、该方法所在类对象 往 MappingRegistry 注册表进行注册,其中会生成 HandlerMethod 处理器(方法的所有信息)对象保存起来。当处理某个请求时,HandlerMapping 找到该请求对应的 HandlerMethod 处理器对象后,就可以通过反射调用相应的方法了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI、微软、智谱AI 等全球 16 家公司共同签署前沿人工智能安全承诺

人工智能&#xff08;AI&#xff09;的安全问题&#xff0c;正以前所未有的关注度在全球范围内被讨论。 日前&#xff0c;OpenAI 联合创始人、首席科学家 Ilya Sutskever 与 OpenAI 超级对齐团队共同领导人 Jan Leike 相继离开 OpenAI&#xff0c;Leike 甚至在 X 发布了一系列…

socket地址理解

socket介绍 套接字的基本概念 1. 套接字的定义&#xff1a; 套接字&#xff08;socket&#xff09;是计算机网络中用于通信的端点&#xff0c;它抽象了不同主机上应用进程之间双向通信的机制。 2. 套接字的作用&#xff1a; 套接字连接应用进程与网络协议栈&#xff0c;使…

JS对象超细

目录 一、对象是什么 1.对象声明语法 2.对象有属性和方法组成 二、对象的使用 1.对象的使用 &#xff08;1&#xff09;查 &#xff08;2&#xff09;改 &#xff08;3&#xff09;增 &#xff08;4&#xff09;删&#xff08;了解&#xff09; &#xff08;5&#xf…

Lazarus - 从 Hello 开始

我们在《Lazarus - 隐秘的神器》一文中了解到了 Lazarus 的历史和特点&#xff0c;此后将正式开始学习Lazarus 开发。 如果你也对 Windows、Pascal、Delphi 开发感兴趣&#xff0c;请关注 Lazarus专栏 &#x1f4f0; 安装开发环境 官网&#xff1a;Lazarus Homepage (lazarus-i…

采用LoRA方法微调llama3大语言模型

文章目录 前言一、Llama3模型简介1.下载llama3源码到linux服务器2.安装依赖3.测试预训练模型Meta-Llama-3-8B4.测试指令微调模型Meta-Llama3-8B-Instruct5.小结 二、LoRA微调Llama31.引入库2.编写配置文件3.LoRA训练的产物 三、测试新模型效果1.编写配置文件2.运行配置文件&…

拼多多暂时超越阿里成为电商第一

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 拼多多的财报又炸裂了&#xff1a; 拼多多发布了第一季度财报&#xff0c;营收868亿&#xff0c;增长了131%&#xff0c;净利润279亿&#xff0c;增长了246%&#xff0c;营销服务收入424亿&#xff0c;也就是商家的…

如何将老板的游戏机接入阿里云自建K8S跑大模型(下)- 安装nvidia/gpu-operator支持GPU在容器中共享

文章目录 安装nvidia/gpu-operator支持GPU在容器中共享 安装nvidia/gpu-operator支持GPU在容器中共享 安装 nvidia/gpu-operator遇到两个问题&#xff1a; 由于我们都懂的某个原因&#xff0c;导致某些镜像一直现在不成功。 解决办法&#xff0c;准备一个&#x1fa9c;&#…

peakcan硬件配置-用于linux的socket_can通讯

1.相关系统环境 工控机型号&#xff1a;Nuvo-8108GC 系统版本&#xff1a;ubuntu 18 工控机内置can卡&#xff1a;peakcan 2.下载并安装peakcan驱动 下载链接1–下载链接2–peakcan8.15.2驱动&#xff0c;支持socketcan编程 2.1 安装依赖库 sudo apt-get install udev sud…

建模:Maya

一、常用按键 1、alt 左键 —— 环绕查看 2、alt 中键 —— 拖动模型所在面板 3、空格 —— 进入三视图模式&#xff1b;空格 左键按住拖动 —— 切换到对应视图 二、骨骼归零 1、T Pose 旋转模式&#xff0c;点击模型&#xff0c;摆好T姿势即可 2、复制模型设置200距离…

线程的概念和控制

文章目录 线程概念线程的优点线程的缺点线程异常线程用途理解虚拟地址 线程控制线程的创建线程终止线程等待线程分离封装线程库 线程概念 什么是线程&#xff1f; 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一…

嵩山是颍水的嵩山

颍水对于嵩山具有特别重要的意义&#xff0c;嵩山的水流大多数汇入了颍河&#xff0c;颍河流域约占登封市总面积88%&#xff0c;从这个角度讲&#xff0c;嵩山就是颍水的嵩山。 再看环嵩山地区&#xff0c;即“嵩山文化圈”&#xff0c;学者们按黄、淮、济分为三个水系区。黄河…

Vue3实战笔记(39)—封装页脚组件,附源码

文章目录 前言一、封装页脚组件二、使用组件总结 前言 在Web开发中&#xff0c;页脚组件是一个重要的部分&#xff0c;它为用户提供关于网站的信息、导航链接以及版权声明等。而封装页脚组件则是一种高效的方法&#xff0c;可以提高代码的可重用性和可维护性。 一、封装页脚组…

C++实现基于http协议的epoll非阻塞模型的web服务器框架(支持访问服务器目录下文件的解析)

使用方法&#xff1a; 编译 例子&#xff1a;./httpserver 9999 ../ htmltest/ 可执行文件 端口 要访问的目录下的 例子&#xff1a;http://192.168.88.130:9999/luffy.html 前提概要 http协议 &#xff1a;应用层协议&#xff0c;用于网络通信&#xff0c;封装要传输的数据&…

如何在Windows下使用Docker Desktop运行CentOS容器

引言&#xff1a; 在Windows操作系统中&#xff0c;我们可以使用Docker Desktop来轻松运行和管理各种Linux容器&#xff0c;包括CentOS。今天&#xff0c;我们就来详细讲解一下如何在Windows环境下使用Docker Desktop来运行CentOS容器。 一、安装Docker Desktop 首先&#x…

贴片反射式红外光电传感器ITR8307

红外光电传感器ITR8307 ITR8307外形 特性 快速响应时间 高灵敏度 非可见波长 薄 紧凑型 无铅 该产品本身将保持在符合RoHS的版本内 描述 ITR8307/S18/TR8是一种光反射开关&#xff0c;它包括一个GaAs IR-LED发射器和一个NPN光电晶体管&#xff0c;该晶体管具有短距离的高…

QGIS DEM数据快速获取

背景 Dem 是非常重要的数据&#xff0c;30 m 的精度也是最容易获取的&#xff0c;目前有很多种方式可以获取&#xff0c;比如地理空间数据云&#xff0c;今天介绍用 QGIS插件获取。 这种方式的最大优势是方便快捷。 插件下载与安装 插件-管理并安装插件-搜索下载 OpenTopogr…

5.23小结

1.java项目创新 目前想添加一个自动回复的功能和设置验证方式有&#xff08;允许任何人添加&#xff0c;禁止添加&#xff0c;设置回答问题添加&#xff0c;普通验证添加&#xff09; 目前只完成画好前端界面&#xff0c;前端发送请求&#xff0c;还有表的修改 因为涉及表字…

Baxter机器人摄像头打不开的一个可能的解决办法

操作过程 1.连上机器人 cd ros_ws/ ./baxter.sh2.查看摄像头&#xff08;最多开两个&#xff09; rosrun baxter_tools camera_control.py -l 3.打开指定的摄像头 rosrun baxter_tools camera_control.py -o left_hand_camera -r 1280x800 另&#xff1a;关闭的话 rosrun…

vscode安装多版本esp-idf

安装 离线安装 vscode设置 建立一个新的配置文件, 这里面的插件是全新的 安装esp-idf 官网下载espidf 安装这一个 选项默认即可 记住各一个路径, 之后要用到 vscode安装插件 安装以后会进入这一个界面, 也可以CtrlShiftP输入ESP-IDFextension进入 使用espressif 问题 这一个…

TreeMap及TreeSet详解

在介绍TreeMap和TraaSet之前我们先来介绍一下Map和Set这样便于大家后续理解。 有这张图我们可以看出Set是继承Collection而Map没有继承任何的类&#xff0c;了解这一点对于后续的学习 是比较有帮助的。 TreeMap和TreeSet实现的底层原理&#xff08;数据结构&#xff09;是相同的…