一文带你掌握Spring Web异常处理方式

一、前言

最近从单位离职了,离开了五年多来朝朝夕夕皆灯火辉煌的某网,激情也好悲凉也罢,觥筹场上屡屡物是人非,调转过事业部以为能换种情绪,岂料和下了周五的班的前同事兼好朋友,匆匆赶往藏身巷弄的小菜馆里时,又次次被迫想起,那破晓时分大厦头顶有点吝啬的阳光。

阿坤:但凡拿我们当自己人,就不会这样…

我 :也许人家想好好表现呢
阿坤:算了,不说了,走着走着天要亮了,回去睡吧
我 :卧槽,真的是,行了不说了,趁着下面还没亮,赶紧回去睡吧
阿坤:下午见

小张目前蜗居赋闲,顺便养一下左肩。

想念七七。

扯远了,不说了,今天来给大家说一下 Spring Web 模块(基于 Servlet)中的异常(以下简称 Spring 异常)处理机制,看完文章,你应该会懂得在 web 开发过程中,怎么处理程序出现的异常。

本文基于 springboot 2.5.1 , 对应 spring framework 版本为 5.3.8

二、本文的异常种类划分

三、Spring Web 模块的请求核心流程解析

上述错误,都是用户在使用浏览器或者 APP 等访问后台时候出现的异常,因此我们有必要去了解一下 Spring Web 模块对用户请求的核心处理流程,只有当熟悉了请求处理流程,我们处理起异常来,才会得心应手。

每一个基于 Servlet 的 web 框架,都会有自己的 Servlet 实现,在 Spring Web 中,它叫 DispatcherServlet ,你所有的请求都会经过它来处理。

而在 Spring 的设计中, DispatcherServlet 中处理请求的那个方法,叫 doDispatch()

话不多说,先来看我精简过的方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        // 。。。小张替你省略部分代码。。。
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 。。。小张替你省略部分代码。。。

                // 根据 request 获取对应的 Handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 根据 Handler 类型找到合适的 Handler 适配器,DispatcherServilet 通过适配器间接调用 Handler ,
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // 。。。小张替你省略部分代码。。。

                // 重头戏来了
                // 步骤1. 下面这一行会遍历拦截器,执行所有拦截器的 preHandle() 方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 步骤2. 当所有拦截器都校验通过的时,下面这一行执行目标 controller 对应的业务方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // 。。。小张替你省略部分代码。。。

                // 步骤3. 当目标 controller 的业务方法执行完毕之后,下面这一行执行所有拦截器的 postHandler() 方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);

                // 步骤4. 下面有两个异常,捕获了 拦截器 preHandler 阶段和 controller 的业务方法执行阶段抛出的异常
            } catch (Exception ex) {
                dispatchException = ex;
            } catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 步骤5. 如果步骤4有异常,就会在这里处理
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

            // 步骤6. 下面两个异常调用所有拦截器的 fterCompletion方法
        } catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));
        }finally {
            // 。。。小张替你省略部分代码。。。
        }
    }

上面代码中,最里面的那个 try … catch 已经把常用情况下的 拦截器、controller 的异常捕获到了,异常处理逻辑在 步骤5 里面:

private void processDispatchResult(HttpServletRequest request,
                                    HttpServletResponse response,
                                    @Nullable HandlerExecutionChain mappedHandler,
                                    @Nullable ModelAndView mv,
                                    @Nullable Exception exception) throws Exception {
    // 。。。小张替你省略部分代码。。。

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
           // 。。。小张替你省略部分代码。。。
        } else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            // 调用 DispatcherServlet 异常处理流程
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
    // 。。。小张替你省略部分代码。。。
}

ModelAndView processHandlerException(HttpServletRequest request,
                                    HttpServletResponse response,
                                    @Nullable Object handler,
                                    Exception ex) throws Exception {
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍历 DispatcherServlet 里加载好的异常处理器
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
            // 交给异常处理器处理
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                // 找到合适的异常处理器就中断
                break;
            }
        }
    }
    // 。。。小张替你省略部分代码。。。
}

