1. Spring Boot自动配置简介
Spring Boot会根据类路径中的jar包、类,为jar包里的类自动配置,这样可以极大的减少配置的数量。简单点说就是它会根据定义在classpath下的类,自动的给你生成一些Bean,并加载到Spring的Context中。自动配置充分的利用了spring 4.0的条件化配置特性,能够自动配置特定的Spring bean,用来启动某项特性。Spring Boot关于自动配置的源码在spring-boot-autoconfigure-1.3.0.x.jar内。
2. Spring Boot自动配置使用案例
- 启动本地redis,端口为6379,通过keys * 查看
1 "ttlMap" 2 "test1"
- 创建service,直接注入StringRedisTemplate
1 @Service 2 public class RedisExample { 3 @Autowired 4 StringRedisTemplate stringRedisTemplate; 5 6 public Set<String> keys() { 7 return stringRedisTemplate.keys("*"); 8 } 9 }
- 创建测试类
1 @RunWith(SpringRunner.class) 2 @SpringBootTest 3 public class SpringbootRedisDemoApplicationTests { 4 @Autowired 5 RedisExample redisExample; 6 7 @Test 8 public void testRedis() { 9 redisExample.keys().forEach(key->System.out.println(key)); 10 } 11 }
- 输出结果为
1 ttlMap 2 test1
从上面的步骤可以看出,没有任何地方配置读取Redis客户端、Server、Port等信息,那么StringRedisTemplate如何知道怎么访问Redis呢?如果在application.properties中直接配置spring.redis.host, spring.redis.port等信息就会生效。StringRedisTemplate是如何读取配置的呢?答案就是“自动配置”。
3.条件注解(@Conditional)
Spring Boot自动配置的实现就是基于Spring 4.x 根据条件的配置来创建Bean的能力。
@Conditional根据满足某一个特定条件创建一个特定的Bean。比如当一个jar包在一个路径下的时候,自动配置一个或多个Bean;或者只有某个Bean被创建才会创建另外一个Bean。
例子:根据运行的系统不同,创建不同的Bean完成不同的功能。比如是window系统则自动注入WindowsListService。
LinuxCondition.java
1 package com.suns.springboot.ch3.conditional; 2 3 import org.springframework.context.annotation.Condition; 4 import org.springframework.context.annotation.ConditionContext; 5 import org.springframework.core.type.AnnotatedTypeMetadata; 6 7 public class LinuxCondition implements Condition { 8 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { 9 return conditionContext.getEnvironment().getProperty("os.name").contains("Linux"); 10 } 11 }
WindowsCondition.java
1 package com.suns.springboot.ch3.conditional; 2 3 import org.springframework.context.annotation.Condition; 4 import org.springframework.context.annotation.ConditionContext; 5 import org.springframework.core.type.AnnotatedTypeMetadata; 6 7 public class WindowsCondition implements Condition { 8 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { 9 return conditionContext.getEnvironment().getProperty("os.name").contains("Windows"); 10 } 11 }
ListService.java
1 package com.suns.springboot.ch3.conditional; 2 3 public interface ListService { 4 String showListCmd(); 5 }
LinuxListService.java
1 package com.suns.springboot.ch3.conditional; 2 3 public class LinuxListService implements ListService { 4 public String showListCmd() { 5 return "ls"; 6 } 7 }
WindowsListService.java
1 package com.suns.springboot.ch3.conditional; 2 3 import org.springframework.context.annotation.Condition; 4 import org.springframework.context.annotation.ConditionContext; 5 import org.springframework.core.type.AnnotatedTypeMetadata; 6 7 public class WindowsCondition implements Condition { 8 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { 9 return conditionContext.getEnvironment().getProperty("os.name").contains("Windows"); 10 } 11 }
ConditionConfig.java
如果WindowsCondition里面的match方法返回true则新建一个WindowsListService并通过@Bean注入到容器,如果LinuxCondition里面的match方法返回true则新建一个LinuxListService并通过@Bean注入到容器
1 package com.suns.springboot.ch3.conditional; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Conditional; 5 import org.springframework.context.annotation.Configuration; 6 7 @Configuration 8 public class ConditionConfig { 9 10 @Bean 11 @Conditional(WindowsCondition.class) 12 public ListService windowsListService() { 13 return new WindowsListService(); 14 } 15 16 @Bean 17 @Conditional(LinuxCondition.class) 18 public ListService linuxListService() { 19 return new LinuxListService(); 20 } 21 }
Main.java
1 package com.suns.springboot.ch3.conditional; 2 3 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 4 5 public class Main { 6 public static void main(String[] args) { 7 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class); 8 ListService listService = context.getBean(ListService.class); 9 System.out.println(context.getEnvironment().getProperty("os.name") + "系统下的列表命令为: " + listService.showListCmd()); 10 } 11 }
4.组合注解与元注解
所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解,组合注解具备元注解的功能。如果对注解也不清楚可以参考我另外一篇博客:Java注解
在SpringBoot的开发过程中,配置类(config)会经常使用@Configuration和@ComponentScan,我们可以把这2个注解组成一个组合注解。
SunsConfiguration.java
1 package com.suns.springboot.ch3.combination_annotation; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 6 import java.lang.annotation.*; 7 8 @Target(ElementType.TYPE) 9 @Retention(RetentionPolicy.RUNTIME) 10 @Documented 11 @Configuration 12 @ComponentScan 13 public @interface SunsConfiguration { 14 15 String[] value() default {}; 16 }
DemoService.java
1 package com.suns.springboot.ch3.combination_annotation; 2 3 import org.springframework.stereotype.Service; 4 5 @Service 6 public class DemoService { 7 8 public void outputResult() { 9 System.out.println("从组合注解配置照样获得的bean"); 10 } 11 }
DemoConfig.java
1 package com.suns.springboot.ch3.combination_annotation; 2 3 @SunsConfiguration("com.suns.springboot.ch3.combination_annotation") 4 public class DemoConfig { 5 }
Main.java
1 package com.suns.springboot.ch3.combination_annotation; 2 3 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 4 5 public class Main { 6 public static void main(String[] args) { 7 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class); 8 DemoService demoService = context.getBean(DemoService.class); 9 demoService.outputResult(); 10 11 context.close(); 12 } 13 14 }
5. Spring Boot自动化配置详解
Spring Boot最重要的注解@SpringBootApplication,它其实是一个组合元注解(组合了SpringBootConfiguration和EnableAutoConfiguration、ComponentScan):
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Inherited 5 @SpringBootConfiguration 6 @EnableAutoConfiguration 7 @ComponentScan( 8 excludeFilters = {@Filter( 9 type = FilterType.CUSTOM, 10 classes = {TypeExcludeFilter.class} 11 ), @Filter( 12 type = FilterType.CUSTOM, 13 classes = {AutoConfigurationExcludeFilter.class} 14 )} 15 ) 16 public @interface SpringBootApplication { 17 ...... 18 }
@SpringBootConfiguration是Spring Boot配置类,进入SpringBootConfiguration注解,有个@Configuration是来标识某个类是配置类
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Configuration 5 public @interface SpringBootConfiguration { 6 }
其中@EnableAutoConfiguration是Spring Boot自动化配置原理的核心,是开启自动配置功能
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Inherited 5 @AutoConfigurationPackage 6 @Import({EnableAutoConfigurationImportSelector.class}) 7 public @interface EnableAutoConfiguration { 8 Class<?>[] exclude() default {}; 9 10 String[] excludeName() default {}; 11 }
@AutoConfigurationPackage是自动配置包,是找到@SpringBootApplication主类当前路径(见下图)。其中@Import是向容器中导入组件,能够导入Configuration配置类,也能够导入实现了ImportSelector接口的类,还有动态注册Bean。在Registrar类中,获取到@SpringBootApplication主类路径
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Inherited 5 @Import({Registrar.class}) 6 public @interface AutoConfigurationPackage { 7 }
EnableAutoConfigurationImportSelector是将所有需要导入组件以全类名的方式返回,这些组件就会添加到Spring容器中。selectImports方法中获取configurations。List configurations = getCandidateConfigurations(metadata,attributes);
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware { private ConfigurableListableBeanFactory beanFactory; private Environment environment; private ClassLoader beanClassLoader; private ResourceLoader resourceLoader; public EnableAutoConfigurationImportSelector() { } public String[] selectImports(AnnotationMetadata metadata) { try { AnnotationAttributes attributes = this.getAttributes(metadata); List<String> configurations = this.getCandidateConfigurations(metadata, attributes); configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(metadata, attributes); configurations.removeAll(exclusions); configurations = this.sort(configurations); this.recordWithConditionEvaluationReport(configurations, exclusions); return (String[])configurations.toArray(new String[configurations.size()]); } catch (IOException var5) { throw new IllegalStateException(var5); } } ..... }
getCandidateConfigurations方法获取configurations的方法是:List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
1 public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware { 2 3 ...... 4 5 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 6 return SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); 7 } 8 9 ...... 10 }
我们看下loadFactoryNames方法,从这里可以看出Spring Boot在启动的时候从类路径下加载所有的META-INF/spring.factories,包括其他starter里面的spring.factories(比如mybatis-plus-boot-starter)。并从中获取org.springframework.boot.autoconfigure.EnableAutoConfiguration值,将这些值注入到Spring容器中。
1 public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { 2 String factoryClassName = factoryClass.getName(); 3 4 try { 5 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); 6 ArrayList result = new ArrayList(); 7 8 while(urls.hasMoreElements()) { 9 URL url = (URL)urls.nextElement(); 10 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); 11 String factoryClassNames = properties.getProperty(factoryClassName); 12 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); 13 } 14 15 return result; 16 } catch (IOException var8) { 17 throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8); 18 } 19 }
我们看一下spring-boot-autoconfigure-1.5.4.RELEASE.jar中的META-INF/spring.factories
1 # Initializers 2 org.springframework.context.ApplicationContextInitializer= 3 org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer 4 5 # Application Listeners 6 org.springframework.context.ApplicationListener= 7 org.springframework.boot.autoconfigure.BackgroundPreinitializer 8 9 # Auto Configure 10 org.springframework.boot.autoconfigure.EnableAutoConfiguration= 11 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, 12 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, 13 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration, 14 org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration, 15 org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration, 16 org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration, 17 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, 18 org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration, 19 org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration, 20 org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration, 21 org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration, 22 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration, 23 org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration, 24 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration, 25 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration, 26 org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration, 27 org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, 28 org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, 29 org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration, 30 org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration, 31 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration, 32 org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration, 33 org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, 34 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration, 35 org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration, 36 org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration, 37 org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration, 38 org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration, 39 org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration, 40 org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration, 41 org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration, 42 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, 43 org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration, 44 org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration, 45 org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, 46 org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration, 47 org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration, 48 org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration, 49 org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration, 50 org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration, 51 org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration, 52 org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, 53 org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration, 54 org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration, 55 org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration, 56 org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, 57 org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration, 58 org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration, 59 org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration, 60 org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration, 61 org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration, 62 org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration, 63 org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, 64 org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration, 65 org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, 66 org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration, 67 org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration, 68 org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration, 69 org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration, 70 org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration, 71 org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration, 72 org.springframework.boot.autoconfigure.session.SessionAutoConfiguration, 73 org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration, 74 org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration, 75 org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration, 76 org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration, 77 org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration, 78 org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration, 79 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration, 80 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, 81 org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration, 82 org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration, 83 org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration, 84 org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration, 85 org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration, 86 org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration, 87 org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration, 88 org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration, 89 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration, 90 org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration, 91 org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration 92 93 # Template availability providers 94 org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider= 95 org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider, 96 org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider, 97 org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider, 98 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider, 99 org.springframework.boot.autoconfigure.velocity.VelocityTemplateAvailabilityProvider, 100 org.springframework.boot.autoconfigure.web.JspTemplateAvailabilityProvider
最终返回的Configuration如下:
这里我们再自己跟进去看下前面例子中使用到的Redis到底是如何完成自动配置。我们找到RedisAutoConfiguration,我们看到这里使用了们前面讲到的条件注解,@ConditionOnClass表示classpath中拥有这些类时才会构建这个bean,在我们在maven引入Redis相关依赖包后,Spring Boot就会把RedisAutoConfiguration注入到Spring容器;@ConditionOnMissingBean表示不存在这个Bean的时候,Spring Boot就会Spring Boot就会把RedisProperties注入到Spring容器。
1 @Configuration 2 @ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class}) 3 @EnableConfigurationProperties 4 public class RedisAutoConfiguration { 5 public RedisAutoConfiguration() { 6 } 7 8 @Bean( 9 name = {"org.springframework.autoconfigure.redis.RedisProperties"} 10 ) 11 @ConditionalOnMissingBean 12 public RedisProperties redisProperties() { 13 return new RedisProperties(); 14 } 15 ... 16 }
接着我们看下RedisProperties,这里就有默认加载Redis的IP、端口等信息。到这里我们Redis相关的自动配置就已经配置完成。
1 @ConfigurationProperties( 2 prefix = "spring.redis" 3 ) 4 public class RedisProperties { 5 private int database = 0; 6 private String host = "localhost"; 7 private String password; 8 private int port = 6379; 9 private int timeout; 10 private RedisProperties.Pool pool; 11 private RedisProperties.Sentinel sentinel; 12 13 public RedisProperties() { 14 } 15 ... 16 17 }
附件
名称 | 作用 |
@ConditionalOnBean | 当容器里有指定的Bean的条件下 |
@ConditionalOnClass | 当类路径下有指定的类条件下 |
@ConditionalOnExpression | 基于SpEl表达式作为判断条件 |
@ConditionalOnJava | 基于JVM版本作为判断条件 |
@ConditionalOnJndi | 在JNDI存在的条件下查找指定的位置 |
@ConditionalOnMissingBean | 当容器里没有指定Bean的情况下 |
@ConditionalOnMissingClass | 当类路径下没有指定的类的条件下 |
@ConditionalOnNotWebApplication | 当前项目不是Web项目的条件下 |
@ConditionalOnProperty | 指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径是否有指定的值 |
@ConditionalOnSingleCandidate | 当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean |
@ConditionalOnWebApplication | 当前项目为Web项目的条件下 |