Springboot自定义Starter
文章目录
1. 简介
SpringBoot的Starter可以理解为一个可拔插式的插件,它提供一系列便利的依赖描述符,您可以获得所需的所有Spring和相关技术的一站式服务。应用程序只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。用一句话描述,就是springboot的场景启动器。
SpringBoot的start名称定义是有规范的,一般官方提供的starter,命名格式为spring-boot-starter-xxx,例如:spring-boot-starter-aop
,spring-boot-starter-web
,为与官方的starter区分开来,官方建议自定义的starter命名方式为xxx-spring-boot-starter,例如:mybatis-spring-boot-starter
。
2. Starter实现原理
SpringBoot的Starter实现原理,基本上是利用了Java的SPI机制,通过SpringBoot的自动装配,加载classpath目录下所有的META-INF/spring.factories文件,将Starter中的Javabean对象装载到Spring容器中。其本质上还是想办法将对象装载到Spring容器中。
3. 自定义Starter
下面通过实战定义一个属于我们自己的Starter。
3.1 定义需求
首先明确我们要写一个什么样的Starter,要实现什么功能。在这里我们实现一个日志服务的功能starter。
3.2 创建Starter工程
首先创建一个springboot工程,工程目录结构如下:
该工程使用的springboot版本为1.5.22.RELEASE
,pom文件中的依赖如下:
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>1.5.22.RELEASE</spring-boot.version>
<google.guava.version>29.0-jre</google.guava.version>
<alibaba-fastjson.version>1.2.73</alibaba-fastjson.version>
<lombok.version>1.16.22</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springboot-依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!--common依赖-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${alibaba-fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
3.3 自定义配置属性类
我们把该Starter所需要的配置统一放到一个配置类中,我们在config目录下,新建一个LogConfigProperties
配置类,代码如下:
@Data
@Slf4j
@ConfigurationProperties(prefix = "log.config")
public class LogConfigProperties {
private Boolean enable;
private String name;
private String type;
}
通过此配置类,我们可以了解到,我们需要在配置文件中配置三个属性:log.config.enable
、log.config。name
、log.config.type
。
3.4 创建业务类
下面创建一个业务类,我们通过此类来打印日志,同时自定义一个注解,作为打印日志的标识和入口,在添加了注解的方法上面打印入参和出参。
我们定义一个注解,代码如下:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogProfiler {
String value() default "";
}
然后我们定义一个业务类,用来解析注解,并打印日志,我们将打印日志的方式可以分为两种:
第一种是在方法前打印入参,在方法执行后打印出参,
第二种是在方法执行完成后,统一打印入参和出参,
所以我们需要先定义一个接口LogAspect
代码如下:
public interface LogAspect {
}
然后实现该接口。
第一种打印方式:
@Slf4j
@Aspect
public class LogAroundAspect implements LogAspect {
@Setter
@Resource
private LogConfigProperties logConfigProperties;
private static final ParameterNameDiscoverer PND = new DefaultParameterNameDiscoverer();
@PostConstruct
public void init() {
log.info("the log config properties is {}", JSON.toJSONString(logConfigProperties));
}
/**
* 需要解析的注解
*/
@Pointcut("@annotation(com.dd.zeppole.common.annotation.LogProfiler)")
public void pointcut() {
// Do nothing because of X and Y.
}
@Around("pointcut()")
public Object execParamLogAspect(ProceedingJoinPoint joinPoint) throws Throwable {
// 如果停用日志打印,则直接执行原有代码逻辑,跳过打印日志
if (!logConfigProperties.getEnable()) {
return joinPoint.proceed();
}
// 入参内容,此处省略代码
if (log.isInfoEnabled()) {
StringBuilder builder = new StringBuilder();
this.buildRequestMessage(joinPoint, builder);
}
// 执行原有方法逻辑
Object result = joinPoint.proceed();
// 打印出参,此处省略代码
if (log.isInfoEnabled()) {
StringBuilder builder = new StringBuilder();
this.buildResponseMessage(joinPoint, result, builder);
}
return result;
}
}
第二种打印方式:
@Slf4j
@Aspect
public class LogAfterAspect implements LogAspect {
@Setter
@Resource
private LogConfigProperties logConfigProperties;
public LogAfterAspect(LogConfigProperties logConfigProperties) {
this.logConfigProperties = logConfigProperties;
}
private static final ParameterNameDiscoverer PND = new DefaultParameterNameDiscoverer();
@PostConstruct
public void init() {
log.info("the log config properties is {}", JSON.toJSONString(logConfigProperties));
}
@Pointcut("@annotation(com.dd.zeppole.common.annotation.LogProfiler)")
public void pointcut() {
// Do nothing because of X and Y.
}
@Around("pointcut()")
public Object execParamLogAspect(ProceedingJoinPoint joinPoint) throws Throwable {
// 如果停用日志打印,则直接执行原有代码逻辑,跳过打印日志
if (!logConfigProperties.getEnable()) {
return joinPoint.proceed();
}
StringBuilder builder = new StringBuilder();
try {
// 构建入参内容,此处省略代码
if (log.isInfoEnabled()) {
this.buildRequestMessage(joinPoint, builder);
}
// 执行原有方法逻辑
Object result = joinPoint.proceed();
// 构建打印出参,此处省略代码
if (log.isInfoEnabled()) {
this.buildResponseMessage(result, builder);
}
return result;
} finally {
// 最终打印日志
if (log.isInfoEnabled()) {
log.info(builder.toString());
}
}
}
}
3.5 创建自动配置类
我们创建好业务类后,接下来就要创建一个自动配置类,目的是将我们的业务类加载到Spring容器中。
@Slf4j
@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties(LogConfigProperties.class)
public class LogProfilerAutoConfiguration {
@Configuration
@ConditionalOnClass({LogAspect.class})
@ConditionalOnMissingBean({LogAspect.class})
@EnableConfigurationProperties(LogConfigProperties.class)
@ConditionalOnProperty(
name = {"log.config.type"},
havingValue = "around",
matchIfMissing = true
)
static class LogAround {
@Bean
public LogAspect config() {
LogAspect logAspect = new LogAroundAspect();
log.info("加载环绕日志对象");
return logAspect;
}
}
@Configuration
@ConditionalOnClass({LogAspect.class})
@ConditionalOnMissingBean({LogAspect.class})
@EnableConfigurationProperties(LogConfigProperties.class)
@ConditionalOnProperty(
name = {"log.config.type"},
havingValue = "after",
matchIfMissing = true
)
static class LogAfter {
@Bean
public LogAspect config(LogConfigProperties logConfigProperties) {
LogAspect logAspect = new LogAfterAspect(logConfigProperties);
log.info("加载后置日志对象");
return logAspect;
}
}
}
此自动配置类会根据log.config.type
的类型来加载相应的解析类,从而达到打印日志的目的。
3.6 创建spring.factories文件
光创建自动配置类是没有用的,因为引用该starter的地方,也就是业务代码,Spring容器扫描的路径并不会扫描到这个自动配置类,所以我们需要想办法让Spring容器扫描到,即我们需要将这个自动配置类,配置到spring.factories
配置文件中。
我们在resources/META-INF
目录下新建spring.factories
文件,添加如下内容:
#org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.dd.zeppole.common.config.LogProfilerAutoConfiguration
至此,我们的自定义Starter工程算是创建完成了。我们接下来要做的就是验证下我们的成果。
3.7 安装jar包
我们可以通过maven命令将我们的Starter的jar包,安装到maven仓库,命令:
mvn clean install
或者直接在IDEA中使用maven工具来进行打包部署。
4. 使用Starter
我们自定义的Starter安装到maven仓库后,我们就可以在我们的项目中来使用了,首先我们要在pom文件中依赖我们的Starter。
<dependency>
<groupId>com.dd.zeppole</groupId>
<artifactId>logprofiler-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
然后来定义一个我们的Controller,并在方法上添加我们的注解。
@LogProfiler(value = "自定义注解打印日志")
@RequestMapping("/demo")
public Object demo(String key) {
log.info("key is {}", key);
JSONObject jsonObject = new JSONObject();
jsonObject.put("key", key);
return jsonObject;
}
最后,不要忘记在配置文件中配置我们Starter所需要的配置。
// 开启日志
log.config.enable=true
// 日志名称
log.config.name=myName
// 日志类型
log.config.type=around
然后访问下我们的请求,看下日志、
我们修改下配置中的og.config.type=after
,然后在重启项目试一下。
至此说明我们自定义的Starter生效了。