在做SpringBoot开发时,各种starter (场景启动器) 必不可少,它们就像可插拔式的插件,只要在pom文件中引用 springboot 提供的场景启动器, 再进行少量的配置就可以使用相应的功能,但SpringBoot并不能囊括我们的所有使用场景,这时候就需要我们自定义starter来实现定制化功能。
项目源码地址:GitHub
命名规范
官方命名(Springboot旗下)
前缀:spring-boot-starter- 模式:spring-boot-starter-{模块名} 举例:spring-boot-starter-test、spring-boot-starter-log4j2
自定义命名(第三方)
后缀:-spring-boot-starter 模式:{模块}-spring-boot-starter 举例:mybatis-spring-boot-starter
Spring Boot Starter工作原理
- 1.SpringBoot在启动的时候会扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包
- 2.读取spring.factories文件获取配置的自动配置类AutoConfiguration
- 3.将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context)
自定义Starter
创建工程
添加gradle依赖
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
implementation 'org.springframework.boot:spring-boot-starter-aop'
创建日志注解
package com.codelong.log.annotion;
import java.lang.annotation.*;
/**
* 日志注解
*
* @author codelong
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyLog {
}
创建Proprerty类
package com.codelong.log.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
/**
* 日志配置文件类
*
* @author codelong
*/
@ConfigurationProperties(value = "mylog")
@PropertySource(value = "classpath:application.yml", encoding = "UTF-8")
public class MyLogProperties {
/**
* 日志开始前缀
*/
private String prefix;
/**
* 日志结束前缀
*/
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
创建AOP注解切面类
package com.codelong.log.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.util.Arrays;
/**
* MyLog注解切面类
*
* @author codelong
*/
@Aspect
public class MyLogAspect {
private MyLogProperties myLogProperties;
public MyLogAspect(MyLogProperties myLogProperties) {
this.myLogProperties = myLogProperties;
}
@Pointcut("@annotation(com.codelong.log.annotion.MyLog)")
public void logAnnotationAnnotationPointcut() {
}
@Around("logAnnotationAnnotationPointcut()")
public Object logInvoke(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(myLogProperties.getPrefix().concat(Arrays.toString(joinPoint.getArgs())));
Object obj = joinPoint.proceed();
System.out.println(myLogProperties.getSuffix().concat(obj.toString()));
return obj;
}
}
创建自动配置类
要让上面类注入spring容器,需要一个自动配置类
package com.codelong.log.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
*
* @author codelong
*/
@Configuration
@EnableConfigurationProperties(MyLogProperties.class) // 该注解可以使MyLogProperties注入spring容器
public class MyLogAutoConfiguration {
@Bean
public MyLogAspect myLogAspect(@Autowired MyLogProperties myLogProperties) {
return new MyLogAspect(myLogProperties);
}
}
@Configuration //指定这个类是一个配置类 @ConditionalOnXXX //指定条件成立的情况下自动配置类生效 @AutoConfigureOrder //指定自动配置类的顺序 @Bean //向容器中添加组件 @ConfigurationProperties //结合相关xxxProperties来绑定相关的配置 @EnableConfigurationProperties //让xxxProperties生效加入到容器中 @ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置 @ConditionalOnMissingBean:当容器(Spring Context)中没有指定Bean的情况下进行自动配置 @ConditionalOnProperty(prefix = “example.service”,name = "auth", value = “enabled”, matchIfMissing = true),当配置文件中example.service.auth.enabled=true时进行自动配置,如果没有设置此值就默认使用matchIfMissing对应的值 @ConditionalOnMissingBean,当Spring Context中不存在该Bean时。 @ConditionalOnBean:当容器(Spring Context)中有指定的Bean的条件下 @ConditionalOnMissingClass:当类路径下没有指定的类的条件下 @ConditionalOnExpression:基于SpEL表达式作为判断条件 @ConditionalOnJava:基于JVM版本作为判断条件 @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置 @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下 @ConditionalOnWebApplication:当前项目是Web项目的条件下 @ConditionalOnResource:类路径下是否有指定的资源 @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean
配置文件
在resources/META-INF/下创建文件spring.factories,SpringBoot在启动的时候会扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR包,读取spring.factories文件获取配置的自动配置类AutoConfiguration,然后将自动配置类下满足条件(@ConditionalOnXxx)的@Bean放入到Spring容器中(Spring Context),这样使用者就可以直接用来注入,因为该类已经在容器中了
spring.factories文件内容:key是固定的org.springframework.boot.autoconfigure.EnableAutoConfiguration,value可以有多个
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.codelong.log.config.MyLogAutoConfiguration
目录结构如下
打包安装到本地
这里使用maven-publish打包插件,不用插件的可以使用maven打包命令
添加插件
id 'maven-publish'
添加打包信息
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
repositories {
mavenLocal()
}
}
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
jar {
enabled = true
}
执行打包任务
测试
新建一个基础项目,添加自己的依赖
implementation 'com.codelong:log-spring-boot-starter:1.0.0'
添加一个接口用于测试
/**
* 测试
*/
@MyLog
@PostMapping("/testLog")
public String testLog(@RequestBody Message message) {
return "日志测试";
}
修改yml文件(会有我们日志配置的提示)
server:
port: 8080
servlet:
context-path: /
mylog:
prefix: 请求开始---
suffix: 请求结束---
启动项目post访问:http://127.0.0.1:8080/testLog
查看控制台日志
拓展动态拔插开关
当我们不用这个功能时,这些bean还是会注入到spring容器中,这时我们就需要动态拔插这个功能,主要是用到了@ConditionalOnBean这个注解,下面我们来改造一下上面写的log-spring-boot-starter项目。
@ConditionalOnBean:当容器(Spring Context)中有指定的Bean的条件下才问生效配置
创建一个标记类
package com.codelong.log.config;
/**
* 开关标记类
*
* @author codelong
*/
public class LogMarker {
}
创建@Enable注解
package com.codelong.log.annotion;
import com.codelong.log.config.LogMarker;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* EnableMyLog注解将LogMarker注入到spring容器
*
* @author longwang
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(LogMarker.class)
public @interface EnableMyLog {
}
修改Configuration配置类
package com.codelong.log.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
*
* @author codelong
*/
@Configuration
@EnableConfigurationProperties(MyLogProperties.class) // 该注解可以使MyLogProperties注入spring容器
@ConditionalOnBean(LogMarker.class) //当容器中有这个LogMarkerBean就会使得下面配置生效
public class MyLogAutoConfiguration {
@Bean
public MyLogAspect myLogAspect(@Autowired MyLogProperties myLogProperties) {
return new MyLogAspect(myLogProperties);
}
}
这里通过EnableMyLog注解来控制是否启动日志功能,只有使用EnableMyLog注解才会将LogMarkerBean注入到Spring容器内,当Spring容器内有LogMarkerBean才会使MyLogAutoConfiguration生效。
所以当我们要开启日志功能时,在springboot启动类上添加@EnableMyLog注解就可以了。