Azhang nga gipatik nga mga artikulo

热部署与日志

  1. springboot 中 devtools 热部署

    1. 1 引言

      ​ 为了进一步提高开发效率,springboot 为我们提供了全局项目热部署,日后在开发过程中修改部分代码以及相关配置文件后,不需要每次重启使修改生效,在项目中开启了 springboot 全局热部署之后只需要在修改之后等待几秒即可使修改生效。

    2. 2 开启热部署

      1. 21 项目中引入依赖

        <!-- 1. devtools 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
      2. 22 当我们修改了类文件后,idea 不会自动编译,得修改 idea 设置。

        • File-Setting-Compiler-Build Project automatically

          image-20220505164635699

        • ctrl + shift + alt + /,选择 Registry,勾上 Compiler autoMake allow when app running

          image-20220505164651913

          image-20220505164728718

  2. 带你弄清混乱的 JAVA 日志体系

    1. 1 日志框架

      日志实现日志门面
      log4jJCL
      jul java.util.loggingSJF4J
      log4j2
      logback(推荐使用)
    2. 2 代码实现

      1. 21 日志实现

        1. 211 jul(注意包名)

          package com.springboot;
          
          import java.util.logging.Logger;
          
          public class JulMain {
              public static void main(String[] args) {
                  Logger logger = Logger.getLogger(JulMain.class.getName());
                  logger.info("崇尚官方开发组:Jul");
              }
          }

          image-20220428214936443

        2. 212 log4j(注意包名)

          导包:

          <!-- log4j 的核心依赖 -->
          <dependency>
              <groupId>log4j</groupId>
              <artifactId>log4j</artifactId>
              <version>1.2.17</version>
          </dependency>

          添加 log4j.properties 文件:

          log4j.rootLogger=trace, stdout
          log4j.appender.stdout=org.apache.log4j.ConsoleAppender
          log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
          log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

          java 代码实现:

          package com.springboot;
          
          import org.apache.log4j.Logger;
          
          public class Log4jMain {
              public static void main(String[] args) {
                  Logger logger = Logger.getLogger(Log4jMain.class.getName());
                  logger.info("崇尚开源开发组:Log4j");
              }
          }

          image-20220428214918691

      2. 22 日志及日志门面实现

        开发标准:记录日志不能直接使用日志实现框架,必须通过日志门面来实现。

        1. 221 Jul (实现) + JCL (门面)

          导包:

          <!-- 引入 JCL 门面依赖 -->
          <dependency>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging</artifactId>
              <version>1.2</version>
          </dependency>

          添加 commons-logging.properties 文件:

          org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger

          java 实现:

          package com.springboot;
          
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
          
          public class JulMain {
              public static void main(String[] args) {
                  Log log = LogFactory.getLog(JulMain.class.getName());
                  System.out.println(log.getClass());
                  log.info("崇尚官方开发组:Jul");
              }
          }
        2. 222 log4j (实现) + slf4j (门面)

          导包:

          <!-- slf4j 的核心依赖 -->
          <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-api</artifactId>
          </dependency>
          <!-- 加上桥接器 -->
          <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-log4j12</artifactId>
          </dependency>

          java 实现:

          package com.springboot;
          
          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          
          public class Log4jMain {
              public static void main(String[] args) {
                  Logger logger = LoggerFactory.getLogger(Log4jMain.class);
                  System.out.println(logger.getClass());
                  logger.info("崇尚开源开发组:Log4j");
              }
          }
  1. 3 桥接器

    image-20220428220818406

  2. 4 将 JCL 转化到 slf4j

    使用 jcl-over-slf4j 或者 jul-to-slf4j

    <!-- 为了日志统一实现,将 JCL 转化到 slf4j
         添加 JCL-slf4j 的适配器
    -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
    </dependency>
  1. logback 日志的集成

    image-20220428232620876

    1. SpringBoot 底层也是使用 slf4j + logback 的方式进行日志记录

      logback 桥接:logback-calssic

    2. SpringBoot 也把其他的日志都替换成了 slf4j;

      • log4j 适配:log4j-over-slf4j
      • jul 适配:jul-to-slf4j
      • jcl-over-slf4j
  2. SpringBoot 日志使用

    • 日志级别

      可以设置 TRACE,DEBUG,INFO,WARN,ERROR 或 OFF 之一

      logging:
        level:
          root: "warn"
          org.springframework.web: "debug"
          org.hibernate: "error"
  • 日志格式

    2022-04-29 16:18:09.667 TRACE 78860 --- [           main] com.springboot.Application               : 跟踪
    2022-04-29 16:18:09.667 DEBUG 78860 --- [           main] com.springboot.Application               : 调试
    2022-04-29 16:18:09.667  INFO 78860 --- [           main] com.springboot.Application               : 信息
    2022-04-29 16:18:09.667  WARN 78860 --- [           main] com.springboot.Application               : 警告
    2022-04-29 16:18:09.667 ERROR 78860 --- [           main] com.springboot.Application               : 异常

    详细介绍:

    可以使用 logging.pattern.console 修改默认的控制台的日志格式:

    %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
    • 2022-04-29 16:18:09.667

      • 日期和时间:毫秒精度,易于排序。
      • %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint}

        • %clr 代表当前内容的颜色 {faint}
        • (%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}) 括号中表示显示的内容

          • ${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}

            使用了 ${value:value2} springboot 的占位符 + null 条件的表达式(如果 value 为 null 则使用 value2)

            LOG_DATEFORMAT_PATTERN:系统环境变量中的值,springboot 底层会根据对应的配置项将值设置到对应的环境变量中 LOG_DATEFORMAT_PATTERN=logging.pattern.dateformat 可通过 dateformat: -yyyy-MM-dd 设置

          • %d{-yyyy-MM-dd HH:mm:ss.SSS}

            %d 表示 logback 的日期显示方式

            {-yyyy-MM-dd HH:mm:ss.SSS} 表示日期的格式

    • TRACE

      • 日志级别。
      • %clr(${LOG_LEVEL_PATTERN:-%5p})

        • %clr 表示内容的颜色,会根据不同的日志级别输出对应的颜色
        • ${LOG_LEVEL_PATTERN:-%5p}

          • %5 代表当前内容所占字符长度
          • p 表示输出日志的级别
    • 78860

      • 进程 ID。
      • %clr(${PID:- }){magenta}

        • %clr 表示当前内容的颜色为 {magenta}
        • ${PID:- } springboot 的占位符 + null 条件的表达式(如果 value 为 null 则使用 value2)
        • PID 是系统环境变量中的进程 ID (由系统分配)
    • ---

      • 一个 --- 分离器来区分实际日志消息的开始。
    • [ main]

      • 线程名称:用方括号括起来(对于控制台输出可能会被截断)。
    • com.springboot.Application

      • 记录日志的类。
    • 跟踪

      • 日志消息
  • 文件输出

    默认情况下,Spring Boot 仅记录到控制台,不写日志文件。如果除了控制台输出外还想写日志文件,则需要设置一个 logging.file.name 或 logging.file.path 属性(例如,在中 application.properties)。

    下表显示了如何 logging.* 一起使用这些属性:

    logging.file.namelogging.file.path实例描述
    (没有)(没有) 仅控制台记录
    指定文件名(没有)my.log写入指定的日志文件
    (没有)具体目录/var/log写入 spring.log 指定的目录
    • logging.file.name

      • 可以设置文件的名称,如果没有设置路径会默认在项目的相对路径下创建
      • 还可以指定路径 + 文件名: D:/my.log
    • logging.file.path

      • 不可以指定文件的名称,必须要指定一个物理文件夹路径,会默认使用 spring.log 为文件名称
  • Spring Boot 的默认日志级别是 INFO

    @SpringBootApplication
    public class Application {
    
        //1. 声明日志记录器
        static Logger logger = LoggerFactory.getLogger(Application.class);
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
    
            logger.trace("跟踪");
            logger.debug("调试");
            //Spring Boot 的默认日志级别是 INFO
            logger.info("信息");
            logger.warn("警告");
            logger.error("异常");
        }
    
    }

    image-20220429155655577

  • 日志迭代(轮转)

    如果您使用的是 Logback,则可以使用 application.properties 或 application.yaml 文件微调日志轮播设置。对于所有其他日志记录系统,您需要直接配置轮转设置(例如,如果使用 Log4J2,则可以添加 log4j.xml 文件)。

    名称描述
    logging.logback.rollingpolicy.file-name-pattern归档的文件名
    logging.logback.rollingpolicy.clean-history-on-start如果应在应用程序启动时进行日志归档清理
    logging.logback.rollingpolicy.max-file-size归档前日志文件的最大大小
    logging.logback.rollingpolicy.total-size-cap删除日志档案之前可以使用的最大大小
    logging.logback.rollingpolicy.max-history保留日志存档的天数(默认为7)
    • logging.logback.rollingpolicy.file-name-pattern

      • ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz

        • ${LOG_FILE} 对应 logging.file.name
        • %d{yyyy-MM-dd} 日期 年-月-日
        • %i 索引,当文件超出指定大小后进行的文件索引递增
  • 自定义日志配置文件

    可以通过在类路径中包含适日志配置文件来激活各种日志记录系统或使用 logging.config

    Logging SystemCustomization
    Logbacklogback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
    log4j2log4j2-spring.xml or log4j2.xml
    JDK(Java Util Logging)logging.properties

    注意:

    • 如果使用自定义日志配置文件,会使 springboot 中全局配置文件的 logging 相关配置失效。
    • 可以结合 springboot 提供的 Profile 来控制日志的生效

      • 要注意的是,一定要将日志配置文件的文件名改成 logback-spring.xml, 因为 logback.xml 会在 springboot 容器加载之前先被 logback 给加载到,那么由于 logback 无法解析 springProfile 将会报错

        image-20220429181012835

      image-20220429181043577

  • 切换日志框架

    • 将 logback 切换成 log4j2

      添加 spring-boot-starter-log4j2 依赖(场景启动器)

      <!-- log4j2 的场景启动器 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>

      排除 logback 的场景启动器

      <!-- starter-web 里面自动添加了 starter-logging 也就是 logback 的依赖 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
              <!-- 排除 starter-logging 也就是 logback 的依赖 -->
              <exclusion>
                  <groupId>org.apache.logging.log4j</groupId>
                  <artifactId>log4j-slf4j-impl</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>org.apache.logging.log4j</groupId>
                  <artifactId>log4j-jul</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>org.slf4j</groupId>
                  <artifactId>jul-to-slf4j</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>*</groupId>
                  <artifactId>*</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      添加 log4j2.xml 文件

      <?xml version="1.0" encoding="UTF-8" ?>
      <configuration status="OFF">
          <appenders>
              <Console name="Console" target="SYSTEM_OUT">
                  <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} +++++++ %msg%n"/>
              </Console>
          </appenders>
          <loggers>
              <root level="info">
                  <appender-ref ref="Console"/>
              </root>
          </loggers>
      </configuration>
 

 - 将 logback 切换成 log4j

   1. 首先将 logback 的桥接器排除

      ```xml
      <!-- starter-web 里面自动添加了 starter-logging 也就是 logback 的依赖 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>ch.qos.logback</groupId>
                  <artifactId>logback-classic</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>org.apache.logging.log4j</groupId>
                  <artifactId>log4j-slf4j-impl</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>org.apache.logging.log4j</groupId>
                  <artifactId>log4j-jul</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>org.slf4j</groupId>
                  <artifactId>jul-to-slf4j</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>*</groupId>
                  <artifactId>*</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      ```

   2. 添加 log4j 的桥接器

      ```xml
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
      </dependency>
      ```

   3. 添加 log4j 的配置文件

      ```properties
      log4j.rootLogger=trace, stdout
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] ======== %m%n
      ```

