Spring Boot全局异常处理器(原理及使用详解)

news2025/1/14 18:04:36

目录

1 什么是全局异常处理器

2 为什么需要全局异常

3 原理和目标

4 @ControllerAdvice注解

4.1 Advice(通知)

4.2 @ControllerAdvice结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到全局不同类型的异常区别处理的目的。

4.3 结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的

4.4 结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行

4.5 @ControllerAdvice注解作用原理

4.6 @RestControllerAdvice

5 编码实现全局异常处理器

1 什么是全局异常处理器

  软件开发springboot项目过程中,不可避免的需要处理各种异常,spring mvc架构中各层会出现大量的try{...} catch{...} finally{...}代码块,不仅有大量的冗余代码,而且还影响代码的可读性。这样就需要定义个全局统一异常处理器,以便业务层再也不必处理异常。

  Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAtribute等注解配套使用。不过跟异常处理相关的只有注解@ExceptionHandler,从字面上看,就是异常处理器的意思。

2 为什么需要全局异常

  • 不用强制写try-catch,由全局异常处理器统一捕获处理。
  • 自定义异常,只能用全局异常来捕获。不能直接返回给客户端,客户端是看不懂的,需要接入全局异常处理器
  • JSR303规范的Validator参数校验器,参数校验不通过会抛异常,是无法使用try-catch语句直接捕获,只能使用全局异常处理器。

3 原理和目标

  简单的说,@ControllerAdvice注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独的一个类,定义一套对各章异常的处理机制,然后在类的签名加上注解@ControllerAdvice,统一对不同阶段的,不同异常进行处理。这就是统一异常处理的原理。

  对异常按阶段进行分类,大体可以分成:进入Controller前的异常和Service层异常

        目标就是消灭95%以上的try catch代码块,并以优雅的Assert(断言)方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的try catch代码块。

4 @ControllerAdvice注解

  @ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理,对于@ControllerAdvice,我们比较熟悉的用法是结合@ExceptionHandler用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice,关于Advice,在Spring的AOP中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解。

4.1 Advice(通知)

  Spring AOP通过PointCut来指定在哪些类的哪写方法上织入横切逻辑,通过Advice来指定在切点上具体做什么事情。如方法前做什么,方法后做什么,抛出异常做什么。再来看一下图

 主要可分为5类增强:

    • MethodBeforeAdvice:目标方法实施前增强
    • AfterReturningAdvice:目标方法实施后增强
    • ThrowsAdvice:异常抛出增强
    • IntroductionAdvice:引介增强,为目标类添加新的属性和方法。可以构建组合对象来实现多继承
    • MethodInterceptor:方法拦截器,环绕增强,在方法的前后实施操作

  前置增强:主要匹配到的切点运行之前执行,在XML配置中使用<aop:before>,相应的接口为MethodBeforeAdvice。当一个Bean对象实现了MethodBeforeAdvice,在XML配置文件中指定这个bean为advice,Spring会自动在切点方法执行前执行MethodBeforeAdvice的接口。

<bean id="helloworld" class="me.aihe.exam.controller.HelloWorld" />
    
    <!-- timelog实现了MethodBeforeAdvice接口 -->
    <bean id="timeLog" class="me.aihe.exam.controller.TimeLoggingAop" />
    <aop:config>
        <aop:pointcut id="hello" expression="execution(public * * (..))"></aop:pointcut>
        
        <!-- advisor的作用为将Pointcut和Advice组装起来 -->
        <aop:advisor
                id="timelogAdvisor"
                advice-ref="timeLog"
                pointcut-ref="hello"
        />
    </aop:config>

 在调用相应的切点方法之前,前置增强都会生效。

  后置增强:弄明白了前置增强,后置增强也是同一个道理,不过后置增强是在切点运行后执行。接口为AfterReturningAdvice,在切点方法运行之后,后置增强会生效。

  异常抛出增强:当切点方法抛出异常时,异常抛出增强才会被执行。其接口为ThrowAdvice,接口没有指定方法,实现这个接口的对象是通过反射来调用其增强方法的。

  增强案例:根据前面的前置,后置,异常抛出增强,看一个完整的案例: 

// TimeLoggingAop实现了前置增强,后置增强,异常环绕增强
public class TimeLoggingAop implements MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice {
    private long startTime = 0;

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        startTime = System.nanoTime();
    }


    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
        long spentTime = System.nanoTime() - startTime;
        String clazzName = target.getClass().getCanonicalName();
        String methodName = method.getName();
        System.out.println("执行" + clazzName + "#" + methodName + "消耗" + new BigDecimal(spentTime).divide(new BigDecimal(1000000)) + "毫秒");
    }

    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
        System.out.println("执行" + method.getName() + "出现异常," + "异常消息为:" + ex.getMessage());
    }
}
// 普通的HelloWorld对象
public class HelloWorld {
    
    public void sayHello(){
        System.out.println("hello");
    }

    public void sayHelloWithException(){
        System.out.println("hello");
        throw new RuntimeException("Hello World运行时出了一点问题");
    }
}

  public static void main(String[] args) {
        //普通对象
        HelloWorld helloWorld = new HelloWorld();
        //增强对象
        BeforeAdvice beforeAdvice = new TimeLoggingAop();
        //组装普通对象和增强对象
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(helloWorld);
        proxyFactory.addAdvice(beforeAdvice);

        //获取组装后的代理对象
        HelloWorld proxyHelloWorld = (HelloWorld) proxyFactory.getProxy();
        //运行
        proxyHelloWorld.sayHello();
        proxyHelloWorld.sayHelloWithException();
    }

//运行结果
hello
执行me.aihe.exam.controller.HelloWorld#sayHello消耗29.392268毫秒

hello
执行sayHelloWithException出现异常,异常消息为:Hello World运行时出了一点问题

        在原本方法之上增加一些额外的东西,原本的功能增强了,所以叫增强,这是中文翻译过来的增强。英文名为Advice,建议,在方法周围建议方法做什么事情,然后真的做了。。。 

4.2 @ControllerAdvice结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到全局不同类型的异常区别处理的目的。

  @ExceptionHandler这个注解表示Controller中任何一个方法发生异常,则会被注解了@ExceptionHandler的方法拦截到。对应的异常类执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方法

package com.liurq.config.shiro.handle;

import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;

@ControllerAdvice
public class UnauthorizedExceptionHandle {

    /**
     * 可以获取请求的request对象,按需进行操作
     * 可以获取抛出的异常对象,按需进行操作
     * 可对请求进行重定向,将请求重定向到某个页面或控制器
     * 需要返回json串儿的话,在方法上添加@ResponseBody注解即可
     * @param request
     * @param e
     * @return
     */
//    @ResponseBody
    @ExceptionHandler({UnauthorizedException.class})
    public String unauthorizedException(NativeWebRequest request, UnauthorizedException e){
        System.out.println(request.getParameter("param1"));
        e.printStackTrace();
        return "redirect:/403";
    }
}

4.3 结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的

  用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的。

  • 由@InitBinder表示的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单到JavaBean属性的绑定。
  • 在使用SpringMVC的时候,经常会遇到表单中的日期字符串和JavaBean的Date类型,而SpringMVC默认不支持这个格式的转换,所以需要手动配置,自定义数据的绑定才能解决这个问题。在需要日期转换的Controller中使用SpringMVC的注解@Initbinder和Spring自带的WebDateBinder类来操作。WebDataBinder是用来绑定请求参数到指定的属性编辑器。由于前端传到controller里的值是String类型的,当往Model里Set这个值的时候,如果set的这个属性是个对象,Spring就会去找对应的editor进行转换,然后再set进去。
  • 标注在controller上的@InitBinder只对当前Controller生效,要想全局生效,可以使用@ControllerAdivce。通过@ControllerAdvice可以将对于控制器的全局配置放置在同一个位置。
@ControllerAdvice
public class MyControllerAdvice {

