SpringBoot 自动配置

一、什么是自动配置?    

        Spring Boot 自动配置是Spring Boot 框架的核心特性之一,它能够根据项目中添加的依赖自动配置Spring应用程序。

        首先我们先来将两个注解:@Condition 和 @Enable

(一)代码简单了解自动配置

<1>不添加 RedisTemplate 的 starter 坐标,观察代码结果可知不会自动注入 RedisTemplate 模板类

<?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.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.apesource</groupId>
    <artifactId>springboot_homework_condition</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_homework_condition</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动类代码:

首先我们新建的项目它的启动类长这样:

package com.apesource.springboot_homework_condition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootHomeworkConditionApplication {

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

}

        这个启动类中本来就有一句代码—— run 方法,我们按住 ctrl 键并且点击 run ,可以进到 run 方法中去,我们可以看到 run 方法是有返回值的,返回值还是一个ConfigurableApplicationContext 对象。 

我们可以打印它的返回值,来取证它是否给我们自动配置。

        启动类中代码就加工了一下:

package com.apesource.springboot_homework_condition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringbootHomeworkConditionApplication {

    public static void main(String[] args) {
        //启动SpringBoot的应用,返回Spring的IOC容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootHomeworkConditionApplication.class, args);
        //获取Bean, redisTemplate<没添加坐标的情况>
        Object redisTemplate = context.getBean("redisTemplate");
        System.out.println(redisTemplate);
    }

}

运行如下:

NoSuchBeanDefinitionException: No bean named 'redisTemplate' 出现找不到的错误,说明没有坐标不会有配置

接下来我们看看加上坐标的情况

<2>添加 RedisTemplate 的 starter 坐标,观察代码结果可知会有自动注入 RedisTemplate 模板类

<?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.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.apesource</groupId>
    <artifactId>springboot_homework_condition</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_homework_condition</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
<!--        添加坐标-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--        -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
//启动类代码
package com.apesource.springboot_condition_01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringbootCondition01Application {

    public static void main(String[] args) {

        //启动SpringBoot的应用,返回Spring的IOC容器
        ConfigurableApplicationContext context =  SpringApplication.run(SpringbootCondition01Application.class, args);
        //获取Bean,redisTemplate
        //情况2 有添加starter坐标,发现有对象
        Object redisTemplate = context.getBean("redisTemplate");
        System.out.println(redisTemplate);
    }

}

 运行结果如下(显示有对象内存地址,说明 RedisTemplate 模板类自动配置成功了):   结论:有坐标就注入模板类, 没有就不注入,它是有选择地注入 

其实这里面所有的条件选择判断都依托于 @Conditional,那我们现在深入了解

(1)Conditionol 注解

        Conditionol 是在 Spring 4.0 增加的条件判断功能,通过这个可以功能实现选择性的创建 Bean 操作。我们通过 RedisTemplate 简单演示代码深入了解。

        首先在 shift+shift 键弹出搜索框(更多的快捷键可以去看初识 IDEA),搜索 Conditional

对该内容的解释
@Target({ElementType.TYPE, ElementType.METHOD}):说明这个注解可以修饰类、修饰方法
@Retention(RetentionPolicy.RUNTIME):说明这个注解运行时生效
@Documented:说明这个注解可以生成 Document 文档
public @interface Conditional {

   /**
    * All {@link Condition} classes that must {@linkplain Condition#matches match}
    * in order for the component to be registered.
    */
   Class<? extends Condition>[] value();

}

它只有一个属性,属性名为 value;属性类型为一个,且继承了 Condition。

然后我们按住 Ctrl 键进去 Condition 看看

发现它有一个 matches 方法,返回 boolean 值( matches 方法两个参数:context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等;metadata:元数据对象,用于获取注解属性。)那就知道了,子继承父,它这个方法也继承了,就得有实现,有 true\false 两返回值,那肯定是 matches 返回 true 就条件成立,注入;返回 false 就条件不成立,不注入。这就是有选择了。

        现在我们用一个实例演示:在 Spring 的 IOC 容器中有一个 User 的 Bean ,要求有选择地加载该 Bean,导入 jedis 坐标后,加载,否则不加载

首先 pom.xml 中导入 jedis 坐标

<!--        jedis坐标-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
<!--        -->

src-main-java-包中 建立一个 domain 包下 User 类,没有任何属性;如果直接在 Uaer 类注解,那直接就注入了,没意义;所以我们需要建立一个 Config 工具类。

package com.apesource.springboot_homework_condition.domain;

public class User {
}
package com.apesource.springboot_homework_condition.config;

import com.apesource.springboot_homework_condition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration//注解标注该类位配置类
public class UserConfig {
    @Bean//代表像容器中注入一个User对象
    public User user(){
        return new User();
    }
}

因为上面我们也看了 @Conditional 它仅一个继承了 Conditional 注解的的接口 Condition ,那我们就得再创建一个 Condition 的实现类被 @Conditional 引用。

package com.apesource.springboot_homework_condition.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ClassCondition implements Condition {
    @Override//重写 matches 方法
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//        写我们的判断,判断 redis.clients.jedis.Jedis.class文件是否存在,找到就返回 true ,注入,否则不注入
    Boolean flag = true;
        try {
            Class<?> cls = Class.forName("redis.clients.jedis.Jedis");//存在一个找不到的异常情况,给放try-catch块
        } catch (ClassNotFoundException e) {
            flag = false;
        }
        return flag;
    }
}

判断条件重写好了,然后给我们的配置类标注该注解,完成它的功能

package com.apesource.springboot_homework_condition.config;

import com.apesource.springboot_homework_condition.condition.ClassCondition;
import com.apesource.springboot_homework_condition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration//注解标注该类位配置类
public class UserConfig {
    @Conditional(value = ClassCondition.class)
    @Bean//代表像容器中注入一个User对象
    public User user(){
        return new User();
    }
}

启动类进行测试

package com.apesource.springboot_homework_condition;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringbootHomeworkConditionApplication {

    public static void main(String[] args) {
        //启动SpringBoot的应用,返回Spring的IOC容器
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootHomeworkConditionApplication.class, args);
        //获取 user 对象
        Object user = context.getBean("user");
        System.out.println(user);
    }

}

测试结果:

1、注入 jedis 坐标情况,返回对象地址


2、不注入 jedis 坐标情况,没找到

总结:

自定义条件:

        ① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。

matches 方法两个参数:

        context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。

        metadata:元数据对象,用于获取注解属性。

        ② 判断条件: 在初始化Bean时,使用@Conditional(条件类.class)注解

SpringBoot 提供的常用条件注解: 一下注解在springBoot-autoconfigure的condition包下         ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean         ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean         ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean         ConditionalOnBean:判断环境中有对应Bean才初始化Bean

(2)Enable 注解

        SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载

 我们查看Enable相关注解,比如 @EnableScheduling 和 @EnableCaching,可以发现除了常规注解,它还分别 Import 导入了 SchedulingConfiguration和CachingConfigurationSelector 类

由此可知:

        @Enable底层依赖于@Import注解导入一些类,使用 @Import 导入的类就会被 Spring 加载到 IOC 容器中。而 @Import 提供四种用法

        可导入 Bean、配置类、ImportSelector 实现类(一般用于加载配置文件的类)和ImportBeanDefinitionRegistar 实现类


我们现在就代码理解一下:

        我们拿多模块试试

新建一个项目 springboot_enable_01,基于该项目新建一个同级模块springboot_enable_other_02

我们在 springboot_enable_other_02 com.apesource.domain包中新建一个 User 类,在 springboot_enable_other_02 com.apesource.config 包中新建一个 UserConfig 配置类

package com.apesource.domain;

public class User {
}
package com.apesource.config;

import com.apesource.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration//注解标注配置类
public class UserConfig {
    @Bean//创建对象
    public User user(){
        return new User();
    }
}