Spring Boot 的配置文件和自动配置原理

  1. 使用 Spring Initializr 快速创建 Spring Boot 项目

    • IDEA:使用 Spring Initializr 快速创建项目

      ​ 继承关系-springboot 的 maven 项目

      • 使用 spring initializr 新建一个父 maven,type 选择 POM
      • 在父 maven 项目中新建模块,使用 spring initializr 新建一个子 maven,type选择 maven project
      • 修改子项目中的继承方式:

        之前:

        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>

        修改后:

        <parent>
            <groupId>com.SpringBoot</groupId>
            <artifactId>springboot_parent</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </parent>

        spring_initializr 继承 springboot_parent 继承 spring-boot-starter-parent

    • 默认生成的 Spring Boot 项目:

      resource文件夹中的目录结构

      • static:保存所有的静态资源,js css images;
      • template:保存所有的模板页面;(Spring Boot 默认 jar 包使用嵌入式的 Tomcat,默认不支持 JSP 页面);可以使用模板引擎(freemarker、thymeleaf)
      • application.properties:Spring Boot 应用的配置文件;可以修改一下默认设置;
  2. 自定义 SpringApplication

    其他的上官网看。

    • lazy-initialization:懒加载

      # 懒加载设置,要在使用 bean 的时候才会去创建 bean 实例,否则不创建
      spring.main.lazy-initialization=true
  3. 配置文件的使用

    SpringBoot 使用一个全局的配置文件或者叫核心配置文件,配置文件名在约定的情况下 名字是固定的

    配置文件的作用:修改 SpringBoot 自动配置的默认值;SpringBoot 在底层都给我们自动配置好;

    两种配置文件的格式:

    1. application.properties 的用法:扁平的 k/v 格式。

      server.port=8081
      server.servlet.context-path=/wyu
    2. application.yml 的用法:树型结构。

      server:
          port: 8081
          servlet:
              context-path: /wyu

      建议使用 yml,因为它的可读性更强。

  • YAML语法:

    k:(空格)v:表示一对键值对(空格必须有);

    以空格的缩进来空值层级关系;只要是左对齐的一列数据,都是同一个层级的;

    属性和值也是大小写敏感;

  • 配置文件加载顺序:

    • application.yml
    • application.yaml
    • application.properties
  • 外部约定配置文件加载顺序:

    springboot 启动还会扫描以下位置的 application.properties 或者 application.yml 文件作为 Spring Boot 的默认配置文件

    下面无序列表优先级从低到高

    • classpath 根目录下的

      image-20220412141330192

    • classpath 根 config/

      image-20220412141257617

    • 项目根目录

      如果当前项目是继承/耦合关系 maven 项目的话,项目根目录为父 maven 项目的根目录。

      image-20220412141847161

    • 项目根目录 /config

      image-20220412142104682

    • 直接子目录 /config

      image-20220412143826880

  • 配置文件值注入

    image-20220425024303286

    可以使用 @Value 标签一个一个属性绑定,也可以使用 @ConfigurationProperties 标签根据 yml 或者 properties 文件一次性绑定

    • 松散绑定

      user:
        user-name: 啊章
      user:
        userName: 啊章
      user:
        user_name: 啊章
      user:
        USERNAME: 啊章

      以上四种命名是可以自动绑定 bean 属性 User.username

    • 如果输出中文乱码,则需要调一下setting:

    image-20220425024608880

    • @Value@ConfigurationProperties 的获取值比较:
    @ConfigurationProperties@Value
    功能批量注入配置文件中的属性一个个指定
    松散绑定支持支持,有限
    SpEL不支持支持
    JSR303数据校验支持不支持
    复杂类型封装支持不支持
    自动提示支持不支持
    • 导入配置文件处理器并且勾选如图设置,以后编写配置就有提示了:
    <!-- 会生成 META-INF 元数据  用于提供 idea 自动提示配置文件的 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <!-- option: 如果依赖不会传播  就是说别的项目依赖本项目,那么本项目的这个依赖不会被别的项目依赖 -->
        <optional>true</optional>
    </dependency>

    image-20220425025520512

    • 配置文件赋值:

      • 对象、Map(属性和值)(键值对):

        • 对象是 k : v 的方式

            girl-friend:
              18 : IU
              20 : 庄达菲
        • 行内写法:

            girl-friend: {18: IU,20: 庄达菲}
      • 数组(List、Set):

        • 用 - 值 表示数组中的一个元素

            hobbies:
              - 唱
              - 跳
              - rap
              - 篮球
        • 行内写法

            hobbies: [唱,跳,rap,篮球]
      • JSR303数据校验

        image-20220425033655210

        <!-- 数据校验 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        @NotNull 要使用 javax.validation 包下面的验证注解

  • 配置文件占位符

    • 随机数

      #随机值
      ${random.value}
      #随机 int 值
      ${random.int}
      #随机值
      ${random.long}
      #随机值
      ${random.uuid}
      # 10 以内的随机 int 值
      ${random.int(10)}
      #范围为 1024 到 65536 的随机 int 值
      ${random.}int[1024,65536]}
    • 占位符获取之前配置的值,如果没有可以使用:指定默认值

  • Profile文件的加载

    对于应用程序来说,不同的环境需要不同的配置。

    1. 多 Profile 文件

      • Spring 官方给出的语法规则是 application-{profile}.properties(.yaml/.yml)
      • 如果需要创建自定义的 properties 文件时,可以用 application-xxx.properties 的命名方式。

        • 开发环境下:

          image-20220412154909418

        • 生产环境下:

          image-20220412154920749

      • 若我们需要在两种环境下进行切换,只需要在 application.properties 中加入如下内容即可。

        spring.profiles.active=prod
    2. 激活指定 profile

      • 在配置文件中指定 spring.profiles.active=dev
      • 命令行:

        java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
        • 还可以通过 spring.config.location 来改变默认的配置文件

          使用 spring.config.location 指定的配置文件,是不会进行互补。

          java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.profiles.location=D:/application.properties
        • 还可以通过 spring.config.name 来改变默认的配置文件

          java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.profiles.name=application-prod;
    3. 所有配置文件按以下顺序考虑:优先级从低到高

      1. 打包在 jar 中配置文件
      2. 打包在 jar 中 profile
      3. 打包在 jar 之外的配置文件
      4. 打包在 jar 之外的 profile
    4. 通配符位置

      如果具有某些 Redis 配置和某些 mysql 配置,则可能需要将这两个配置分开,同时要求这两个配置都存在于 application.properties 文件中。这可能会导致两个不同的 application.properties 文件安装在诸如不同的位置 /config/redis/application.properties 和 /config/mysql/application.properties。在这种情况下,通配符位置为 config/*/,将导致两个文件都被处理。

    5. 配置文件读取方式:优先级从低到高

      • @PropertySource@Configuration 类上的注解。请注意,Environment 在刷新应用程序上下文之前,不会将此类属性源添加到中。现在配置某些属性(如 logging. 和 spring.main. 在刷新开始之前先读取)为时已晚。

        • 会和约定的配置文件形成互补
        • 一定要指定 .properties 配置
        @SpringBootApplication
        @PropertySource("classpath:appSource.properties")
        public class Application {
        
            public static void main(String[] args) throws IOException {}
      

    - 默认属性(通过设置指定 SpringApplication.setDefaultProperties)

      - 会和约定的配置文件形成互补

      ```java
      @SpringBootApplication
      public class Application {
      
          public static void main(String[] args) throws IOException {
              SpringApplication springApplication = new SpringApplication(Application.class);
      
              // 创建 properties
              Properties properties = new Properties();
              // 通过当前类的 ClassLoader
              InputStream is = Application.class.getClassLoader().getResourceAsStream("app.properties");
              // 将输入流读取成 properties 对象
              properties.load(is);
      
              springApplication.setDefaultProperties(properties);
              springApplication.run(args);
          }
      
      }
      ```

      

    - 配置数据(例如 application.properties 文件)

      - 约定配置文件

    - 操作系统环境变量。

      - 会使约定配置文件失效

      - idea 

        ![image-20220422040551533](https://static-upyun.hackerjk.top/markdown/image-20220422040551533.png!)

      - windows

        ![image-20220422040837561](https://static-upyun.hackerjk.top/markdown/image-20220422040837561.png!)

    - Java 系统属性(System.getProperties())。

      - 会使约定配置文件失效

      - idea

        ![image-20220422041130067](https://static-upyun.hackerjk.top/markdown/image-20220422041130067.png!)

      - 命令行 java 属性

        ![image-20220422041323887](https://static-upyun.hackerjk.top/markdown/image-20220422041323887.png!)

    - JNDI 属性 java:comp/env。

    - ServletContext 初始化参数。

      ![image-20220422041429270](https://static-upyun.hackerjk.top/markdown/image-20220422041429270.png!)

    - ServletConfig 初始化参数。

      ![image-20220422041509283](https://static-upyun.hackerjk.top/markdown/image-20220422041509283.png!)

    - 来自的属性 SPRING_APPLICATION_JSON(嵌入在环境变量或系统属性中的嵌入式 JSON)。

    - 命令行参数。

      - 会使约定配置文件失效

      ```cmd
      java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.profiles.location=D:/application.properties
      ```

      

    - properties 测试中的属性。可用于测试应用程序的特定部分 @SpringBootTest 的测试注释和注释。

    - @TestPropertySource 测试中的注释。

      - 用在单元测试上的

        ![image-20220422043022209](https://static-upyun.hackerjk.top/markdown/image-20220422043022209.png!)

    - $HOME/.config/spring-boot 当 devtools 处于活动状态时,目录中的 Devtools全局设置属性。
  • 配置文件加载位置
  1. Spring Boot 的配置和自动配置原理

    • @SpringBootApplication:Spring Boot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类,SpringBoot 需要运行这个类的 main 方法来启动 SpringBoot 应用;

      image-20220427150357061

      @Target(ElementType.TYPE)    //设置当前注解可以标记在哪
      @Retention(RetentionPolicy.RUNTIME)    //当注解标注的类编译以什么方式保留
          RetentionPolicy.RUNTIME 会被 jvm 加载 | 反射的方式:class.getAnnotionbytype(Autowreid.class)
      @Documented        //java doc 会生成注解信息
      @Inherited        //是否会被继承
      
      @SpringBootConfiguration    //Spring Boot 的配置类;标注在某个类上,表示这是一个 Spring Boot 的配置类;
      
      @Configuration    //配置类上来标注这个注解;配置类——配置文件;配置类也是同期中的一个组件;@Component
      
      @EnableAutoConfiguration    //开启自动配置功能;以前我们需要配置的东西,Spring Boot 帮我们自动配置;@EnableAutoConfiguration 告诉 SpringBoot 开启自动配置功能;这样自动配置才能生效;
      
      @ComponentScan    //扫描包 相当于在 spring.xml 配置中 <context:component-scan>    但是没有指定 basepackage,如果没有指定,spring 底层会自动扫描当前配置类所有在的包
      
      TypeExcludeFilter:springboot    //对外提供的扩展类,可以供我们去按照我们的方式进行排除
      
      AutoConfigurationExcludeFilter    //排除所有配置类并且是自动配置类中里面的其中一个
      
      @AutoConfigurationPackage    //将当前配置类所在包保存在 BasePackages 的 Bean 中。供 spring 内部使用
    • 以 HttpEncodingAutoConfiguration (Http 编码自动配置)为例解释自动配置原理;

      @Configuration(proxyBeanMethods = false)
      @EnableConfigurationProperties(ServerProperties.class)
      @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
      @ConditionalOnClass(CharacterEncodingFilter.class)
      @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
      public class HttpEncodingAutoConfiguration {
      
          private final Encoding properties;
      
          public HttpEncodingAutoConfiguration(ServerProperties properties) {
              this.properties = properties.getServlet().getEncoding();
          }
      
          @Bean
          @ConditionalOnMissingBean
          public CharacterEncodingFilter characterEncodingFilter() {
              CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
              filter.setEncoding(this.properties.getCharset().name());
              filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
              filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
              return filter;
          }
      • @Configuration(proxyBeanMethods = false)

        标记了 @Configuration,Spring 底层会给配置创建 cglib 动态代理。

        作用:防止每次调用本类的 Bean 方法而重新创建对象,Bean 是默认单例的。

      • @EnableConfigurationProperties(ServerProperties.class)

        启用可以再配置类设置的属性 对应的类

      • @xxxConditional 根据当前不同的条件判断,决定这个配置类是否生效?
      • @Conditional 派生注解(Spring 注解版原生的 @Conditional 作用)

        作用:必须是 @Conditional 指定的条件成立,才给容器中添加组件,配置配里面的所有内容才能生效;

        @Conditional 扩展注解作用(判断是否满足当前指定条件)
        @ConditionalOnJava系统的 java 版本是否符合要求
        @ConditionalOnBean容器中存在指定的 Bean;
        @ConditionalOnMissingBean容器中不存在指定的 Bean;
        @ConditionalOnExpression满足 SpEL 表达式指定
        @ConditionalOnClass系统中有指定的类
        @ConditionalOnOnMissingClass系统中没有指定的类
        @ConditionalOnSingleCandidate容器中只有一个指定的 Bean,或者这个 Bean 是首选 Bean
        @ConditionalOnProperty系统中指定的属性是否有指定的值
        @ConditionalOnResource类路径下是否存在指定资源文件
        @ConditionalOnWebApplication当前是 Web 环境
        @ConditionalOnNotWebApplication当前不是 Web 环境
        @ConditionalOnJndiJNDI 存在指定项
      • 可以通过设置配置文件中:启用 debug=true 属性来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效

        ============================
        CONDITIONS EVALUATION REPORT
        ============================
        
        
        Positive matches:    **表示自动配置类启用的**
        -----------------
        ......
        
        
        Negative matches:    **没有匹配成功的自动配置类**
        -----------------
        ......

Spring Boot 简介及快速搭建

  1. 导入依赖:

    <!-- 继承 SpringBoot 的父项目 -->
    <!-- spring boot 要加上下面的 parent 代码才能将当前项目标记为 spring boot 项目 -->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.6.6</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    
    <!-- 下面的 plugin 中的 spring-boot-maven-plugin 可以告诉服务器 SpringBoot 的启动类在哪 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  2. 建包并创建控制器

    @RestController  // 相当于 @Controller + @ResponseBody
    @RequestMapping("/hello")
    public class HelloController {
    
        @RequestMapping("/world")
        public String sayHi() {
            return "hello world!";
        }
    
    }
  3. 编写启动类

    @SpringBootApplication      // 标记为 SpringBoot 的启动类  整个工程只有一个
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
  4. 修改端口及添加项目名

    在resource文件夹中创建一个 application.properties 文件(文件名只能是这个)

    # 端口
    server.port=8080
    # 项目虚拟路径
    server.servlet.context-path=/wyu

    ​ server.port 是用来选用端口的

    ​ server.servlet.context-path 则是用来添加项目名,注意:没有使用 server.servlet.context-path 之前的访问地址为localhost:8080/hello/world,使用之后地址变为 localhost:8080/wyu/hello/world

  5. 部署服务器

    在 pom.xml 文件中加入下面的代码

    <!-- 下面的 plugin 中的 spring-boot-maven-plugin 可以告诉服务器 SpringBoot 的启动类在哪 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  6. 代码讲解

    <!-- 引入父 Maven 项目,继承父 Maven 项目中所有的配置信息
        spring-boot-starter-parent: 又引入一个父 Maven 项目
        <parent>
            <artifactId>spring-boot-dependencies</artifactId>
            <groupId>org.springframework.boot</groupId>
            <version>2.6.6</version>
        </parent>
        spring-boot-dependencies 帮我们管理了 SpringBoot 应用中所有的依赖和版本
                以后我们导入已有依赖就不需要写版本号了,它帮我们解决了第三方库之间的版本冲突问题
                名次:SpringBoot 的版本仲裁中心
    -->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.6.6</version>
    </parent>
    
    <!--
        starter 场景启动器:不同场景启动器维护了所对应的所有依赖,从而简化 maven 文件书写。
        spring-boot-starter-web:使用 Spring MVC 构建 Web (包括RESTful)应用程序。
            使用 Tomcat 作为默认的嵌入式容器
    -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    
    <!-- 下面的 plugin 中的 spring-boot-maven-plugin 可以告诉服务器 SpringBoot 的启动类在哪 -->
    <!-- 部署 SpringBoot 的插件,只有加了这个插件,当运行 java -jar xxx.jar 才能正常启动 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  7. 面试题:

    • 描述一下 SpringBoot 的作用
    • SpringBoot 有哪些特性?

Spring 中的事务管理

1、事务简介
  • 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。
  • 事务就是一系列的动作,它们被当做一个单独的工作单元。这些动作要么全部完成,要么全部不起作用
  • 事务的四个关键属性 (ACID)

    • 原子性 (atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
    • 一致性 (consistency):一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
    • 隔离性 (isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    • 持久性 (durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该收到影响。通常情况下,事务的结果被写到持久化存储器中。
2、Spring 中的事务管理
  • 作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制。
  • Spring 既支持编程式的事务管理,也支持声明式的事务管理。
  • 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。
  • 声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring通过 Spring AOP 框架支持声明式事务管理。
3、Spring 中的事务管理器
  • Spring 从不同的事务管理 API 中抽象了一整套的事务机制。开发人员不必了解底层的事务 API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。
  • Spring 的核心事务管理抽象是 Interface PlatformTransactionManager 管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略 (编程式或声明式),事务管理器都是必须的。
  • Spring 中的事务管管理器的不同实现

    • Class DataSourceTransactionManager:在应用程序中主需要处理一个数据源,而且通过 JDBC 存取

      配置事务管理器:

      <!-- 配置事务管理器 -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
      <!-- 启用事务注解 -->
      <tx:annotation-driven transaction-manager="transactionManager"/>

      添加事务注解 @Transactional:

      @Repository("bookShopService")
      public class BookShopServiceImpl implements BookShopService {
      
          @Autowired
          private BookShopDao bookShopDao;
      
          //添加事务注解
          @Transactional
          @Override
          public void purchase(String isbn, String username) {
              //1.获取书的单价
              int price = bookShopDao.findBookPriceByIsbn(isbn);
      
              //2.更新书的库存
              bookShopDao.updateBookStock(isbn);
      
              //3.更新用户余额
              bookShopDao.updateUserAccount(username, price);
      
          }
      
      }
    • Class JtaTransactionManager:在 JavaEE 应用服务器上用 JTA (Java Transaction API) 进行事务管理
    • Class HibernateTransactionManager:用 Hibernate 框架存取数据库
    • ……
    • 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中
4、事务的传播属性
  • 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务中运行。
  • 事务的传播行为可以由 传播属性指定。Spring 定义了 7 中类传播行为。
  • 传播属性描述
    REQUIRED如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
    REQUIRED_NEW当前的方法必须启动新事务,并在它自己的事务给运行。如果有事务正在运行,应该将它挂起。
    SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。
    NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
    MANDATORY当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
    NEVER当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
    NESTED如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
    • REQUIRED 传播行为

      当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时,它默认会在现有的事务内运行。这个默认的传播行为就是 REQUIRED。 因此在 checkout() 方法的开始和终止边界内只有一个事务。这个事务只在 checkout() 方法结束的时候被提交,结果用户一本书都买不了。

      事务传播属性可以在 @Transactional 注解的 propagation 属性中定义。

    • REQUIRES_NEW 传播行为

      另一种常见的传播行为是 REQUIRES_NEW。它表示该方法必须启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

5、事务的其他属性
  • 隔离级别

    • 使用 ioslation 指定事务的隔离级别,最常用的取值为 READ_COMMITTED。
  • 回滚

    • 默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下取默认值即可。
  • 只读

    • 使用 readOnly 指定事务是否为只读。
    • 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
    • 若真的只是一个只读取数据库值的方法,应设置 readOnly = true。
  • 过期

    • 使用 timeout 指定强制回滚之前事务可以占用的时间。
    • 单位是 秒(s)。
6、基于 xml 文件的方式配置事务管理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>

        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>

    <!-- 配置 Spring 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置 bean -->
    <bean id="bookShopDao" class="com.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="bookShopService" class="com.spring.tx.xml.service.impl.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"/>
    </bean>
    <bean id="cashier" class="com.spring.tx.xml.service.impl.CashierImpl">
        <property name="bookShopService" ref="bookShopService"/>
    </bean>

    <!-- 1.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 2.配置事务属性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 根据方法名指定事务的属性 -->
            <tx:method name="purchase" propagation="REQUIRES_NEW"/>
            <!-- 通常情况下,对于 get 或者 find 开头的方法 read-only 的值为 true -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.spring.tx.xml.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>
7、Spring 整合 Hibernate
  • Spring 整合 Hibernate 整合什么?

    • 由 IOC 容器来管理 Hibernate 的 SessionFactory
    • 让 Hibernate 使用上 Spring 的声明式事务
  • 整合步骤:

    • 加入 hibernate

      • 导入 jar 包

        <dependency>
                <groupId>org.jboss.logging</groupId>
                <artifactId>jboss-logging</artifactId>
                <version>3.1.0.GA</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.spec.javax.transaction</groupId>
                <artifactId>jboss-transaction-api_1.1_spec</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.15.0-GA</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.javax.persistence</groupId>
                <artifactId>hibernate-jpa-2.0-api</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>4.2.4.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.common</groupId>
                <artifactId>hibernate-commons-annotations</artifactId>
                <version>4.0.2.Final</version>
            </dependency>
            <dependency>
                <groupId>dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>1.6.1</version>
            </dependency>
            <dependency>
                <groupId>antlr</groupId>
                <artifactId>antlr</artifactId>
                <version>2.7.7</version>
            </dependency>
      • 添加 hibernate 的配置文件:hibernate.cfg.xml

        <?xml version='1.0' encoding='utf-8'?>
        <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
        <hibernate-configuration>
          <session-factory>
            <!-- 配置 hibernate 的基本信息 -->
            <!-- 1. 数据源需配置到 IOC 容器中,所以在此处不需要再配置数据源 -->
            <!-- 2. 关联的 .hbm.xml 也在 IOC 容器配置 SessionFactory 实例时再进行配置 -->
            <!-- 3. 配置 hibernate 的基本属性:方言,SQL 显示及格式化,生成数据表的策略以及二级缓存等 -->
            <property name="hibernate.dialect"> org.hibernate.dialect.MySQL5InnoDBDialect </property>
        
            <property name="hibernate.show_sql"> true </property>
            <property name="hibernate.format_sql"> true </property>
        
            <property name="hibernate.hbm2ddl.auto"> update </property>
        
            <!-- 配置 hibernate 二级缓存相关的属性 -->
        
            
          </session-factory>
        </hibernate-configuration>
  • 加入 Spring
  • 整合

Spring 对 JDBC 的支持

  1. 创建资源文件 db.properties
    jdbc.user=root
    jdbc.password=jie13727507037
    jdbc.driverClass=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql:///spring4?serverTimezone=UTC
    
    jdbc.initPoolSize=5
    jdbc.maxPoolSize=10
  2. 在 xml 文件中导入资源文件
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    
        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>
  3. 配置 JdbcTemplate
    <!-- 配置 Spring 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  4. java 代码
    package com.spring.jdbc;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class JDBCTest {
    
        private ApplicationContext app = null;
        private JdbcTemplate jdbcTemplate;
        private EmployeeDao employeeDao;
        private DepartmentDao departmentDao;
    
        {
            app = new ClassPathXmlApplicationContext("applicationContext.xml");
            jdbcTemplate = (JdbcTemplate) app.getBean("jdbcTemplate");
            employeeDao = (EmployeeDao) app.getBean("employeeDao");
            departmentDao = (DepartmentDao) app.getBean("departmentDao");
        }
    
        @Test
        public void testDepartmentDao() {
            System.out.println(departmentDao.get(2));
        }
    
        @Test
        public void testEmployeeDao() {
            System.out.println(employeeDao.get(1));
        }
    
        /**
         * 获取单个列的值,或做统计查询
         * 使用 queryForObject(String sql, Class<Long> requiredType) 方法
         */
        @Test
        public void testQueryForObject2() {
            String sql = "SELECT count(id) FROM employees";
            long count = jdbcTemplate.queryForObject(sql, Long.class);
    
            System.out.println(count);
        }
    
        /**
         * 查到实体类的集合
         * 注意调用的不是 queryForList 方法
         */
        @Test
        public void testQueryForList() {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5);
    
            System.out.println(employees);
        }
    
        /**
         * 从数据库中获取一条记录,实际得到对应的一个对象
         * 注意不是调用 queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法
         * 而需要调用 queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
         * 1.其中的 RowMapper 指定如何去映射结果集的行,常用的实现类为 BeanPropertyRowMapper
         * 2.使用 SQL 中的别名完成列名和类的属性名的映射,例如 last_name laseName
         * 3.不支持级联属性,JdbcTemplate 到底是一个 JDBC 的小工具,而不是 ORM 框架
         */
        @Test
        public void testQueryForObject() {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
    
            System.out.println(employee);
        }
    
        /**
         * 执行批量更新:批量 INSERT,UPDATE,DELETE
         * 最后一个参数是 Object[] 的 List 类型:因为修改一条记录需要一个 Object 的数组,那么多条就需要多个 Object 的数组
         */
        @Test
        public void testBatchUpdate() {
            String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
            List<Object[]> batchArgs = new ArrayList<Object[]>();
    
            batchArgs.add(new Object[]{"AA", "aa@123.com", 1});
            batchArgs.add(new Object[]{"BB", "bb@123.com", 2});
            batchArgs.add(new Object[]{"CC", "cc@123.com", 3});
            batchArgs.add(new Object[]{"DD", "cc@123.com", 3});
            batchArgs.add(new Object[]{"EE", "ee@123.com", 2});
    
            jdbcTemplate.batchUpdate(sql, batchArgs);
        }
    
        /**
         * 执行 INSERT,UPDATE,DELETE
         */
        @Test
        public void testUpdate() {
            String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
            jdbcTemplate.update(sql, "Jack", 5);
        }
    
        @Test
        public void testDataSource() throws SQLException {
    
            DataSource dataSource = (DataSource) app.getBean("dataSource");
            System.out.println(dataSource.getConnection());
    
        }
    
    }
    
    package com.spring.jdbc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    import org.springframework.stereotype.Repository;
    
    import javax.sql.DataSource;
    
    /**
     * 不推荐使用 JdbcDaoSupport,而推荐使用 JdbcTemplate 作为 Dao 类的成员变量
     */
    
    @Repository
    public class DepartmentDao extends JdbcDaoSupport {
    
        @Autowired
        public void setDataSource2(DataSource dataSource) {
            setDataSource(dataSource);
        }
    
        public Department get(Integer id) {
            String sql = "SELECT id, dept_name name FROM departments WHERE id = ?";
            RowMapper<Department> rowMapper = new BeanPropertyRowMapper<>(Department.class);
            Department department = getJdbcTemplate().queryForObject(sql, rowMapper, id);
    
            return department;
        }
    
    }
    
    package com.spring.jdbc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class EmployeeDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public Employee get(Integer id) {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
    
            return employee;
        }
    }
    
  5. 在 JDBC 模板中使用具名参数
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate = (NamedParameterJdbcTemplate) app.getBean("namedParameterJdbcTemplate");
    /**
     * 可以为参数起名字,
     * 1.好处:若有多个参数则不用再去对应位置,直接对应参数名,便于维护
     * 2.缺点:较为麻烦
     */
    @Test
    public void testNamedParameterJdbcTemplate() {
        String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln, :email, :deptid)";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("ln", "FF");
        paramMap.put("email", "ff@123.com");
        paramMap.put("deptid", 2);
        namedParameterJdbcTemplate.update(sql, paramMap);
    }
    /**
     * 使用具名参数时,可以使用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作
     * 1. sql 语句中的参数名和类的属性一致
     * 2.使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数
     */
    @Test
    public void testNamedParameterJdbcTemplate2() {
        String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:lastName, :email, :deptId)";
    
        Employee employee = new Employee();
        employee.setLastName("XZJ");
        employee.setEmail("xzj@95.com");
        employee.setDeptId(1);
    
        SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(employee);
    
        namedParameterJdbcTemplate.update(sql, parameterSource);
    }

