自定义一个starter

1. starter工程的命名

starter 是一个开箱即用的组件,减少不必要的重复代码,重复配置。

例如,redis 的 starter,spring-boot-starter-data-redis

Spring 官方定义的 starter 通常命名遵循的格式为 spring-boot-starter-{name}。

例如 spring-boot-starter-web

非官方 starter 命名应遵循 {name}-spring-boot-starter 的格式。

例如,dubbo-spring-boot-starter自定义一个starter

2. 需求

定义 ​@AspectLog​ 注解,用于标注需要打印执行时间的方法

3. 实现

3.1 创建一个SpringBoot项目

这里项目名称为 aspectlog-spring-boot-starter

3.2 导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

关于 spring-boot-configuration-processor 的说明,引自 springBoot 官方文档

Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file ( META-INF/spring-autoconfigure-metadata.properties ). If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time. It is recommended to add the following dependency in a module that contains auto-configurations

大概意思就是:写 starter 时,在 pom 中配置 spring-boot-configuration-processor,在编译时会自动收集配置类的条件,写到一个 META-INF/spring-configuration-metadata.properties 文件中

3.3 编写自动配置逻辑

3.3.1 各种 Conditions
Conditions 描述
@ConditionalOnBean 在存在某个 bean 的时候
@ConditionalOnMissingBean 不存在某个 bean 的时候
@ConditionalOnClass 当前 classpath 可以找到某个类型的类时
@ConditionalOnMissingClass 当前 classpath 不可以找到某个类型的类时
@ConditionalOnResource 当前 classpath 是否存在某个资源文件
@ConditionalOnProperty 当前 jvm 是否包含某个系统属性为某个值
@ConditionalOnWebApplication 当前 spring context 是 web 应用程序才加载
@ConditionalOnNotWebApplication

不是web应用才加载

这里选用 ​@ConditionalOnProperty​。即配置文件中的属性aspectLog.enable=true,才加载我们的配置类。

3.3.2 定义 @AspectLog 注解

该注解用于标注需要打印执行时间的方法

package org.example.aspectlog.interfaces;

import java.lang.annotation.*;

/**
 * 定义AspectLog注解,该注解用于标注需要打印执行时间的方法。
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AspectLog {
    
}
3.3.3 定义配置文件类
package org.example.aspectlog.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = AspectLogProperties.PREFIX)
public class AspectLogProperties {

    public static final String PREFIX = "aspectlog";

    private boolean enable;

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }
}
3.3.4 定义自动配置类
package org.example.aspectlog.configuration;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.PriorityOrdered;

@Aspect
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@EnableConfigurationProperties(AspectLogProperties.class)
@Configuration
@ConditionalOnProperty(prefix = AspectLogProperties.PREFIX, name = "enable", havingValue = "true", matchIfMissing = true)
public class AspectLogAutoConfiguration implements PriorityOrdered {

    private static final Logger LOGGER = LoggerFactory.getLogger(AspectLogAutoConfiguration.class);

    @Around("@annotation(org.example.aspectlog.interfaces.AspectLog)")
    public Object isOpen(ProceedingJoinPoint pjp) throws Throwable {
        String taskName = pjp.getSignature().toString().substring(pjp.getSignature().toString().indexOf(" "), pjp.getSignature().toString().indexOf("("));
        taskName = taskName.trim();
        long time = System.currentTimeMillis();
        Object result = pjp.proceed();
        LOGGER.info("method:{} run:{} ms", taskName, (System.currentTimeMillis() - time));
        return result;
    }

    @Override
    public int getOrder() {
        // 保证事务等切面先执行
        return Integer.MAX_VALUE;
    }
}

上面 ​@ConditionalOnProperty 中的参数说明

prefix = AspectLogProperties.PREFIX,即AspectLogProperties中常量属性PREFIX="aspectlog"

name = "enable",name的名称为enable时

havingValue = "true",上面的name为enable的值为true时才开启

matchIfMissing = true,匹配不到对应的属性的时也开启

即:

当配置文件有aspectLog.enable=true时开启,如果配置文件没有设置aspectLog.enable也开启。

3.4 配置 META-INF/spring.factories

META-INF/spring.factories是spring的工厂机制,在这个文件中定义的类,都会被自动加载。多个配置使用逗号分割,换行用\

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.aspectlog.configuration.AspectLogAutoConfiguration

3.5 打包测试

3.5.1 目录结构

自定义一个starter

3.5.2 mvn install到本地

自定义一个starter

3.5.3 其他项目中引用测试
3.5.3.1 pom引用
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>aspectlog-spring-boot-starter</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
3.5.3.2 测试代码
@RestController
@RequestMapping("/test")
public class TestController {

    @AspectLog
    @GetMapping("/test")
    public String test () throws InterruptedException {
        return "success";
    }
}
3.5.3.3 测试结果

自定义一个starter

上一篇:生活物联网平台创建项目的流程操作


下一篇:RapidJavaEE 项目 开发流程说明