在微服务概念兴起的今天,不少公司转型使用微服务做为架构。在技术选型上Spring Cloud 是很是好的选择,它提供了一站式的分布式系统解决方案,而Spring Cloud中的每一个组件都是基于Spring Boot构建的,Spring Boot提供J2EE一站式解决方案,具备如下优势:java
模块 | java文件数 |
---|---|
spring-boot | 551 |
spring-boot-actuator | 423 |
spring-boot-autoconfigure | 783 |
spring-boot-devtools | 169 |
spring-boot-cli | 180 |
spring-boot-tools | 355 |
spring-boot-autoconfigure spring-boot spring-boot-tools
咱们把SpringBoot源码导入IntelliJ IDEA,查看artifact的所有依赖关系。
IDEA有个Maven Projects窗口,通常在右侧可以找到,若是没有能够从菜单栏打开:View>Tool Windows>Maven Projects;
选择要分析的maven module(idea的module至关于eclipse的project),右击show dependencies,会出来该module的所有依赖关系图,很是清晰细致。
例如,spring-boot-starter-freemarker的依赖图分析以下:react
在spring-boot-build 的pom中,咱们能够看到:git
<modules> <module>spring-boot-dependencies</module> <module>spring-boot-parent</module> <module>spring-boot-tools</module> <module>spring-boot</module> <module>spring-boot-test</module> <module>spring-boot-autoconfigure</module> <module>spring-boot-test-autoconfigure</module> <module>spring-boot-actuator</module> <module>spring-boot-devtools</module> <module>spring-boot-docs</module> <module>spring-boot-starters</module> <module>spring-boot-actuator-docs</module> <module>spring-boot-cli</module> </modules>
其中,在spring-boot-dependencies中,SpringBoot项目维护了一份庞大依赖。这些依赖的版本都是通过实践,测试经过,不会发生依赖冲突的。就这样一个事情,就大大减小了Spring开发过程当中,出现jar包冲突的几率。spring-boot-parent依赖spring-boot-dependencies。github
下面咱们简要介绍一下SpringBoot子modules。web
好比:redis
Spring boot中的starter概念是很是重要的机制,可以抛弃之前繁杂的配置,统一集成进starter,应用者只须要引入starter jar包,spring boot就能自动扫描到要加载的信息。
starter让咱们摆脱了各类依赖库的处理,须要配置各类信息的困扰。Spring Boot会自动经过classpath路径下的类发现须要的Bean,并织入bean。
例如,若是你想使用Spring和用JPA访问数据库,你只要依赖 spring-boot-starter-data-jpa 便可。
目前,github上spring-boot项目的最新的starter列表spring-boot/spring-boot-starters以下:spring
spring-boot-starter spring-boot-starter-activemq spring-boot-starter-actuator spring-boot-starter-amqp spring-boot-starter-aop spring-boot-starter-artemis spring-boot-starter-batch spring-boot-starter-cache spring-boot-starter-cloud-connectors spring-boot-starter-data-cassandra spring-boot-starter-data-couchbase spring-boot-starter-data-elasticsearch spring-boot-starter-data-jpa spring-boot-starter-data-ldap spring-boot-starter-data-mongodb spring-boot-starter-data-mongodb-reactive spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest spring-boot-starter-data-solr spring-boot-starter-freemarker spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-jersey spring-boot-starter-jetty spring-boot-starter-jooq spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix spring-boot-starter-jta-narayana spring-boot-starter-log4j2 spring-boot-starter-logging spring-boot-starter-mail spring-boot-starter-mobile spring-boot-starter-mustache spring-boot-starter-parent spring-boot-starter-reactor-netty spring-boot-starter-security spring-boot-starter-social-facebook spring-boot-starter-social-linkedin spring-boot-starter-social-twitter spring-boot-starter-test spring-boot-starter-thymeleaf spring-boot-starter-tomcat spring-boot-starter-undertow spring-boot-starter-validation spring-boot-starter-web spring-boot-starter-web-services spring-boot-starter-webflux spring-boot-starter-websocket
这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。它的项目依赖图以下:mongodb
能够看出,这些starter只是配置,真正作自动化配置的代码的是在spring-boot-autoconfigure里面。同时spring-boot-autoconfigure依赖spring-boot工程,这个spring-boot工程是SpringBoot的核心。
SpringBoot会基于你的classpath中的jar包,试图猜想和配置您可能须要的bean。
例如,若是你的classpath中有tomcat-embedded.jar,你可能会想要一个TomcatEmbeddedServletContainerFactory Bean (SpringBoot经过获取EmbeddedServletContainerFactory来启动对应的web服务器。经常使用的两个实现类是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。
其余的全部基于Spring Boot的starter都依赖这个spring-boot-starter。好比说spring-boot-starter-actuator的依赖树,以下图:shell
SpringBoot 自动配置主要经过 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @ConfigurationProperties 等几个注解来进行自动配置完成的。
@EnableAutoConfiguration 开启自动配置,主要做用就是调用 Spring-Core 包里的 loadFactoryNames(),将 autoconfig 包里的已经写好的自动配置加载进来。
@Conditional 条件注解,经过判断类路径下有没有相应配置的 jar 包来肯定是否加载和自动配置这个类。
@EnableConfigurationProperties的做用就是,给自动配置提供具体的配置参数,只须要写在application.properties 中,就能够经过映射写入配置类的 POJO 属性中。数据库
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
每次咱们直接直接启动这个启动类,SpringBoot就启动成功了,而且帮咱们配置了好多自动配置类。其中最重要是 @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 {
@SpringBootApplication声明时,引用了三个重要的注解:
之前咱们须要配置的东西,Spring Boot帮咱们自动配置;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration声明时,引用了两个重要的注解:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }
它实际上是注册了一个Bean的定义。
new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的同级以及子级的包组件。
以上图为例,DemoApplication是和demo包同级,可是demo2这个类是DemoApplication的父级,和example包同级
也就是说,DemoApplication启动加载的Bean中,并不会加载demo2,这也就是为何,咱们要把DemoApplication放在项目的最高级中。
能够从图中看出AutoConfigurationImportSelector 继承了 DeferredImportSelector继承了ImportSelector,ImportSelector有一个方法为:selectImports。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
能够看到第九行,它实际上是去加载public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有不少自动配置的
类。以下:
咱们以RedisTemplate为例:
@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
咱们每次在Spring中使用Redis,都会使用到RedisTemplate这个工具类,可是他默认给咱们返回的这个工具类,可能不是很符合咱们的要求。好比:咱们想要开启事务,或者想要改变它默认的序列化。
根据前面的分析,只要咱们在容器中放入一个RedisTemplate Bean便可。
1 @Bean("redisTemplate") 2 public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) { 3 RedisTemplate<Object, Object> template = new RedisTemplate<>(); 4 template.setConnectionFactory(redisConnectionFactory); 5 // 修改序列化为Jackson
6 template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); 7 // 开启事务
8 template.setEnableTransactionSupport(true); 9 return template; 10 }
咱们本身定义咱们的RedisTemplate模板,修改序列化,开启事务等操做。
咱们将咱们本身的Bean加入到IoC容器中之后,他就会默认的覆盖掉原来的RedisTemplate,达到定制的效果。
咱们在以Kafka为例:
假设咱们想要消费的对象不是字符串,而是一个对象呢?好比Person对象,或者其余Object类呢?
而后直接在application.properties修改默认序列化就好,连Bean都不须要本身重写。
相似这种,可使用Spring提供的Json序列化,也能够自动使用第三方框架提供的序列化,好比Avro, Protobuff等
spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.consumer.key-deserializer=com.example.common.MyJson spring.kafka.consumer.value-deserializer=com.example.common.MyJson
在这么多xxxxAutoConfiguration中,咱们以HttpEncodingAutoConfiguration(Http自动编码)为例
1 @Configuration 2 //表示这是一个配置类,之前编写的配置文件同样,也能够给容器中添加组件 3 4 @EnableConfigurationProperties(HttpEncodingProperties.class) 5 //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中 6 7 @ConditionalOnWebApplication 8 //Spring底层@Conditional注解(Spring注解版),根据不一样的条件,若是知足指定的条件,整个配置类里面的配置就会生效;这里是判断当前应用是不是web应用,若是是,当前配置类生效 9 10 @ConditionalOnClass(CharacterEncodingFilter.class) 11 //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; 12 13 @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) 14 //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;若是不存在,判断也是成立的 15 //即便咱们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的; 16 17 public class HttpEncodingAutoConfiguration { 18 19 //他已经和SpringBoot的配置文件映射了 20 private final HttpEncodingProperties properties; 21 22 //只有一个有参构造器的状况下,参数的值就会从容器中拿 23 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { 24 this.properties = properties; 25 } 26 27 @Bean 28 //给容器中添加一个组件,这个组件的某些值须要从properties中获取 29 @ConditionalOnMissingBean(CharacterEncodingFilter.class) 30 //判断容器没有这个组件 31 public CharacterEncodingFilter characterEncodingFilter() { 32 CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); 33 filter.setEncoding(this.properties.getCharset().name()); 34 filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); 35 filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); 36 return filter; 37 } 38 } 39 ....... 40 }
经过上面的类的注解能够看到,经过使用@EnableConfigurationProperties,能够把配置文件中的属性与HttpEncodingProperties类绑定起来而且加入到IOC容器中,进入HttpEncodingProperties类,能够看到他是经过@ConfigurationProperties 注解把配置文件中的spring.http.encoding值与该类的属性绑定起来的。
@ConfigurationProperties(prefix = "spring.http.encoding") public class HttpEncodingProperties
同时咱们能够注意到上面的类中使用了@ConditionalOnClass与@ConditionalOnWebApplication注解,这两个都是@Conditional的派生注解,做用是必须是@Conditional指定的条件成立,才给容器中添加组件,配置里的内容才会生效
下面咱们以@ConditionalOnClass为例,来分析一下他的源代码。
@Conditional(OnClassCondition.class) public @interface ConditionalOnClass {
进入OnClassCondition类,查看他的类继承信息,能够看到他继承SpringBootCondition类,SpringBootCondition又实现了Condition接口
OnClassCondition又override了SpringBootCondition的getMatchOutcome方法,该方法会返回条件匹配结果。
getMatchOutcome方法源代码以下:
public ConditionOutcome getMatchOutcome(ConditionContext context, ..... List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader); ...... List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader); }
能够看到getMatchOutcome中主要调用了getMatches方法
进入getMatches方法
private List<String> getMatches(Collection<String> candidates, MatchType matchType, ClassLoader classLoader) { List<String> matches = new ArrayList<String>(candidates.size()); for (String candidate : candidates) { if (matchType.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; }
getMatches又调用了MatchType的matches方法。
private enum MatchType { PRESENT { @Override public boolean matches(String className, ClassLoader classLoader) { return isPresent(className, classLoader); } }, MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; private static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } } private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return classLoader.loadClass(className); } return Class.forName(className); } public abstract boolean matches(String className, ClassLoader classLoader); }
进入MatchType类中,能够看到他有两个枚举类,进一步看枚举类中的matches的源代码能够发现最终是利用loadClass以及forName 方法,判断类路径下有没有这个指定的类。
下面列举出的是Spring Boot对@Conditional的扩展注解。
用好SpringBoot只要把握这几点: