运行文中的代码需要在项目构建中引入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 去指定需要满足某些条件,默认配置中的类才会被加载。