异常最终会交给 DispatcherServlet 里的 this.handlerExceptionResolvers 集合来处理,而这个东西也是我们自己规划的异常处理器最终汇聚的地方,它的类型是 HandlerExceptionResolver 接口

这是一个接口,只有异常处理的方法签名

public interface HandlerExceptionResolver {

    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

注意,返回值 ModelAndView 不为空,证明该异常处理器处理了异常,spring 不会再让剩下的异常处理器处理该异常

四、异常处理手段

章节二的划分对应的处理手段有下面这几种,我们一一举例

// 步骤1. 使用注解修饰异常处理类
@ControllerAdvice
public class ErrorHandlerDemo {

    // 步骤2. 搭配使用注解,处理指定异常
    @ExceptionHandler(CacheException.class)
    @ResponseBody
    public String cacheException(HandlerMethod handlerMethod, Exception e) {
        return defaultErrorHandler(handlerMethod, e);
    }

    // 步骤2. 搭配使用注解,处理指定异常
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public String defaultErrorHandler(HandlerMethod handlerMethod, Exception e) {
        return revertMessage(e);
    }

    private String revertMessage(Exception e) {
        String msg = "系统异常";
        if (e instanceof CacheException) {
            msg = e.getMessage();
        }
        return msg;
    }
}

对应异常处理的方法返回值类型,类比 @RequestMapping 方法的返回值类型,比如,也可以是 ModelAndView 类型

原理剖析

A. @ControllerAdvice + @ExceptionHandler 注解修饰的类的解析

首先,被 @ControllerAdvice 注解修饰的类,会被 Spring 包装成 ControllerAdviceBean ,这个东西把修饰的类的 Class<?> 保存成 beanType ,并且是 ExceptionHandlerMethodResolver 的构造函数入参,唯一的构造函数唯一的入参。

ExceptionHandlerMethodResolver 又是个什么东西? 异常处理器方法解析器?什么玩意儿!先看它都干了什么吧

public class ExceptionHandlerMethodResolver {

public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);

    // 参数 handlerType 就是上面说提到的 beanType,就是上面实例代码中的 ErrorHandlerDemo 类
    public ExceptionHandlerMethodResolver(Class handlerType) {

        // 这个 EXCEPTION_HANDLER_METHODS 是个函数式接口
        // MethodIntrospector.selectMethods() 方法用来查找 @ControllerAdvice 类里面被 @ExceptionHandler 注解修饰的方法并缓存起来
        for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
            for (Class exceptionType : detectExceptionMappings(method)) {

                // 以 异常类型:异常处理方法 格式缓存起来
                addExceptionMapping(exceptionType, method);
            }
        }
    }

}

细心读下注释,可以发现 ExceptionHandlerMethodResolver 已经把我们自定义的异常处理类和异常处理方法都已经收集、准备完毕了。

有人说了,我搞了多个 @ControllerAdvice 修饰的类啊,你敢不敢都给解析了?

敢!接下来就告诉你 @ControllerAdvice 修饰的类在哪里解析的!

B. @ControllerAdvice + @ExceptionHandler 注解修饰的类的加载

有个类叫 ExceptionHandlerExceptionResolver (什么玩意儿?异常处理器异常解析器?),别懵,不翻译它,看它是个啥

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    @Nullable
    private ApplicationContext applicationContext;

    // 没错,就是这里,缓存了 ErrorHandlerDemo 和它内部的异常处理方法对应的 ExceptionHandlerMethodResolver
    private final Map exceptionHandlerAdviceCache = new LinkedHashMap<>();

    // 。。。小张替你省略部分代码。。。

    // 这里是 InitializingBean 的实现方法
    @Override
    public void afterPropertiesSet() {
        // 这就是加载 ControllerAdviceBean 的地方
        initExceptionHandlerAdviceCache();

        // 。。。小张替你省略部分代码。。。
    }

    private void initExceptionHandlerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }

        // 加载重点来了,通过上 Spring 上下文获取被 @ControllerAdvice 修饰的 ErrorHandlerDemo
        List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }

            // 创建与 ErrorHandlerDemo 中的异常处理方法对应的 ExceptionHandlerMethodResolver,就是上面A小节的解析部分
            ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);

            if (resolver.hasExceptionMappings()) {
                // ControllerAdviceBean 与 ExceptionHandlerMethodResolver 一一对应,缓存起来
                this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
            }

            // 。。。小张替你省略部分代码。。。
        }

        // 。。。小张替你省略部分代码。。。
    }
}

