starter项目
先来看一下Starter的官方解释:
Spring Boot Starter 是一种方便的依赖管理方式,它封装了特定功能或技术栈的所有必要依赖项和配置,使得开发者可以快速地将这些功能集成到Spring Boot项目中。Spring Boot官方提供了一系列的Starters,涵盖了从Web开发到安全、数据访问等多个方面
其实Starter是一种SDK思想,基于SDK高度抽象快速构建功能块或技术块是当下企业技术部门实现技术资产快速开发的利器,基于Spring的starter自然就是最高效的方法。
starter项目结构分析
我们先来看一下mybatis-spring-boot-starter的项目结构
抛开mybatis的jdbc等逐步演化的核心功能依赖,只专注我们想要参观的starter部分可以看到,mybatis-spring-boot-starter被引入后是以两个jar的形式存在,即
- mybatis-spring-boot-autoconfigure
- mybatis-spring-boot-starter
其中,mybatis-spring-boot-starter 项目中并无代码,仅仅一个pom文件指明依赖
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>2.1.4</version>
</parent>
<artifactId>mybatis-spring-boot-starter</artifactId>
<name>mybatis-spring-boot-starter</name>
<properties>
<module.name>org.mybatis.spring.boot.starter</module.name>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--mybatis-starter的代码项目-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 支撑mybatis-starter的其他依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
</project>
也就是说, mybatis-starter与SpringBoot对接的主要逻辑集中在mybatis-spring-boot-autoconfigure 中,mybatis-spring-boot-starter 仅仅是个壳子,它统一管理mybatis在适配SpringBoot中的资源依赖统一管理。
根据mybatis-starter以及其他标准的start开发、SpringBoot的官方资料,我们进行自定义一个starter的形式应该是以maven的多module形式创建一个项目,该项目下包含两个module,一个是XXX-spring-boot-starter,另一个是XXX-spring-boot-autoconfigure,且XXX-spring-boot-starter 仅仅是一个只包含pom依赖的的空白项目。
编写一个自定义的starter包的流程
在之前的SpringBoot中通过自定义注解使用AOP里,我们使用AOP实现了日志监听,这里还是以这个为例子,将它改造为一个开箱即用的starter。
第一步:创建module
根据SpringBoot的官方结构,创建module项目lognote-spring-boot
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>lognote-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>lognot-spring-boot-starter</module>
<module>lognote-spring-boot-autoconfgure</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
lognote-spring-boot下分别包含两个module,作为公共对外的lognote-spring-boot-starter和与SpringBoot框架完成自动注入对接的lognote-spring-boot-autoconfigure
第二步:管理依赖
根据上述分析mybatis的例子,对starter子module的pom进行设置,让其依赖autoconfigure,以下是lognote-spring-boot-starter的pom文件依赖:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>lognote-spring-boot-autoconfgure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
对autoconfigure项目进行所需依赖引入,引入对接SpringBoot的核心依赖autoconfigure和一些常用的依赖,以下是lognote-spring-boot-autoconfigure的pom文件 :
<dependencies>
<!-- starter包必须引入该依赖,完成对SpringBoot的适配 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<!--其他相关依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.8</version>
</dependency>
</dependencies>
第三步:autoconfigure项目中增加配置文件
剩下的相关功能及相关配置代码均在lognote-spring-boot-autoconfigure项目中完成
使用新建基础配置启动类:
@Configuration
@ComponentScan(basePackages = "org.example.lognote.*")
public class ApplicationConfiguration {
}
配置spring.factories文件
在resources目录下新建META-INFO目录,新建spring.factories文件,文件中指定lognote-spring-boot-autoconfigure项目的启动类路径即可:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.example.lognote.ApplicationConfiguration
配置yml文件及相关配置内容(可选)
可根据组件情况是否进行yml相关配置,这里进行是否启用日志记录的开关配置,需要在yml中增加配置参数:
#yml文件下新增配置,默认为关闭
lognote:
enable: false
当被其他SpringBoot项目引用时,可在其yml文件中进行配置即可。
创建参数的实体类
@ConfigurationProperties(prefix = "lognote")
@Data
@Configuration
public class LogNoteProperties {
private Boolean enable = false;
}
创建判定条件逻辑类,可基于配置内容,决定是否注入或者使用内部相关功能,(在下面具体代码中会使用该条件判定)
public class LogNoteCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String able= conditionContext.getEnvironment().getProperty("lognote.enable");
if(null!= able && Boolean.parseBoolean(able) ){
return true;
}
return false;
}
这里提供的是基于实现条件接口进行自定义代码的逻辑处理方式来判定条件,也可以使用:@ConditionalOnProperty等注解来实现这个诉求
第四步: 封装功能编写代码
上述内容完成后,对于lognote-spring-boot-autoconfigure来说,直接进行相关代码的编写,就像在SpringBoot项目中一样,可以直接使用SpringBoot中的相关注解等信息,我们在starter中创建的bean以及一些对象,在starter被SpringBoot的项目引用后,会一并交由引用方的Spring上下文去管理。
相关AOP及注解内容、逻辑细节可在 SpringBoot中通过自定义注解使用AOP中了解,这里仅仅放一下核心代码部分。
首先定义一个注解:
@Target(ElementType.METHOD) // 指定注解的适用范围
@Retention(RetentionPolicy.RUNTIME) //指定运行时
//根据条件确定该注解是否生效
@Conditional(LognoteCondition.class)
public @interface LogNote {
//方法描述
String desc() default "";
//是否记录方法执行耗时
boolean timeSpan() default true;
}
进行切面逻辑开发
@Component
@Aspect
@Slf4j(topic = "LogNote")
public class LogNoteAspect {
@Around("@annotation(org.example.LogNote)")
public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
//获取被调用方法
Method method = signature.getMethod();
//取出被调用方法的注解,方便后续使用注解中的属性
ApiLog loglinstener = method.getAnnotation(ApiLog.class);
log.info("----------------------method[{}]start--------------------",method.getName());
log.info("方法描述:{}",loglinstener.desc());
log.info("参数 :{}",point.getArgs());
long startTime = System.currentTimeMillis();
Object proceed = point.proceed();
long endTime = System.currentTimeMillis();
log.info("耗时:{}ss",endTime-startTime);
log.info("----------------------method[{}] end--------------------\n",method.getName())
return proceed;
}
}
第五步:测试&打包
使用maven将整个lognote-spring-boot-starter项目进行打包,将该依赖引入到自己项目中,在yml中进行相关配置开启,然后进行使用:
<!-- 在相关项目中引入自定义的lognote-spring-boot-starter依赖 -->
<dependency>
<groupId>org.example</groupId>
<artifactId>lognote-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
使用:
@LogNote(desc = "执行Es查询")
public JSONObject seachEsData(String indexName, SearchSourceBuilder searchSourceBuilder) {
JSONObject resultMap = new JSONObject();
.......
return resultMap;
}
运行效果:
2023-06-05 20:00:00 [LogNote] :--------------------method[searchESData]start---------------
2023-06-05 20:00:00 [LogNote] :方法描述:执行Es查询
2023-06-05 20:00:00 [LogNote] :参数 :{"query":{"match_all:{}","size":1,"from":0}},log
2023-06-05 20:00:00 [LogNote] :耗时 : 58ss
2023-06-05 20:00:00 [LogNote] :--------------------method[searchESData] end---------------
补充:starter项目的规范结构
结合上面的例子,其实不难发现,starter的开发核心是与SpringBoot进行连接,包括Spring上下文的串通、SpringBoot的自动化配置串通等,其核心是在于lognote-spring-boot-autoconfigure,甚至直接进行lognote-spring-boot-configure开发,第三方直接引用lognote-spring-boot-configure都可以。亦或者只创建一个lognote-spring-boot-starter项目,把原本写在autoconfigure的代码和spring.factories配置放到里面都行,这样甚至更简便,那么官方为什么还要引导使用module的形式呢。此种方式可以参考这个项目
其实官方的推荐是站在组件的角度去考虑,starter是以组件级为基础单位,所以采用module的方式进行开发具有更好的解耦性,可以明确层次的组件边界的概念。
使用该结构的优势主要有两点
组件开发管理规范化
使用module可以使组件的开发更加规范化,同一组件相关逻辑和内容可以集中在一个module中而不是四散的各个独立的项目。
另外,对于开发中的依赖管理也可以更加友好,例如原本在lognote-spring-boot-autoconfigure中的一堆依赖,目前仅仅是单独在autoconfigure中,如果再有新的组件扩展需要,新项目可能还要再额外进行引入依赖,这中间各个项目的版本管理以及组件本身的版本管理, 如果使用module集中管理的方式,组件相关的扩展以及核心代码都在XXX-spring-boot这个父项目中,各子包、组件所需的依赖也可以统一在一处管理,对于组件的版本、发布都是十分规范的。
例如上述例子中,可以将autoconfigure的通用依赖统一放到lognote-spring-boot这个父类的pom中统一引入管理
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>lognote-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>lognot-spring-boot-starter</module>
<module>lognote-spring-boot-autoconfgure</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- 公共依赖 -->
<dependencyManagement>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.8</version>
</dependency>
</dependencyManagement>
组件的扩展和演进更友好
XXX-spring-boot-starter作为整个组件的唯一出口,是对外交流的,所以只需要是“接口”、“封面"性质即可,autoconfigure是作为与SpringBoot的Spring上下文和自动化配置对接的一个衔接逻辑,本质也是在对接层,而核心的组件则可以以jar进行灵活开发。例如mybatis,它的核心jdbc以及mybtais相关逻辑组件,都可以独立进行开发,最后统一通过autoconfigure进行与SpringBoot进行对接即可兼容SpringBoot,日后再有其他框架,也可以在autoconfigure层进行适配。其核心部分