SpringBoot自定义日志Starter,可动态拔插配置

2,949 阅读5分钟

在做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)

img

自定义Starter

创建工程

image-20210110102712650

添加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

目录结构如下

image-20210110145850252

打包安装到本地

这里使用maven-publish打包插件,不用插件的可以使用maven打包命令

添加插件

id 'maven-publish'

添加打包信息

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
    repositories {
        mavenLocal()
    }
}
tasks.withType(GenerateModuleMetadata) {
    enabled = false
}

jar {
    enabled = true
}

执行打包任务

image-20210110144525750

测试

新建一个基础项目,添加自己的依赖

implementation 'com.codelong:log-spring-boot-starter:1.0.0'

添加一个接口用于测试

    /**
     * 测试
     */
    @MyLog
    @PostMapping("/testLog")
    public String testLog(@RequestBody Message message) {
        return "日志测试";
    }

修改yml文件(会有我们日志配置的提示)

image-20210110144913031
server:
  port: 8080
  servlet:
    context-path: /
mylog:
  prefix: 请求开始---
  suffix: 请求结束---

启动项目post访问:http://127.0.0.1:8080/testLog

查看控制台日志

image-20210110145321969

拓展动态拔插开关

当我们不用这个功能时,这些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注解就可以了。

image-20210110152004693