springmvc5.x-mvc实现原理及源码实现

news2024/10/6 0:27:14

上文:spring5.x-声明式事务原理及源码实现


系列文章:

                    spring5.x-声明式事务原理及源码实现

                    spring5.x-AOP实现原理及源码分析

                    spring5.x-监听器原理及源码实现

                    spring5.x-解决循环依赖分析

                    spring5.x-IOC模块源码学习

                    spring5.x介绍及搭配spring源码阅读环境


基础知识

请看原来写的文章:springmvc

源码学习

cca5a76aada179e072014928521a51f5.png

@RequestMapping("/{id}")
    public String showUserInfo(ModelMap modelMap, @PathVariable("id")Integer id){
        Student student = new Student();
        student.setId(id);
        student.setAge(100);
        student.setName("test");
        modelMap.addAttribute("name", student.getName());
        modelMap.addAttribute("age", student.getAge());
        modelMap.addAttribute("id", student.getId());
        return "result";
    }

springmvc的初始化

  1. DispatcherServlet 初始化:DispatcherServlet 是 Spring MVC 的前端控制器,在 web.xml 或 WebApplicationInitializer 中配置 Servlet 容器时会初始化 DispatcherServlet。DispatcherServlet 的初始化源码位置为 org.springframework.web.servlet.DispatcherServlet。

<servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

代码位置:org.springframework.web.servlet.DispatcherServlet#initStrategies

//用于初始化 Spring MVC 的各个策略组件。
protected void initStrategies(ApplicationContext context) {
      //初始化处理文件上传的解析器,用于解析请求中的 multipart 数据。
        this.initMultipartResolver(context);
      //初始化处理国际化的解析器,用于解析请求中的语言区域信息。
        this.initLocaleResolver(context);
      //初始化处理主题的解析器,用于解析请求中的主题信息。
        this.initThemeResolver(context);
      //初始化处理器映射器,用于将请求映射到对应的处理器(Controller)。
        this.initHandlerMappings(context);
      //初始化处理器适配器,用于调用处理器的方法并从中获取 ModelAndView 对象。
        this.initHandlerAdapters(context);
      //初始化处理器异常解析器,用于处理请求过程中发生的异常。
        this.initHandlerExceptionResolvers(context);
      //初始化请求到视图名称的转换器,用于将处理器返回的逻辑视图名称转换为实际的视图路径。
        this.initRequestToViewNameTranslator(context);
      //初始化视图解析器,用于将视图名称解析为具体的视图类型。
        this.initViewResolvers(context);
      //初始化 FlashMap 管理器,用于处理请求间的数据传递。
        this.initFlashMapManager(context);
    }

dc032971efa1ce353e8c28da77fc3fe8.png

  1. 注册 ServletContextListener:在 Servlet 容器启动时,通常会注册一个监听器(ServletContextListener)来初始化 Spring MVC 的上下文。在监听器的 contextInitialized 方法中实现 Spring MVC 的初始化,例如加载配置文件、创建 ApplicationContext 等。

代码位置:org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener

//执行应用程序事件监听器
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            //调用监听器的 并传递当前发生的应用程序事件作为参数。
            listener.onApplicationEvent(event);
        } catch (ClassCastException var6) {
            String msg = var6.getMessage();
            if (msg != null && !this.matchesClassCastMessage(msg, event.getClass().getName())) {
                throw var6;
            }

            Log logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Non-matching event type for listener: " + listener, var6);
            }
        }

    }
  1. WebApplicationContext 初始化:Spring MVC 使用了自己的容器(WebApplicationContext),该容器会在 ServletContextListener 初始化时创建并配置。关于 WebApplicationContext 的初始化可以参考 org.springframework.web.context.support.XmlWebApplicationContext 或 org.springframework.web.context.support.AnnotationConfigWebApplicationContext 源码。

da408f84bedb1e065eb92149bcf7dc26.png

  1. HandlerMapping 和 HandlerAdapter 初始化:HandlerMapping 负责将请求映射到对应的处理器(Controller),而 HandlerAdapter 则负责调用相应的处理器方法。这些组件的初始化涉及配置文件、注解扫描等过程,相关源码位置为 org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping、org.springframework.web.servlet.handler.SimpleUrlHandlerMapping,以及 org.springframework.web.servlet.handler.BeanNameUrlHandlerAdapter、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 等。

90d762452f9c9b0ad1331f787b3c0601.pnga02ec54c3a625cdd83c7e76f1bb511f0.png

注意监听器:org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener

1e32edea4a64e084cb4983a003009cbb.png

  1. 视图解析器初始化:视图解析器(ViewResolver)负责将处理器方法的返回值解析为具体的视图。Spring MVC 支持多种类型的视图解析器,如 InternalResourceViewResolver、FreeMarkerViewResolver 等。初始化过程中会配置视图解析器的相关属性和位置,例如视图前缀、后缀等。相关源码位置为 org.springframework.web.servlet.view.InternalResourceViewResolver。

由于我们配的视图是:

<!-- 配置视图解析器 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"></property>
    <property name="suffix" value=".jsp"></property>
  </bean>

所以解析出来是:

代码位置:org.springframework.beans.factory.BeanFactoryUtils#beansOfTypeIncludingAncestors(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<T>, boolean, boolean)

5e453c7e2e35df940479ed79abea1754.png

最终我们都会调onRefresh()完成初始化。代码位置:org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext

if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      onRefresh(wac);
    }

springmvc分发实现

那么spring加载完成后,就是调用的问题,这里注意,会根据不同的调用方式来进行分发,比如http tcp 等的分发方式都不太一样。那最终都会调到doDispatch.

代码位置:org.springframework.web.servlet.DispatcherServlet#doDispatch

//分发方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //初始化请求信息
    HttpServletRequest processedRequest = request;
        //用于存储根据请求对象匹配到的处理器对象
    HandlerExecutionChain mappedHandler = null;
        //标识是否已解析多部分请求
    boolean multipartRequestParsed = false;
      //获取当前WebAsyncManager对象(异步处理)
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
            //初始化视图模型
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
                //检查请求是否为多部分请求(文件或),通过检查请求头中的 "Content-Type" 是否以 "multipart/" 开头来判断。
        processedRequest = checkMultipart(request);
                //判断是否一致,如果是则为true
        multipartRequestParsed = (processedRequest != request);

        // 获取处理对象
        mappedHandler = getHandler(processedRequest);
                //为空就是没找着路劲 返回404
        if (mappedHandler == null) {
          noHandlerFound(processedRequest, response);
          return;
        }

        // 获取处理对
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 获取请求方法
        String method = request.getMethod();
                //判断是否为get请求
        boolean isGet = "GET".equals(method);
                //如果是 或头为HEAD
        if (isGet || "HEAD".equals(method)) {
                    //获取最后时间
          long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
          if (logger.isDebugEnabled()) {
            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
          }
                    //新请一个响应并检查,如果不通过直接中止
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }
              //调用处理器的预处理方法 ,如果不通过中止
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }

        // 调用适配器
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
              //检查异步处理是否已经开始
        if (asyncManager.isConcurrentHandlingStarted()) {
          return;
        }
              //设置默认的视图名到ModelAndView中
        applyDefaultViewName(processedRequest, mv);
                //调用后置处理器方法
        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);
        }
      }
    }
  }

15bdc30486a7e6764ddc821df1631bf7.png

接着细节深入

processedRequest = checkMultipart(request);

代码位置:org.springframework.web.servlet.DispatcherServlet#checkMultipart

//将请求转换为分段请求,并使分段解析程序可用。如果未设置多部分解析程序,则只需使用现有请求。
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        //不为空 且 请求是否是multipart类型
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            //将请求转换为 multipart类型的请求对象,
      if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
        logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
            "this typically results from an additional MultipartFilter in web.xml");
      }
                //检查当前请求是否已经发生过多部分请求解析失败的异常
      else if (hasMultipartException(request) ) {
        logger.debug("Multipart resolution failed for current request before - " +
            "skipping re-resolution for undisturbed error rendering");
      }
      else {
        try {
                    //转换为HttpServletRequest 并返回
          return this.multipartResolver.resolveMultipart(request);
        }
        catch (MultipartException ex) {
          if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
            logger.debug("Multipart resolution failed for error dispatch", ex);
            // Keep processing error dispatch with regular request handle below
          }
          else {
            throw ex;
          }
        }
      }
    }
    // If not returned before: return original request.
    return request;
  }

上面这个方法解析multipart类型的请求。

接着:mappedHandler = getHandler(processedRequest); 这个是用于确定当前请求的处理程

Nullable
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
      //不为空
    if (this.handlerMappings != null) {
            //循环调用每个 HandlerMapping 的 getHandler(request) 方法,传入当前的 HttpServletRequest 对象作为参数,来获取对应的处理器。
      for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
          logger.trace(
              "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
          return handler;
        }
      }
    }
    return null;
  }

当面这个用于获取HandlerExecutionChain,其实就是请求处理器(Handler)的方法。

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

根据刚刚获取的处理器进行获取HandlerAdapter,其实就是决定用来调modelandview或其他视图,有很多种,比如:

  1. RequestMappingHandlerAdapter:适配处理器函数、带有注解的控制器等类型的处理器。

  2. SimpleControllerHandlerAdapter:适配基于实现 Controller 接口的控制器。

  3. HttpRequestHandlerAdapter:适配实现 HttpRequestHandler 接口的处理器,用于处理原始的 HttpServletRequest 与 HttpServletResponse。

  4. HandlerAdapter 的默认实现 DefaultHandlerAdapter:用于处理没有明确适配器的处理器类型,默认使用 ServletInvocableHandlerMethod 来执行处理器方法。

  5. 针对异步请求的适配器:例如 AsyncHandlerInterceptor 和 Callable 所对应的适配器。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
            //循环判断是哪种类型匹配,匹配就返回
      for (HandlerAdapter ha : this.handlerAdapters) {
        if (logger.isTraceEnabled()) {
          logger.trace("Testing handler adapter [" + ha + "]");
        }
                //获取最终的HandlerAdapter
        if (ha.supports(handler)) {
          return ha;
        }
      }
    }
    throw new ServletException("No adapter for handler [" + handler +
        "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  }

mappedHandler.applyPreHandle(processedRequest, response),这个方法用于记录拦截器的执行位置。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = 0; i < interceptors.length; i++) {
        HandlerInterceptor interceptor = interceptors[i];
                //调用前置拦截器方法 如果返回值为 false,表示拦截器不允许继续执行后续的处理逻辑
        if (!interceptor.preHandle(request, response, this.handler)) {
                    //方法进行拦截器链的后置处理,并直接返回 false
          triggerAfterCompletion(request, response, null);
          return false;
        }
                //记录位置
        this.interceptorIndex = i;
      }
    }
    return true;
  }
//用于触发拦截器链的后置处理(afterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
      throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
      //不为空
    if (!ObjectUtils.isEmpty(interceptors)) {
            //循环执行拦截器的后置处理逻辑,通常用于资源清理、日志记录等操作。
      for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = interceptors[i];
        try {
          interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
          logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
      }
    }
  }

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

接受三个参数:processedRequest 是经过前置处理器链处理后的请求对象,response 是响应对象,mappedHandler.getHandler() 是映射到的请求处理器对象。

在执行 handle() 方法时,会根据请求处理器的类型调用相应的处理逻辑。不同的请求处理器可能是不同类型的对象,例如 Controller、HttpRequestHandler 或 HttpMessageConverter 等。

注意:一般HTTP 请求中可以包含多种类型的参数,常见的有以下几种类型:

  1. 查询参数(Query Parameters):位于 URL 中,以 ? 开头,键值对使用 key=value 的形式表示,多个参数之间使用 & 分隔。例如: http://hong.com/api?key1=value1&key2=value2。可以通过解析 URL 来获取查询参数。

  2. 路径参数(Path Parameters):位于 URL 路径中,用于表示特定资源的标识符或属性。路径参数通常用于 RESTful 风格的路由中,使用占位符来代表参数值。例如: http://hong.com/api/users/{userId},其中 {userId} 就是路径参数。

  3. 请求体参数(Request Body Parameters):位于请求体中,通常使用表单数据或 JSON 格式来传递。可以通过 HTTP 请求的 Content-Type 头部字段来确定参数的类型。常见的参数类型有:

    • 表单参数(Form Parameters):使用表单数据格式传递,即 key=value 的形式。

    • JSON 参数(JSON Parameters):使用 JSON 格式传递,请求体中的数据是一个合法的 JSON 对象。

    • 文件参数(File Parameters):用于上传文件,请求体中包含文件的二进制数据。

判断请求参数的方式取决于你使用的服务器端框架或编程语言。大多数框架提供了相应的工具或库来解析和获取请求参数。一般而言,可以通过从请求对象中获取相应的参数来获取请求参数。例如,在 Java 的 Spring 框架中,可以使用 @RequestParam 注解、HttpServletRequest 对象等来获取请求参数。

这个位置非常复杂。有兴趣可以深入。

那么有些同学会疑问,springmvc可以支持哪些参数?

  1. 查询参数(Query Parameters):将查询参数作为方法参数进行接收。可以使用 @RequestParam 注解将参数与请求中的查询参数绑定,还可以指定默认值、是否必需等属性。

  2. 路径参数(Path Parameters):通过在请求路径中使用占位符来接收参数。使用 @PathVariable 注解将路径参数与方法参数进行绑定。

  3. 请求体参数(Request Body Parameters):通常用于接收 POST 或 PUT 请求中的数据。可以使用 @RequestBody 注解将请求体中的数据绑定到方法参数上。支持的数据格式包括 JSON、XML 等。

  4. 头部信息(Request Header):可以使用 @RequestHeader 注解将特定的请求头信息与方法参数绑定。

  5. Cookie 参数(Cookie Parameters):使用 @CookieValue 注解将特定的 Cookie 值与方法参数进行绑定。

  6. 表单参数(Form Parameters):适用于接收表单提交的参数。可以使用 @RequestParam 注解或 @ModelAttribute 注解将表单字段与方法参数进行绑定。

  7. 文件上传(File Upload):接收文件上传请求时,可以使用 MultipartFile 类型的方法参数来接收上传的文件数据。

当然上面是我所看源码了解到的,目前有没有其它暂时没看到。可以HandlerMethodArgumentResolver

6cf5361b22eadd6ab088a4d983c577a0.png

mappedHandler.applyPostHandle(processedRequest, response, mv);

上面这段是拦截器的一些实现,我们有些请求或不开放的接口权限等可以结合这个来进行拦截。

代码位置:org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = interceptors.length - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = interceptors[i];
                //调用拦截器
        interceptor.postHandle(request, response, this.handler, mv);
      }
    }
  }

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

这个是最后的视图的实现了,但是视图有好多种 如下:

  1. JSP 视图(InternalResourceView):使用 JSP(JavaServer Pages)作为视图技术。通过 InternalResourceViewResolver 视图解析器,将逻辑视图名映射到 JSP 文件,并将模型数据传递给 JSP 进行渲染。

  2. Thymeleaf 视图(ThymeleafView):使用 Thymeleaf 模板引擎进行视图渲染。Thymeleaf 是一个现代化的 Java 模板引擎,可以与 HTML、XML、JavaScript 等文件进行集成。

  3. Freemarker 视图(FreeMarkerView):使用 FreeMarker 模板引擎进行视图渲染。FreeMarker 是一个模板引擎,通过模板文件和数据模型生成最终的输出。

  4. Velocity 视图(VelocityView):使用 Apache Velocity 模板引擎进行视图渲染。Velocity 是一个基于 Java 的模板引擎,可用于生成文本、HTML、XML 等格式的输出。

  5. JSON 视图(MappingJackson2JsonView):将模型数据以 JSON 格式返回给客户端。通过 Jackson 库将模型数据序列化为 JSON 字符串,并通过 HttpServletResponse 返回给客户端。

  6. XML 视图(MarshallingView):将模型数据以 XML 格式返回给客户端。通过 JAXB(Java Architecture for XML Binding)将模型数据转换为 XML,并通过 HttpServletResponse 返回给客户端。

  7. 等等:除了上述视图以外,Spring 还支持自定义视图解析器和自定义视图类型,可以根据业务需求使用其他视图技术来进行视图渲染。

代码位置:org.springframework.web.servlet.DispatcherServlet#processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

    boolean errorView = false;
      //不为空
    if (exception != null) {
            //匹配类型为ModelAndViewDefiningException
      if (exception instanceof ModelAndViewDefiningException) {
        logger.debug("ModelAndViewDefiningException encountered", exception);
                //转换成ModelAndViewDefiningException
        mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
                //获取自定义异常
        Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                //转换
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
      }
    }

    // 不为空且 非被清除
    if (mv != null && !mv.wasCleared()) {
            //进行视图创建
      render(mv, request, response);
      if (errorView) {
        WebUtils.clearErrorRequestAttributes(request);
      }
    }
    else { //证明被解析过了
            //打印日志
      if (logger.isDebugEnabled()) {
        logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
            "': assuming HandlerAdapter completed request handling");
      }
    }
      //若请求是异步处理的(Concurrent handling started during a forward),则直接返回,不做后续处理。
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
    }
      //不为空打印日志
    if (mappedHandler != null) {
      mappedHandler.triggerAfterCompletion(request, response, null);
    }
  }

后面就是一些异常和finally的处理,都是清空缓存的一些处理。不细看,有兴趣可以了解一下。

最后

springmvc非常重要,特别源码这块,涉及视图解析以及如何拦截等逻辑,这些核心特别是想在spring方面有所提升的同学,建议可以再细详深入debug一行一行把核心逻辑过一下,真的后面想走得深入或做架构方面及整合一些框架这些流程先后顺序必须懂,否则很容易吃一大亏。当然以上仅是本人看法。

参考文章:

https://www.yii666.com/blog/452442.html

https://blog.csdn.net/weixin_56644618/article/details/127594065

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

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

相关文章

Xcode 清空最近打开的项目

打开Xcode任意项目 File -> Open Recent -> Clear Menu

桌面应用小程序,一种创新的跨端开发方案

Qt Group在提及2023年有桌面端应用程序开发热门趋势时&#xff0c;曾经提及三点&#xff1a; 关注用户体验&#xff1a;无论您是为桌面端、移动端&#xff0c;还是为两者一起开发应用程序&#xff0c;有一点是可以确定的&#xff1a;随着市场竞争日益激烈&#xff0c;对产品的期…

怎么批量在图片名后加相同的文字

怎么批量在图片名后加相同的文字&#xff1f;有个小伙伴通过私信想我咨询一个问题&#xff0c;它从事的是摄影类的工作&#xff0c;每天会在电脑上存储非常多的图片&#xff0c;时间一久电脑上保存的图片非常的多&#xff0c;这让图片的管理和查找变得比较麻烦&#xff0c;有时…

从智能手机到智能机器人:小米品牌的高端化之路

原创 | 文 BFT机器人 前言 在前阵子落幕的2023世界机器人大会“合作之夜”上&#xff0c;北京经济技术开发区管委会完成了与世界机器人合作组织、小米机器人等16个重点项目签约&#xff0c;推动机器人创新链和产业链融合&#xff0c;其中小米的投资额达到20亿&#xff01; 据了…

E5061B/是德科技keysight E5061B网络分析仪

181/2461/8938产品概述 是德科技E5061B(安捷伦)网络分析仪在从5 Hz到3 GHz的宽频率范围内提供通用的高性能网络分析。E5061B提供ENA系列常见的出色RF性能&#xff0c;还提供全面的LF(低频)网络测量能力&#xff1b;包括内置1 Mohm输入的增益相位测试端口。E5061B从低频到高频的…

通过cpolar内网穿透,在家实现便捷的SSH远程连接公司内网服务器教程

文章目录 1. Linux CentOS安装cpolar2. 创建TCP隧道3. 随机地址公网远程连接4. 固定TCP地址5. 使用固定公网TCP地址SSH远程 本次教程我们来实现如何在外公网环境下&#xff0c;SSH远程连接家里/公司的Linux CentOS服务器&#xff0c;无需公网IP&#xff0c;也不需要设置路由器。…

VLAN间路由:单臂路由与三层交换

文章目录 一、定义二、实现方式单臂路由三层交换 三、单臂路由与三层路由优缺点对比四、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、定义 VLAN间路由是一种网络配置方法&#xff0c;旨在实现不同虚拟局域网&#xff08;VLAN&#xff09;之…

pdf文件过大如何缩小上传?pdf压缩跟我学

在我们日常工作和生活中&#xff0c;经常会遇到PDF文件过大的问题&#xff0c;给文件传输和存储带来了很大的不便。那么&#xff0c;如何缩小PDF文件大小以便上传呢&#xff1f;下面就给大家分享几个压缩方法&#xff0c;一起来了解下PDF文件压缩方法吧~ 方法一&#xff1a;嗨格…

数据结构——七大排序[源码+动图+性能测试]

本章代码gitee仓库&#xff1a;排序 文章目录 &#x1f383;0. 思维导图&#x1f9e8;1. 插入排序✨1.1 直接插入排序✨1.2 希尔排序 &#x1f38a;2. 选择排序&#x1f38b;2.1 直接选择排序&#x1f38b;2.2 堆排序 &#x1f38f;3. 交换排序&#x1f390;3.1 冒泡排序&#…

TS编译选项

