SpringBoot系列课程(四)-自动化配置原理

网友投稿 219 2022-09-24

SpringBoot系列课程(四)-自动化配置原理

1.前言

当然,作为Spring Boot的精髓,自动配置原理的工作过程往往只有在“面试”的时候才能用得上,但是如果在工作中你能够深入的理解Spring Boot的自动配置原理,将无往不利。

Spring Boot的出现,得益于“​​习惯优于配置​​”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。

2.SpringBoot的入口

我们开发任何一个Spring Boot项目,都会用到如下的启动类

@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

从上面代码可以看出,Annotation定义(​​@SpringBootApplication​​​)和类定义(​​SpringApplication.run​​)最为耀眼,所以要揭开SpringBoot的神秘面纱,我们要从这两位开始就可以了。

3.SpringBootApplication背后的秘密

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {...}

虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

@Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)@EnableAutoConfiguration@ComponentScan

所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:

@Configuration@EnableAutoConfiguration@ComponentScanpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

每次写这3个比较累,所以写一个​​@SpringBootApplication​​方便点。接下来分别介绍这3个Annotation。

4.@Configuration

这里的​​@Configuration​​​对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个​​@Configuration​​​,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。 举几个简单例子回顾下,XML跟config配置方式的区别:

表达形式层面

基于XML配置的方式是这样:

而基于JavaConfig的配置方式是这样:

@Configurationpublic class MockConfiguration{ //bean定义}

任何一个标注了​​@Configuration​​的Java类定义都是一个JavaConfig配置类。

注册bean定义层面

基于XML的配置形式是这样:

...

而基于JavaConfig的配置形式是这样的:

@Configurationpublic class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(); }}

任何一个标注了​​@Bean​​的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

表达依赖注入关系层面

为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:

而基于JavaConfig的配置形式是这样的:

@Configurationpublic class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(dependencyService()); } @Bean public DependencyService dependencyService(){ return new DependencyServiceImpl(); }}

如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。

5.@ComponentScan扫描bean

我们原来使用spring的使用不会在xml中一个一个配置bean,我们在再类上加上​​@Repository​​​,​​@Service​​​,​​@Controller​​​,​​@Component​​​,并且注入时可以使用@AutoWired的注解注入。 这一切的功能都需要我们配置包扫描​​.​​ 然而现在注解驱动开发已经没有了配置文件,不能配置。但是提供了@ComponentScan,我们可以在配置类上面加上这个注解也是一样,并且也能扫描配置包项目的相关注解,也能完成自动注入。

接下来我们先来看扫描组件,后面再看注入

package com.bruceliu.service;import com.bruceliu.bean.User;import org.springframework.stereotype.Service;@Servicepublic class UserService { public User getUser(Long id){ System.out.println("userservice..."); return null; }}

package com.bruceliu.controller;import com.bruceliu.bean.User;import org.springframework.stereotype.Controller;@Controllerpublic class UserController { //先不拷贝页面,直接打印即可 public User getUser(Long id){ System.out.println("usercontroller..."); return null; }}

//注解类==配置文件@Configuration //告诉spring这是一个注解类@ComponentScan("com.bruceliu")public class MainConfig { //相当于在xml中配置了 @Bean("userDao") //指定bean的名字 public UserDao userDao01(){ return new UserDaoImpl(); }}

public class MainConfigTest { @Test public void testIoc(){ ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); for (String beanName : context.getBeanDefinitionNames()) { System.out.println(beanName); } }}

注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

6.@EnableAutoConfiguration

​​@EnableAutoConfiguration​​作为一个复合Annotation,其自身定义关键信息如下:

@SuppressWarnings("deprecation")@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { ...}

这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:

这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

7.自动配置生效

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:

@ConditionalOnBean:当容器里有指定的bean的条件下。@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。@ConditionalOnClass:当类路径下有指定类的条件下。@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。

至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。

而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。

可能到目前为止还是有所疑惑,但面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:

Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。

SpringBoot自动化配置关键组件关系图

mybatis-spring-boot-starter、spring-boot-starter-web等组件的META-INF文件下均含有spring.factories文件,自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,工厂实例来生成组件具体需要的bean。

可以发现其最终实现了ImportSelector(选择器)和BeanClassLoaderAware(bean类加载器中间件),重点关注一下AutoConfigurationImportSelector的selectImports方法。

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

该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。我们知道,如果获取到类信息,spring自然可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组建的类信息在select方法中也是可以被获取到的,不要急我们继续向下分析。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }

该方法中的getCandidateConfigurations方法,通过方法注释了解到,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法,查看该方法

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator();

@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})这个注解的意思是:当存在SqlSessionFactory.class, SqlSessionFactoryBean.class这两个类时才解析MybatisAutoConfiguration配置类,否则不解析这一个配置类,make sence,我们需要mybatis为我们返回会话对象,就必须有会话工厂相关类。

@ConditionalOnMissingBean(MapperFactoryBean.class)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行(该类源码较长,篇幅限制不全粘贴)

因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringCloud服务监控-hystrixDashboard
下一篇:SocialMarketing:百度洗脑广告,我看了30遍,哈哈哈哈!
相关文章

 发表评论

暂时没有评论,来抢沙发吧~