完成!然后想在 springboot_enable_01 中调用 User对象,这就是多模块编程了,怎么联系起来呢?只需要将 springboot_enable_other_02 坐标导入 springboot_enable_01中。

复制 02 配置信息:

将该坐标粘贴至 01

<?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.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.apesource</groupId>
    <artifactId>springboot_enable_01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_enable_01</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
<!--        导入 02 坐标-->
        <dependency>
            <groupId>com.apesource</groupId>
            <artifactId>springboot_enable_other_02</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
<!--        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

启动类:

package com.apesource.springboot_enable_01;

import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
@ComponentScan("com.apesource.config")
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

结果(有对象地址,获取对象成功):

启动类(@Import 导入 Bean):

package com.apesource.springboot_enable_01;

import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
//@ComponentScan("com.apesource.config")
/*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/
@Import(User.class)//加载类
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

 结果(有对象地址,获取对象成功):

启动类(@Import 导入 配置类):

package com.apesource.springboot_enable_01;

import com.apesource.config.UserConfig;
import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
//@ComponentScan("com.apesource.config")
/*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/
//@Import(User.class)//加载类
@Import(UserConfig.class)//加载配置类
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

 结果(有对象地址,获取对象成功):

除过这三种方式,我们还可以仿照 Enable 的注解自定义一个 Enable 的注解,我们在 02 的config 的包中新建一个注解

复制 Enable 注解(随便点进去一个 Enable 注解都可以复制)中可复制前三行常规注解

package com.apesource.config;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;
/*复制的前三行常规注解*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Import(UserConfig.class)//导入UserConfig配置类
public @interface EnableUser {
}

又回到 01 启动类,用我们自己造的注解——(@Import 导入 配置类):

package com.apesource.springboot_enable_01;

import com.apesource.config.EnableUser;
import com.apesource.config.UserConfig;
import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
//@ComponentScan("com.apesource.config")
/*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/
//@Import(User.class)//加载类
//@Import(UserConfig.class)//加载配置类
@EnableUser
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

 结果(有对象地址,获取对象成功):


@Import 导入ImportSelector 实现类(一般用于加载配置文件的类)

        我们在 02 config 包新建一个 ImportSelector 的实现类,重写方法的这个 String[] 数组内容可以被 Import 直接导入,为了方便看,我们 domain 新建了个 Student 类,然后将 User 类和 Student 类都导入,那我们的配置类就要多创建一个 Bean 对象

package com.apesource.domain;

public class Student {
}
package com.apesource.config;

import com.apesource.domain.Student;
import com.apesource.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration//注解标注配置类
public class UserConfig {
    @Bean//创建对象
    public User user(){
        return new User();
    }
    @Bean//创建对象
    public Student student(){
        return new Student();
    }
}
package com.apesource.config;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //目前字符串内容写死的
        return new String[]{"com.apesource.domain.User","com.apesource.domain.Student"};
    }
}

又回到 01 启动类:

package com.apesource.springboot_enable_01;

import com.apesource.config.EnableUser;
import com.apesource.config.MyImportSelector;
import com.apesource.config.UserConfig;
import com.apesource.domain.Student;
import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
//@ComponentScan("com.apesource.config")
/*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/
//@Import(User.class)//加载类
//@Import(UserConfig.class)//加载配置类
//@EnableUser
@Import(MyImportSelector.class)
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);

        Student student = context.getBean(Student.class);
        System.out.println(student);
    }
}

结果(有对象地址,获取对象成功):


@Import 导入ImportBeanDefinitionRegistar 实现类

        我们在 02 config 包新建一个 ImportBeanDefinitionRegistar 的实现类,重写registerBeanDefinitions 方法,

package com.apesource.config;

import com.apesource.domain.User;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    //registerBeanDefinitions 注册 Bean 的信息对象
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        //AnnotationMetadata注解
        //BeanDefinitionRegistry向spring容器中注入
       
        //1.获取user类的definition对象
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        //2.通过beanDefinition属性信息,向Spring容器中注册id为user的对象
        registry.registerBeanDefinition("user",beanDefinition);//id,类型
    }
}
又回到 01 启动类:
package com.apesource.springboot_enable_01;

import com.apesource.config.EnableUser;
import com.apesource.config.MyImportBeanDefinitionRegistrar;
import com.apesource.config.MyImportSelector;
import com.apesource.config.UserConfig;
import com.apesource.domain.Student;
import com.apesource.domain.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

/*仅仅导入02坐标我们还不足以拿到 Bean 对象,需要注解:
 *@ComponentScan:@SpringBootApplication中有@ComponentScan注解,
 *扫描范围:当前引导类所在包及其子包(不在同一包就扫描不到)*/