那么到这里就明白了, ExceptionHandlerExceptionResolver 里面缓存了 ControllerAdivceBean 和它对应的具体的异常处理方法包装(即 ExceptionHandlerMethodResolver)。

读到这里,也许朋友你会问, ExceptionHandlerExceptionResolver 是缓存了,但是,这个玩意儿怎么用的呢,在哪里用的呢?

C. @ControllerAdvice + @ExceptionHandler 的启用

在 spring-webmvc 模块中,有个类叫 WebMvcConfigurationSupport 它用来支持 web 的相关配置,他有一个创建 Bean 的方法

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    // 。。。小张替你省略部分代码。。。

    /**
    * 创建异常处理器组合
    **/
    @Bean
    public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
        List exceptionResolvers = new ArrayList<>();
        // 这里是空方法1,可以自定义实现,一般不拓展这个方法
        configureHandlerExceptionResolvers(exceptionResolvers);
        // 如果没有重写上面的方法,则会走这里,创建默认的异常处理器
        if (exceptionResolvers.isEmpty()) {
            addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
        }
        // 这里是空方法2,可以自定义实现,一般拓展这个方法往所给的异常处理器集合里添加自定义异常处理器
        extendHandlerExceptionResolvers(exceptionResolvers);

        // 把异常处理器集合组装到 HandlerExceptionResolverComposite 里, 而 HandlerExceptionResolverComposite  是接口 HandlerExceptionResolver 的实现类
        HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
        composite.setOrder(0);
        composite.setExceptionResolvers(exceptionResolvers);
        return composite;
    }

    /**
    * 根据提供的异常处理器创建异常处理器组合
    **/
    protected final void addDefaultHandlerExceptionResolvers(List exceptionResolvers,
            ContentNegotiationManager mvcContentNegotiationManager) {
        //创建 B 章节里的异常处理器
        ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
        // 。。。小张替你省略部分代码。。。

        // 调用 InitializingBean 接口方法
        exceptionHandlerResolver.afterPropertiesSet();
        // 添加创建的异常处理器到集合中
        exceptionResolvers.add(exceptionHandlerResolver);

        // 。。。小张替你省略部分代码。。。
    }

    // 直接 new 了一个 ExceptionHandlerExceptionResolver
    protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
        return new ExceptionHandlerExceptionResolver();
    }
}

上面的 @Bean 创建方法做了下面这些事

到此为止 @Bean 方法已经做完了异常处理器的整合过程,常常与 @Bean 方法搭配使用的,是 @Configuration 注解修饰的配置类,然而 WebMvcConfigurationSupport 并没有这个注解

鸡贼的 spring 把 @Configuration 注解放到了它的子类 DelegatingWebMvcConfiguration 上!

package org.springframework.web.servlet.config.annotation;

// 配置类,自动加载
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // 这个 WebMvcConfigurer 就是我们在 web 项目中自定义拦截器、异常处理器等需要实现的接口
    @Autowired(required = false)
    public void setConfigurers(List configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}

章节 3.3 (markdown 什么时候原生支持页面内跳转)已经有了接口简单描述,我们直接来个接口实现类 demo

public class ExceptionHandlerDemo implements HandlerExceptionResolver {

    private final ModelAndView EMPTY_MODEL_VIEW = new ModelAndView();

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        // 自定义异常处理逻辑,可以输出状态到 response
        // 记得返回一个空 ModelAndView,证明异常已经被处理
        return EMPTY_MODEL_VIEW;
    }
}

现在实现类有了, 我们把它加载到 spring 的 web 环境中去

// 配置类
@Configuration
public class WebConfigDemo implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new IpInterceptor()).addPathPatterns(
                Arrays.asList(
                        "/index",
                        "/apply",
                        "/product/i",
                        "/product/d/*"
                ));
    }

    // 就是这里,添加异常处理器
    @Override
    public void extendHandlerExceptionResolvers(List resolvers) {
        resolvers.add(new ExceptionHandlerDemo());
    }
}

