运行文中的代码需要在项目构建中引入springboot 相关依赖.

① @configuration 

   configuration,用来将bean加入到ioc容器。代替传统xml中的bean配置。代码示例:

定义一个普通类:

public class Person {


}  

定义一个配置类,用来将此类注册到ioc容器中:

@Configuration
public class PersonConfig {


    @Bean
    public Person person(){
        return new Person();
    }

}

测试类:

 

public class App {
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(PersonConfig.class);
        System.out.println(ac.getBean("person"));
    }
}

 运行结果:

 ②  @ComponentScan

   用来扫包,相当于xml配置的 :<context:component-scan base-package="" />

代码示例:

  首先我们看一下项目的目录结构:

     

 

 

 目前所有的类都是在CompentScanTest下面

  一个普通的service bean:

@Service
public class UserService {


}

一个配置类,这里会使用 @ComponentScan 注解:

@ComponentScan
public class ScanConfig {


}

一个测试类:

public class CompentScanTest {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(ScanConfig.class);
        Stream.of(ac.getBeanDefinitionNames()).forEach(System.out::println);
    }
}

运行结果:

 

 

 本包下的加了注解的类都被扫描了,也就是说  @ComponentScan 默认扫描本包下面加了对应注解的类。

可以添加一个熟悉 然后测试 :

@ComponentScan(basePackages = "com.llicat.stage3.springboot")
public class ScanConfig {


}

此时把configuration包下的类也会扫描出来:

 

 

 还有其他的像排除之类的,这里不在复述。

③  @EnableAutoConfiguration 

主要有两个注解: 

 

 @import 的说明:

 xml 中有如下配置:

   <import resource="classpath:spring-dao.xml"/> 引入其他配置文件的配置。

 代码结构:

 

 

一个简单对象类:

public class SimpleBean {

}

配置类,用来把对象加入ioc容器:

@Configuration
public class SimpleBeanConfig {

    @Bean
    public  SimpleBean simpleBean(){
        return  new SimpleBean();
    }
}

测试类:

public class App {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(SimpleBeanConfig.class);
        Stream.of(ac.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println(ac.getBean("simpleBean"));
    }
}

运行结果:

 

 

 扫描了当前包下面存在注解的类。

在other包下面存在一个bean 和对应的配置类,我们修改一下代码:

@Configuration
@Import(OtherBeanConfig.class)
public class SimpleBeanConfig {

    @Bean
    public  SimpleBean simpleBean(){
        return  new SimpleBean();
    }
}

然后运行发现,其他类也被扫描了。区别与compentScan @import 引入动态的配置类,可以使得扫描更加灵活和动态化

 

 

 

 

 

 注意到这一个配置:

 

 

 

 AutoConfigurationImportSelector实现了ImportSelector,importSelector是spring提供的一个用来支持动态注入的配置。这里可以模仿实现:

定义一个接口与两个实现类:

public interface Logger {

    void  doLogger();

}

public class DevLogger implements Logger {
    @Override
    public void doLogger() {
        System.out.println("[DEBUG]...");
    }
}

public class TestLogger implements Logger {
    @Override
    public void doLogger() {
        System.out.println("[INFO]");
    }
}

然后定义一个importSelector:

public class LoggerSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> include = importingClassMetadata.getAnnotationAttributes(EnableAutoSwitchLogger.class.getName());
        Class [] clzs=null==include.get("include")?null:((Class[]) include.get("include"));
        ArrayList<String> classList=new ArrayList<>();
        Stream.of(clzs).forEach(clz->{
            if(clz.isInterface()){
                return;
            }
            classList.add(clz.getName());
        });
        return classList.toArray(new String[classList.size()]);
    }
}

这个类主要是为了获取元数据信息,然后返回需要被扫描注入到ioc的类。

定义@EnableAutoSwitchLogger 并且使用LoggerSelector:

 1 @Target(ElementType.TYPE)
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Inherited
 5 @Import(LoggerSelector.class)
 6 public @interface EnableAutoSwitchLogger {
 7 
 8     /**
 9      * 这里可以指定要返回的日志实现类
10      * @return
11      */
12     Class<?>[] include() default {};
13 }

测试类:

 1 @Configuration
 2 @EnableAutoSwitchLogger(include ={TestLogger.class,DevLogger.class})
 3 public class App {
 4 
 5 
 6     public static void main(String[] args) {
 7 
 8         AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(App.class);
 9         Stream.of(ac.getBeanDefinitionNames()).forEach(System.out::println);
10     }
11 
12 }

测试结果:

 

 

 这个实际上就是springboot实现动态注入的一个关键点。分析AutoConfigurationImportSelector 的核心实现:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
                autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

点进去loadMetadata,会加载配置文件,其实就是条件注解,只有当前扫描的路径下存在条件中配置的类 然后去加载spring.factories中对应的第三方类。为了保证性能,不能是所有依赖的第三方的jar的类都被

加载,而是你配置了,才会去加载。

    protected static final String PATH = "META-INF/"
            + "spring-autoconfigure-metadata.properties";

    private AutoConfigurationMetadataLoader() {
    }

    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, PATH);
    }

getAutoConfigurationEntry方法调用了getCandidateConfigurations 使用了 spring中的spi工具类SpringFactoriesLoader去加载配置文件META-INF/spring.factories中key为
org.springframework.boot.autoconfigure.EnableAutoConfiguration(注解的全限定名),对应的value会作为字符串被返回,然后作为需要被加载到ioc中的信息。spi是用来spring用来把第三方的jar包里面的类加载到
ioc容器的一种技术,需要满足指定路径下指定文件,且key匹配。@configuration 扫包扫不到第三方jar包。

 总结:

   @SpringBootApplication=@ComponentScan+@Configuration+@EnableAutoConfiguration

ComponentScan用来指定当前包路径,在这个路径下的都会被扫描到,@Configuration 用来说明这是一个配置类,可以去加载bean到ioc中。@EnableAutoConfiguration ,核心依AutoConfigurationImportSelector的实现:首先在META-INF/spring.factories 中预先配置了许多默认需要被加载的bean,然后在 spring-autoconfigure-metadata.properties配置了,这些默认bean被加载的条件,

并且通过条件可以过滤不需要被加载的bean,为了满足更大的客户化需求,在使用 @SpringBootApplication 可以指定 exclude属性,这些会作为元素数据,去移除掉不需要加载的类,同时也可以使用@ConditionalOnClass 去指定需要满足某些条件,默认配置中的类才会被加载。