基于Spring Boot应用@FunctionalInterface注解

本文详细介绍了Java @FunctionalInterface注解的作用、定义及如何在实际中创建和应用函数式接口,涵盖了无参和带参接口实例,以及其在SpringBoot中的应用。重点讲解了函数式接口的特点和在Lambda表达式、方法引用中的使用。

记录:291

场景:为了理解和应用@FunctionalInterface注解。@FunctionalInterface是Java语言规范定义函数式接口的注解。它作用在只有一个抽象方法的接口。默认方法(default定义的方法)、静态方法、覆盖Object的方法(比如:boolean equals(Object obj))不计入抽象方法中。

版本:

JDK 1.8
Spring Boot 2.6.3

一、理解函数式接口

(1)@FunctionalInterface是Java语言规范定义函数式接口的注解。

(2)@FunctionalInterface作用在只有一个抽象方法的接口上。不能作用在annotation type, enum,class上。

(3)默认方法(default定义的方法)、静态方法、覆盖Object的方法(比如:boolean equals(Object obj))不算抽象方法。

(4)创建函数式接口实例方式,包括lambda表达式、方法引用、构造函数引用。

二、@FunctionalInterface的定义

@FunctionalInterface在jdk中位置:java.lang.FunctionalInterface。

jdk中的定义:

// Since:1.8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

三、应用@FunctionalInterface

在Java中的接口interface可以没有方法、只有一个方法、有多个方法等场景。

使用@FunctionalInterface注解到interface上,那么接口必须有且只能有一个抽象方法。如果没有一个抽象方法、或者有多个抽象方法情况下,编译器会提示报错。

1.函数式接口(抽象方法无入参)

1.1定义函数式接口Executable01

Executable01只有一个抽象方法且无入参。

@FunctionalInterface
public interface Executable01 {
    void execute();
}

1.2应用函数式接口Executable01

在main函数中应用Executable01。

(1)使用匿名类方式调用接口。

(2)使用lambda表达式调用接口。

(3)实现Executable01接口,使用构造函数创建对象调用接口。

public class FunctionalPractice01 {
  public static void main(String[] args) {
  
    // 1. 使用匿名类方式调用接口
    f1(new Executable01() {
        @Override
        public void execute() {
            System.out.println("使用匿名类调用函数式接口.");
        }
    });
	
    // 2. 使用lambda表达式调用接口
    f1(() -> {
        System.out.println("使用lambda表达式调用函数式接口.");
    });
	
    // 3. 实现Executable01接口,使用构造函数创建对象调用接口
    f1(new Executable01Impl());
  }
  
  public static void f1(Executable01 exe01) {
    System.out.println("f1开始...");
    exe01.execute();
    System.out.println("f1结束...");
  }
  
  public static class Executable01Impl implements Executable01 {
    @Override
    public void execute() {
        System.out.println("实现Executable01接口,使用构造函数创建对象调用接口.");
    }
  }
}

2.函数式接口(抽象方法有入参)

2.1定义函数式接口Executable02

Executable02只有一个抽象方法且有入参。

@FunctionalInterface
public interface Executable02 {
  void execute(String cityName);
}

2.2应用函数式接口Executable02

在main函数中应用Executable02。

(1)使用匿名类方式调用接口。

(2)使用lambda表达式调用接口。

(3)实现Executable02接口,使用构造函数创建对象调用接口。

public class FunctionalPractice02 {
  public static void main(String[] args) {
  
    // 1. 使用匿名类方式调用接口
    f1(new Executable02() {
      @Override
      public void execute(String cityName) {
        System.out.println("使用匿名类调用函数式接口.");
        System.out.println("接收入参,城市名称: " + cityName);
      }
    }, "杭州");
  
    // 2. 使用lambda表达式调用接口(cityName,代表抽象方法的入参)
    f1((cityName) -> {
      System.out.println("使用lambda表达式调用函数式接口.");
      System.out.println("接收入参,城市名称: " + cityName);
    }, "北京");
  
    // 3. 实现Executable02接口,使用构造函数创建对象调用接口
    f1(new FunctionalPractice02.Executable02Impl(), "厦门");
  
  }
  
  public static void f1(Executable02 exe02, String cityName) {
    System.out.println("f1开始...");
    exe02.execute(cityName);
    System.out.println("f1结束...");
  }
  
  public static class Executable02Impl implements Executable02 {
  
   @Override
   public void execute(String cityName) {
    System.out.println("实现Executable01接口,使用构造函数创建对象调用接口.");
    System.out.println("接收入参,城市名称: " + cityName);
  
   }
  }
}

3.函数式接口包括多个方法

函数式接口包括多个方法,使用@FunctionalInterface后,抽象方法只能有一个。

3.1定义函数式接口Executable03

Executable03包含多个方法。

(1)抽象方法。

(2)默认方法。

(3)静态方法。

(4)equals是重写了Object的equals,不计入抽象方法。

@FunctionalInterface
public interface Executable03 {
  // 1.抽象方法,且只有一个
  void execute();
  // 2.默认方法
  default void execute01(){
      System.out.println("函数式接口的默认(default)方法.");
  }
  // 3.静态方法
  static void execute02(){
      System.out.println("函数式接口的静态(static)方法.");
  }
  // 4. equals是重写了Object的equals,不计入抽象方法
  boolean equals(Object obj);
}

3.2应用函数式接口Executable03

在main函数中应用Executable03。

(1)使用lambda表达式调用接口

public class FunctionalPractice03 {
  public static void main(String[] args) {
    f1(()->{
        System.out.println("函数式接口的抽象(abstract)方法.");
    });
  }
  