嗯,就是这样简单。

什么?你问我加载进去之后怎么生效的?

在 章节 4.1.1 中的 C 小节有提到类 DelegatingWebMvcConfiguration ,它的 setConfigurers(List configurers) 方法自动注入了咱们的 WebConfigDemo 配置类,并且,它重写了其父类 WebMvcConfigurationSupport 中的 extendHandlerExceptionResolvers()configureHandlerExceptionResolvers() 方法

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

    // 当作是一个 配置类集合
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // 注入 WebConfigDemo
    @Autowired(required = false)
    public void setConfigurers(List configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }

    @Override
    protected void configureHandlerExceptionResolvers(List exceptionResolvers) {
        this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
    }

    // 加载 ExceptionHandlerDemo
    @Override
    protected void extendHandlerExceptionResolvers(List exceptionResolvers) {
        this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
    }

    // 。。。小张替你省略部分代码。。。
}

这样在章节 4.1.1 中 C 小节, 执行 @Bean 方法 handlerExceptionResolver() 方法时候,空方法2 就指向了这里,进而加载到自定义的 ExceptionHandlerDemo

拦截器有3个方法签名

public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }

}

其中针对方法 afterCompletion() 抛出的异常,spring 只是简单打印了一个错误日志,并没有处理,也许 spring 认为,到这里,请求内容已经处理完了,所以不再把错误返回给调用方

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable ex2) {
            // 仅打印错误日志
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

那么剩下的两个方法,其实章节 3.2 当中已经指明了, preHandle()postHandle 方法内出现的异常,与 controller 实体请求中的异常一起被处理了,所以章节 4.1 当中的异常处理方式,对于拦截器异常,同样生效。

介绍 errorPath 之前,先说一下 spring 对于未捕获到的异常的处理方式

对于未捕获到的异常,spring 会返回 500 http 状态码给调用方,并且转发请求到一个指定地址,这个地址默认值为 /error
在 spring boot 中的默认配置为 server.error.path=/error

以上,小张姑且称之为: &#x9519;&#x8BEF;&#x8F6C;&#x53D1;&#x673A;&#x5236; ,其实不仅仅是 500 状态,404 状态也会转发,你还能再找出些状态吗 ?

那么我们可以自已实现一个 /error controller 来处理异常吗?可以的,得益于 SpringBoot,我们可以借助另外一个叫 ErrorAttributes 的 bean 来获取异常信息

当请求出现异常时候,我们可以从 Request 当中读取出来

@Controller
public class ErrorHandleController implements ErrorController {

    private final ErrorAttributes errorAttributes;

    // 注入 SpringBoot 已经替我们创建好的 ErrorAttributes
    public ErrorHandleController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    @RequestMapping(value = "/error", produces = "text/html")
    @ResponseBody
    public String errorPage(HttpServletRequest request) {
        return this.getErrorAttributesMapString(request);
    }

    @RequestMapping(value = "/error")
    @ResponseBody
    public String errorHandler(HttpServletRequest request) {
        return this.getErrorAttributesMapString(request);
    }

    private String getErrorAttributesMapString(HttpServletRequest request) {
        ServletWebRequest webRequest = new ServletWebRequest(request);
        // 利用 ErrorAttributes 读取 request 当中的异常,这里仅仅是简单地打印到页面上
        return this.errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults()).toString();
    }

}

五、结尾

本文并没有提供相应的 demo 演示,只是侧重于带领大家把 spring 的异常处理从头到尾过一遍,如果想实验,自己动手,结果会更快乐的。

有疑问的同学,欢迎评论区留言交流。

想念七七。

