SpringBoot自动配置主要是因为 @SpringBootApplication注解,@SpringBootApplication是一个复合注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
想明白Spring Boot自动配置需要先知道一些注解的作用,例如注解@Configuration、@Bean、@Component、@Import、@ComponentScan、@Conditional、@ImportResources、@ConfigurationProperties等 想看源码直接看第二部分哈,第一部分是一些注解的说明
第一部分:
@Configuration、@Bean
@Configuration告诉SpringBoot这是一个配置类
例如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="mySchool" class="com.example.demo.pojo.School">
<property name="sname" value="aynu"/>
</bean>
<bean id="myStudent" class="com.example.demo.pojo.Student">
<property name="age" value="20"/>
<property name="name" value="张三"/>
</bean>
</beans>
Student.java
package com.example.demo.pojo;
/**
* company: www.abc.com
* Author: Administrator
* Create Data: 2019/10/21 0021
*/
public class School {
public School() {
}
public School(String sname) {
this.sname = sname;
}
private String sname;
public void setSname(String sname) {
this.sname = sname;
}
@Override
public String toString() {
return "School{" +
"sname='" + sname + '\'' +
'}';
}
}
School.java
package com.example.demo.pojo;
/**
* company: www.abc.com
* Author: Administrator
* Create Data: 2019/10/21 0021
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age ;
}
}
而用@Configuration和@Bean
package com.example.demo.config;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //@Configuration(proxyBeanMethods = false)代理对象存在就新建默认为true 单例 false不会检测MyConfig.class下面的@Bean 所以没有组件依赖是false就会很快
public class MyConfig {
//@Bean给容器内添加组件,以方法名作为组件的id 默认是单例的
@Bean
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
测试:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
//查看容器里面的组件
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
Student student = bean.student();
Student student2 = bean.student();
System.out.println(student == student2);
}
}
结果截图
说明school和student成功被注入到Spring容器中
注意默认是单例的,因为需要解决循环依赖问题,至今我见到都是单例,还没有见过用原型的
@Import
1.给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
package com.example.demo.config;
import ch.qos.logback.core.db.dialect.MySQLDialect;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//@Import: 给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
@Import({Student.class, MySQLDialect.class})
@Configuration
public class MyConfig {
@Bean//给容器内添加组件,以方法名作为组件的id 默认是单例的
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
代码:
//获得组件
String[] beanNamesForType = run.getBeanNamesForType(Student.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
MySQLDialect bean1 = run.getBean(MySQLDialect.class);
System.out.println(bean1);
2.ImportSelector:返回需要导入的组件的全类名数组;把这些类导入到容器内
package com.example.demo.abc;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.example.demo.pojo.car","com.example.demo.pojo.cat"};
}
}
AnnotationMetadata是能找到原注解所在位置的所有注解:
Debug如下:
结果:
3.ImportBeanDefinitionRegistrar
自定义bean注入容器中
package com.example.demo.abc;
import com.example.demo.pojo.fox;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注解类
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
* */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
boolean cat = registry.containsBeanDefinition("com.example.demo.pojo.cat");
boolean car = registry.containsBeanDefinition("com.example.demo.pojo.car");
if (car && cat){
//判断fox.class是否存在 已经指定Bean定义信息,Bean的类型等等
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(fox.class);
//指定bean名 向容器内注入一个组件
registry.registerBeanDefinition("fox",rootBeanDefinition);
}
}
}
fox类
package com.example.demo.pojo;
public class fox {
}
在MyConfig类中加入
@Import({Student.class, MySQLDialect.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@ComponentScan
//包扫描,只要标注了@Controller、@Service、@Repository、@Component、@Configuration、@Bean都会自动扫描加进容器中
例如:@ComponentScan(value = "com.example.demo")
package com.example.demo.config;
import ch.qos.logback.core.db.dialect.MySQLDialect;
import com.example.demo.pojo.People;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.*;
//@Import: 给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
@Import({Student.class, MySQLDialect.class})
@Configuration
@ConditionalOnMissingBean(name = "cat") //当没有cat是才会运行下面的代码,就是一个条件判断
//@ConditionalOnBean(name = "cat");
@ImportResource("classpath:bean1.xml") //导入bean1.xml
@EnableConfigurationProperties(People.class)
//1.开启People配置绑定功能
//2.把这个People这个组件自动注册到容器中 People上面不需要@Component
@ComponentScan(value = "com.example.demo")
//包扫描,只要标注了@Controller、@Service、@Repository、@Component、@Configuration、@Bean都会自动扫描加进容器中
public class MyConfig {
@Bean//给容器内添加组件,以方法名作为组件的id 默认是单例的
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
我们创建一个类,加入容器中
测试:
package com.example.demo;
import com.example.demo.config.MyConfig;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
class ApplicationTests {
@Test
void contextLoads() {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
String[] beanDefinitionNames = annotationConfigApplicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
@ComponentScan(value = "com.example.demo")
结果就没有 school2 组件
@Conditional
条件装配:当满足特定的条件,则进行组件注入
alt+h可以看 继承树 如图:
例如:
package com.example.demo.config;
import ch.qos.logback.core.db.dialect.MySQLDialect;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//@Import: 给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
@Import({Student.class, MySQLDialect.class})
@Configuration
@ConditionalOnMissingBean(name = "cat") //当没有cat是才会运行下面的代码,就是一个条件判断
//@ConditionalOnBean(name = "cat");
public class MyConfig {
@Bean//给容器内添加组件,以方法名作为组件的id 默认是单例的
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
@ImportResources
导入Spring配置文件
package com.example.demo.config;
import ch.qos.logback.core.db.dialect.MySQLDialect;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
//@Import: 给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
@Import({Student.class, MySQLDialect.class})
@Configuration
@ConditionalOnMissingBean(name = "cat") //当没有cat是才会运行下面的代码,就是一个条件判断
//@ConditionalOnBean(name = "cat");
@ImportResource("classpath:bean1.xml") //导入bean1.xml
public class MyConfig {
@Bean//给容器内添加组件,以方法名作为组件的id 默认是单例的
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
@ConfigurationProperties
配置绑定
package com.example.demo.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
@Component
@ConfigurationProperties(prefix = "people")
public class People {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
application.properties
#访问端口号 server.port=8080 people.name=xiaohong people.age=19
测试:
package com.example.demo.web;
import com.example.demo.pojo.People;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebTest {
@Autowired
People people;
@RequestMapping("/people")
public People people(){
return people;
}
}
结果:
@EnableConfigurationProperties
配置绑定和
package com.example.demo.config;
import ch.qos.logback.core.db.dialect.MySQLDialect;
import com.example.demo.pojo.People;
import com.example.demo.pojo.School;
import com.example.demo.pojo.Student;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
//@Import: 给容器自动创建出这两个类型的组件,默认组件的名字就是全类名
@Import({Student.class, MySQLDialect.class})
@Configuration
@ConditionalOnMissingBean(name = "cat") //当没有cat是才会运行下面的代码,就是一个条件判断
//@ConditionalOnBean(name = "cat");
@ImportResource("classpath:bean1.xml") //导入bean1.xml
@EnableConfigurationProperties(People.class)
//1.开启People配置绑定功能
//2.把这个People这个组件自动注册到容器中 People上面不需要@Component
public class MyConfig {
@Bean//给容器内添加组件,以方法名作为组件的id 默认是单例的
public School school(){
return new School("beida");
}
@Bean
public Student student(){
return new Student("小明",18);
}
}
第二部分
@SpringBootApplication是复合注解主要是以下三个注解组成@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} )
@SpringBootConfiguration
里面有一个@Configuration 说明@SpringBootApplication是一个配置类SpringBoot主启动类是一个启动类
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
@ComponentScan
//包扫描,只要标注了@Controller、@Service、@Repository、@Component、@Configuration、@Bean都会自动扫描加进容器中
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
excludeFilters = {@Filter( //排除过滤器
type = FilterType.CUSTOM, //类型是FilterType.CUSTOM
classes = {TypeExcludeFilter.class} //具体是TypeExcludeFilter.class 这些可以不看,都是springboot默认规则
)
是指定扫描的目录:
例如:@SpringBootApplication 就可以写成如下:
@EnableAutoConfiguration
最主要的是@EnableAutoConfiguration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
主要包含了@AutoConfigurationPackage、@Import
@AutoConfigurationPackage
自动配置包
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
@Import({Registrar.class})
Registrar.class:
这个是@Import注解的高级用法:registerBeanDefinitions,看上面注解解释就
就是自定义的组件注入到容器中,metadata是获得当前类的所有注解,前面注解也说了
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
这个@AutoConfigurationPackage就是将 当前包(其实就是Application.class)下的所有组件注入到容器中
@Import({AutoConfigurationImportSelector.class})
这个是最重要的: