springboot(5)

个人积累,请勿私自转载,转载前请联系
代码及文章资源https://github.com/jedyang/DayDayUp/tree/master/java/springboot
基于SpringBootCookBook的读书笔记,重在个人理解和实践,而非翻译。

编写自己的starter

理解springboot的自动配置

使用springboot开发的时候,和以前相比很强烈的感觉是,简单。完全没有xml配置,也没有过多的注解。这种能力与其说是来自spring,倒不如说是来自java的配置能力。
当我们把一个个starter加到pom里,springboot会对这些依赖进行整合,做出合适的决定,将合适的bean注入的上下文中。

springboot给我们准备了详细的自动配置报告。
查看方法是启动时加上DEBUG=true参数。
如在项目目录下运行DEBUG=true mvn spring-boot:run启动应用程序。
我是使用的idea。

debug.png

启动在控制台可以看到:

report.png

仔细看报告,分为两部分positive match和negative match。
positive match告诉你匹配的配置类。
negative match告诉你为什么不匹配。
在springboot中有很多配置类,在org.springframework.boot.autoconfigure包下。
比如:

    @Configuration
    @ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
    @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
    public class AopAutoConfiguration {

比如@ConditionalOnClass注解要求检查指定的类是否在classpath中存在。
@ConditionalOnProperty检查指定的属性是否存在。等等
满足就是positive match,该配置类会被执行,加载到spring的上下文中。
不满足就是negative match。

创建自己的starter

这一节创建一个自己的starter。 db-count-starter
starter的作用是创建一个CommandLineRunner,然后打印数据库中书的数目。

做法

  1. starter需要时自己单独的一个项目。新建一个当前mvn工程的子项目。
    加一下依赖。

     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot</artifactId>
             <!-- version继承父模块的-->
         </dependency>
         <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-commons</artifactId>
             <version>1.9.3.RELEASE</version>
         </dependency>
     </dependencies>
    
  2. 新建一个包/bookpubstarter/dbcount

  3. 包下新建一个类DbCountRunner。实现CommandLineRunner。

     package org.test.bookpubstarter.dbcount;
     
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.boot.CommandLineRunner;
     import org.springframework.data.repository.CrudRepository;
     
     import java.util.Collection;
     
     public class DbCountRunner implements CommandLineRunner {
         protected final Logger logger = LoggerFactory.getLogger(DbCountRunner.class);
         private Collection<CrudRepository> repositories;
     
         public DbCountRunner(Collection<CrudRepository> repositories) {
             this.repositories = repositories;
         }
     
         @Override
         public void run(String... strings) throws Exception {
             // lamda 需要jdk8
             repositories.forEach(crudRepository -> {
                 logger.info(String.format("%s has %s entries",
                         getRepositoryName(crudRepository.getClass()),
                         crudRepository.count()));
             });
         }
     
         private static String getRepositoryName(Class crudRepositoryClass) {
             for (Class repositoryInterface : crudRepositoryClass.getInterfaces()) {
                 if (repositoryInterface.getName().startsWith("org.test.bookpub.repository")) {
                     return repositoryInterface.getSimpleName();
                 }
             }
             return "UnknownRepository";
         }
     }
    
  4. 像springboot自己的配置类一样。我们需要给commandLineRunner创建一个配置类。

     package org.test.bookpubstarter.dbcount;
     
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.data.repository.CrudRepository;
     
     import java.util.Collection;
     
     @Configuration
     public class DbCountAutoConfiguration {
         @Bean
         public DbCountRunner dbCountRunner(Collection<CrudRepository> repositories) {
             return new DbCountRunner(repositories);
         }
     }
    
  5. 我们需要告诉springboot,我们的jar包里有自动配置文件。
    在main下创建resources/META-INF路径。创建文件spring.factories
    内容是:

     org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.test.bookpubstarter.dbcount.DbCountAutoConfiguration
    
  6. mvn install一下,发布到本地仓库里

  7. 在外层的父pom中加上对starter工程的依赖

     <dependency>
        <groupId>org.test</groupId>
        <artifactId>db-count-starter</artifactId>
        <version>0.0.1-SNAPSHOT</version>
     </dependency>
    
  8. run

     2017-09-07 10:29:16.737  INFO 4720 --- [           main] o.t.b.dbcount.DbCountRunner              : PublisherRepository has 1 entries
     2017-09-07 10:29:16.739  INFO 4720 --- [           main] o.t.b.dbcount.DbCountRunner              : ReviewerRepository has 0 entries
     2017-09-07 10:29:16.742  INFO 4720 --- [           main] o.t.b.dbcount.DbCountRunner              : AuthorRepository has 1 entries
     2017-09-07 10:29:16.744  INFO 4720 --- [           main] o.t.b.dbcount.DbCountRunner              : BookRepository has 1 entries
    

可以看到我们的starter已经运行了。

原理

在应用启动的时候,springboot会使用SpringFactoriesLoader类在配置文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration关键字标识的配置项。
Spring Boot会遍历在各个jar包种META-INF目录下的spring.factories文件来查找这个配置。

除了EnableAutoConfiguration关键字对应的配置文件,还有其他类型的配置文件:

  • org.springframework.context.ApplicationContextInitializer
  • org.springframework.context.ApplicationListener
  • org.springframework.boot.SpringApplicationRunListener
  • org.springframework.boot.env.PropertySourceLoader
  • org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
  • org.springframework.test.contex.TestExecutionListener

配置bean的初始化

在第一节中,我们知道springboot可以根据一些条件来决定配置是否执行。
现在我们尝试一下,对上一节中的DbCountRunner进行配置,没有其他DbCountRunner实例时才会创建,一个单例。

  1. 加一个注解
    @ConditionalOnMissingBean意思是不存在这个bean的条件下,方法可以执行

     @Configuration
     public class DbCountAutoConfiguration {
         @Bean
         @ConditionalOnMissingBean
         public DbCountRunner dbCountRunner(Collection<CrudRepository> repositories) {
             return new DbCountRunner(repositories);
         }
     }
    
  2. run
    可以看到在positive match中

     DbCountAutoConfiguration#dbCountRunner matched:
           - @ConditionalOnMissingBean (types: org.test.bookpubstarter.dbcount.DbCountRunner; SearchStrategy: all) did not find any beans (OnBeanCondition)
    
  3. 然后我们在主启动类中增加手动创建一个dbCountRunner

     @Bean
     public DbCountRunner dbCountRunner(Collection<CrudRepository> repositories) {
         return new DbCountRunner(repositories) {
             public void run(String... args) throws Exception {
                 logger.info("Manually Declared DbCountRunner");
             }
         };
     }
    
  4. 再试一次
    这次发现在negative match中:

     DbCountAutoConfiguration#dbCountRunner:
           Did not match:
              - @ConditionalOnMissingBean (types: org.test.bookpubstarter.dbcount.DbCountRunner; SearchStrategy: all) found bean 'dbCountRunner' (OnBeanCondition)
    

使用@Enable*注解触发配置

有时候我们需要一个starter库的消费者明确的定义是否触发starter的配置类,而不是交由springboot去自动决定。

  1. 注释掉spring.factories中自动配置的配置项

  2. 创建一个自定义注解

     package org.test.bookpubstarter.dbcount;
     
     import org.springframework.context.annotation.Import;
     
     import java.lang.annotation.Documented;
     import java.lang.annotation.ElementType;
     import java.lang.annotation.Retention;
     import java.lang.annotation.RetentionPolicy;
     import java.lang.annotation.Target;
     
     @Target(ElementType.TYPE)
     @Retention(RetentionPolicy.RUNTIME)
     @Import(DbCountAutoConfiguration.class)
     @Documented
     public @interface EnableDbCounting {
     }
    
  3. 使用新创建的自定义注解@EnableDbCounting去注解主启动类BookPubApplication。
    同时把手动创建DbCountRunner的代码注释掉

     @SpringBootApplication
     @EnableScheduling
     @EnableDbCounting
     public class BookPubApplication {
    
  4. run
    一切正常

一般来说,@Component注解的作用范围就是在BookPubApplication所在的目录以及各个子目录,即com.test.bookpub.*,而DbCountAutoConfiguration是在org.test.bookpubstarter.dbcount目录下,因此不会被扫描到。

@EnableDbCounting注解通过@Import(DbCountAutoConfiguration.class)找到对应的配置类,因此通过用@EnableDbCounting修饰BookPubApplication,就是告诉Spring Boot在启动过程中要把DbCountAutoConfiguration加入到应用上下文中。

这样我们将配置的决定权交给了使用者。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容