乐趣区

关于java:Java业务开发常见错误100例代码篇2

Java 业务开发常见谬误 100 例(代码篇 -2)

11 丨空值解决:分不清楚的 null 和宜人的空指针

  1. 业务代码中 5 种最容易呈现空指针异样的写法

    1. 参数值是 Integer 等包装类型,应用时因为主动拆箱呈现了空指针异样;
    2. 字符串比拟呈现空指针异样;
    3. 诸如 ConcurrentHashMap 这样的容器不反对 Key 和 Value 为 null,强行 put null 的 Key 或 Value 会呈现空指针异样;
    4. A 对象蕴含了 B,在通过 A 对象的字段取得 B 之后,没有对字段判空就级联调用 B 的办法呈现空指针异样;
    5. 办法或近程服务返回的 List 不是空而是 null,没有进行判空就间接调用 List 的办法呈现空指针异样;
  2. 通过 Optional 配合 Stream 能够防止大多数简短的 if-else 判空逻辑,实现一行代码优雅判空。另外,要定位和修复空指针异样,除了能够通过减少日志进行排查外,在生产上应用 Arthas 来查看办法的调用栈和入参会更快捷。
  3. POJO 中字段的 null 定位,从服务端的角度往往很难分分明,到底是客户端心愿疏忽这个字段还是无意传了 null,因而咱们尝试用 Optional 类来辨别 null 的定位。同时,为防止把空值更新到数据库中,能够实现动静 SQL,只更新必要的字段。
  4. 数据库字段容许保留 null,会进一步减少出错的可能性和复杂度。会有 NULL、空字符串和字符串 null 三种状态
  5. MySQL sum 函数、count 函数,以及 NULL 值条件可能踩的坑

    1. sum 函数没统计到任何记录时,会返回 null 而不是 0,能够应用 IFNULL 函数把 null 转换为 0;
    2. count 字段不统计 null 值,COUNT(*) 才是统计所有记录数量的正确形式。
    3. =NULL 并不是判断条件而是赋值,对 NULL 进行判断只能应用 IS NULL 或者 IS NOT NULL。

12 丨异样解决:别让本人在出问题的时候变为瞎子

大多数业务利用都采纳的三层架构:

业务性质上异样可能分为 业务异样 零碎异样 两大类:

对于自定义的业务异样 :以 Warn 级别的日志记录异样以及以后 URL、执行办法等信息后,提取异样中的错误码和音讯等信息,转换为适合的 API 包装体返回给 API 调用方;
对于无奈解决的零碎异样:以 Error 级别的日志记录异样和上下文信息(比方 URL、参数、用户 ID)后,转换为普适的“服务器忙,请稍后再试”异样信息,同样以 API 包装体返回给调用方。

  • Repository 层出现异常或者能够疏忽,或者能够降级,或者须要转化为一个敌对的异样。如果一律捕捉异样仅记录日志,很可能业务逻辑曾经出错,而用户和程序自身齐全感知不到。
  • Service 层往往波及数据库事务,出现异常同样不适宜捕捉,否则事务无奈主动回滚。此外 Service 层波及业务逻辑,有些业务逻辑执行中遇到业务异样,可能须要在异样后转入分支业务流程。如果业务异样都被框架捕捉了,业务性能就会不失常。
  • 如果上层异样回升到 Controller 层还是无奈解决的话,Controller 层往往会给予用户敌对提醒,或是依据每一个 API 的异样表返回指定的异样类型,同样无奈对所有异样厚此薄彼。
  1. 谬误打 log 姿态:

    1. 捕捉了异样后间接生吞
    2. 没有生吞,然而抛弃异样的原始信息

      @GetMapping("wrong1")
      public void wrong1(){
          try {readFile();
          } catch (IOException e) {
              //wrong1: 原始异样信息失落
              //throw new RuntimeException("零碎忙请稍后再试");
              
              //wrong2: 只保留了异样音讯,栈没有记录
              //log.error("文件读取谬误, {}", e.getMessage());
              //throw new RuntimeException("零碎忙请稍后再试");
              
              //correct1:
              log.error("文件读取谬误", e);
              throw new RuntimeException("零碎忙请稍后再试");
              
              //correct2:
              throw new RuntimeException("零碎忙请稍后再试", e);
          }
      }
    3. 抛出异样时不指定任何音讯

      throw new RuntimeException();
  2. 如果你捕捉了异样打算解决的话,除了通过日志正确记录异样原始信息外,通常还有
    三种解决模式:

    1. 转换,即转换新的异样抛出。对于新抛出的异样,最好具备特定的分类和明确的异样音讯,而不是轻易抛一个无关或没有任何信息的异样,并最好通过 cause 关联老异样。
    2. 重试,即重试之前的操作。比方近程调用服务端过载超时的状况,自觉重试会让问题更
      重大,须要思考当前情况是否适宜重试。
    3. 复原,即尝试进行降级解决,或应用默认值来代替原始数据。
  3. 小心 finally 中的异样

    1. 尽管 try 中的逻辑呈现了异样,但却被 finally
      中的异样笼罩了

      @GetMapping("wrong")
      public void wrong() {
          try {log.info("try");
              // 异样失落
              throw new RuntimeException("try");
          } finally {
              // wrong
              //log.info("finally");
              //throw new RuntimeException("finally");
              
              //correct
              log.info("finally");
              try {throw new RuntimeException("finally");
              } catch (Exception ex) {log.error("finally", ex);
              }
          }
      }
    2. 能够改为 try-with-resources 模式

      @GetMapping("useresourceright")
      public void useresourceright() throws Exception {try (TestResource testResource = new TestResource()){testResource.read();
          }
      }
  4. 千万别把异样定义为动态变量, 务必确保异样是每次 new 进去的。否则可能会引起栈信息的错乱。
  5. 确保正确处理了线程池中工作的异样:

    1. 如果工作通过 execute 提交,那么出现异常会导致线程退出,大量的异样会导致线程反复创立引起性能问题,咱们应该尽可能确保工作不出异样,同时设置默认的未捕捉异样处理程序来兜底;
    2. 如果工作通过 submit 提交意味着咱们关怀工作的执行后果,应该通过拿到的 Future 调用其 get 办法来取得工作运行后果和可能呈现的异样,否则异样可能就被生吞了。

