Spring Exception 常见错误

news2024/9/20 18:28:31

今天,来学习 Spring 的异常处理机制。Spring 提供了一套健全的异常处理框架,以便我们在开发应用的时候对异常进行处理。但是,我们也会在使用的时候遇到一些麻烦,接下来我将通过两个典型的错误案例,带着你结合源码进行深入了解。

案例 1:小心过滤器异常

为了方便讲解,我们还是沿用之前在事务处理中用到的学生注册的案例,来讨论异常处理的问题:

@Controller
@Slf4j
public class StudentController {
    public StudentController(){
        System.out.println("construct");
    }


    @PostMapping("/regStudent/{name}")
    @ResponseBody
    public String saveUser(String name) throws Exception {
        System.out.println("......用户注册成功");
        return "success";
    }
}

为了保证安全,这里需要给请求加一个保护,通过验证 Token 的方式来验证请求的合法性。这个 Token 需要在每次发送请求的时候带在请求的 header 中,header 的 key 是 Token。

为了校验这个 Token,我们引入了一个 Filter 来处理这个校验工作,这里我使用了一个最简单的 Token:111111。

当 Token 校验失败时,就会抛出一个自定义的 NotAllowException,交由 Spring 处理:

@WebFilter
@Component
public class PermissionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");

        if (!"111111".equals(token)) {
            System.out.println("throw NotAllowException");
            throw new NotAllowException();
        }
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

NotAllowException 就是一个简单的 RuntimeException 的子类:

public class NotAllowException extends RuntimeException {
    public NotAllowException() {
        super();
    }
}

同时,新增了一个 RestControllerAdvice 来处理这个异常,处理方式也很简单,就是返回一个 403 的 resultCode:

@RestControllerAdvice
public class NotAllowExceptionHandler {
    @ExceptionHandler(NotAllowException.class)
    @ResponseBody
    public String handle() {
        System.out.println("403");
        return "{\"resultCode\": 403}";
    }
}

为了验证一下失败的情况,我们模拟了一个请求,在 HTTP 请求头里加上一个 Token,值为 111,这样就会引发错误了,我们可以看看会不会被 NotAllowExceptionHandler 处理掉。

然而,在控制台上,我们只看到了下面这样的输出,这其实就说明了 NotAllowExceptionHandler 并没有生效。

throw NotAllowException

 想下问题出在哪呢?我们不妨对 Spring 的异常处理过程先做一个了解。

案例解析

我们先来回顾一下过滤器执行流程图,这里我细化了一下:

从这张图中可以看出,当所有的过滤器被执行完毕以后,Spring 才会进入 Servlet 相关的处理,而 DispatcherServlet 才是整个 Servlet 处理的核心,它是前端控制器设计模式的实现,提供 Spring Web MVC 的集中访问点并负责职责的分派。正是在这里,Spring 处理了请求和处理器之间的对应关系,以及这个案例我们所关注的问题——统一异常处理。 

其实说到这里,我们已经了解到过滤器内异常无法被统一处理的大致原因,就是因为异常处理发生在上图的红色区域,即 DispatcherServlet 中的 doDispatch(),而此时,过滤器已经全部执行完毕了。

下面我们将深入分析 Spring Web 对异常统一处理的逻辑,深刻理解其内部原理。

首先我们来了解下 ControllerAdvice 是如何被 Spring 加载并对外暴露的。在 Spring Web 的核心配置类 WebMvcConfigurationSupport 中,被 @Bean 修饰的 handlerExceptionResolver(),会调用 addDefaultHandlerExceptionResolvers() 来添加默认的异常解析器。

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
   List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
   configureHandlerExceptionResolvers(exceptionResolvers);
   if (exceptionResolvers.isEmpty()) {
      addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
   }
   extendHandlerExceptionResolvers(exceptionResolvers);
   HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
   composite.setOrder(0);
   composite.setExceptionResolvers(exceptionResolvers);
   return composite;
}

最终按照下图的调用栈,Spring 实例化了 ExceptionHandlerExceptionResolver 类。

从源码中我们可以看出,ExceptionHandlerExceptionResolver 类实现了 InitializingBean 接口,并覆写了 afterPropertiesSet()。

public void afterPropertiesSet() {
   // Do this first, it may add ResponseBodyAdvice beans
   initExceptionHandlerAdviceCache();
    //省略非关键代码
}

并在 initExceptionHandlerAdviceCache() 中完成了所有 ControllerAdvice 中的 ExceptionHandler 的初始化。其具体操作,就是查找所有 @ControllerAdvice 注解的 Bean,把它们放到成员变量 exceptionHandlerAdviceCache 中。

在我们这个案例里,就是指 NotAllowExceptionHandler 这个异常处理器。

private void initExceptionHandlerAdviceCache() {
   //省略非关键代码
   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      if (resolver.hasExceptionMappings()) {
         this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
      }
 //省略非关键代码
}

到这,我们可以总结一下,WebMvcConfigurationSupport 中的 handlerExceptionResolver() 实例化并注册了一个 ExceptionHandlerExceptionResolver 的实例,而所有被 @ControllerAdvice 注解修饰的异常处理器,都会在 ExceptionHandlerExceptionResolver 实例化的时候自动扫描并装载在其类成员变量 exceptionHandlerAdviceCache 中。

当第一次请求发生时,DispatcherServlet 中的 initHandlerExceptionResolvers() 将获取所有注册到 Spring 的 HandlerExceptionResolver 类型的实例,而 ExceptionHandlerExceptionResolver 恰好实现了 HandlerExceptionResolver 接口,这些 HandlerExceptionResolver 类型的实例则会被写入到类成员变量 handlerExceptionResolvers 中。

private void initHandlerExceptionResolvers(ApplicationContext context) {
   this.handlerExceptionResolvers = null;

   if (this.detectAllHandlerExceptionResolvers) {
      // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
            .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
         // We keep HandlerExceptionResolvers in sorted order.
         AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
      }
      //省略非关键代码
}

接着我们再来了解下 ControllerAdvice 是如何被 Spring 消费并处理异常的。下文贴出的是核心类 DispatcherServlet 中的核心方法 doDispatch() 的部分代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   //省略非关键代码

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;
      try {
         //省略非关键代码
         //查找当前请求对应的 handler,并执行
         //省略非关键代码
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   //省略非关键代码

Spring 在执行用户请求时,当在“查找”和“执行”请求对应的 handler 过程中发生异常,就会把异常赋值给 dispatchException,再交给 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) {
      if (exception instanceof ModelAndViewDefiningException) {
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }
   //省略非关键代码

进一步处理后,即当 Exception 不为 null 时,继续交给 processHandlerException 处理。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {
   //省略非关键代码
   ModelAndView exMv = null;
   if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
         exMv = resolver.resolveException(request, response, handler, ex);
         if (exMv != null) {
            break;
         }
      }
   }
   //省略非关键代码
}

然后,processHandlerException 会从类成员变量 handlerExceptionResolvers 中获取有效的异常解析器,对异常进行解析。

显然,这里的 handlerExceptionResolvers 一定包含我们声明的 NotAllowExceptionHandler#NotAllowException 的异常处理器的 ExceptionHandlerExceptionResolver 包装类。

问题修正

为了利用 Spring MVC 的异常处理机制,我们需要对 Filter 做一些改造。手动捕获异常,并将异常 HandlerExceptionResolver 进行解析处理。

我们可以这样修改 PermissionFilter,注入 HandlerExceptionResolver:

@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;

然后,在 doFilter 里捕获异常并交给 HandlerExceptionResolver 处理:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String token = httpServletRequest.getHeader("token");
        if (!"111111".equals(token)) {
            System.out.println("throw NotAllowException");
            resolver.resolveException(httpServletRequest, httpServletResponse, null, new NotAllowException());
            return;
        }
        chain.doFilter(request, response);
    }

当我们尝试用错误的 Token 请求,控制台得到了以下信息:

throw NotAllowException
403

返回的 JSON 是:

{"resultCode": 403}

再换成正确的 Token 请求,这些错误信息就都没有了,到这,问题解决了。

案例 2:特殊的 404 异常

