3.2 Spring Boot自动配置原理
那么,有没有一种方案,可以把上面这些繁杂费时费力的重复性劳动“一键打包、开箱即用”?
接下来,我们就逐步展示Spring Boot是怎样通过自动配置和提供一系列开箱即用的启动器starter来封装上面的复杂性使其简单化的。
3.2.1 Java配置
在整个Spring Boot应用程序中,我们将看不到一个传统意义上的Spring XMI配置文件。其实,在Spring3.x和Spring4.x中就出现了大量简化XML配置的解决方案。例如:
❑组件扫描(Component Scan):Spring去自动发现应用上下文中创建的Bean。
❑自动装配(Autowired):Spring自动创建Bean之间的依赖。
❑通过JavaConfig方式实现Java代码配置Bean。
下面是一个使用Java Config方式配置Thymeleaf视图模板引擎的代码示例:
@Configuration @ComponentScan(basePackages = { "com.easy.Spring Boot"}) @EnableWebMvc // 启用WebMVC配置(关于WebMVC的自定义配置我们将在后面章节中介绍) public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public TemplateResolver templateResolver() {//配置模板解析器 TemplateResolver templateResolver = new ServletContextTemplateResolver(); templateResolver.setPrefix("/WEB-INF/views/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode("HTML5"); templateResolver.setCacheable(false); return templateResolver; } @Bean public SpringTemplateEngine templateEngine() {//配置模板引擎 SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; } @Bean public ThymeleafViewResolver viewResolver() {//配置视图解析器 ThymeleafViewResolver thymeleafViewResolver = new ThymeleafView Resolver(); thymeleafViewResolver.setTemplateEngine(templateEngine()); thymeleafViewResolver.setCharacterEncoding("UTF-8"); return thymeleafViewResolver; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry)
{//静态资源处理器配置 registry.addResourceHandler("/resources/**").addResourceLocations ("/resources/"); } … @Bean(name = "messageSource") public MessageSource configureMessageSource(){//消息源配置 ReloadableResourceBundleMessageSource messageSource = new Reloada bleResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setCacheSeconds(5); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } }
在WebMvcConfig.java配置类中,我们做了如下的配置:
❑将它标记为使用@Configuration注释的Spring配置类。
❑启用基于注释的Spring MVC配置,使用@EnableWebMvc。
❑通过注册TemplateResolver、SpringTemplateEngine、ThymeleafViewResolver Bean来配置Thymeleaf ViewResolver。
❑注册的ResourceHandlers Bean用来配置URI/resources/**静态资源的请求映射到/resources/目录下。
❑配置的MessageSource Bean从classpath路径下的ResourceBundle中的messages-{country-code}.properties消息配置文件中加载i18n消息。
这些样板化的Java配置代码比XML要更加简单些,同时易于管理。而Spring Boot则是引入了一系列的约定规则,将上面的样板化配置抽象内置到框架中去,用户连上面的Java配置代码也将省去。
3.2.2 条件化Bean
Spring Boot除了采用Java、Config方式实现“零XML”配置外,还大量采用了条件化Bean方式来实现自动化配置,本节就介绍这个内容。
1.条件注解@Conditional
假如你想一个或多个Bean只有在应用的路径下包含特定的库时才创建,那么使用这节我们所要介绍的@Conditional注解定义条件化的Bean就再适合不过了。
Spring4.0中引入了条件化配置特性。条件化配置通过条件注解@Conditional来标注。条件注解是根据特定的条件来选择Bean对象的创建。条件注解根据不同的条件来做出不同的事情(简单说就是if else逻辑)。在Spring中条件注解可以说是设计模式中状态模式的一种体现方式,同时也是面向对象编程中多态的应用部分。
常用的条件注解如表3-1所示。
表3-1 常用的条件注解
2.条件注解使用实例
下面我们通过实例来说明条件注解@Conditional的具体工作原理。
1)创建示例工程。
为了精简篇幅,这里只给出关键步骤。首先使用Spring Initializr创建一个Spring Boot工程,选择Web Starter依赖,配置项目名称和存放路径,配置Gradle环境,最后导入到IDEA中,完成工程的创建工作。
2)实现Condition接口。
下面我们来实现org.springframework.context.annotation.Condition接口,实现类是MagicCondition。
实现类的“条件”逻辑是:当application.properties配置文件中存在“magic”配置项,同时当值是true的时候:
magic=true #magic=false
就表示条件匹配。
新建MagicCondition类,实现Condition接口。在IDEA中会自动提示我们实现其中的方法,如图3-1所示。
图3-1 IDEA会自动提示我们实现其中的方法
选择要实现的matches函数,如图3-2所示。
图3-2 选择要实现的matches函数
完整的实现代码如下:
class MagicCondition : Condition { override fun matches(context: ConditionContext, metadata: AnnotatedType Metadata): Boolean { val env = context.getEnvironment() if (env.containsProperty("magic")) // 检查application.properties配置 文件中是否存在magic属性key { val b = env["magic"] //获取magic属性key的值 return b == "true" //如果是true,返回true } return false // 返回false } }
实现这个Condition接口只需要实现matches方法。如果matches方法返回true就创建该Bean,如果返回false则不创建Bean。这就是否创建MagicService Bean的条件。
matches方法中的第1个参数类型ConditionContext是一个接口,它的定义如下:
public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); ClassLoader getClassLoader(); }
ConditionContext中的方法API说明如表3-2所示。
表3-2 ConditionContext中的方法API
matches方法中的第2个参数类型AnnotatedTypeMetadata,则能够让我们检查带有@Bean注解的方法上是否有其他注解。AnnotatedTypeMetadata接口的定义如下:
public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationType); Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annota tionType); MultiValueMap<String, Object> getAllAnnotationAttributes(String annota tionType, boolean classValuesAsString); }
使用isAnnotated()方法,能够判断带有@Bean注解的方法是不是还有其他特定的注解。使用另外的几个方法,我们能够检查@Bean注解的方法上,所标注的其他注解的属性。
例如Spring4使用@Conditional对多环境部署配置文件功能实现的ProfileCondition类的代码如下:
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationA ttributes(Profile.class.getName()); if (attrs ! = null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles((String[]) value)) { return true; } } return false; } return true; } }
我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性:
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes (Profile.class.getName());
然后循环遍历attrs这个Map中的属性“value”的值(包含了Bean的profile名称),使用ConditionContext中的Environment来检查这个value进而决定使用哪个Profile处于激活状态。
3)条件配置类ConditionalConfig。
Spring4提供了一个通用的基于特定条件创建Bean的方式:@Conditional注解。编写条件配置类ConditionalConfig代码如下:
@Configuration @ComponentScan(basePackages = ["com.easy.Spring Boot.demo_conditional_bean"]) class ConditionalConfig { @Bean @Conditional(MagicCondition::class) //指定条件类 fun magicService(): MagicServiceImpl { return MagicServiceImpl() } }
逻辑是当Spring容器中存在MagicCondition Bean,并满足MagicCondition类的条件时,去实例化magicService这个Bean。否则不注册这个Bean。
4)MagicServiceImpl逻辑实现。
MagicServiceImpl业务Bean的逻辑很简单,就是打印一个标识信息。实现代码如下:
class MagicServiceImpl : MagicService { override fun info(): String { return "THIS IS MAGIC" // 打印一个标识信息 } } interface MagicService { fun info(): String }
5)测试MagicController。
我们使用一个HTTP接口来测试条件化Bean的注册结果:
@RestController class MagicController { @GetMapping("magic") fun magic(): String { try { val magicService = SpringContextUtil.getBean("magicService") as MagicService // 从Spring容器中获取magicService Bean return magicService.info() //调用info()方法 } catch (e: Exception) { e.printStackTrace() } return "null" } }
其中SpringContextUtil实现代码如下:
object SpringContextUtil { lateinit var applicationContext: ApplicationContext fun setGlobalApplicationContext(context: ApplicationContext) { applicationContext = context }
fun getBean(beanId: String): Any { return applicationContext.getBean(beanId) } }
在Spring Boot启动入口类中,我们把Spring Boot应用的上下文对象放到Spring Context-Util中的这个applicationContext成员变量中:
@Spring BootApplication class DemoConditionalBeanApplication fun main(args: Array<String>) { val context = runApplication<DemoConditionalBeanApplication>(*args) SpringContextUtil.setGlobalApplicationContext(context) }
完整的项目代码参考示例工程源代码。
6)运行测试。
我们先来测试magic = true。在application.properties中配置:
magic=true
重新启动应用程序,浏览器输入:http://127.0.0.1:8080/magic,输出“THIS IS MAGIC”。
再来测试magic = false. 在application.properties中配置:
magic=false
重新启动应用程序,浏览器输入:http://127.0.0.1:8080/magic,输出:“null”。
这个时候,我们看到应用程序后台日志有如下输出:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'magicService' available …… com.easy.Spring Boot.demo_conditional_bean.controller.MagicController.magic (MagicController.kt:14)
表明magicService这个Bean没有注册到Spring容器中。条件化注册Bean验证OK。
3.2.3 组合注解
组合注解就是将现有的注解进行组合,生成一个新的注解。使用这个新的注解就相当于使用了该组合注解中所有的注解。这个特性还是蛮有用的,例如Spring Boot应用程序的入口类注解@Spring BootApplication就是典型的例子:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Spring BootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExclude Filter.class) }) public @interface Spring BootApplication
早期版本的Spring Boot中,用户需要使用如下三个注解来标注应用入口main类:
❑@Configuration
❑@EnableAutoConfiguration
❑@ComponentScan
在Spring Boot1.2.0中只需用一个统一的注解@Spring BootApplication。