  public static void f1(Executable03 exe03) {
    System.out.println("f1开始...");
    // 1.抽象方法
    exe03.execute();
    // 2.默认方法
    exe03.execute01();
    // 3.静态方法
    Executable03.execute02();
    System.out.println("f1结束...");
  }
}

四、小结

1.应用小结

使用@FunctionalInterface注解到Interface中,最大特点就是函数式接口的中这个抽象方法,使用者可以按需定义方法内的具体业务实现。将关注点只需聚焦到这一个抽象方法,其它事情交给Java底层去做。

2.jdk中函数式接口例子

在jdk 1.8中的java.util.function包中定义了很多函数式接口,这是研究@FunctionalInterface注解的很好素材。

以下,随机摘取了几个函数式接口,具体请移步jdk 1.8及其以上版本源码或者操作手册。

java.util.function.BiConsumer
java.util.function.Consumer
java.util.function.Function
java.util.function.DoubleFunction
java.util.function.IntFunction
java.util.function.Predicate

以上,感谢。

2022年8月18日

<think>我们正在讨论Spring Boot中的CommandLineRunner接口。根据用户的问题,我们需要解释这个接口的作用并提供使用示例。 根据引用[1]:CommandLineRunner是Spring Boot提供的一个接口,用于在应用程序启动后执行初始化任务。通过实现run方法,可以执行各种初始化操作,并且可以访问命令行参数。 引用[3]提到:使用CommandLineRunner可以在应用启动后执行一次性的代码逻辑,这些代码可以使用任何已经初始化的依赖。 引用[2]展示了Spring Boot内部如何调用CommandLineRunner:在启动过程中,Spring Boot会收集所有实现了CommandLineRunner(或ApplicationRunner)的bean,按照顺序(通过@Order注解或Ordered接口)排序,然后依次调用它们的run方法。 使用步骤: 1. 创建一个类实现CommandLineRunner接口。 2. 实现run方法,在其中编写启动后要执行的逻辑。 3. 将该类注册为Spring Bean(通常使用@Component注解)。 注意:如果多个CommandLineRunner需要指定顺序,可以使用@Order注解。 示例:创建一个简单的CommandLineRunner,在应用启动后打印信息。 但是,引用[5]提到了一个潜在的问题:如果CommandLineRunner中的代码执行时间很长或者出现异常,可能会影响应用的启动。因此,在实际使用中,我们应确保run方法中的代码是健壮的,并且不会无限期阻塞。 下面我们提供一个使用示例。</think>### Spring Boot CommandLineRunner 接口详解 #### 一、接口作用 CommandLineRunner 是 Spring Boot 提供的特殊接口,主要作用包括: 1. **启动初始化**:在应用*完全启动后*执行一次性初始化任务[^1][^3] 2. **命令行参数处理**:可直接访问启动时传递的命令行参数(`String... args`)[^1][^4] 3. **依赖安全使用**:确保所有 Spring Bean 已初始化完成,可安全使用依赖注入的组件[^3] 4. **批处理触发**:常用于触发批处理任务、数据预加载等场景[^3] #### 二、使用示例 ##### 基础实现 ```java import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class InitRunner implements CommandLineRunner { @Override public void run(String... args) { System.out.println("应用已启动!命令行参数:"); for (String arg : args) { System.out.println("▶ " + arg); } // 执行初始化逻辑(如加载配置、预热缓存等) initDatabaseConnection(); } private void initDatabaseConnection() { System.out.println("初始化数据库连接..."); } } ``` ##### 带顺序控制的实现 当存在多个 Runner 时,使用 `@Order` 控制执行顺序: ```java import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Order(1) // 数值越小优先级越高 @Component public class PrimaryRunner implements CommandLineRunner { @Override public void run(String... args) { System.out.println("最先执行的核心初始化"); } } @Order(2) @Component class SecondaryRunner implements CommandLineRunner { @Override public void run(String... args) { System.out.println("次级初始化任务"); } } ``` #### 三、注意事项 1. **阻塞风险**: ```java @Component public class RiskRunner implements CommandLineRunner { @Override public void run(String... args) { while(true) { // 错误示例:会阻塞主线程 // ... } } } ``` 长时间运行的任务需使用异步线程[^5] 2. **异常处理**: ```java @Override public void run(String... args) { try { // 业务代码 } catch (Exception ex) { throw new IllegalStateException("初始化失败", ex); // 会终止应用启动 } } ``` 未捕获的异常会导致应用启动失败[^5] 3. **与 ApplicationRunner 区别**: - `CommandLineRunner` 接收原始字符串参数:`run(String... args)` - `ApplicationRunner` 接收封装参数:`run(ApplicationArguments args)`,支持解析 `--option=value` 格式 #### 四、执行原理 Spring Boot 启动过程中会执行: 1. 扫描所有实现 `CommandLineRunner` 的 Bean 2. 按 `@Order` 或 `Ordered` 接口排序 3. 依次调用 `run()` 方法[^2] ```java // Spring Boot 内部调用逻辑 private void callRunners(...) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); // 排序 for (Object runner : runners) { ((CommandLineRunner)runner).run(args); // 执行 } } ``` #### 五、典型应用场景 1. 数据库脚本执行 2. 缓存预热 3. 外部服务连接测试 4. 批量数据处理任务[^3] 5. 发送启动通知(邮件/消息) > 最佳实践:轻量级初始化操作建议在 `CommandLineRunner` 中完成,耗时任务建议使用 Spring Batch 或异步线程池[^3][^5] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值