继续沿用学生注册的案例,为了防止一些异常的访问,我们需要记录所有 404 状态的访问记录,并返回一个我们的自定义结果。

一般使用 RESTful 接口时我们会统一返回 JSON 数据,返回值格式如下:

{"resultCode": 404}

但是 Spring 对 404 异常是进行了默认资源映射的,并不会返回我们想要的结果,也不会对这种错误做记录。

于是我们添加了一个 ExceptionHandlerController,它被声明成 @RestControllerAdvice 来全局捕获 Spring MVC 中抛出的异常。

ExceptionHandler 的作用正是用来捕获指定的异常:

@RestControllerAdvice
public class MyExceptionHandler {
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String handle404() {
        System.out.println("404");
        return "{\"resultCode\": 404}";
    }
}

我们尝试发送一个错误的 URL 请求到之前实现过的 /regStudent 接口,并把请求地址换成 /regStudent1,得到了以下结果:

{"timestamp":"2021-05-19T22:24:01.559+0000","status":404,"error":"Not Found","message":"No message available","path":"/regStudent1"}

很显然,这个结果不是我们想要的,看起来应该是 Spring 默认的返回结果。那是什么原因导致 Spring 没有使用我们定义的异常处理器呢?

案例解析

我们可以从异常处理的核心处理代码开始分析,DispatcherServlet 中的 doDispatch() 核心代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //省略非关键代码
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         //省略非关键代码
}

首先调用 getHandler() 获取当前请求的处理器,如果获取不到,则调用 noHandlerFound():

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (this.throwExceptionIfNoHandlerFound) {
      throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
            new ServletServerHttpRequest(request).getHeaders());
   }
   else {
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
   }
}

noHandlerFound() 的逻辑非常简单,如果 throwExceptionIfNoHandlerFound 属性为 true,则直接抛出 NoHandlerFoundException 异常,反之则会进一步获取到对应的请求处理器执行,并将执行结果返回给客户端。

到这,真相离我们非常近了,我们只需要将 throwExceptionIfNoHandlerFound 默认设置为 true 即可,这样就会抛出 NoHandlerFoundException 异常,从而被 doDispatch() 内的 catch 俘获。进而就像案例 1 介绍的一样,最终能够执行我们自定义的异常处理器 MyExceptionHandler。

于是,我们开始尝试,因为 throwExceptionIfNoHandlerFound 对应的 Spring 配置项为 throw-exception-if-no-handler-found,我们将其加入到 application.properties 配置文件中,设置其值为 true。

设置完毕后,重启服务并再次尝试,你会发现结果没有任何变化,这个问题也没有被解决。

