SpringBoot 自动装配原理 & 自定义一个 starter

目录

  • [1、pom.xml 文件](#1、pom.xml 文件)
    • [1.1、parent 模块](#1.1、parent 模块)
      • 1.1.1、资源文件
        • [1.1.1.1、`resources` 标签说明](#1.1.1.1、resources 标签说明)
        • [1.1.1.2、从 Maven 视角:资源处理全流程​](#1.1.1.2、从 Maven 视角:资源处理全流程)
      • 1.1.2、插件
    • [1.2、dependencies 模块](#1.2、dependencies 模块)
  • 2、启动器
  • 3、主程序
    • [3.1、`@SpringBootApplication` 注解](#3.1、@SpringBootApplication 注解)
    • [3.2、`@SpringBootConfiguration` 注解](#3.2、@SpringBootConfiguration 注解)
      • [3.2.1、`@Configuration` 和 `@Component` 区别](#3.2.1、@Configuration@Component 区别)
    • [3.3、`@EnableAutoConfiguration` 注解](#3.3、@EnableAutoConfiguration 注解)
      • [3.3.1、`@AutoConfigurationPackage` 注解](#3.3.1、@AutoConfigurationPackage 注解)
        • [3.3.1.1、`AutoConfigurationPackages.Registrar#registerBeanDefinitions()` 方法:](#registerBeanDefinitions()` 方法:)
      • [3.3.2、`@Import` 注解](#3.3.2、@Import 注解)
      • 3.3.3、`AutoConfigurationImportSelector`
    • 3.4、自动装配原理
  • [4、自定义一个 starter](#4、自定义一个 starter)
    • 4.1、基础版
      • [4.1.1、创建一个 SpringBoot 工程](#4.1.1、创建一个 SpringBoot 工程)
      • [4.1.2、新建一个 Properteis 类](#4.1.2、新建一个 Properteis 类)
      • [4.1.3、新建一个自动配置类,关联 Properties 类](#4.1.3、新建一个自动配置类,关联 Properties 类)
      • 4.1.4、添加一个对外服务接口
      • 4.1.5、注册自动配置类
      • [4.1.6、`mvn install`](#4.1.6、mvn install)
      • [4.1.7、引用 starter](#4.1.7、引用 starter)
        • [4.1.7.1、pom.xml 中引入 starter](#4.1.7.1、pom.xml 中引入 starter)
        • [4.1.7.2、使用 starter](#4.1.7.2、使用 starter)
    • 4.2、引入条件装配
      • [4.2.1、修改配置类 `ReadingAutoConfiguration`](#4.2.1、修改配置类 ReadingAutoConfiguration)
      • [4.2.2、修改使用 starter](#4.2.2、修改使用 starter)
    • [4.3、重写 starter 的默认配置](#4.3、重写 starter 的默认配置)
    • [4.4、重写 starter 默认实现](#4.4、重写 starter 默认实现)
    • [4.5、实现一个自己的 `@EnableXXX`](#4.5、实现一个自己的 @EnableXXX)
      • [4.5.1、在 starter 中添加一个 selector](#4.5.1、在 starter 中添加一个 selector)
      • [4.5.2、在 starter 中删除 `resources/META-INF/spring.factories` 文件](#4.5.2、在 starter 中删除 resources/META-INF/spring.factories 文件)
      • [4.5.3、在 starter 中添加一个注解 `@EnableReading`](#4.5.3、在 starter 中添加一个注解 @EnableReading)
      • 4.5.4、在当前项目中做出修改

1、pom.xml 文件

创建一个 SpringBoot 工程,查看 pom.xml 文件,当前工程是依赖 parent 模块。如下图:

1.1、parent 模块

parent 模块主要加载配置文件【yml】和插件。如下图:

parent 模块又依赖 dependencies 模块

1.1.1、资源文件

src/main/resources 目录下的 yml/yaml/properties 文件进行占位符替换【第一个 resource:启用过滤】,其它文件不进行处理【第二个 resource:防止其它文件误被修改】,最终都会打包

1.1.1.1、resources 标签说明

Maven 处理 <resources> 时采用​​顺序合并策略 ​​,每个 <resource> 块定义不同的过滤规则:

  • 第一个资源块(精准过滤)
    • 处理范围:仅 application*.yml/yaml/properties
    • 操作:开启过滤(filtering=true
    • 目标:动态替换这些配置文件中的 @variable@ 占位符
  • 第二个资源块(全量保护)
    • 处理范围:排除上述配置文件后的​​其他所有资源​
    • 操作:默认关闭过滤(filtering=false
    • 目标:原样复制静态资源(如图片、HTML、XML 等)

处理流程如下图:

1.1.1.2、从 Maven 视角:资源处理全流程​

如下图:

关键结论:

  1. 所有资源文件都会被打包​​,无论是否经过过滤
  2. 过滤(filtering)只是对文件内容的处理,与是否打包无关
  3. 两个资源块共同作用确保:
    • 配置文件:动态内容替换
    • 其他资源:保持原始状态

1.1.2、插件

1.2、dependencies 模块

dependencies 模块主要管理了各种依赖的版本。所以,在 SpringBoot 工程中导入依赖时,不需要指定版本,已有默认的版本。如下图:

2、启动器

启动器:SpringBoot 启动的场景。如:spring-boot-starter-web,会帮我们自动导入 web 环境的所有的依赖:

xml 复制代码
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

SpringBoot 将所有的功能场景变成一个个启动器。当我们需要使用什么功能时,就只需要导入对应的启动器即可。

3、主程序

java 复制代码
@SpringBootApplication
public class MybootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybootApplication.class, args);
    }

}
  1. @SpringBootApplication:标注这个类是一个 SpringBoot 应用
  2. SpringApplication.run() 方法启动 SpringBoot 应用

3.1、@SpringBootApplication 注解

@SpringBootApplication 注解是一个复合注解

java 复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
public @interface SpringBootApplication {
	//...
}

3.2、@SpringBootConfiguration 注解

java 复制代码
// 保证了 bean 的唯一性。@Component 无法保证
@Configuration
public @interface SpringBootConfiguration {
	// 默认使用 CGLIB 代理该类
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

@SpringBootConfiguration 注解相当于 @Configuration 注解

3.2.1、@Configuration@Component 区别

  • @Component 在 Spring 中是代表 LITE 模式的配置注解,这种模式下的注解不会被 Spring 所代理,就是一个标准类,如果在这个类中有 @Bean 标注的方法,那么方法间的相互调用,其实就是普通 Java 类的方法的调用。

  • @Configuration 在 Spring 中是代表 FULL 模式的配置注解,这种模式下的类会被 Spring 所代理,那么在这个类中的 @Bean 方法的相互调用,就相当于调用了代理方法,那么在代理方法中会判断,是否调用 getBean 方法还是 invokeSuper 方法,这里就是这两个注解的最根本的区别。@Configuration 中所有带 @Bean 注解的方法都会被动态代理,且该方法返回的都是同一个实例。且 @Configuration(proxyBeanMethods = false)@Component 作用一样

如下:

①:使用 @Configuration 注解:

java 复制代码
@Configuration
//@Component
public class UserConfig {

    @Bean
    public User user() {
        System.out.println("user..........");
        return new User();
    }

    @Bean
    public Teacher teacher() {
        // 通过代理调用, 返回同一实例
        return new Teacher(user());
    }

}

②:测试

java 复制代码
@GetMapping("/testAuto")
public void testAuto() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(UserConfig.class);
    User configB1 = ctx.getBean(User.class);
    User configB2 = ctx.getBean(UserConfig.class).user();
    // 输出 true
    System.out.println(configB1 == configB2);
	
	ApplicationContext ctx = new AnnotationConfigApplicationContext(UserComponent.class);
    User component1 = ctx.getBean(User.class);
    User component2 = ctx.getBean(UserConfig.class).user();
    // 输出 false
    System.out.println(configB1 == configB2);
}

@Component 类中,若需要依赖其他 @Bean,应通过参数注入而非直接调用方法:

java 复制代码
@Component
public class UserConfig {

    @Bean
    public User user() {
        System.out.println("user..........");
        return new User();
    }

    /*@Bean
    public Teacher teacher() {
        // 通过代理调用, 返回同一实例
        return new Teacher(user());
    }*/

    @Bean
    public Teacher teacher(User user) {
        return new Teacher(user);
    }

}

3.3、@EnableAutoConfiguration 注解

java 复制代码
@AutoConfigurationPackage
// 导入参数类到 IOC 容器中
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
	//...
}

3.3.1、@AutoConfigurationPackage 注解

java 复制代码
// 保存扫描路径,提供给 spring-data-jpa 查询【@Entity】
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
3.3.1.1、AutoConfigurationPackages.Registrar#registerBeanDefinitions() 方法:
java 复制代码
public abstract class AutoConfigurationPackages {

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

    private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
        String[] existing = (String[])((String[])constructorArguments.getIndexedArgumentValue(0, String[].class).getValue());
        Set<String> merged = new LinkedHashSet();
        merged.addAll(Arrays.asList(existing));
        merged.addAll(Arrays.asList(packageNames));
        return StringUtils.toStringArray(merged);
    }

    
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new PackageImport(metadata)).getPackageName());
        }
    }
}

3.3.2、@Import 注解

@Import 注解:SpringBoot 自动装配的核心注解。它有三种用法:

  1. 参数如果是普通类:将该类实例化并交给 IOC 容器管理
  2. 参数如果是 ImportBeanDefinitionRegistrar 的实现类,则支持手工注册 Bean
  3. 参数如果是 ImportSelector 的实现类,注册 selectImports() 方法返回的数组【类全路径】到 IOC 容器 【批量注册】

3.3.3、AutoConfigurationImportSelector

selectImports() 方法:查询导入的组件

java 复制代码
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

getAutoConfigurationEntry() 方法:

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
    	// 获取注解中的属性
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 获取候选的配置
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

getCandidateConfigurations() 方法:获取候选的配置

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

SpringFactoriesLoader#loadFactoryNames() 方法:

java 复制代码
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories() 方法:

java 复制代码
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        Map<String, List<String>> result = new HashMap();

        try {
            Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }

            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

加载 autoconfigure 包下的 META-INF/spring.factories 自动配置类到 IOC 容器中去:

3.4、自动装配原理

SpringBoot 启动的时候会通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有自动配置类,并对其进行加载【但不一定生效,要判断条件是否成立】。只要导入了对应的 starter,就有对应的启动器,自动配置类就会生效,然后就配置成功。

  • 这些自动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个 Java 配置类形式的 Spring 容器配置类,它能通过以 Properties 结尾命名的类中取得在全局配置文件中配置的属性如:server.port
  • XxxxProperties 类是通过 @ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。

AutoConfigurationImportSelector#selectImports() 方法通过 SpringLoaderFactories#loadFactories() 方法扫描所有具有 META-INFO/spring.factories 文件的 jar 包(spring-boot-autoconfigure.jar 就有)。这个spring.factories 文件也是一组一组的 key=value 的形式,其中一个 key 是 EnableAutoConfiguration 类的全类名,而它的 value 是一个 xxxxAutoConfiguration 的类名的列表,这些类名以逗号分隔。在SpringApplication.run(...) 的内部就会执行 selectImports()方法,找到所有 JavaConfig 自动配置类的全限定名对应的 class,然后将所有自动配置类加载到 Spring 容器中

4、自定义一个 starter

4.1、基础版

结构图如下:

4.1.1、创建一个 SpringBoot 工程

创建一个 SpringBoot 工程:read-spring-boot-starter【官方推荐命名:××-spring-boot-starter】,其 pom.xml 如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>

    <groupId>com.zzc</groupId>
    <artifactId>read-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>read-spring-boot-starter</name>
    <description>read-spring-boot-starter</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>

    <dependencies>
        <!--自动配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.1.2、新建一个 Properteis 类

java 复制代码
@Data
@ConfigurationProperties(prefix = "reading")
public class ReadingProperties {

    // 类型
    private String type = "txt";
}

4.1.3、新建一个自动配置类,关联 Properties 类

java 复制代码
@Configuration
@EnableConfigurationProperties(ReadingProperties.class)
public class ReadingAutoConfiguration {

    @Autowired
    private ReadingProperties readingProperties;

 	// 若当前IOC容器中没有ReadingService时,提供一个默认的ReadingService实现
    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(readingProperties);
    }

}

4.1.4、添加一个对外服务接口

java 复制代码
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
public class ReadingServiceImpl implements ReadingService {

    private ReadingProperties readingProperties;

    @Override
    public void reading() {
        log.info("start reading..., type is {}", readingProperties.getType());
    }
}

4.1.5、注册自动配置类

添加文件 resources/META-INF/spring.factories,其内容为:

properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zzc.read.auto.ReadingAutoConfiguration

key 为 EnableAutoConfiguration 类的全限定名,value 为配置类的全限定名

4.1.6、mvn install

执行 mvn install 命令进行打包

4.1.7、引用 starter

4.1.7.1、pom.xml 中引入 starter
xml 复制代码
<dependency>
    <groupId>com.zzc</groupId>
    <artifactId>read-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
4.1.7.2、使用 starter
java 复制代码
@Slf4j
@RestController
public class TestController {

    @Autowired
    private ReadingService readingService;

    @GetMapping("/testAuto")
    public void testAuto() {
        readingService.reading();
    }
}

结果如下:

4.2、引入条件装配

4.2.1、修改配置类 ReadingAutoConfiguration

修改配置类 ReadingAutoConfiguration

java 复制代码
@Configuration
@EnableConfigurationProperties(ReadingProperties.class)
// 当存在reading.enable属性,且其值为true时,才初始化该Bean
@ConditionalOnProperty(name = "reading.enable", havingValue = "true")
public class ReadingAutoConfiguration {

    @Autowired
    private ReadingProperties readingProperties;

 	// 若当前IOC容器中没有ReadingService时,提供一个默认的ReadingService实现
    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(readingProperties);
    }

}

再重新 mvn install

4.2.2、修改使用 starter

yml 文件添加配置:

yml 复制代码
reading:
  enable: true
java 复制代码
@RestController
public class TestController {

    @Autowired(required = false)
    private ReadingService readingService;

    @GetMapping("/testAuto")
    public void testAuto() {
        readingService.reading();
    }
}

4.3、重写 starter 的默认配置

之前创建 starter 时,reading.type 默认配置为 reading.type=txt,想重写默认配置也是很简单的,只需要在当前项目 application.yml 中稍作添加:

yml 复制代码
reading:
  enable: true
  type: json

再次调用,结果如下:

4.4、重写 starter 默认实现

如果觉得 starter 默认的 ReadingService 实现不够好,那么也可以自定义 ReadingService 实现。因为 starter 构造 ReadingService 实现那里加上了 @ConditionalOnMissingBean(ReadingService.class) 。所以,只需要在当前工程自行实现 ReadingService 接口,并将其注册到 SpringIOC 中,则 starter 中默认 ReadingService 实现将不会被初始化。

添加类 MyReadingServiceImpl

java 复制代码
@Slf4j
@Service
public class MyReadingServiceImpl implements ReadingService {

    @Autowired
    private ReadingProperties readingProperties;

    @Override
    public void reading() {
        log.info("My reading start reading..., type is {}", readingProperties.getType());
    }
}

重启项目后,结果如下:

4.5、实现一个自己的 @EnableXXX

application.yml 配置 reading.enable=true 这样的方式让自动装配生效,其实不够优雅。那么我们也可以像别的 starter 那样提供 @EnableXXX@EnableScheduling@EnableAsync@EnableCaching...)注解,然后在 SpringApplication 启动类加上 @EnableXXX,让我们的自动装配生效

4.5.1、在 starter 中添加一个 selector

创建一个 ReadingSelector,用来替换之前的 ReadingAutoConfiguration

java 复制代码
@Configuration
@EnableConfigurationProperties(ReadingProperties.class)
public class ReadingSelector {

    @Autowired
    private ReadingProperties readingProperties;

    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(readingProperties);
    }

}

4.5.2、在 starter 中删除 resources/META-INF/spring.factories 文件

4.5.3、在 starter 中添加一个注解 @EnableReading

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Import: 往Spring IOC中导入一个类
@Import(ReadingSelector.class)
public @interface EnableReading {

}

再重新 mvn install

4.5.4、在当前项目中做出修改

①:修改 yml 文件:

yml 复制代码
reading:
  type: json

②:修改主启动类,添加注解 @EnableReading

java 复制代码
@EnableReading
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

}

③:删除类 MyReadingServiceImpl

④:测试接口

java 复制代码
@RestController
public class TestController {

    @Autowired
    private ReadingService readingService;

    @GetMapping("/testAuto")
    public void testAuto() {
        readingService.reading();
    }
}

结果如下:

相关推荐
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧2 分钟前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
csdn_freak_dd4 分钟前
查看单元测试覆盖率
java·单元测试
爱吃烤鸡翅的酸菜鱼7 分钟前
【SpringMVC】详解cookie,session及实战
java·http·java-ee·intellij-idea
Wyc7240910 分钟前
JDBC:java与数据库连接,Maven,MyBatis
java·开发语言·数据库
老任与码34 分钟前
Spring AI(3)——Chat Memory
java·人工智能·spring ai
贺函不是涵35 分钟前
【沉浸式求职学习day36】【初识Maven】
java·学习·maven
纪元A梦1 小时前
贪心算法应用:顶点覆盖问题详解
java·算法·贪心算法
bing_1582 小时前
Spring MVC 中Model, ModelMap, ModelAndView 之间有什么关系和区别?
java·spring·mvc
268572592 小时前
JVM 监控
java·开发语言·jvm
promise5242 小时前
JVM之jcmd命令详解
java·linux·运维·服务器·jvm·bash·jcmd