自动监控编译 tsc xxx.ts -w 在一个文件夹下&#xff0c;创建 tsconfig.json 文件&#xff0c;在用命令 tsc 就可以自动编译当前文件夹下的ts文件 tsconfig.json文件配置如下&#xff1a; {/*tsconfig.json 是ts编译器的配置文件&#xff0c;ts编译器可以根据它的信息来对代…

mysql 的增删改查以及模糊查询、字符集语句的使用

一、mysql启动与登陆(windows下的mysql操作) 1.启动mysql服务 net start mysql81 2.登陆mysql mysql -uroot -p 3.查看所有数据库 show databases; 二、模糊查询&#xff08;like&#xff09; 1. _代表查询单个 2.%代表查询多个 3.查找所有含有schema的数据库&#xff1b;…

钡铼技术BL122DN实现DNP3.0转Profinet协议的双向数据传输

引言 随着工业自动化技术的不断发展&#xff0c;各种通信协议和标准层出不穷。BL122DN DNP3.0转Profinet协议网关作为一种高效的解决方案&#xff0c;在工业自动化领域发挥着重要作用。本文将详细介绍BL122DN DNP3.0转Profinet协议网关的特点、功能和优势&#xff0c;以及在实…

使用ppt和texlive生成eps图片(高清、可插入latex论文)

一、说明 写论文经常需要生成高清的图片插入到论文中&#xff0c;本文以ppt画图生成高质量的eps图片的实现来介绍具体操作方法。关于为什么要生成eps图片&#xff0c;一个是期刊要求&#xff08;也有不要求的&#xff09;&#xff0c;另一个是显示图像的质量高。 转化获得eps…

SpringCloud(36):Nacos服务发现基础应用

1 服务发现数据模型 Nacos在经过阿里内部多年生产经验后提炼出的数据模型,则是一种服务-集群-实例的三层模型,这样基本可以满足服务在所有场景下的数据存储和管理。 命名空间(Namespace) 用于进行租户粒度的配置隔离,命名空间不仅适用于nacos的配置管理,同样适用于服务发…

【Vue】实现当前页面刷新的四种方法

目录 前言方法一&#xff1a;location.reload方法二&#xff1a;$router.go(0)方法三&#xff1a;provide、inject和$nextTick方法四&#xff1a;创建空白页 前言 这两周在写一个后台管理&#xff0c;每次调用接口实现增删改查的过程中&#xff0c;都需要刷新当前页面或者刷新…

web pdf 拖拽签章

web pdf 拖拽签章 主要通过火狐的pdfjs 来实现 1. 下载js 并编译 地址 https://mozilla.github.io/pdf.js/ 按照官网当下下载并编译就得到了js 2.其实也没有什么好讲的&#xff0c;都是用的js中的方法&#xff0c;官网中都有 按照步骤就能生成一个document元素&#xff0c;然…

低压配电室电力安全解决方案

低压电气安全监控运维系统是力安科技基于物联网核心技术自主开发的高可靠性安全监测系统。其工作原理是利用物联网、云计算、大数据、数字传感技术及RFID无线射频识别技术来获取低压配电回路电压、电流、温度、有功、无功、功率因数等全电量的采集及配电线路的漏电、温度的实时…

Java智慧工地大数据中心源码

智慧工地技术架构&#xff1a;微服务JavaSpring Cloud VueUniApp MySql 智慧工地形成安全、质量、进度、人员、机械、绿色施工六大针对性解决方案。 安全管理 围绕重大危险源提供管控&#xff0c;可视化跟踪消防、安防、基坑、高支模、临边防护、卸料平台等设施设备的安全状态…

pytorch-v2.0.1 cuda arm64 aarch64 torch 2.0.1+cu118 源码编译笔记

之前的记录 Kylin Linux Advanced Server V10 (Tercel) aarch64安装NVIDIA-Linux-aarch64-520.61.05.run驱动Tesla T4 16G笔记_hkNaruto的博客-CSDN博客 Ubuntu 22.04 x86_64 源码编译 pytorch-v2.0.1 笔记_hkNaruto的博客-CSDN博客 Ubuntu 22.04 x86_64 源码编译 pytorch-v…

机器学习部分知识点总结

文章目录 基本概念N与NP泛化能力性能度量比较检验 线性回归逻辑回归神经网络 基本概念 N与NP P问题&#xff1a;一个问题可以在多项式&#xff08;O(n^k) 的时间复杂度内解决 例如&#xff1a;n个数的排序&#xff08;不超过O(n^2)&#xff09; NP问题&#xff1a;一个问题的解…