实际上这里还存在另一个坑,在 Spring Web 的 WebMvcAutoConfiguration 类中,其默认添加的两个 ResourceHandler,一个是用来处理请求路径 /webjars/**,而另一个是 /**。

即便当前请求没有定义任何对应的请求处理器,getHandler() 也一定会获取到一个 Handler 来处理当前请求,因为第二个匹配 /** 路径的 ResourceHandler 决定了任何请求路径都会被其处理。

mappedHandler == null 判断条件永远不会成立,显然就不可能走到 noHandlerFound(),那么就不会抛出 NoHandlerFoundException 异常,也无法被后续的异常处理器进一步处理。

下面让我们通过源码进一步了解下这个默认被添加的 ResourceHandler 的详细逻辑 。

首先我们来了解下 ControllerAdvice 是如何被 Spring 加载并对外暴露的。

同样是在 WebMvcConfigurationSupport 类中,被 @Bean 修饰的

resourceHandlerMapping(),它新建了 ResourceHandlerRegistry 类实例,并通过

addResourceHandlers() 将 ResourceHandler 注册到 ResourceHandlerRegistry 类实例中:

@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
      @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
      @Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

   Assert.state(this.applicationContext != null, "No ApplicationContext set");
   Assert.state(this.servletContext != null, "No ServletContext set");

   ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
         this.servletContext, contentNegotiationManager, urlPathHelper);
   addResourceHandlers(registry);

   AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
   if (handlerMapping == null) {
      return null;
   }
   handlerMapping.setPathMatcher(pathMatcher);
   handlerMapping.setUrlPathHelper(urlPathHelper);
   handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
   handlerMapping.setCorsConfigurations(getCorsConfigurations());
   return handlerMapping;
}

最终通过 ResourceHandlerRegistry 类实例中的 getHandlerMapping() 返回了 SimpleUrlHandlerMapping 实例,它装载了所有 ResourceHandler 的集合并注册到了 Spring 容器中:

protected AbstractHandlerMapping getHandlerMapping() {
   //省略非关键代码
   Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
   for (ResourceHandlerRegistration registration : this.registrations) {
      for (String pathPattern : registration.getPathPatterns()) {
         ResourceHttpRequestHandler handler = registration.getRequestHandler();
         //省略非关键代码
         urlMap.put(pathPattern, handler);
      }
   }
   return new SimpleUrlHandlerMapping(urlMap, this.order);
}

我们查看以下调用栈截图:

可以了解到,当前方法中的 addResourceHandlers() 最终执行到了 WebMvcAutoConfiguration 类中的 addResourceHandlers(),通过这个方法,我们可以知道当前有哪些 ResourceHandler 的集合被注册到了 Spring 容器中:

public void addResourceHandlers(ResourceHandlerRegistry registry) {
   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }
   Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
   CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
   if (!registry.hasMappingForPattern("/webjars/**")) {
      customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/")
            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
   }
   String staticPathPattern = this.mvcProperties.getStaticPathPattern();
   if (!registry.hasMappingForPattern(staticPathPattern)) {
      customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
            .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
   }
}

 从而验证我们一开始得出的结论,此处添加了两个 ResourceHandler,一个是用来处理请求路径 /webjars/**, 而另一个是 /**。

这里你可以注意一下方法最开始的判断语句,如果 this.resourceProperties.isAddMappings() 为 false,那么会直接返回,后续的两个 ResourceHandler 也不会被添加。

   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }

​至此,有两个 ResourceHandler 被实例化且注册到了 Spirng 容器中,一个处理路径为 /webjars/** 的请求,另一个处理路径为 /** 的请求 。

同样,当第一次请求发生时,DispatcherServlet 中的 initHandlerMappings() 将会获取所有注册到 Spring 的 HandlerMapping 类型的实例,而 SimpleUrlHandlerMapping 恰好实现了 HandlerMapping 接口,这些 SimpleUrlHandlerMapping 类型的实例则会被写入到类成员变量 handlerMappings 中。

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;
//省略非关键代码
   if (this.detectAllHandlerMappings) {
      // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<>(matchingBeans.values());
         // We keep HandlerMappings in sorted order.
         AnnotationAwareOrderComparator.sort(this.handlerMappings);
      }
   }
   //省略非关键代码
}

接着我们再来了解下被包装为 handlerMappings 的 ResourceHandler 是如何被 Spring 消费并处理的。

我们来回顾一下 DispatcherServlet 中的 doDispatch() 核心代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //省略非关键代码
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }
         //省略非关键代码
}

这里的 getHandler() 将会遍历成员变量 handlerMappings:

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;
}

因为此处有一个 SimpleUrlHandlerMapping,它会拦截所有路径的请求:

所以最终在 doDispatch() 的 getHandler() 将会获取到此 handler,从而 mappedHandler==null 条件不能得到满足,因而无法走到 noHandlerFound(),不会抛出 NoHandlerFoundException 异常,进而无法被后续的异常处理器进一步处理。

问题修正

那如何解决这个问题呢?还记得 WebMvcAutoConfiguration 类中 addResourceHandlers() 的前两行代码吗?如果 this.resourceProperties.isAddMappings() 为 false,那么此处直接返回,后续的两个 ResourceHandler 也不会被添加。

public void addResourceHandlers(ResourceHandlerRegistry registry) {
   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }
   //省略非关键代码
}

 其调用 ResourceProperties 中的 isAddMappings() 的代码如下:

public boolean isAddMappings() {
   return this.addMappings;
}

到这,答案也就呼之欲出了,增加两个配置文件如下:

spring.resources.add-mappings=false
spring.mvc.throwExceptionIfNoHandlerFound=true

修改 MyExceptionHandler 的 @ExceptionHandler 为 NoHandlerFoundException 即可:

@ExceptionHandler(NoHandlerFoundException.class)

这个案例在真实的产线环境遇到的概率还是比较大的,知道如何解决是第一步,了解其内部原理则更为重要。而且当你进一步去研读代码后,你会发现这里的解决方案并不会只有这一种,而剩下的就留给你去探索了。

重点回顾

通过以上两个案例的介绍,相信你对 Spring MVC 的异常处理机制,已经有了进一步的了解,这里我们再次回顾下重点:

  • DispatcherServlet 类中的 doDispatch() 是整个 Servlet 处理的核心,它不仅实现了请求的分发,也提供了异常统一处理等等一系列功能;
  • WebMvcConfigurationSupport 是 Spring Web 中非常核心的一个配置类,无论是异常处理器的包装注册(HandlerExceptionResolver),还是资源处理器的包装注册(SimpleUrlHandlerMapping),都是依靠这个类来完成的。

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

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

相关文章

Centos7.9环境源码编译安装ffmpeg6.x

1.官网ffmpeg下载源码 https://ffmpeg.org/download.html#build-windows 2.未安装x264库则先安装配置 可以先查询x264库: whereis libx264 安装编译工具和依赖库&#xff1a; sudo yum install gcc make cmake mercurial git yasm pkgconfig autoconf automake libtool sudo…

Android ANR 日志分析定位

ANR 是 Android 应用程序中的 "Application Not Responding" 的缩写&#xff0c;中文意思是 "应用程序无响应"。这是当应用程序在 Android 系统上运行时&#xff0c;由于某种原因不能及时响应用户输入事件或执行一个操作&#xff0c;导致界面无法更新&…

NodeJs 版本升级时Vue工程报错

最近把nodejs 的版本更新了&#xff0c;原来用的 16.15.1 现在用的 18.16.1&#xff0c;结果所有的vue工程都启不动了&#xff0c;一直报错。 1.报错截图 2.原因分析 error:03000086:digital envelope routines::initialization error &#xff0c;这个是nodejs版本的问题&am…

Linux基础指令(任务调度、磁盘分区挂载、网络配置、进程管理)

Linux基础指令 1、任务调度简介基本语法crontab【选项】at定时任务 2、磁盘分区、挂载3、网络配置配置网络环境设置主机名和host映射主机名解析过程分析 4、进程管理ps指令终止进程查看进程树服务管理打开或关闭指定端口动态监控进程&#xff08;top&#xff09;监控网络状态 1…

Vue3 使用动态组件 component

component 标签&#xff1a;用于动态渲染标签或组件。 语法格式&#xff1a; <component is"标签或组件名">标签内容</component> 动态渲染标签&#xff1a; <template><h3>我是父组件</h3><component is"h1">动态…

Android App启动时黑屏或白屏

1.自己解决&#xff0c;在style中自定义一个样式&#xff0c;放一个启动图背景。 <style name"AppTheme.StartingWindowTheme" >//这里设置的是启动图片<item name"android:windowBackground">mipmap/starttu</item><item name"…

“金三银四”招聘季,大厂争招鸿蒙人才

在金三银四的招聘季中&#xff0c;各大知名互联网企业纷纷加入了对鸿蒙人才的争夺战。近日&#xff0c;包括淘宝、京东、得物等在内的知名APP均宣布启动鸿蒙星河版原生应用开发计划。这一举措不仅彰显了鸿蒙生态系统的迅猛发展&#xff0c;还催生了人才市场的繁荣景象。据数据显…

使用全局事件总线实现任意组件间的通讯

本文以vue2中爷孙组件通讯为例&#xff0c;需求是点击孙组件的按钮&#xff0c;实现关闭爷组件的弹窗。 全局事件总线是通过Vue实例的事件系统来实现组件之间的通讯&#xff0c;可以方便地在任何组件中进行事件的触发和监听。 以下是使用全局事件总线实现爷孙组件通讯的步骤&a…

数据可视化基础与应用-02-基于powerbi实现连锁糕点店数据集的仪表盘制作

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍基于powerbi实现一个连锁糕点店数据集的仪表盘制作。 数据集描述 有一个数据集&#xff0c;包含四张工作簿&#xff0c;每个工作簿是一张表&#xff0c;其中可以销售表可以划分为事实表&#xff0c;产品表&am…

SpringBoot+Vue+MySQL:装修管理新架构探索

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)

目录 &#x1f6a9;了解题意 &#x1f6a9;算法分析 第一种方法&#xff1a;双指针 &#x1f6a9;代码实现一 第二种方法&#xff1a;三指针 &#x1f6a9;代码实现二 &#x1f6a9;了解题意 本题将整数0&#xff0c;1&#xff0c;2代表红白篮&#xff0c;nums中的整数并…

数据之光:探索数据库技术的演进之路

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

【C++】继承与多态的常见问题解析

文章目录 继承1.什么是菱形继承&#xff1f;菱形继承的问题是什么&#xff1f;2. 什么是菱形虚拟继承&#xff1f;如何解决数据冗余和二义性的3. 继承和组合的区别&#xff1f;什么时候用继承&#xff1f;什么时候用组合&#xff1f;1.继承&#xff08;Inheritance&#xff09;…

数据迁移DTS | 云上MySQL 数据库迁移至达梦数据库

引入 云上 MySQL 数据库 —> 向达梦国产化数据库迁移 下载&安装 达梦客户端工具 DM->可参考之前国产化专栏达梦文章 创建模式 在客户端分别依次执行以下命令脚本&#xff08;这里没有通过客户端管理工具去创建达梦数据库的模式&#xff0c;当然也可以通过图形化界…

Find My运动相机|苹果Find My技术与相机结合,智能防丢,全球定位

运动相机设计用于在各种运动和极限环境中使用&#xff0c;如徒步、登山、攀岩、骑行、滑翔、滑雪、游泳和潜水等&#xff0c;它们通常具有防抖防震、深度防水和高清画质的特点&#xff0c;能够适应颠簸剧烈的环境&#xff0c;甚至可以承受一定程度的摔落&#xff0c;一些运动相…

自动化测试实例—Web登录功能性测试(无验证码)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、什么是自动化测试 把人为驱动的测试行为转化为机器执行的一…

数据防泄漏的具体方法有哪些?数据防泄漏教程分享

曾经有一个非常严重的数据泄漏事件&#xff0c;是一家制造业相关的公司&#xff0c;经过调查&#xff0c;发现是员工在未经授权的情况下将客户数据通过电子邮件发送给了外部人员。 这一事件导致了客户隐私的泄露&#xff0c;给该机构带来了巨大的法律风险和声誉损失。 其实每…

DolphinScheduler——蔚来汽车数据治理开发平台的应用改造

目录 一、业务痛点 二、应用现状 三、技术改造 3.1 稳定性 3.1.1 滚动重启黑名单机制精准路由 3.2 易用性 依赖节点优化 补数任务优化 多 SQL 执行 原文大佬的这篇基于调度系统的数据治理案例有借鉴意义&#xff0c;这里摘抄下来用作学习和知识沉淀。 一、业务痛点 蔚…

tomcat 搭建博客 及破解数据库密码

一 tomcat 搭建博客 &#xff08;一&#xff09;博客安装包 1&#xff0c; 把博客war包 放到 webapps 文件夹下 2&#xff0c;会自动解压 3&#xff0c;做个软连接 方便后续操作 可以注意到 因为war包 是又tomcat 自己解压的 所以属主数组还是 tomcat &#xff08…

Redis性能攻略:Redis-benchmark工具与实用性能优化技巧

Redis作为一种高性能的内存数据库&#xff0c;广泛应用于各种业务场景。然而&#xff0c;随着业务规模的扩大和数据量的增长&#xff0c;Redis的性能问题逐渐凸显出来。为了提高Redis的性能&#xff0c;本文将深入探讨Redis性能优化方案&#xff0c;包括参数配置、数据结构、多…