SpringBoot自动装配
这里写目录标题
- SpringBoot自动装配
- 启动器
- 主程序
- 自定义扫描包
- @SpringBootApplication
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @Import({AutoConfigurationImportSelector.class})选择器
- AutoConfigurationEntry
- getCandidateConfigurations
- 组件添加
- 代理设置
- @import注解
- Conditional注解
- @ImportResource注解
- 配置绑定
- @ConfigurationProperties注解
- @EnableConfigurationProperties注解
- 主函数
- @EnableAutoConfiguration注解
- @AutoConfigurationPackage注解
- @Import({AutoConfigurationImportSelector.class})
- 按需开启
- 自动装配流程
- 条件装配流程
- 修改默认配置
我们通过观察创建的SpringBoot项目的pom.xml文件发现他的核心依赖大部分在他的父工程中,长按ctrl并点击父项目就可以进入到父项目的文件,从浏览器上面下载压缩包的那种创建springBoot方式不能查看夫项目的文件。
spring-boot-dependencies是parent的父项目。
在这里制定了大量的依赖版本,还有过滤器。
我们的pom.xml文件与之前不同的就是我们在引入依赖的时候可以不指定依赖的版本,这就是因为在父工程中指定了依赖的版本,我们没必要再写。当然,如果要引入他没有规定的依赖还是要声明版本的。
如果我们对他的依赖版本不满意,想要使用我们自己的,我们可以指定需要的版本
这个就是修改mysql版本的方法。
启动器
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
项目自带的启动器,下面有很多的在他的基础上添加后缀的依赖,比如说-web配置内置的tomcat容器等
访问下面这个网址可以找到对于启动器的介绍
Build Systems :: Spring Boot
这里可以查询到我常用的依赖。如aop,缓存等的,我们也可以使用idea的分析依赖树功能,更好的查看我们项目中的依赖。
主程序
首先alt+回车查看他的返回类型ConfigurableApplicationContext是一个IOC容器,我们通过getBeanDefinitionNames来打印一下它里面的所有组件。
package com.kang.start;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class StartApplication {public static void main(String[] args) {//ConfigurableApplicationContext是一个IOC容器ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);String[] names = run.getBeanDefinitionNames();for (String name:names) {System.out.println(name);}}
}
说明他配置了dispathcer,我们之前学习的基本上所有的组件都可以在这里找到。
自定义扫描包
注意在官网上说的包结构的安排,只有把组件放在项目目录下才能扫描到。
当然想要改变扫描的包也很简单,加一个这个
@SpringBootApplication(scanBasePackages = "com.kang")
当然也有第二种方法就是直接把它里面的注解拽出来,这样配置就不会与SpringBootApplication标签里面的包扫描冲突
/*@SpringBootApplication*/@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.kang")
public class StartApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);String[] names = run.getBeanDefinitionNames();for (String name:names) {System.out.println(name);}}
}
这里我们也可以得出一个结论
@SpringBootApplication
等价于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.kang")
在 [application.properties] 文件中我们发现,我们点开配置文件的源码以后,里面绑定的都是类,最后这些类都会注入到IOC容器中。
@SpringBootApplication
他的主程序很少,其中最关键的注解就是@SpringBootApplication声明它是springBoot程序,查看他的源文件会发现两个最重要的注解
@SpringBootConfiguration
@SpringBootConfiguration@Configuration@Component//这个注解代表他本质上还是一个spring的组件
@EnableAutoConfiguration
@EnableAutoConfiguration//自动导入配置文件;@AutoConfigurationPackage//自动配置包@Import({AutoConfigurationImportSelector.class})//导入选择器
@AutoConfigurationPackage
@Import({Registrar.class})
通过Regist类我们可以发现的本质就是一个自动注册包的类
@Import({AutoConfigurationImportSelector.class})选择器
这两个配置注解在AutoConfigurationImportSelector这个类中有一个核心语句就是获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
AutoConfigurationEntry
AutoConfigurationEntry获得自动配置的实体
getCandidateConfigurations
了解一下getCandidateConfigurations的
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());List<String> configurations = importCandidates.getCandidates();Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");return configurations;
}
其中 "No auto configuration classes found in META-INF/spring/"这里说的就是自动配置的核心文件
就是它,点开我们可以看到很多的配置,比如说常见的aop,jdbc等的配置。但是我们点开以后发现很多的都是爆红的,这说明我们并没有配置该组件,我们只要在pom文件中添加依赖,下载完成以后就不爆红了。
组件添加
首先创建实体类
User类
package com.kang.start.pojo;public class User {String name;int age;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}public User() {}public User(String name, int age) {this.name = name;this.age = age;}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;}
}
Pet类
package com.kang.start.pojo;public class Pet {String name;public Pet() {}@Overridepublic String toString() {return "Pet{" +"name='" + name + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public Pet(String name) {this.name = name;}
}
然后注册到容器中的方法有两种
方法一:
配置beans.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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user01" class="com.kang.start.pojo.User"><property name="age" value="18"/><property name="name" value="zhangsan"/></bean><bean id="Tomcat" class="com.kang.start.pojo.Pet"><property name="name" value="Tomcat"/></bean>
</beans>
方法二:
使用配置类(配置类本身也是组件)+注解
- @Configuration//声明配置类==xml文件
- @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
- @Bean(“Tom”)//自定义名称
package com.kang.start.config;import com.kang.start.pojo.Pet;
import com.kang.start.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration//声明配置类==xml文件
public class myConfig {@Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。public User user01(){return new User("xiaom",18);}@Bean("Tom")//自定义名称public Pet TomcatPet(){return new Pet("Tomcat");}
}
我们在主函数中输出一下
public class StartApplication {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);String[] names = run.getBeanDefinitionNames();for (String name:names) {System.out.println(name);}}
}
得到了我们注册的两个类
代理设置
@Configuration(proxyBeanMethods = true)
proxyBeanMethods参数是设置代理,当他默认值为true的时候,当我们创建两个对象的时候,他们的哈希地址是一样的,设置为false的时候就是不同的。
这就涉及到两种模式:
- Full:proxyBeanMethods = true,把创建的对象放在容器中,调用的时候从容器中取出来
- Lite:proxyBeanMethods = false,每次调用的时候创建对象。(轻量级模式)
举个例子:如果我们在User对象中绑定一个Pet类型的cat,当我们代理为true的时候,我们绑定的宠物就是容器中的cat,这就成功的让User绑定到了cat,但是如果代理设置为false,那么我们绑定的Pet就是新创建的,并不是我们在配置类中绑定的那个Pet。
总的来说,当我放入IOC的对象存在IOC依赖的时候就需要打开代理,当我们没有IOC依赖的时候就调成false,让我们更快的加载。因为他跳过了在IOC中检测的步骤。
@import注解
这个组件可以帮助我们在IOC容器中创建指定类型的组件
比如说,我们随便import一个组件,是自定义的
@Import({User.class})
@Configuration(proxyBeanMethods = true)
public class myConfig {@Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。public User user01(){return new User("xiaom",18);}@Bean("Tom")//自定义名称public Pet TomcatPet(){return new Pet("Tomcat");}
}
输出一下
public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);String[] names = run.getBeanNamesForType(User.class);for (String name:names) {System.out.println(name);}}
他的名字就是类地址名。这就代表成功注册了
Conditional注解
他的作用是在指定条件下注册指定的类,看下他的源文件
ctrl+H,我们发现他有很多很多的派生注解。
首先解释一个方法containsBean,它可以检测容器中是否存在指定的类
我们先把tom组件注释掉
public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);boolean tom = run.containsBean("tom");System.out.println("输出是否存在组件tom:"+tom);boolean user01 = run.containsBean("user01");System.out.println("输出是否存在组件user01:"+user01);}
我们首先对User和Pet建立依赖
package com.kang.start.pojo;public class User {String name;int age;Pet pet;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;}public Pet getPet(Pet pet) {return this.pet;}public void setPet(Pet pet) {this.pet = pet;}public User(String name, int age) {this.name = name;this.age = age;}
}
@Configuration(proxyBeanMethods = true)
public class myConfig {@Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。public User user01(){User user = new User("xiaom", 18);user.setPet(TomcatPet());return user;}@Bean("Tom")//自定义名称public Pet TomcatPet(){return new Pet("Tomcat");}
}
使用一个注解@ConditionalOnBean(name = “Tom”)
@Configuration(proxyBeanMethods = true)
public class myConfig {@Bean("Tom")//自定义名称public Pet TomcatPet(){return new Pet("Tomcat");}@ConditionalOnBean(name = "Tom")//检测容器中是否存在Tom,如果存在则创建user01@Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。public User user01(){User user = new User("xiaom", 18);user.setPet(TomcatPet());return user;}
}
检测是否存在
当我们注释掉Tom时
user01也不会注册;也可以把它注解在整个类以外,如果存在这个类,下面所有的配置才会生效。
@ImportResource注解
它可以帮助我们自动导入beans.xml文件,比如说,在我们运行一个比较老的项目的时候他的配置用的全都是xml文件注册的bean,我们想要快速的把他的xml文件中的beans注册到我们的IOC容器中,我们就可以使用在各个注解。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="hehe" class="com.kang.start.pojo.Pet"><property name="name" value="Tomcat"/></bean><bean id="haha" class="com.kang.start.pojo.User"><property name="age" value="18"/><property name="name" value="zhangsan"/></bean>
</beans>
这样一个xml文件
@Configuration(proxyBeanMethods = true)
@ImportResource("classpath:beans.xml")//快速引入指定的bean注册文件
public class myConfig {@Bean("Tom")//自定义名称public Pet TomcatPet(){return new Pet("Tomcat");}@ConditionalOnBean(name = "Tom")@Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。public User user01(){User user = new User("xiaom", 18);user.setPet(TomcatPet());return user;}
}
配置绑定
@ConfigurationProperties注解
简单来说就是使用Java读取到propertie文件中的内容,把它封装到JavaBean中,方便使用。
传统的方法就是使用Properties类读取配置文件的Key和value,遍历它们,然后一一对应的封装到JavaBean中。
这时可以使用一个注解@ConfigurationProperties(prefix = “(前缀)”)
prefix是前缀
具体的使用步骤:
首先新建Car类,为他加入get/set方法还有tostring方法,有参无参构造方法等
使用@Component注解来注册到IOC容器中
@ConfigurationProperties(prefix = “mycar”)//读取properties文件中前缀为mycar的方法
注意:properties文件中的前缀必须全小写,不能使用驼峰写法
package com.kang.start.pojo;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {public String brand;public Integer pirce;@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", pirce=" + pirce +'}';}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Integer getPirce() {return pirce;}public void setPirce(Integer pirce) {this.pirce = pirce;}public Car() {}public Car(String brand, Integer pirce) {this.brand = brand;this.pirce = pirce;}
}
properties文件:
mycar.brand=baoma
mycar.pirce=10000
在Controller类中用@Autowried注解来吧IOC容器中的car类拽下来
@RestController
public class myConfig {@AutowiredCar car;@RequestMapping("/car")public Car car(){return car;}
}
访问这个页面
@EnableConfigurationProperties注解
当然,我们还要考虑一种情况,就是这个组件如果不是我们自定义的我们要把它注册到IOC容器中怎么办呐?我们也不能在人家的源文件中添加@Component注解
这个时候,我们可以在配置类当中使用这个注解
@EnableConfigurationProperties(类名.class)
代替@Component注解;
主函数
@EnableAutoConfiguration注解
查看他的源文件里面有两个核心的注解
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage注解
他的源码:
@Import({Registrar.class})//给容器导入组件
public @interface AutoConfigurationPackage {
其中导入组件的时候用的是Registrar来导入一系列组件
所以说@EnableAutoConfiguration注解的一个作用就是导入指定包下的所有组件
具体来说就是main程序所在包下的所有组件
@Import({AutoConfigurationImportSelector.class})
@EnableAutoConfiguration还有一个重要的注解
@Import({AutoConfigurationImportSelector.class})
这个注解,先查看AutoConfigurationImportSelector这个方法,他是一个批处理方法。
其中有一个selectImports。
研究一下getAutoConfigurationEntry(annotationMetadata)这个方法
他就是给IOC容器中批量导入组件,我们来Debug一下
getAutoConfigurationEntry(annotationMetadata)这个方法就是调用
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
这个方法,获取所有要导入容器中的类。
此外还有一个重要的工厂方法;
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
利用工厂方法加载。
SpringFactoriesLoader方法
默认他会扫描这个位置META-INF/spring.factories
在我们的自动装配核心类包spring-boot-autoconfigure-3.4.2.jar中
说明这个文件里面声明了启动springBoot时要注册的所有配置类
按需开启
虽然所有的配置文件会全部配置,但是真正使用的时候只会按需配置。
比如说我们随便打开一个包
条件配置,只有配置了指定的类才会继续下面的配置。最终会按照条件配置规则,按需装配
自动装配流程
条件装配流程
首先阅读一下aop类的源码
由于这个配置要求有Advice类,所以暂且将其称之为复杂配置。
将这个要求不高的称之为简单配置。下面的ClassProxyingConfiguration就是对aop的配置。
修改默认配置
看一下dipatcherServlet的配置类DispatcherServletAutoConfiguration
位置:org\springframework\boot\autoconfigure\web\servlet\DispatcherServletAutoConfiguration.class
最下面有一个有趣的方法,文件上传解析器
@Bean
@ConditionalOnBean({MultipartResolver.class})//检测容器中是否有MultipartResolver类
@ConditionalOnMissingBean(name = {"multipartResolver"}//如果这个类的名字不是multipartResolver
)
public MultipartResolver multipartResolver(MultipartResolver resolver) {return resolver;//修正并返回正确的解析器
}