Springboot自定义Starter

本文详细介绍了如何创建一个自定义的SpringBoot Starter,用于实现日志服务功能。从需求定义、Starter工程创建、配置属性类、业务类、自动配置类的编写,到spring.factories文件的配置,最后展示了如何在实际项目中使用该Starter进行日志打印。通过这个过程,读者可以了解到SpringBoot Starter的实现原理和创建步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Springboot自定义Starter

1. 简介

SpringBoot的Starter可以理解为一个可拔插式的插件,它提供一系列便利的依赖描述符,您可以获得所需的所有Spring和相关技术的一站式服务。应用程序只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。用一句话描述,就是springboot的场景启动器。
SpringBoot的start名称定义是有规范的,一般官方提供的starter,命名格式为spring-boot-starter-xxx,例如:spring-boot-starter-aopspring-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.enablelog.config。namelog.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生效了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值