Spring AOP (面向切面编程)

1、AOP 前奏
package com.spring.aop.helloworld;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    public int add(int i, int j) {
        System.out.println("The method add begins with[" + i + "," + j + "]");
        int result = i + j;
        System.out.println("The method add ends with " + result);
        return result;
    }

    public int sub(int i, int j) {
        System.out.println("The method sub begins with[" + i + "," + j + "]");
        int result = i - j;
        System.out.println("The method sub ends with " + result);
        return result;
    }

    public int mul(int i, int j) {
        System.out.println("The method mul begins with[" + i + "," + j + "]");
        int result = i * j;
        System.out.println("The method mul ends with " + result);
        return result;
    }

    public int div(int i, int j) {
        System.out.println("The method div begins with[" + i + "," + j + "]");
        int result = i / j;
        System.out.println("The method div ends with " + result);
        return result;
    }

}

问题:

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
  • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
2、使用动态代理解决上述问题
  • 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始的对象上。

    public class ArithmeticCalculatorLoggingProxy {
    
        //要代理的对象
        private ArithmeticCalculator target;
    
        public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
            this.target = target;
        }
    
        public ArithmeticCalculator getLoggingProxy() {
            ArithmeticCalculator proxy = null;
    
            //代理对象由哪一个类加载器负责加载
            ClassLoader loader = target.getClass().getClassLoader();
            //代理对象的类型,即其中有哪些方法
            Class [] interfaces = new Class[]{ArithmeticCalculator.class};
            //当调用代理对象其中的方法时,该执行的代码
            InvocationHandler h = new InvocationHandler() {
                /**
                 * @param proxy:正在返回的那个代理对象,一般情况下,在 invoke 方法中都不使用该对象
                 * @param method:正在被调用的方法
                 * @param args:调用方法时传入的参数
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                    String methodName = method.getName();
                    //日志
                    System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
                    //执行方法
                    Object result = method.invoke(target, args);
                    //日志
                    System.out.println("The method " + methodName + " ends with " + result);
                    return result;
                }
            };
    
            proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
    
            return proxy;
        }
    
    }
    
    
    
    public class Main {
    
        public static void main(String[] args) {
    
            //ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorLoggingImpl();
    //        arithmeticCalculator = new ArithmeticCalculatorImpl();
    
            ArithmeticCalculator target = new ArithmeticCalculatorImpl();
            ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
    
            int result = proxy.add(1, 2);
            System.out.println("-->" + result);
            result = proxy.div(4, 2);
            System.out.println("-->" + result);
    
        }
    
    }
3、AOP 简介
  • AOP (Aspect-OrientedProgramming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-OrientedProgramming,面向对象编程)的补充。
  • AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点
  • 在应用 AOP 编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
  • AOP 的好处:

    • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
    • 业务模块更简洁,只包含核心业务代码。
  • image-20220216005157686
4、AOP 术语
  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  • 通知(Advice):切面必须要完成的工作
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmeticCalculator#add() 方法执行前的连接点,执行点为 ArithmeticCalculator#add();方位为该方法执行前的位置。
  • 切点(pointcut):每个类都拥有多个连接点;例如:ArithmeticCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
5、在 Spring 中启用 AspectJ 注解支持
  • 要在 Spring 应用中使用 Aspect 注解,必须在 classpath 下包含 AspectJ 类库:aopalliance.jar、aspectj.weaver,jar 和 spring-aspects.jar
  • 将 aopSchema 添加到 <beans> 根元素中。
  • 要在 Spring IOC 容器中启用 AspectJ注解支持,只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy> 
  • 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时,会自动为与 AspectJ 切面匹配的 Bean 创建代理。
6、使用 SpringAOP 步骤
  1. 加入 jar 包:

    <dependencies>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
  2. 在配置文件中加入 aop 的命名空间

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  3. 基于注解的方式

    • 在配置文件中加入如下配置

      <!-- 使 AspectJ 注解起作用:自动为匹配的类生成代理对象-->
      <aop:aspectj-autoproxy/>
    • 把横切关注点的代码抽象到切面的类中。

      • 切面首先是一个 IOC 中的 bean ,即加入 @Component 注解
      • 切面还需要加入 @Aspect 注解
      • //把这个类声明为一个切面:需要把该类放入到 IOC 容器中、再声明为一个切面
        @Aspect
        @Component
        public class LoggingAspect {
        
            //声明该方法是一个前置通知:在目标方法开始之前执行
            @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int, int))")
            public void beforeMethod(JoinPoint joinPoint) {
                String methodName = joinPoint.getSignature().getName();
                List<Object> args = Arrays.asList(joinPoint.getArgs());
        
                System.out.println("The method " + methodName + " begins with " + args);
            }
        
    • 在类中声明各种通知

      AspectJ 支持 5 种类型的通知注解:

      • @Before:前置通知,在方法执行之前执行
      • @After:后置通知,在方法执行之后执行
      • @AfterRunning:返回通知,在方法返回结果之后执行

        @AfterThrowing:异常通知,在方法抛出异常之后执行

      • @Around:环绕通知,围绕着方法执行
    • 可以在通知方法中声明一个类型为 JoinPoint 的参数,然后就能访问链接细节,如方法名称和参数值。

      //声明该方法是一个前置通知:在目标方法开始之前执行
      @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int, int))")
      public void beforeMethod(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          List<Object> args = Arrays.asList(joinPoint.getArgs());
      
          System.out.println("The method " + methodName + " begins with " + args);
    
    ##### 7、AspectJ 支持 5 种类型的通知注解:
  4. @Before:前置通知,在方法执行之前执行

    /**
    * 在 com.spring.aop.ArithmeticCalculator 接口的每一个实现类的每一个方法开始之前执行一段代码
    * @param joinPoint
    */
    @Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
     String methodName = joinPoint.getSignature().getName();
     List<Object> args = Arrays.asList(joinPoint.getArgs());
    
     System.out.println("The method " + methodName + " begins with " + args);
    }
  5. @After:后置通知,在方法执行之后执行,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止。

    /**
    * 在方法执行之后执行的代码,无论该方法是否出现异常
    * @param joinPoint
    */
    @After("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " ends.");
    }
  6. @AfterRunning:返回通知,在方法返回结果之后执行

    /**
    * 在方法正常结束后执行的代码
    * 返回通知是可以访问到方法的返回值的
    */
    @AfterReturning(value = "execution(public int com.spring.aop.ArithmeticCalculator.*(..))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " ends with " + result);
    }
  7. @AfterThrowing:异常通知,在方法抛出异常之后执行

    /**
    * 在目标方法出现异常时会执行的代码
    * 可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
    * @param joinPoint
    * @param ex
    */
    @AfterThrowing(value = "execution(public int com.spring.aop.ArithmeticCalculator.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " occurs exception: " + ex);
    }
  8. @Around:环绕通知,围绕着方法执行

    /**
    * 环绕通知需要携带 ProceedingJoinPoint 类型的参数
    * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 这个类型的参数可以决定是否执行目标方法
    * 且环绕通知必须有返回值,返回值即为目标方法的返回值
    * @param pjd
    */
    @Around("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd) {
     Object result = null;
     String methodName = pjd.getSignature().getName();
    
     //执行目标方法
     try {
         //前置通知
         System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
         result = pjd.proceed();
         //返回通知
         System.out.println("The method " + methodName + " ends with " + result);
     } catch (Throwable ex) {
         //异常通知
         System.out.println("The method " + methodName + " occurs exception: " + ex);
     }
     //后置通知
     System.out.println("The method " + methodName + " ends.");
    
     return result;
    }
    8、指定切面的优先级
  9. 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。
  10. 切面的优先级可以通过实现 Ordered 接口或者利用 @Order 注解指定。
  11. 实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。
  12. 若使用 @Order 注解,序号出现在注解中。

    /**
    * 可以使用 @Order 注解指定切面的优先级,值越小优先级越高
    */
    
    @Order(1)
    @Aspect
    @Component
    public class VlidationAspect {
    
     @Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
     public void validateArgs(JoinPoint joinPoint) {
         System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));
     }
    
    }
    
    9、重用切点表达式

    使用 @PointCut 来声明切入点表达式。

    后面的其他通知直接使用方法名来引用当前的切入点表达式。

    /**
     * 定义一个方法,用于声明切入点表达式。一般地,该方法不再需要舔入其他代码
     */
    @Pointcut("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void declareJoinPointExpression() {}

@Before("declareJoinPointExpression()")
public void validateArgs(JoinPoint joinPoint) {

System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));

}


##### 10、基于配置文件的方法来配置 AOP

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"

   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 配置 bean -->
<bean id="arithmeticCalculator" class="com.spring.xml.ArithmeticCalculatorImpl"/>

<!-- 配置切面的 bean -->
<bean id="loggingAspect" class="com.spring.xml.LoggingAspect"/>
<bean id="vlidationAspect" class="com.spring.xml.VlidationAspect"/>

<!-- 配置 AOP -->
<aop:config>
    <!-- 配置切点表达式 -->
    <aop:pointcut id="pointcut" expression="execution(public int com.spring.xml.ArithmeticCalculator.*(..))"/>
    <!-- 配置切面及通知 -->
    <aop:aspect ref="loggingAspect" order="2">
        <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
        <aop:after method="afterMethod" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
        <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
    </aop:aspect>

    <aop:aspect ref="vlidationAspect" order="1">
        <aop:before method="validateArgs" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

前言

在我们日常的程序管理中

数据库是避免不了要解除的

而一般都方法都是使用命令行进行操作

当然也有一些拥有图形化界面的第三方软件

比如大名鼎鼎的`Navicat Premium 15

Navicat Premium 是一套多连接数据库开发工具,让你在单一应用程序中同时连接多达七种数据库:MySQLMariaDBMongoDBSQL ServerSQLiteOraclePostgreSQL,可一次快速方便地访问所有数据库。

问题

但是正版的Navicat Premium 15的价格也是不菲

所以秉着以学习为目的的原则

盘它!

教程

一、准备工作

[hide]

点击下载

[/hide]

在评论区评论留言,博主将直接下载链接奉上

二、开始安装

1、安装

  • 打开安装程序的“.exe”文件。
  • 在欢迎画面点击“下一步”。

  • 请阅读许可协议。接受并点击“下一步”。

  • 接受安装位置点击“下一步”。如果你想选择另一个文件夹,请点击“浏览”。

  • 选择开始目录点击“下一步”。

  • 添加桌面图标(根据个人需求),点击“下一步”。

  • 点击安装

  • 点击完成

  • 如有其它问题可查看官方的安装指南

注意!

此时千万不要运行已经安装好的软件!

应该直接查看下一步!

2、激活

  • 打开解压好的激活工具,双击运行

  • 点击右上角的Patch按钮选择软件安装目录下的navicat.exe文件,然后点击打开

  • 弹出这个窗口即为导入成功,点击确定即可

  • 然后点击Generate获取"2"的注册码

  • 回到桌面,打开刚刚安装的Navicat

  • 点击注册

  • 然后把密钥复制过去

  • 点击手动激活

  • 复制请求码 > Generate > 粘贴生成的激活码 > 激活

  • 激活成功

3、Final

即可正常使用

——转载自:https://kk.hackerjk.top/win/navicat.html

基于注解的方式

1、在 classpath 中扫描组件
  • 组件扫描(component scanning):Spring 能够从 classpath 下自动扫描,侦测和实例化具有特定注解的组件。
  • 特定组件包括:

    • @Component:基本注解,标识了一个受 Spring 管理的组件
    • @Respository:标识持久层组件
    • @Service:标识服务层(业务层)组件
    • @Controller:标识表现层组件
  • 对于扫描到的组件, Spring 有默认的命名策略:使用非限定类名,第一个字母小写;也可以在注解中通过 value 属性值标识组件的名称。
  • 当在组件类上使用了特定的注解之后,还需要在 Spring 的配置文件中声明 <context:component-scan> :

    • base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类。
    • 当需要扫描多个包时,可以使用逗号分隔。
    • 如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:

      <context:component-scan
          base-package="com.spring.beans"
          resource-pattern="autowire/*.class"/>
    • <context:include-filter> 子节点表示要包含的目标类
    • <context:exclude-filter> 子节点表示要排除在外的目标类
    • <context:component-scan> 下可以拥有若干个<context:include-filter> 和 <context:exclude-filter> 子节点。
    • context:include-filter过滤器有五种type:

      • annotation-指定扫描使用某个注解的类
      • assignable-指定扫描某个接口派生出来的类
  • 代码:

    java 代码:

    @Component
    public class TestObject {
    
    }
    
    public interface UserRepository {
    
        void save();
    
    }
    
    @Repository("userRepository")
    public class UserRepositoryImpl implements UserRepository {
        @Override
        public void save() {
            System.out.println("UserRepository save ...");
        }
    }
    
    @Service
    public class UserService {
    
        public void add() {
            System.out.println("UserService add...");
        }
    
    }
    
    @Controller
    public class UserController {
    
        public void execute() {
            System.out.println("UserController execute...");
        }
    
    }
    
    public class Main11 {
    
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml");
    
            TestObject to = (TestObject) ctx.getBean("testObject");
            System.out.println(to);
    
            UserController userController = (UserController) ctx.getBean("userController");
            System.out.println(userController);
    
            UserService userService = (UserService) ctx.getBean("userService");
            System.out.println(userService);
    
            UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository");
            System.out.println(userRepository);
    
        }
    
    }

    xml 代码:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 指定 Spring IOC 容器扫描包 -->
        <!-- 可以通过 resource-pattern 指定扫描的资源 -->
        <!--
        <context:component-scan
                base-package="com.spring.beans.annotation"
                resource-pattern="service/*.class"/>
        -->
    
        <!-- context:exclude-filter 子节点指定排除哪些指定表达式的组件 -->
        <!-- context:include-filter 子节点指定包含哪些表达式的组件,该子节点需要 use-default-filters 配合使用 -->
        <context:component-scan
                base-package="com.spring.beans.annotation"
                use-default-filters="true">
            <!--
            <context:exclude-filter type="annotation"
                                    expression="org.springframework.stereotype.Repository"/>
            -->
            <!--
            <context:include-filter type="annotation"
                                    expression="org.springframework.stereotype.Repository"/>
            -->
            <!--
            <context:exclude-filter type="assignable"
                                    expression="com.spring.beans.annotation.repository.UserRepository"/>
            -->
            <context:include-filter type="assignable"
                                    expression="com.spring.beans.annotation.repository.UserRepository"/>
        </context:component-scan>
    
    </beans>
2、组件装配

​ <context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例,该实例可以自动装配具有 @Autowired 和 @Resource、@Inject 注解的属性。

  • 使用 @Autowired 注解自动装配具有兼容类型的单个 Bean 属性

    • 构造器,普通字段(即使是非 public ),一切具有参数的方法都可以应用 @Autowired 注解
    • 默认情况下,所有使用 @Autowired 注解的属性都需要被设置。当 Spring 找不到匹配的 Bean 装配属性时,会抛出异常,若某一属性允许不被设置,可以设置 @Autowired 注解的 required 属性为 false

      @Repository("userRepository")
      public class UserRepositoryImpl implements UserRepository {
      
          @Autowired(required=false)
          private TestObject testObject;
      
          @Override
          public void save() {
              System.out.println("UserRepository save ...");
              System.out.println(testObject);
          }
      
      }
    • 默认情况下,当 IOC 容器里存在多个类型兼容的 Bean 时,通过类型的自动装配将无法工作,此时可以在 @Qualifier 注解里提供 Bean 的名称。Spring 允许对方法的入参标注 @Qualifier 已指定注入 Bean 的名称 ,也可以在声明 bean 的时候 bean 的名字跟这个属性的字段名相一致

      @Service
      public class UserService {
      
          private UserRepository userRepository;
      
      //    @Autowired
      //    @Qualifier("userRepository")
      //    public void setUserRepository(UserRepository userRepository) {
      //        this.userRepository = userRepository;
      //    }
      
          @Autowired
          public void setUserRepository(@Qualifier("userRepository") UserRepository userRepository) {
              this.userRepository = userRepository;
          }
      
          public void add() {
              System.out.println("UserService add...");
              userRepository.save();
          }
      
      }
    • @Autowired 注解也可以应用在数据类型的属性上,此时 Spring 将会把所有匹配的 Bean 进行自动装配。
    • @Autowired 注解也可以应用在集合属性上,此时 Spring 读取该集合的类型信息,然后自动装配所有与之兼容的 Bean。
    • @Autowired 注解用在 java.util.Map 上时,若该 Map 的键值为 String,那么 Spring 将自动装配与之 Map 值类型兼容的 Bean,此时 Bean 的名称作为键值。
  • 使用 @Resource 或 @Inject 自动装配 Bean

    • Spring 还支持 @Resource 和 @Inject 注解,这两个注解和 @Autowired 注解的功用类似
    • @Resource 注解需要提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称
    • @Inject 和 @Autowired 注解一样也是按类型匹配注入的 Bean,但没有 required 属性
    • 建议使用 @Autowired 注解

泛型依赖注入

image-20220209233634373