@SpringBootApplication
//@ComponentScan("com.apesource.config")
/*可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器*/
//@Import(User.class)//加载类
//@Import(UserConfig.class)//加载配置类
//@EnableUser
//@Import(MyImportSelector.class)
@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootEnable01Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable01Application.class, args);
        //获取 Bean 对象
        User user = context.getBean(User.class);
        System.out.println(user);

//        Student student = context.getBean(Student.class);
//        System.out.println(student);
    }
}
结果(有对象地址,获取对象成功):

(3)@EnableAutoConfiguration 注解

        SpringBoot 中提供了很多 Enable 开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现 Bean 的动态加载

        关于 Springboot 的自动配置,其实最重要的就是我们启动类上的这个核心注解@SpringBootApplication,SpringBootApplication翻译过来就是 Springboot 主配置文件,它也确实是自动配置的支持。

        我们按住 Ctrl 键点进去该注解看看

        前三行就是常规的注解嘛,在此解释后三个。

        @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class):扫描启动类及其子类所有的内容

        @SpringBootConfiguration:SpringBoot 配置类,Ctrl 键点进去该注解看看

        @Configuration——配置类,所以@SpringBootConfiguration只是进行了二次封装,那也就是说,我们的启动类本质上是一个配置类

        @EnableAutoConfiguration——自动配置,Ctrl 键点进去该注解看看

解释:

        @AutoConfigurationPackage:自动配置包,帮我们找当前启动类所在的包,交给@ComponentScan 扫描

        @Import(AutoConfigurationImportSelector.class)——做自动配置功能的注解

Ctrl 键点进去该注解看看 AutoConfigurationImportSelector 这个类

Ctrl 键点进去该注解看看 DeferredImportSelector,就是 ImportSelector 接口

然后我们就又回到这里:

往下滑,我们可以看到

它这个字符串将 autoConfigurationEntry.getConfigurations() 的结果集打包成字符串了,那我们点进去 getAutoConfigurationEntry 方法看看

返回字符串类型的集合,返回 configurations 这个属性,想知道这个集合咋来的,点进去getCandidateConfigurations 方法看看

我们可以 debug 看看它这个集合哪里来的,打断点

回到我们的去启动类,注释掉刚刚我们获取的对象,测试启动类本身

一直按红圈圈圈住的那个按钮——下一步,直到出现我们的 List 集合,144个,展开可以看到好多

所以我们的启动类这么多的都自动配置了。

我们可以在这里找到那么多的配置的源头在 META-INF/spring.factories 这。

        spring.factories 就在项目的 jar 包里。项目的 External Libraries 中

autoconfigure 自动配置,展开就能看到 META-INF。META-INF里面就有 spring.factories文件

点开就可以看到那144个代表自动配置的键值对


说明:

启动类:

        启动类的注解@SpringBootApplication 标注一个主程序类,说明是一个 Spring Boot 应用。

        SpringApplication.run(SpringbootApplication.class, args) 实际上启动了一个服务

@SpringBootApplication注解内部

@ComponentScan

        对应 XML 配置中的元素;

        作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

@SpringBootConfiguration

        作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类

@AutoConfigurationPackage

        自动配置包

@EnableAutoConfiguration

        开启自动配置功能:以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能,这样自动配置才能生效

@Import({AutoConfigurationImportSelector.class})

        给容器导入组件

AutoConfigurationImportSelector

        自动配置导入选择器,给容器中导入一些组件 


