SpringBoot系列之自动装配原理详解
创始人
2024-05-01 11:29:45

文章目录

  • 前言
  • 一、SpringBoot自动配置-Condition-1
    • 1、观察spring自动创建bean过程
    • 2、创建自定义bean对象
    • 3、根据条件创建自定义bean
  • 二、 SpringBoot自动配置-Condition-2
  • 三、SpringBoot自动配置-切换内置web服务器
    • 1、查看继承关系图
    • 2、shift+delete 排除Tomcat
  • 四、SpringBoot自动配置-Enable注解原理
  • 五、SpringBoot自动配置-@Import详解
    • ①导入Bean
    • ②导入配置类
    • ③导入 ImportSelector 实现类
    • ④导入 ImportBeanDefinitionRegistrar 实现类
  • 六、SpringBoot自动配置-@EnableAutoConfiguration详解
  • 总结


前言

Springboot目前是Java开发中最主流的框,因此在我们的工作和面试中都会经常用到,SpringBoot主要解决了传统spring的重量级xml配置Bean,实现了自动装配。接下来将从注解已经源码来学习SpringBoot自动装配原理。


一、SpringBoot自动配置-Condition-1

Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载相应的Bean。
@Conditional要配和Condition的实现类(ClassCondition)进行使用

接下来我们以下面例子来学习:
• 创建模块 springboot-condition

1、观察spring自动创建bean过程

改造启动类如下:

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象Object redisTemplate = run.getBean("redisTemplate");System.out.println(redisTemplate);}

启动:获取不到对象
在这里插入图片描述导入 redis起步依赖

org.springframework.bootspring-boot-starter-data-redis

再启动则可以获取到bean对象。
在这里插入图片描述

2、创建自定义bean对象

①新建user实体类com.lp.springbootcondition.pojo.User

package com.lp.springbootcondition.pojo;
public class User {int id;String name;int age;public User() {}public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';}
}

②新建配置类com.lp.springbootcondition.config.ConditionConfig

@Configuration
public class ConditionConfig {@Beanpublic User user() {return new User();}
}

③启动类获取。测试可以获取到

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述

3、根据条件创建自定义bean

创建ClassCondition 类com.lp.springbootcondition.condition.ClassCondition

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return false;
}
}

改造userConfig

@Configuration
public class ConditionConfig {@Bean@Conditional(ClassCondition.class)public User user(){return new User();}
}

启动起启动类,测试不能自动创建user这个bean
在这里插入图片描述

1、改造ClassCondition。根据是否导入redis来决定是否创建userBean

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {try {Class.forName("redis.clients.jedis.Jedis");return true;} catch (ClassNotFoundException e) {e.printStackTrace();return false;}}
}

测试。获取不到userBean

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述导入Redis依赖,再测试

redis.clientsjedis2.9.0

可以获取到userBean
在这里插入图片描述

二、 SpringBoot自动配置-Condition-2

需求:将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。
1、创建自定义条件注解类ConditionClass

@Target(ElementType.TYPE) //可以用在哪些地方
@Documented//生成javadoc
@Retention(RetentionPolicy.RUNTIME) //运行时起作用
@Conditional(ClassCondition.class)
public @interface ConditionClass {String[] value();
}

2、改造ClassCondition类

public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {try {//必须引入动态传来的包名,才生成对象Map annotationAttributes = metadata.getAnnotationAttributes("com.lp.springbootcondition.condition.ConditionClass");System.out.println(annotationAttributes);if (annotationAttributes != null) {String[] values = (String[]) annotationAttributes.get("value");for (String value : values) {Class.forName(value);}}return true;} catch (ClassNotFoundException e) {e.printStackTrace();return false;}}
}

3、改造ConditionConfig
注意: 此处@ConditionOnClass为自定义注解

@Configuration
public class ConditionConfig {
//    @Bean
//    public User user() {
//        return new User();
//    }@Bean
//    @Conditional(ClassCondition.class)@ConditionOnClass({"redis.clients.jedis.Jedis"})public User user(){return new User();}

4、测试User对象的创建

@SpringBootApplication
public class SpringbootConditionApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootConditionApplication.class, args);System.out.println(run);//只要引入Redis起步依赖,就有了RedisTemplate对象
//        Object redisTemplate = run.getBean("redisTemplate");
//        System.out.println(redisTemplate);User user = (User) run.getBean("user");System.out.println(user);}
}

在这里插入图片描述查看Springboot条件注解源码
在这里插入图片描述我们会发现Springboot都已经帮我们写好了
SpringBoot 提供的常用条件注解:

ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

三、SpringBoot自动配置-切换内置web服务器

如果我们需要切换内置web服务器可以按一下操作进行切换

1、查看继承关系图

在这里插入图片描述

2、shift+delete 排除Tomcat