13 丨日志:日志记录真没你设想的那么简略

记录日志引起的坑,容易出错次要在于三个方面:

  • 日志框架的兼容问题
  • 日志文件配置简单且容易出错
  • 日志记录自身就有些误区,比方没思考到日志内容获取的代价、胡乱应用日志级别等

    Java 体系的日志框架,的确十分多,而不同的类库,还可能抉择应用不同的日志框架。这样一来,日志的对立治理就变得十分艰难。为了解决这个问题,就有了 SLF4J((SimpleLogging Facade For Java))。而个别咱们也都是应用 SLF4J 去治理:

SLF4J 实现了三种性能:

  • 提供了对立的日志门面 API,即图中紫色局部,实现了中立的日志记录 API。
  • 提供桥接性能,即图中蓝色局部,用来把各种日志框架的 API(图中绿色局部)桥接到 SLF4J API。这样一来,即使你的程序中应用了各种日志 API 记录日志,最终都能够桥接到 SLF4J 门面 API。
  • 提供适配性能,即图中红色局部,能够实现 SLF4J API 和理论日志框架(图中灰色局部)的绑定。SLF4J 只是日志规范,咱们还是须要一个理论的日志框架。日志框架自身没有实现 SLF4J API,所以须要有一个前置转换。Logback 就是依照 SLF4J API 规范实现的,因而不须要绑定模块做转换。

常见问题:

  1. 如果程序启动时呈现 SLF4J 的谬误提醒,那很可能是配置呈现了问题,能够应用 Maven 的 dependency:tree 命令梳理依赖关系
  2. Logback 是 Java 最罕用的日志框架,其配置比较复杂,你能够参考官网文档中对于 Appender、Layout、Filter 的配置,切记不要随便从其余中央复制他人的配置,避免出现谬误或与以后需要不符。
  3. 应用异步日志解决性能问题,是用空间换工夫。但空间毕竟无限,当空间满了之后,咱们要思考是阻塞期待,还是抛弃日志。如果更心愿不抛弃重要日志,那么抉择阻塞期待;如果更心愿程序不要因为日志记录而阻塞,那么就须要抛弃日志。
  4. 应用日志占位符,而不是 字符串拼接