总结:

        @EnableAutoConfiguration 注解内部使用         @Import(AutoConfigurationImportSelector.class) 来加载配置类。

 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类。

        当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

二、自定义启动器

目的:自定义 redis-starter,要求当导入redis坐标时,SpringBoot自动创建 Jedis 的 Bean

starter

首先新建项目 Springboot_starter ,据此项目新建一个同级模块,符合启动器命名规范,叫 redis-spring-boot-starter,将来要导入它,redis-spring-boot-starter,它不能有主配置文件、启动类和测试类,得删掉。然后将 redis-spring-boot-starter 的项目信息:

导入到Springboot_starter 中:

<?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.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.apesource</groupId>
    <artifactId>springboot_starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_starter</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
<!--        redis-spring-boot-starter 的项目信息-->
        <dependency>
            <groupId>com.apesource</groupId>
            <artifactId>redis-spring-boot-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

redis-spring-boot-starter 它不可能造 redis 文件,所以它也得导入一个坐标。又新建一模块redis-spring-boot-autoconfigure ,它同样不能有启动类和测试类,得删掉。因为它不能单独启动或运行。redis-spring-boot-autoconfigure 的项目信息导入 redis-spring-boot-starter 中

<!--        redis-spring-boot-autoconfigure 的项目信息-->
        <dependency>
            <groupId>com.apesource</groupId>
            <artifactId>redis-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

目的:自定义 redis-starter,要求当导入redis坐标时,SpringBoot自动创建 Jedis 的 Bean

starter,要在 redis-spring-boot-autoconfigure 中操作。所以第一步,导入 jedis 坐标,即

<!--        jedis 坐标-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

第二步、需要将 jedis 注入容器中,所以第二步建一个测试类——RedisAutoconfiguration

package com.apesource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration
public class RedisAutoconfiguration {
    //注入 jedis
    @Bean
    public Jedis jedis(){
        return new Jedis("localhost",6379);
    }
}

配置类写好了,但是怎么加载?我们前面看到springboot那些自启动项都在在META-INF/spring.factories中,那我们在src-main-resource中找到META-INF包和包里spring.factories文件,文件可以看到我们的我们的自启动类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.apesource.RedisAutoconfiguration

既然都找到了,那我们去 Springboot_starter 启动类测试

package com.apesource.springboot_starter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import redis.clients.jedis.Jedis;

@SpringBootApplication
public class SpringbootStarterApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootStarterApplication.class, args);
        Jedis bean = context.getBean(Jedis.class);
        System.out.println(bean);
    }

}

结果:

结果出现 localhost:6379 ,我们的自定义启动器成功创建。


优化:动态端口

一、  Springboot_starter 的 yml 文件(新建)或者 properties 文件中添加相应键值对,我以 yml 文件进行演示。

        想要实现:我配了端口和ip,就用我配的,我没配就按照默认的《yml 文件中配信息》

spring:
  redis:
    port: 6069
    host: 127.0.0.1

         这个配置的端口和 id 得在 Jedis 注入时就生效,所以 redis-spring-boot-autoconfigure 在运行时得读取到 yml 文件。

        在 redis-spring-boot-autoconfigure 新建一类 RedisProperties ,用来实现读取到 yml 文件并完成注入操作

package com.apesource;

import org.springframework.boot.context.properties.ConfigurationProperties;
//该类实现读取到 yml 文件并完成注入
@ConfigurationProperties(prefix = "spring.redis")//批量注入
public class RedisProperties {
    //定义默认端口号和 id
    private String host="localhost";
    private int port=6379;
    
    /*************get\set方法*************/
    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
}
package com.apesource;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
//@EnableConfigurationProperties作用RedisPropertie中批量注入生效
public class RedisAutoconfiguration {
    @Bean
    public Jedis jedis(RedisProperties redisProperties){//装配该类
        return new Jedis(redisProperties.getHost(),redisProperties.getPort());
    }
}

结果为我们自己装配的端口和 id:

        

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值