在这里插入图片描述
pom文件中的排除依赖效果

org.springframework.bootspring-boot-starter-webspring-boot-starter-tomcatorg.springframework.boot
spring-boot-starter-jettyorg.springframework.boot

根据上面的例子,我们能够知道 为什么引入了starter-data-redis起步依赖,我们就能在项目中,直接拿redistemplate?
因为在springboot中的autoconfigure工程里把常用的对象的配置类都有了,只要工程中,引入了相关起步依赖,这些对象在我们本项目的容器中就有了。
在这里插入图片描述

四、SpringBoot自动配置-Enable注解原理

重要:SpringBootApplication 由三个注解组成
在这里插入图片描述

@SpringBootConfiguration 自动配置相关
@EnableAutoConfiguration
@ComponentScan 扫本包及子包

SpringBoot不能直接获取在其他工程中定义的Bean。
springboot-enable工程,编写主启动类

代码如下(示例):

/*** @ComponentScan 扫描范围:当前引导类所在包及其子包** //1.使用@ComponentScan扫描com.lp.springbootenableother.config包* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* //3.可以对Import注解进行封装。**/
package com.lp.springbootenable;import com.lp.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

pom中引入springboot-enable-other

com.lpspringboot-enable-other0.0.1-SNAPSHOT

新建springboot-enable-other工程
在这里插入图片描述编写User

public class User {}

编写UserConfig

@Configuration
public class UserConfig {@Beanpublic User user() {return new User();}
}

启动主启动类,确实,本工程中没有这个第三方jar包中的bean对象
在这里插入图片描述原因:@ComponentScan 扫描范围:当前引导类所在包及其子包
三种解决方案:
1.使用@ComponentScan扫描lp.config包
2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
3.可以对Import注解进行封装。


@SpringBootApplication
//@ComponentScan("com.lp.springbootenableother.config")
//@Import(UserConfig.class)
@EnableUser
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

编写EnableUser注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

启动,能够获取
![在这里插入图片描述](https://img-blog.csdnimg.cn/249b1a85258747c597aa1e8ebe81dad4.png

重点:Enable注解底层原理是使用@Import注解实现Bean的动态加载

五、SpringBoot自动配置-@Import详解

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

而@Import提供4中用法:

①导入Bean

。注意bean名字是全限定名(@Import(UserConfig.class))。

示例代码

@Import(User.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

②导入配置类

示例代码

@Import(UserConfig.class)
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);User user = (User) run.getBean("user");System.out.println(user);}
}

③导入 ImportSelector 实现类

一般用于加载配置文件中的类

示例代码

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.lp.domain.User", "com.lp.domain.Role"};}
}

在这里插入图片描述

④导入 ImportBeanDefinitionRegistrar 实现类

@Import({MyImportBeanDefinitionRegistrar.class})

示例代码

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();registry.registerBeanDefinition("user", beanDefinition);}
}

在这里插入图片描述SpringbootEnableApplication测试代码

   Import4中用法:*  1. 导入Bean
*  2. 导入配置类
*  3. 导入ImportSelector的实现类。
*  4. 导入ImportBeanDefinitionRegistrar实现类*/
@SpringBootApplication
//@ComponentScan("com.lp.springbootenableother.config")
//@Import(User.class)
//@EnableUser
//@Import(MyImportSelector.class)
//@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootEnableApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(SpringbootEnableApplication.class, args);
//        User user = (User) run.getBean("user");
//        System.out.println(user);Map beansOfType = run.getBeansOfType(User.class);System.out.println(beansOfType);//        Jedis jedis = (Jedis) run.getBean("jedis");
//        System.out.println(jedis);
//        jedis.set("hello", "world");
//        String hello = jedis.get("hello");
//        System.out.println(hello);}
}

@EnableAutoConfiguration中使用的是第三种方式:@Import(AutoConfigurationImportSelector.class)

六、SpringBoot自动配置-@EnableAutoConfiguration详解

在这里插入图片描述

  • @EnableAutoConfiguration 注解内部使用
    @Import(AutoConfigurationImportSelector.class)来加载配置类。
  • 配置文件位置:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,该配置文件中定义了大量的配置类,当 SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
  • 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

总结

SpringBoot中的主启动类@SpringBootApplication由三个注解组成,分别是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan其中

@SpringBootConfiguration表示启动类为配置类;

@ComponentScan实现启动时扫描启动类所在的包以及子包下所有标记为bean的类由IOC容器注册为bean。

@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)注解来加载AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector中的selectImports方法去读取spring-boot-autoconfigure 下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的全类名,并根据一定规则过滤掉不符合的全类名,然后将剩余读取到的类全名集合返回给IOC容器并将这些组件注册为bean。

相关内容

热门资讯

阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...