SpringBoot(八)Starter机制 - 自定义Star

作者: 龙二丶 | 来源:发表于2020-01-01 18:40 被阅读0次

    前言

            最近在学习Spring Boot相关的课程,过程中以笔记的形式记录下来,方便以后回忆,同时也在这里和大家探讨探讨,文章中有漏的或者有补充的、错误的都希望大家能够及时提出来,本人在此先谢谢了!

    开始之前呢,希望大家带着几个问题去学习:
    1、SpringBoot Starter 是什么?
    2、这个功能有什么用?
    3、怎么实现的?
    4、这个功能能应用在工作中?
    这是对自我的提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。好了,接下来进入主题。

    1、起源

            在 Spring 时代,搭建一个 Web 应用通常需要在 pom 文件中引入多个 Web 模块相关的 Maven 依赖,如 SpringMvcTomcat 等依赖,而 SpringBoot 则只需引入 spring-boot-starter-web 依赖即可。这就是 SpringBoot 的 Starter 特性,用来简化项目初始搭建以及开发过程,它是一个功能模块的所有 Maven 依赖集合体。接下来,我们进行详细讨论。

    注:本篇文章所用到的 Spring Boot版本是 2.0.3.RELEASE

    2、SpringBoot Starter 原理

            SpringBoot 提供了非常多的 Starter,下面列出常用的几个:

    名称 功能
    spring-boot-starter-web 支持 Web 开发,包括 Tomcat 和 spring-webmvc
    spring-boot-starter-redis 支持 Redis 键值存储数据库,包括 spring-redis
    spring-boot-starter-test 支持常规的测试依赖,包括 JUnit、Hamcrest、Mockito 以及 spring-test 模块
    spring-boot-starter-aop 支持面向切面的编程即 AOP,包括 spring-aop 和 AspectJ
    spring-boot-starter-data-elasticsearch 支持 ElasticSearch 搜索和分析引擎,包括 spring-data-elasticsearch
    spring-boot-starter-jdbc 支持JDBC数据库
    spring-boot-starter-data-jpa 支持 JPA ,包括 spring-data-jpa、spring-orm、Hibernate

    可以看到这些 Starter 的名称都是以 spring-boot-starter 为开头,后面跟着具体的模块名,所有官方的 Starter 遵循相似的命名模式。这些 Starter 其实不包含 Java 代码,核心是它的 pom 文件。我们以 spring-boot-starter-web 为例,来看看该 Starter 的 pom 文件包含的内容。

    先在项目中引入以下依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.0.3.RELEASE</version>
    </dependency>
    

    然后找到引入的 spring-boot-starter-web 依赖的文件夹位置:

    image

    打开该 pom 文件进行查看:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starters</artifactId>
            <version>2.0.3.RELEASE</version>
        </parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.0.3.RELEASE</version>
        <name>Spring Boot Web Starter</name>
        
        ...
        
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>2.0.3.RELEASE</version>
                <scope>compile</scope>
            </dependency>
    
            <!-- 对 json 解析的支持 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-json</artifactId>
                <version>2.0.3.RELEASE</version>
                <scope>compile</scope>
            </dependency>
    
            <!-- 提供 Tomcat 容器。通过这可以看到  ServletWeb 默认的容器是 Tomcat -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <version>2.0.3.RELEASE</version>
                <scope>compile</scope>
            </dependency>
    
            <!-- hibernate 的校验框架 -->
            <dependency>
                <groupId>org.hibernate.validator</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>6.0.10.Final</version>
                <scope>compile</scope>
            </dependency>
    
            <!-- 提供了核心 HTTP 集成,用于集成其它 web 框架的基础结构 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>5.0.7.RELEASE</version>
                <scope>compile</scope>
            </dependency>
    
            <!-- 提供了对 Spring MVC 的支持 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.0.7.RELEASE</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </project>
    

    可以看到,在该 pom 文件中已经定义好了 Web 模块需要的各个组件。之后,引入的 Starter 依赖可以与 SpringBoot 的自动装配特性、外部化配置特性进行无缝衔接,来达到快速开发的目的。关于 SpringBoot 自动装配和外部化配置大家可以分别参考《SpringBoot 自动装配(二)》《SpringBoot 外部化配置(二)》这两篇文章。接下来,通过实现自定义的 Starter 来理解整体逻辑。

    3、自定义 Starter

            先创建一个项目,在该项目中定义 Starter 的内容,然后通过 Maven 将其打成 jar 包,之后在另一个项目中使用该 Starter 。

    3.1 创建 Starter

    1、创建一个 Maven 项目,在其 pom 文件中引入自动装配的依赖,并定义好 Starter 的名称。非官方的 Starter 命名需遵循 xxx-spring-boot-starter 的格式。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.loong</groupId>
        <artifactId>demo-spring-boot-starter</artifactId>
        <version>1.0.0.RELEASE</version>
        <name>demo</name>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
                <version>2.0.3.RELEASE</version>
            </dependency>
        </dependencies>
    
    </project>
    

    2、新建一个 Properties 配置类,用于保存外部化配置文件中定义的配置数据,其中配置文件包括 properties 或 yml 。

    // 定义配置文件中的属性前缀
    @ConfigurationProperties(prefix = "demo")
    public class DemoProperties {
    
        private String name;
    
        private String date;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDate() {
            return date;
        }
    
        public void setDate(String date) {
            this.date = date;
        }
    }
    

    关于外部化配置底层实现,大家可以参考《SpringBoot 外部化配置(二)》这篇文章。

    3、新建一个功能类,主要用来返回 DemoProperties 中的 name 和 date 属性。

    public class DemoService {
    
        private DemoProperties demoProperties;
    
        public DemoService(DemoProperties demoProperties) {
            this.demoProperties = demoProperties;
        }
    
        public String getName() {
            return demoProperties.getName();
        }
    
        public String getDate() {
            return demoProperties.getDate();
        }
    }
    
    

    4、创建自动配置类,在该配置类中完成 Starter 的功能。这里,通过构造器注入 DemoProperties 配置类对象,并初始化 DemoService 功能类。

    @Configuration
    @EnableConfigurationProperties(value = DemoProperties.class)
    public class DemoAutoConfiguration {
    
        private final DemoProperties demoProperties;
    
        public DemoAutoConfiguration(DemoProperties demoProperties) {
            this.demoProperties = demoProperties;
        }
    
        @Bean
        // 当前项目是否包含 DemoService Class 
        @ConditionalOnMissingBean(DemoService.class)
        public DemoService demoService() {
            return new DemoService(demoProperties);
        }
    }
    

    自动配置类是 SpringBoot 自动装配特性不可或缺的一环,关于 SpringBoot 自动装配底层实现,大家可以参考《SpringBoot 自动装配(二)》这篇文章。

    5、自定义初始化器和监听器,这是 SpringBoot 提供的扩展点,主要在 SpringBoot 的不同生命周期执行相应操作。

    public class DemoApplicationContextInitializer implements
            ApplicationContextInitializer<ConfigurableApplicationContext> {
    
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            System.out.println(" DemoApplicationContextInitializer 初始化成功 ");
        }
    }
    
    public class DemoApplicationListener implements ApplicationListener<SpringApplicationEvent> {
    
        @Override
        public void onApplicationEvent(SpringApplicationEvent springApplicationEvent) {
            if (springApplicationEvent instanceof ApplicationStartingEvent) {
                System.out.println(" DemoApplicationListener 监听 ApplicationStartingEvent 事件");
            }
        }
    }
    

    关于初始化器和监听器大家可以参考《Spring Boot SpringApplication启动类(一)》的 2.2 和 2.3 小节 。

    6、在 src/main/resources 目录下创建 META-INF 文件夹,并在文件夹中创建 spring.factories 文件,定义如下内容:

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    com.loong.demo.context.DemoApplicationContextInitializer
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.loong.demo.configuration.DemoAutoConfiguration
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    com.loong.demo.listener.DemoApplicationListener
    

    这是 SpringBoot 的规约,当我们自定义的初始化器、监听器及自动配置类需要被 SpringBoot 读取时,必须定义成该格式。关于原理,在前几篇文章说过,这里不再叙述。

    最后,所有的类已经定义完成,项目结构如下:

    image

    打开通过右侧的 Maven 工具栏,点击 install 打包到本地的 Maven 库。

    image

    之后,自定义的 Starter 就可以使用,我们来测试一下。

    3.2 测试自定义 Starter

    1、在另一个项目中引入该 Starter 的 Maven 依赖:

    <dependency>
        <groupId>com.loong</groupId>
        <artifactId>demo-spring-boot-starter</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>
    

    2、在 properties 文件中定义配置数据:

    demo.name = loong
    demo.date = 2020.01.01
    

    3、在启动类中,获取 DemoService Bean ,并调用它的 getDate 和 getName 方法获取配置文件中的数据:

    @SpringBootApplication
    public class DiveInSpringBootApplication {
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(DiveInSpringBootApplication.class, args);
    
            DemoService bean = run.getBean(DemoService.class);
            System.out.println(bean.getDate() + " === " + bean.getName());
    
        }
    }
    

    最后,查看控制台的输出:

    /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA CE.app...
     DemoApplicationListener 监听 ApplicationStartingEvent 事件
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.0.3.RELEASE)
    
     DemoApplicationContextInitializer 初始化成功 
    2020-01-01 13:14:02.023  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-01-01 13:14:02.189  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6b19b79: startup date [Wed Jan 01 13:13:59 CST 2020]; root of context hierarchy
    2020-01-01 13:14:02.257  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.loong.diveinspringboot.Chapter1.controller.HelloWorldController.helloWorld(java.lang.String)
    2020-01-01 13:14:02.260  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
    2020-01-01 13:14:02.261  INFO 55657 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    2020-01-01 13:14:02.296  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-01-01 13:14:02.296  INFO 55657 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
    2020-01-01 13:14:02.341  WARN 55657 --- [           main] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
    2020-01-01 13:14:02.718  INFO 55657 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
    2020-01-01 13:14:02.726  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    2020-01-01 13:14:02.727  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
    2020-01-01 13:14:02.728  INFO 55657 --- [           main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto protected java.util.Map<java.lang.String, java.util.Map<java.lang.String, org.springframework.boot.actuate.endpoint.web.Link>> org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.links(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    2020-01-01 13:14:02.766  INFO 55657 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
    2020-01-01 13:14:02.822  INFO 55657 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    2020-01-01 13:14:02.826  INFO 55657 --- [           main] c.l.d.C.DiveInSpringBootApplication      : Started DiveInSpringBootApplication in 3.607 seconds (JVM running for 3.984)
    2020.01.01 === loong
    
    

    可以看到,结果正确输出,且初始化器和监听器都已被加载。这里只是一个简单的演示,Starter 较为简单,大家可以根据实际情况实现一个更为复杂的。

    SpringBoot Starter 的内容就介绍到这,如果文章中有错误或者需要补充的请及时提出,本人感激不尽。

    相关文章

      网友评论

        本文标题:SpringBoot(八)Starter机制 - 自定义Star

        本文链接:https://www.haomeiwen.com/subject/rkdgoctx.html