    /**
     * 转换前端传入的日期变量参数为指定格式。
     *
     * @param binder 数据绑定参数。
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Date.class,
                new CustomDateEditor(new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT), false));
    }
}

4.4 结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行

  搭配@ModelAttribute注解可以做全局数据绑定,在到达控制器前,将属性放入modelMap中,可以通过name属性指定数据在数据在map中的key,如果不指定的话,有相同返回类型会产生覆盖,建议每个都通过name参数指定key。

4.5 @ControllerAdvice注解作用原理

  我们看看Spring是怎么实现的,首先前端控制器DispatcherServlet对象在创建时会初始化一系列的对象: 

public class DispatcherServlet extends FrameworkServlet{
  //......
  protected void initStrategies(ApplicationContext context){
      initMultipartResolver(context);
      initLocaleResolver(context);
      initThemeResolver(context);
      initHandlerMappings(context);
      initHandlerAdapters(context);
      initHandlerExceptionResolvers(context);
      initRequestToViewNameTranslator(context);
      initViewResolvers(context);
      initFlashMapManager(context);        
  }    
  //......  
}

 对于@ControllerAdvice注解,我们重点关注initHandlerAdapters(context)和initHandlerExceptionResolvers(context)这两个方法。

  • initHandlerAdapters(context)方法会取得所有实现了HandlerAdapter接口的bean并保存起来,其中就有一个类型为RequestMappingHandlerAdapter的bean,这个bean就是@RequestMapping注解能起作用的关键,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理:找到所有ModelAttribute标注的方法并缓存起来,找到所有InitBinder标注的方法并缓存起来。经过处理之后,@ModelAttribute和@InitBinder就能起作用了。
  • DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,七张就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理。

4.6 @RestControllerAdvice

  点进去看到@RestControllerAdvice是一个组合注解,是@ResponseBody@ControllerAdvice的组合

   而函数体中用到了@AliasFor注解,因此可以通过@RestControllerAdvice的属性传递将属性值传给@ControllerAdvice。

   因此,在使用时可直接将包名传给@RestControllerAdvice来指定Controller范围

 @ResponseBody:

    • 注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区(响应体中),通常用来返回JSON数据或者是XML数据,需要注意的是在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,它的效果等同于通过response对象输出指定格式的数据。
    • 需要通过@RestponseBody注解将返回的JSON字符串放入响应体中,然后再前台才能拿到json字符串进行解析,如果不加,响应体中就没有放入json字符串,前台自然是拿不到数据的。
    • 默认情况下,使用@ResponseBody返回的数据只能是String类型,其它类型返回时会出现异常:java.lang.IllegalArgumentException:  No converter found for return value of type。这里提示没有对应的转换器,这里可以添加转换器将数据转换为特定的格式,JSON是数据通信的主要格式,

   这里做了这样的解释:    

    • @responsebody这个注解表示你的返回值将存在responsebody中返回到前端,也就是将return返回值作为请求返回值,return的数据不会解析成返回跳转路径,将java对象转为json格式的数据,前端接收后会显示将数据到页面,如果不加的话 返回值将会作为url的一部分,页面会跳转到这个url,也就是跳转到你返回的这个路径。

    • @ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。

    • @ResponseBody这个注解使用情景:当返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用,常用在ajax异步请求中,可以通过 ajax 的“success”:fucntion(data){} data直接获取到。

    • @ResponseBody这个注解一般是作用在方法上的,加上该注解表示该方法的返回结果直接写到Http response Body中,在RequestMapping中 return返回值默认解析为跳转路径,如果你此时想让Controller返回一个字符串或者对象到前台。

5 编码实现全局异常处理器

  • 步骤1:封装异常内容,统一存储在枚举类中
/**
 *  返回应答中的错误信息
 * **/
public enum ErrorCodeEnum {

    NO_ERROR("没有错误"),       DATA_VALIDATED_FAILED("数据验证事白,请核对!"),

    UNHANDLED_EXCEPTION("未处理的异常!");

    ErrorCodeEnum(String errorMessage) { this.errorMessage = errorMessage; }

    private final String errorMessage;

    public String getErrorMessage(){
        return errorMessage;
    }
}
  • 步骤2:封装Controller的异常结果
@Data
public class CallResult{
    private static final CallResult OK = new CallResult();   
    private boolean success = true;
    private String errorMessage = null;
    private JSONObject data;

    public static CallResult create(String errorMessage){      
        return errorMessage == null ? ok() " error(errorMessage);
    }

    public static CallResult ok() { return OK; }

    public static CallResult ok(JSONObject data){       
        CallResult result = new CallResult();
        result.data = data;
        return result;
    }

    public static CallResult error(String errorMessage) {
        CallResult result = new CallResult();
        result.success = false;
        result.errorMessage = errorMessage;
        return result;
    }
    
    public static <T> CallResult error(String errorMessage, T data){
        CallResult result = new CallResult();
        result.success = false;
        result.errorMessge = errorMessage;
        JSONObject jsonObject  new JSONObject();
        jsonObject.put("errorData", data);
        result.data = jsonObject;
        return result;
    }
}
  • 步骤3:加个全局异常处理器,对异常进行处理(为图方便,这里只先捕捉了注解@NotBlank的异常抛出,@NotBlank会抛出一个MethodArgumentNotValidException类型的异常,这里先处理这个异常)
package com.****.demo.common;

import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice("com.****.demo")
public class MyExceptionHandler {