14 丨文件 IO:实现高效正确的文件读写并非易事

  1. 如果须要读写字符流,那么须要确保文件中字符的字符集和字符流的字符集是统一
    的,否则可能产生乱码。

    1. 不指定 Charset(程序会主动以以后机器的默认字符集来读取文件的)

      char[] chars = new char[10];
      String content = "";
      try (FileReader fileReader = new FileReader("hello.txt")) {
          int count;
          while ((count = fileReader.read(chars)) != -1) {content += new String(chars, 0, count);
          }
      }
      log.info("result:{}", content);
    2. 指定 Charset

      Files.write(Paths.get("hello2.txt"), "你好 hi".getBytes(Charsets.UTF_8));
      byte[] content = Files.readAllBytes(Paths.get("hello2.txt"));
      log.info("bytes:{}",Hex.encodeHexString(content));
  2. 应用 Files 类的一些流式解决操作,留神应用 try-with-resources 包装 Stream,确保底层文件资源能够开释,防止产生 too many open files 的问题。

    1. 不应用 try-with-resources(后盾不会敞开过程,而是会始终新开一个过程,直到无奈再开新的线程)

      LongAdder longAdder = new LongAdder();
      IntStream.rangeClosed(1, 1000000).forEach(i -> {
          try {Files.lines(Paths.get("demo.txt")).forEach(line -> longAdder.increment} catch (IOException e) {e.printStackTrace();
          }
      });
      log.info("total : {}", longAdder.longValue());
      
    2. 应用 try-with-resources

      LongAdder longAdder = new LongAdder();
      IntStream.rangeClosed(1, 1000000).forEach(i -> {try (Stream<String> lines = Files.lines(Paths.get("demo.txt"))) {lines.forEach(line -> longAdder.increment());
          } catch (IOException e) {e.printStackTrace();
          }
      });
      log.info("total : {}", longAdder.longValue());
  3. 进行文件字节流操作的时候,个别状况下不思考进行逐字节操作,应用缓冲区进行批量读写缩小 IO 次数,性能会好很多。个别能够思考间接应用缓冲输入输出流 BufferedXXXStream,谋求极限性能的话能够思考应用 FileChannel 进行流转发。

    1. 不对数据进行解决,间接把原文件数据写入指标文件;

      private static void perByteOperation() throws IOException {try (FileInputStream fileInputStream = new FileInputStream("src.txt");
              FileOutputStream fileOutputStream = new FileOutputStream("dest.txt"))
              int i;
              while ((i = fileInputStream.read()) != -1) {fileOutputStream.write(i);
              }
          }
      }
      

      复制一个 35MB 的文件,耗时 190 秒

    2. 改进后,应用 100 字节作为缓冲区

      private static void bufferOperationWith100Buffer() throws IOException {try (FileInputStream fileInputStream = new FileInputStream("src.txt");
              FileOutputStream fileOutputStream = new FileOutputStream("dest.txt"))
              byte[] buffer = new byte[100];
              int len = 0;
              while ((len = fileInputStream.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, len);
              }
          }
      }

      复制一个 35MB 的文件,耗时 26 秒
      (能够看到,在进行文件 IO 解决的时候,应用适合的缓冲区能够明显提高性能)

    3. 应用 BufferedXXXStream,其外部实现了一个默认 8KB 大小的缓冲区(然而,在应用 BufferedInputStream 和 BufferedOutputStream 时,还是倡议大家再应用一个缓冲进行读写,不要因为它们实现了外部缓冲就进行逐字节的操作)。
      这里我间接贴出三种形式,具体代码 放在 code repository 里,可自行翻阅:
      Java 业务开发常见谬误 100 例

      1. 间接应用 BufferedInputStream 和 BufferedOutputStream;
      2. 额定应用一个 8KB 缓冲,应用 BufferedInputStream 和 BufferedOutputStream;
      3. 间接应用 FileInputStream 和 FileOutputStream,再应用一个 8KB 的缓冲。
        最初,三者的性能别离是 1.4、110 毫秒 和 110 毫秒
      4. 应用 FileChannel,速度最快,可达 50 毫秒,比最原始的 190 秒,快了足足 数千倍

最初要强调一点的是,文件操作因为波及操作系统和文件系统的实现,JDK 并不能确保所有 IO API 在所有平台的逻辑一致性,代码迁徙到新的操作系统(比方上到测试场或者生产场)或文件系统时,要从新进行功能测试和性能测试。

15 丨序列化:一来一回你还是原来的你吗?