Original: https://www.cnblogs.com/qnlcy/p/16573098.html
Author: 去哪里吃鱼
Title: 一文带你掌握Spring Web异常处理方式

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/577896/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • pod(二):创建包含多个容器的pod(sidecar)

    服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 x86_64…

    Linux 2023年6月7日
    0103
  • 【论文笔记】(模型压缩)Do Deep Nets Really Need to be Deep?

    摘要 作者通过模型压缩(model compression)使浅层的网络学习与深层网络相同的函数,以达到深层网络的准确率(accuracy)。当与深浅模型的参数量相同时,浅层模型可…

    Linux 2023年6月7日
    0105
  • 有道云笔记迁移到为知笔记

    背景 &#x4E4B;&#x524D;&#x4E00;&#x76F4;&#x7528;&#x7684;&#x6709;&am…

    Linux 2023年6月14日
    0106
  • 5.8 Vim多窗口编辑模式

    在编辑文件时,有时需要参考另一个文件,如果在两个文件之间进行切换则比较麻烦。可以使用 Vim 同时打开两个文件,每个文件分别占用一个窗口。 例如,在査看 /etc/passwd 时…

    Linux 2023年6月7日
    0173
  • Prometheus+Grafana监控-基于docker-compose搭建

    前言 Prometheus Prometheus 是有 SoundCloud 开发的开源监控系统和时序数据库,基于 Go 语言开发。通过基于 HTTP 的 pull 方式采集时序数…

    Linux 2023年6月7日
    088
  • Ubuntu系统报错The system is running in low-graphics mode

    我遇到过两次这种请况,这次解决了。很nice! 在csdn上搜到的大部分操作是: 鼠标进入系统 使用快捷键 Ctrl+Alt+F1 进入用户 输入密码 然后按照以下代码进行 cd …

    Linux 2023年5月27日
    0102
  • 国产银河麒麟Kylin V10操作系统-如何把常用文件夹加入左侧侧边栏(类似windows文件资源管理器中的收藏夹)

    国产银河麒麟Kylin V10操作系统-如何把常用文件夹加入左侧侧边栏(类似windows文件资源管理器中的收藏夹) 第一步:确保侧边栏正确显示。 打开”我的电脑&#8…

    Linux 2023年6月14日
    0157
  • Kubernetes中的网络

    一、引子 既然Kubernetes中将容器的联网通过插件的方式来实现,那么该如何解决这个的联网问题呢? 如果你在本地单台机器上运行docker容器的话注意到所有容器都会处在 doc…

    Linux 2023年6月14日
    096
  • 灵感来袭,基于Redis的分布式延迟队列(续)

    背景 上一篇(灵感来袭,基于Redis的分布式延迟队列)讲述了基于Java DelayQueue和Redis实现了分布式延迟队列,这种方案实现比较简单,应用于延迟小,消息量不大的场…

    Linux 2023年5月28日
    078
  • Kubernetes 容器平台实战

    一、什么是Kubernetes? Kubernetes是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署,自动扩缩容,维护等功能. 通过Kubernetes可以做到…

    Linux 2023年6月14日
    0104
  • shell: 获取每行文本的最后几个字符

    tail方式 tail参数-c就可以获取最后的几个字节 -c, –bytes=[+]NUM output the last NUM bytes; or use -c +NUM t…

    Linux 2023年6月7日
    0116
  • SSH 完全教程 1

    SSH(Secure Shell 的缩写)是一种网络协议,用于加密两台计算机之间的通信,并且支持各种身份验证机制。 实务中,它主要用于保证远程登录和远程通信的安全,任何网络服务都可…

    Linux 2023年6月7日
    079
  • 闭包、装饰器

    闭包: 闭包的演变过程: 闭包的概念: “闭包”的本质就是函数的嵌套定义,即在函数内部再定义函数 “闭包”有两种不同的方式,第一种是…

    Linux 2023年6月8日
    081
  • rocksdb列族笔记

    1、简介 列族(Column Families)是rocksdb3.0提出的一个机制,用于对同一个数据库的记录(键值对)进行逻辑划分。默认情况下所有的记录都会存储在一个默认列族里(…

    Linux 2023年6月7日
    0107
  • 增加Apache响应时间

    在apache的配置文件 httpd.conf 最下面加上下面代码,增加响应时间 FcgidProcessLifeTime 8200 FcgidIOTimeout 8200 Fcg…

    Linux 2023年6月7日
    0101
  • VirtualBox安装Ubuntu教程

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 准备工作 virtualBox可在官网下载,Ubuntu镜像可在 阿里云下载,选择对应电脑位数的镜像。 开始安装 1、点击&#8…

    Linux 2023年5月27日
    0174
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球