    //@NotBlank抛出异常处理
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CallResult bindExceptionHandle(MethodArgumentNotValidException ex){
        String message = ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return CallResult.error(message);
    }
}
  • 创建实体
public class User implements Serializable {

    @ApiModelProperty(value = "名称")
    @NotBlank(message = "name不能为空(哈哈,自定义哒)")
    private String demoName;

    @ApiModelProperty(value = "年龄")
    @NotNull(message = "age 不能为空")
    private Integer age;

    @ApiModelProperty(value = "id")
    @NotNull(message = "id 不能为空")
    private Integer id;
}
  • Controller
@RestController    
public class UserController{

    @PostMapping("/setUser")
    @ApiOperation(value = "获取用户信息")
    public CallResult updateUser(@RequestBody @Valid User user) {
        userService.setUser(user);
        return CallResult.ok();
    }
}
  • 测试,为测试@NotBlank,将demoName值设为空

结果显示:

没有try-catch块的优雅代码……完美!

本文引用了很多大神的博客,基本都加了链接,如果有漏加链接侵权的,请联系作者删除,谢谢!

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

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

相关文章

【Unity】使用 System.Windows.Forms 的问题

因为最近开发需要用到使用 Windows 自带的窗口打开、文件选择等功能&#xff0c;然后兜兜转转需要使用 System.Windows.Forms 这个库。这个库在 WinForm 里是很常见的&#xff0c;但是要在 Unity 里使用&#xff0c;并打包出来还是有些坑的。 我这里使用的 Unity 版本&#xf…

MATLAB算法实战应用案例精讲-【连接分析】PageRank

前言 拉里佩奇 Larry Page和谢尔盖布林 Sergey Brin于1996年在斯坦福大学开发了PageRank算法,此后于1998年基于该算法,共同创立了Google公司。PageRank这个名字一语双关,既源于其算法创始人Larry Page,也源于网页 Web Page。 PageRank算法将网页按重要性进行排序。有了这…

搭建nacos

一、nacos的介绍 开启nacos的作用&#xff1a; 在项目开发过程中我们可以把服务提供者的网络地址&#xff08;IP&#xff0c;端口号&#xff09;等编码到代码中&#xff0c; 这种做法存在许多问题&#xff1a; 一旦服务提供者地址变化&#xff0c;就需要手工修改代码一旦是…

Wireshark TS | Packet Challenge 之 DNS 案例分析

前言 来自于 Sharkfest Packet Challenge 中的一个数据包案例&#xff0c;Sharkfest 是 Wireshark 官方组织的一年一度的大会&#xff0c;致力于在 Wireshark 开发人员和用户社区之间分享知识、经验和最佳实践。印象中早期是一年一次&#xff0c;近几年发展成一年两次&#xf…

艾美捷超氧化物歧化酶检测试剂盒参数说明

超氧化物歧化酶是催化超氧化物离子O2-的歧化反应2O2-&#xff0b;2H →O2&#xff0b;H2O2的酶.EC1&#xff0e;15&#xff0e;1&#xff0e;1.是金属酶&#xff0c;已知在金属酶中有真核生物细胞质的深绿色Cu-Zn酶&#xff08;分子量约3万&#xff09;、线粒体、细菌红紫色的M…

Debian系列-在新的GNOME下无法启动Qt

Debian系列-在新的GNOME下无法启动Qt 文章目录Debian系列-在新的GNOME下无法启动Qt摘要启动Qt 报错 No protocol specified解决方法1 打开一个新的终端2 echo $DISPLAY3 export DISPLAY更新一下关键字&#xff1a; Debian、 Qt、 xcb、 No protocol specified、 关键字5摘要 …

转行做程序员,哪种编程语言既高薪又适合你?

“你为什么学习这门编程语言”&#xff1f;大多人当初面对选择这个问题的时候&#xff0c;都只是单纯的“听朋友说”或是百度看到了表面&#xff0c;或者是一拍脑门决定的。其实并不了解这个编程语言的特性和市场现状以及这个语言是否适合自己。 这类的情况还是算是好的&#x…

并发编程学习(四):wait()、nitify()

1、wait、notify原理 Owner 线程发现条件不满足&#xff0c;调用wait()方法&#xff0c;即可进入WaitSet&#xff0c;变为WAITING 状态。BLOCKED和WAITING的线程都处于阻塞状态&#xff0c;不占用CPU时间片。BLOCKED线程会在Owner线程释放锁时唤醒。WAITING线程会在Owner线程调…

【数据库】简答题知识点

数据库系统的结构 数据库系统的逻辑结构可以分为用户级、概念级和物理级三个层次。每个层次的数据库都有自身对数据进行逻辑描述的模式&#xff0c;分另称为外模式、概念模式和内模式。 外模式&#xff1a;是与和具体的应用或者项目有关的逻辑表示&#xff0c;用户可以操作的…

【Proteus仿真】【51单片机】自动浇花灌溉系统设计

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602液晶、按键、DS18B20、PCF8591 ADC、土壤湿度传感器、水位传感器、蜂鸣器模块等。 系统运行后&#xff0c;LCD1602显示传感器检测的温度、湿…

【Linux操作系统】多线程(一)

文章目录1. 线程概念2. 线程控制2.1 创建线程2.2 线程ID2.3 线程等待2.4 线程终止2.5 线程分离3. 线程同互斥与同步3.1 互斥量3.2 死锁3.3 同步-条件变量3.4 生产者消费者模型3.5 POSIX信号量3.6 基于环形队列的生产消费者模型1. 线程概念 线程&#xff1a;是在进程内部运行的…

解决资源消耗,top的运用记录

第一条命令uptime load average 后面的三个数字&#xff0c;分别代表1分钟、5分钟和15分钟内机器的平均负载 使用top命令解决负载问题 Cpu(s)这一行提供了CPU运行情况信息 这些缩写分别代表了不同含义 (1)us&#xff1a;用户CPU时间 运行非优雅的用户进程所占CPU时间的百…

离线下载NLTK依赖包([WinError 10061] 由于目标计算机积极拒绝,无法连接)的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…

房产管理系统CAD图形管理应用有哪些?

数图互通房产管理 数图互通房产管理系统建设涵盖学校所有房屋资源管理业务以及CAD图纸的管理&#xff0c;对不同类型的房屋实现相应的业务子系统&#xff0c;实现完善、高效、可溯的房屋资源业务管理&#xff0c;保证数据的完整性、一致性和精确性&#xff0c;及业务的。 通过…

向内而生 向远而行 | 希尔贝壳荣获“2022年度最具投资价值创新企业”奖

12月22日&#xff0c;以“向内而生 向远而行”为主题的2022年度猎云网创投奖项评选正式落下帷幕&#xff0c;各项榜单评选结果正式发布。希尔贝壳凭借先进的技术创新能力获评“2022年度最具投资价值创新企业”奖。 获奖理由 北京希尔贝壳科技有限公司成立于2017年&#xff0c;…

可视化 相机pose或者 pose视锥

可视化 相机pose或者 pose视锥 https://www.camcalib.io/post/visualizing-camera-calibration-results https://bitbucket.org/iviso/camcalib_multiview_pointcloud_example/src/master/ https://bitbucket.org/iviso/ 其他&#xff1a; https://towardsdatascience.com/c…

快速部署PHP Web环境(nginx php mysql redis)

先看最终效果&#xff0c;如下&#xff1a; 它是什么&#xff1f; 它是 docker 容器虚拟化技术。总共只有几KB大小的描述文件&#xff0c;文件里定义了要安装什么、配置什么&#xff0c;一执行就全自动处理好了。 它有什么用&#xff1f; 解决新来同事搭环境搭半天的问题解…

矽昌-- Station 模式支持

Station 模式支持 1. 修改配置 ​ 在/etc/config/wireless中增加一段即可&#xff0c;实例如下&#xff1a; config wifi-ifaceoption key 12345678option ifname sfi0option network wwanoption encryption psk2ccmpoption device radio0option mode staoption bssid A8:5A:…

玩以太坊链上项目的必备技能(修改器 [modifier]-Solidity之旅十五)

修改器&#xff08;modifier&#xff09; 在讲修改器&#xff08;modifier&#xff09;之前&#xff0c;我们使用前面几篇文章所学到的知识来实现一个简单的 token 类合约。 // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0;contract InheritanceModifierExampl…

Python获取与处理文件路径/目录路径

这里写目录标题文件目录结构说明一、路径获取1.1 获取当前文件的绝对路径1.2.1 获取当前文件的所在目录1.2.2 获取当前文件的所在目录的上一级目录1.3 获取当前文件名1.4 获取当前文件对于基准路径的相对路径二、路径判断2.1 判断路径是否存在2.2 判断路径是否为绝对路径2.3 判…