基于 Redis 和 Web API 的入参和出参两个场景,介绍 序列化和反序列化时须要避开的几个坑

  1. 要确保序列化和反序列化算法的一致性。因为,不同序列化算法输入必然不同,要正确处理序列化后的数据就要应用雷同的反序列化算法。
  2. Jackson 有大量的序列化和反序列化个性,能够用来微调序列化和反序列化的细节。须要留神的是,如果自定义 ObjectMapper 的 Bean,小心不要和 Spring Boot 主动配置的 Bean 抵触。
  3. 在调试序列化反序列化问题时,咱们肯定要捋分明三点:是哪个组件在做序列化反序列化、整个过程有几次序列化反序列化,以及目前到底是序列化还是反序列化
  4. 对于反序列化默认状况下,框架调用的是 无参构造方法,如果要调用自定义的有参构造方法,那么须要告知框架如何调用。更正当的形式是,对于须要序列化的 POJO 思考尽量不要自定义构造方法。
  5. 枚举不倡议定义在 DTO 中跨服务传输,因为会有版本问题,并且波及序列化反序列化时会很简单,容易出错。因而,只倡议在程序外部应用枚举。

16 | 用好 Java 8 的日期工夫类,少踩一些“老三样”的坑

Java Date 系列已成为遗留产品,新的 Java8 中的工夫新个性,曾经能够全面替换旧的了,旧的不仅可读性差、易用性差、应用起来冗余繁琐,还有线程平安问题,所以也强烈建议大家应用 JDK8 的。除了好用之外,二者有区别的中央还在于:

java.util.Date 类是因为应用 UTC 示意,所以没有时区概念,实质是工夫戳;
而 LocalDateTime,严格上能够认为是一个日期工夫的示意,而不是一时间点。

  1. 初始化工夫:(例子:2019 年 12 月 31 日 11 点 12 分 13 秒)

    1. jdk8 之前: Date date = new Date(2019 - 1900 , 11, 31, 11, 12, 13);有国际化需要,须要应用到 Calendar 类

      jdk8 之前: 年应该是和 1900 的差值,月应该是从 0 到 11 而不是从 1 到 12。

    2. jdk8 后: LocalDateTime date = LocalDateTime.of(2019, 12, 31, 11, 12, 13);
  2. 时区问题:解决好工夫和时区问题首先就是要正确保留日期工夫。这里有两种保留形式:

    1. 以 UTC 保留,保留的工夫没有时区属性,是不波及时区时间差问题的世界对立
      工夫,后面咱们说过,Date 类就是存得是 UTC 的工夫戳,
    2. 以字面量保留,比方年 / 月 / 日 时: 分: 秒,肯定要同时保留时区信息。

    时区因素会带来两个问题:

    1. Date 存得是 UTC 工夫戳,不同时区服务器读出的工夫是不一样的,例如:拿 2020-01-02 22:00:00,这个工夫作为例子,别离依照默认程序时区,和指定 NewYork 时区,输入 解析后的工夫

      String stringDate = "2020-01-02 22:00:00";
      SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      Date date1 = inputFormat.parse(stringDate);
      System.out.println(date1 + ":" + date1.getTime());
      inputFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
      Date date2 = inputFormat.parse(stringDate);
      System.out.println(date2 + ":" + date2.getTime());

      输入:(发现相差 13 个小时,所以说,如果你的公司服务器有跨时区的,)

      Thu Jan 02 22:00:00 CST 2020:1577973600000
      Fri Jan 03 11:00:00 CST 2020:1578020400000

      解决方案:务必指定 存和读的 时区是统一的。存的时候,须要应用正确的以后时区来保留,这样 UTC 工夫才会正确;读的时候,也只有正确设置本地时区,能力把 UTC 工夫转换为正确的当地工夫。

    2. 更好的计划:Java 8 推出了新的工夫日期类 ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime
      和 DateTimeFormatter,解决时区问题更简略清晰。

      LocalDateTime 不带有时区属性,所以命名为本地时区的日期工夫;
      而 ZonedDateTime=LocalDateTime+ZoneId,具备时区属性。
      因而,LocalDateTime 只能认为是一个工夫示意,ZonedDateTime 才是一个无效的工夫

      咱们拿上海、纽约和东京,举个例子:仍旧是 2020-01-02 22:00:00这个 time

      1. 代码:

        String stringDate = "2020-01-02 22:00:00";
        ZoneId timeZoneSH = ZoneId.of("Asia/Shanghai");
        ZoneId timeZoneNY = ZoneId.of("America/New_York");
        ZoneId timeZoneJST = ZoneOffset.ofHours(9);
        
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        ZonedDateTime date = ZonedDateTime.of(LocalDateTime.parse(stringDate, dateTimeFormatter), timeZoneJST);
        
        DateTimeFormatter outputFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
        System.out.println(timeZoneSH.getId() + outputFormat.withZone(timeZoneSH).format(date));
        System.out.println(timeZoneNY.getId() + outputFormat.withZone(timeZoneNY).format(date));
        System.out.println(timeZoneJST.getId() + outputFormat.withZone(timeZoneJST).format(date));
      2. 输入:

        Asia/Shanghai2020-01-02 21:00:00 +0800
        America/New_York2020-01-02 08:00:00 -0500
        +09:002020-01-02 22:00:00 +0900

      3. 论断:要正确处理国际化工夫问题,举荐应用 Java 8 的日期工夫类,即应用 ZonedDateTime 保留工夫,而后应用设置了 ZoneId 的 DateTimeFormatter 配合 ZonedDateTime 进行工夫格式化失去本地工夫示意。这样的划分非常清晰、细化,也不容易出错。
  3. 日期工夫格式化和解析

    1. Date –”YYYY-MM-dd 驰名 Bug“– 提前跨年,”这明明是一个 2019 年的日期,怎么应用 SimpleDateFormat 格式化后就提前跨年了“。

      1. 例如:初始化一个 Calendar,设置日期工夫为 2019 年 12 月 29 日,应用大写的 YYYY 来初始化 SimpleDateFormat。但最初输入的,却是 2020 年 12 月 29 日,好家伙,间接多了一年!
      2. 其起因在于:开发人员混同了 SimpleDateFormat 的各种格式化模式。JDK 的文档中有阐明:小写 y 是年,而大写 Y 是 week year,也就是所在的周属于哪一年。
      3. 而依照以后 zh_CN 区域来说,2020 年第一周的条件是,从周日开始的残缺 7 天,2020 年蕴含 1 天即可。显然,2019 年 12 月 29 日周日到 2020 年 1 月 4 日周六是 2020 年第一周,得出的 weekyear 就是 2020 年。然而如果你把时区换成 France,就不会有问题。
    2. Date – 定义的 static 的 SimpleDateFormat 可能会呈现线程平安问题
    3. Date – 当须要解析的字符串和格局不匹配的时候,SimpleDateFormat 体现得很宽容,例如:

      应用 yyyyMM 来解析 20160901,它竟然不报错,然而后果是:
      2091 年 1 月 1 日,起因在于:把 0901 当成了月份,相当于 75 年,无语子。。。

    4. 相比旧的 Date,新的 JDK8 Date 就没有这些问题,也不必管是 YYYY 还是 yyyy,DateTimeFormatter 要是线程平安的。
  4. 日期工夫的计算:

    1. 日期工夫的计算,一个很多开发常踩的坑。有人间接应用工夫戳进行工夫计算,比方心愿失去以后工夫之后 30 天的工夫,会这么写代码:间接把 newDate().getTime 办法失去的工夫戳加 30 天对应的毫秒数,也就是 30 天 1000 毫秒 3600 秒 *24 小时。然而会发现 后果基本不对

      其起因在于 int 产生溢出,修复形式就是把 30 改为 30L
      但还是很繁琐,且容易出错,所以 jdk8 之前,更举荐应用 Calendar

    2. jdk8 后,日期工夫类型,能够间接进行各种计算,更加简洁、不便和弱小。

      但 计算两个日期差时可能会踩坑,
      Period.between 失去了两个 LocalDate 的差,返回的是两个日期差几年零几月零几天。如果心愿得悉两个日期之间差几天,间接调用 Period 的 getDays() 办法失去的只是最初的“零几天”,而不是算总的距离天数。

17 丨别以为“自动挡”就不可能呈现 OOM

通常而言,Java 程序的 OOM 有如下几种可能:

  1. 程序的确须要超出 JVM 配置的内存下限的内存。不论是程序实现的不合理,还是因为各种框架对数据的反复解决、加工和转换,雷同的数据在内存中不肯定只占用一份空间。针对内存量应用超大的业务逻辑,比方缓存逻辑、文件上传下载和导出逻辑,咱们在做容量评估时,可能还须要理论做一下 Dump,而不是进行简略的假如。
  2. 呈现内存泄露,其实就是咱们认为没有用的对象最终会被 GC,但却没有。GC 并不会回收强援用对象,咱们可能常常在程序中定义一些容器作为缓存,但如果容器中的数据有限增长,要特地小心最终会导致 OOM。应用 WeakHashMap 是解决这个问题的好方法,但值得注意的是,如果强援用的 Value 有援用 Key,也无奈回收 Entry。
  3. 不合理的资源需要配置,在业务量小的时候可能不会呈现问题,但业务量一大可能很快就会撑爆内存。比方,随便配置 Tomcat 的 max-http-header-size 参数,会导致一个申请应用过多的内存,申请量大的时候呈现 OOM。在进行参数配置的时候,咱们要意识到,很多限度类参数限度的是背地资源的应用,资源始终是无限的,须要依据理论需要来正当设置参数。

最初想说的是,在呈现 OOM 之后,也不必过于缓和。咱们能够依据谬误日志中的异样信息,再联合 jstat 等命令行工具察看内存应用状况,以及程序的 GC 日志,来大抵定位呈现 OOM 的内存区块和类型。其实,咱们遇到的 90% 的 OOM 都是堆 OOM,对 JVM 过程进行堆内存 Dump,或应用 jmap 命令剖析对象内存占用排行,个别都能够很容易定位到问题。

18 丨当反射、注解和泛型遇到 OOP 时,会有哪些坑?

尽管咱们日常业务我的项目中简直都是增删改查,用到反射、注解和泛型这些高级个性的机会少之又少,没啥好学的。然而,只有学好、用好这些高级个性,能力开发出更简洁易读的代码,而且简直所有的框架都应用了这三大高级个性。比方,要缩小反复代码,就得用到反射和注解。

  1. 反射调用办法并不是通过调用时的传参确定办法重载,而是在获取办法的时候通过办法名和参数类型来确定的。遇到办法有包装类型和根本类型重载的时候,所以须要特地留神这一点。
  2. 反射获取类成员,须要留神 getXXX 和 getDeclaredXXX 办法的区别,其中 XXX 包含 Methods、Fields、Constructors、Annotations。
  3. 泛型因为类型擦除会导致泛型办法 T 占位符被替换为 Object,子类如果应用具体类型笼罩父类实现,编译器会生成桥接办法。这样既满足子类办法重写父类办法的定义,又满足子类实现的办法有具体的类型。应用反射来获取办法清单时,所以须要特地留神这一点。
  4. 自定义注解能够通过标记元注解 @Inherited 实现注解的继承,不过这只实用于类。如果要继承定义在接口或办法上的注解,能够应用 Spring 的工具类 AnnotatedElementUtils,并留神各种 getXXX 办法和 findXXX 办法的区别。

19 丨 Spring 框架:IoC 和 AOP 是扩大的外围

  1. 让 Spring 容器治理对象,要思考对象默认的 Scope 单例是否适宜,对于有状态的类型,单例可能产生内存泄露问题。
  2. 如果要为单例的 Bean 注入 Prototype 的 Bean,绝不是仅仅批改 Scope 属性这么简略。因为单例的 Bean 在容器启动时就会实现一次性初始化。最简略的解决方案是,把 Prototype 的 Bean 设置为通过代理注入,也就是设置 proxyMode 属性为 TARGET_CLASS。
  3. 如果一组雷同类型的 Bean 是有程序的,须要明确应用 @Order 注解来设置程序。能够再回顾下,两个不同优先级切面中 @Before、@After 和 @Around 三种加强的执行程序,是什么样的。

20 丨 Spring 框架:框架帮咱们做了很多工作也带来了复杂度

  1. Spring Cloud 会应用 Spring Boot 的个性,依据以后引入包的状况做各种主动拆卸。如果咱们要扩大 Spring 的组件,那么只有清晰理解 Spring 主动拆卸的运作形式,能力甄别运行时对象在 Spring 容器中的状况,不能想当然认为代码中能看到的所有 Spring 的类都是 Bean。
  2. 对于配置优先级的案例,剖析配置源优先级时,如果咱们认为看到 PropertySourcesPropertyResolver 就看到了假相,后续进行扩大开发时就可能会踩坑。咱们肯定要留神,剖析 Spring 源码时,你看到的表象不肯定是理论运行时的状况,还须要借助日志或调试工具来理清整个过程。如果没有调试工具,能够借助 Arthas,来剖析代码调用门路。
退出移动版