SpringBoot自定义日志Starter(二十五)上

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 一. AOP 实现日志功能一.一 pom.xml 添加依赖一.二 HelloController 中添加方法一.三 日志切面 LogAspect一.四 测试二. 自定义 日志Starter二.一 注解 MyLog二.二 日志展示信息 LogVo二.三 自定义参数配置 MyLogProperties二.四 定义服务 Service二.五 服务配置 LogConfiguration二.六 切面配置 LogAspect

一. AOP 实现日志功能

关于 AOP 切面的知识, 可以看:

云深i不知处 前辈的文章: 切面AOP实现权限校验:实例演示与注解全解

我们在 上一章节的 StarterApply 项目中 添加 切面实现日志的功能

一.一 pom.xml 添加依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--添加我们自定义的依赖-->
        <dependency>
            <groupId>top.yueshushu</groupId>
            <artifactId>starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--添加aop 的依赖信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--添加json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
    </dependencies>

一.二 HelloController 中添加方法

HelloApplication.java 是普通的启动类.

HelloController 添加三个简单的方法

@RestController
public class HelloController {
    // 无参
    @GetMapping("/")
    public OutputResult toHello(){
        return OutputResult.success("无参数响应");
    }
    //相加
    @GetMapping("/add/{a}/{b}")
    public OutputResult add(@PathVariable("a") int a, @PathVariable("b") int b){
        System.out.println("进行添加");
        return OutputResult.success(a+b);
    }
    //可能会出现异常的方法
    @GetMapping("/div/{a}/{b}")
    public OutputResult div(@PathVariable("a") int a, @PathVariable("b") int b){
        return OutputResult.success(a/b);
    }
}

方法可以正常的访问.

一.三 日志切面 LogAspect

package top.yueshushu.learn.aop;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
 * 可以使用
 * @date 2021/10/25 10:53
 * @author zk_yjl
 */
@Slf4j
@Aspect //定义切面的注解
@Component
@Order(1)  // 顺序是第一个
public class LogAspect {
    /**
     * 异常,输出完整的stack trace
     */
    private boolean printFullStackTraceForException = true;
    /**
     * (输入输出)参数最大输出长度. -1表示不限制
     */
    private int paramMaxPrintLength = 20000;
    //定义多个切点的位置, 用 || 分隔
    @Pointcut("(execution(public * top.yueshushu.learn.controller.*.*(..))) " +
            "|| (execution(public * top.yueshushu.learn.controller2.*.*(..)))")
    public void log(){
    }
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }
    @AfterReturning(value = "log()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }
    //主要是这一个 
    @Around("log()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String invokeMethodFullPath = buildInvokeMethodFullPath(joinPoint);
        String requestParams = buildRequestParams(joinPoint);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String method = Optional.ofNullable(attributes).map(attr -> attr.getRequest().getMethod()).orElse(null);
        StringBuilder logInfo = new StringBuilder();
        logInfo.append("request method: ").append(invokeMethodFullPath).append("; ");
        logInfo.append("request type: ").append(method).append("; ");
        logInfo.append("request param: ").append(requestParams).append("; ");
        long startMs =  System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long cost = System.currentTimeMillis() - startMs;
            logInfo.insert(0, "cost(ms): " + cost + "; ");
            logInfo.append("    -----    response: ").append(toJsonString(result));
            log.info(logInfo.toString());
            return result;
        } catch (Throwable throwable) {
            long cost = System.currentTimeMillis() - startMs;
            logInfo.insert(0, "cost(ms): " + cost + "; ");
            if (printFullStackTraceForException) {
                log.error("error. " + logInfo.toString(), throwable);
            } else {
                log.error("error. " + throwable.getMessage() + "; " + logInfo.toString());
            }
            throw throwable;
        }
    }
    private String toJsonString(Object result) {
        String json = JSON.toJSONString(result);
        if (paramMaxPrintLength <= 0) {
            return json;
        }
        if (json.length() > paramMaxPrintLength) {
            return json.substring(0, paramMaxPrintLength) + "...";
        }
        return json;
    }
    private String buildRequestParams(ProceedingJoinPoint point) {
        try {
            Map<String, Object> requestP = new LinkedHashMap<>();
            Method m = ((MethodSignature) point.getSignature()).getMethod();
            Parameter[] parameters = m.getParameters();
            for (int i = 0, iLen = parameters.length; i < iLen; i++) {
                //过滤Request、Response or InputStreamSource对象,防止序列化异常
                Object arg = point.getArgs()[i];
                if (null == arg) {
                    continue;
                }
                if (arg instanceof HttpServletRequest
                        || arg instanceof HttpServletResponse
                        || arg instanceof InputStreamSource
                        || arg instanceof Errors)  {
                    continue;
                }
                requestP.put(parameters[i].getName(), arg);
            }
            // 提前构造入参信息,防方法内修改入参对象,异常时再构造入参会不准
            return toJsonString(requestP);
        } catch (Exception e) {
            log.warn("请求参数构造失败. error msg: " + e.getMessage());
            return "build error";
        }
    }
    private String buildInvokeMethodFullPath(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();
        // 执行方法的路径
        return targetClass.getSimpleName() + " " + signature.getName();
    }
}

一.四 测试

输入网址: http://localhost:8081/Log/add/1/2

4.png

可以发现,日志输出打印了

输入网址: http://localhost:8081/Log/div/2/1

5.png

输入网址 : http://localhost:8081/Log/div/2/0

6.png

可以发现,切面日志是正常工作的.

接下来,将 切面日志做成 自定义Starter 的方式.

二. 自定义 日志Starter

一般都是采用 注解的方式, 哪个方法上添加了相应的注解,就对哪个方法进行日志处理.

二.一 注解 MyLog

MyLog.java

// 适用于方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLog {
    String module() default "默认模块";
    String optType() default "默认类型";
    String description() default "默认说明";
}

二.二 日志展示信息 LogVo

package top.yueshushu.log;
import lombok.Data;
import java.io.Serializable;
/**
 * @ClassName:LogVo
 * @Description 自定义日志的输出展示对象
 * @Author zk_yjl
 * @Date 2021/10/25 10:22
 * @Version 1.0
 * @Since 1.0
 **/
@Data
public class LogVo implements Serializable {
    /**
     @param className 请求的类
     @param methodName 请求的方法名称
     @param params 请求的参数
     @param returnValue 返回值
     @param model 模块  从Log 注解里面拿
     @param optType 操作类型 从Log 注解里面拿
     @param description 操作说明  从 Log 注解里面拿
     @param reqUrl 请求的路径
     @param reqIp 请求的ip地址
     @param reqTime 请求的时间
     @param execTime 执行的时长
     @param excName 异常名称
     @param excInfo 异常的信息
     */
    private String className;
    private String methodName;
    private String params;
    private String returnValue;
    private String model;
    private String optType;
    private String description;
    private String reqUrl;
    private String reqIp;
    private String reqTime;
    private Long execTime;
    private String excName;
    private String excInfo;
    // ... 其他后期扩展字段
    @Override
    public String toString() {
        return "LogVo{" +
                "className='" + className + '\'' +
                ", methodName='" + methodName + '\'' +
                ", params='" + params + '\'' +
                ", returnValue='" + returnValue + '\'' +
                ", model='" + model + '\'' +
                ", optType='" + optType + '\'' +
                ", description='" + description + '\'' +
                ", reqUrl='" + reqUrl + '\'' +
                ", reqIp='" + reqIp + '\'' +
                ", reqTime=" + reqTime +
                ", execTime=" + execTime +
                ", excName='" + excName + '\'' +
                ", excInfo='" + excInfo + '\'' +
                '}';
    }
}

接下来,就跟前面的自定义 Starter 差不多了.

二.三 自定义参数配置 MyLogProperties

MyLogProperties.java

package top.yueshushu.log;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * @ClassName:MyLogProperties
 * @Description 日志配置类
 * @Author zk_yjl
 * @Date 2021/10/25 17:07
 * @Version 1.0
 * @Since 1.0
 **/
@ConfigurationProperties("mylog")
public class MyLogProperties {
    /**
     定义默认的信息
     */
    public static final Long DEFAULT_RUNTIME=0L;
    public static Boolean DEFAULT_EXC_FULL_SHOW=true;
    public static Integer DEFAULT_RESULT_LENGTH=0;
    /**
     * @runTime 方法的运行时长  当方法的运行时间> 设置的值时,才记录。 默认为0
     */
    private Long runTime=DEFAULT_RUNTIME;
    /**
     * @excFullShow 异常的信息 是否全部展示 保存
     */
    private Boolean excFullShow=DEFAULT_EXC_FULL_SHOW;
    /**
     * @resultLength 输出结果的长度  0 表示全部输出
     */
    private Integer resultLength=DEFAULT_RESULT_LENGTH;
    // ...... 其他的默认的信息,后期可以补充其他的
   // ... 构造方法和默认的 setter, gett方法
}

二.四 定义服务 Service

日志的处理,可以单独的打印到控制台,可以放置到数据库里面,也可以输出到文件里面。

这个 定义一个接口和 默认的实现

二.四.一 日志接口 LogService

public interface LogService {
    /**
     * 日志处理
     * @date 2021/10/25 19:56
     * @author zk_yjl
     * @param
     * @return void
     */
   public void logHandler(LogVo logVo);
}

二.四.二 默认的日志接口实现 DefaultLogServiceImpl

打印到控制台

@Log4j2
public class DefaultLogServiceImpl implements LogService{
    /**
     * 默认的日志实现,打印到控制台
     * @date 2021/10/29 17:53
     * @author zk_yjl
     * @param logVo
     * @return void
     */
    @Override
    public void logHandler(LogVo logVo) {
        log.info("默认处理日志:>>>"+logVo);
    }
}

二.五 服务配置 LogConfiguration

@Configuration
@EnableConfigurationProperties(MyLogProperties.class)
public class LogConfiguration {
    @Bean
    public MyLogProperties myLogProperties(){
        return new MyLogProperties();
    }
    /**
     外界没有 LogService 的实现时,用默认的
     */
    @Bean
    @ConditionalOnMissingBean
    public LogService getLogService(){
        return new DefaultLogServiceImpl();
    }
    /**
     * 创建切面
     * @date 2021/10/29 17:57
     * @author zk_yjl
     * @param myLogProperties
     * @param logService
     * @return top.yueshushu.log.LogAspect
     */
    @Bean
    public LogAspect logAspect(MyLogProperties myLogProperties,LogService logService){
        return new LogAspect(myLogProperties,logService);
    }
}

二.六 切面配置 LogAspect

package top.yueshushu.log;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
@Aspect
/**
 * 自定义日志输出AOP切面,定义以添加了MyLog注解的所有方法作为连接点,
 * 这些连接点触发时定义对应正常方法返回时通知以及异常发生时通知
 */
public class LogAspect{
    private LogService logService;
    private MyLogProperties mylogProperties;
    public LogAspect(MyLogProperties myLogProperties,LogService logService){
        this.logService=logService;
        this.mylogProperties=myLogProperties;
    }
    /**
     * 切点连接点:在MyLog注解的位置切入  和 controller 下面进行配置
     */
    @Pointcut(value ="(@annotation(top.yueshushu.log.MyLog)) ||(execution(public * *..controller.*.*(..))))")
    public void doMyLogCut() {
    }
    /**
     * MyLog注解方法执行 Around 触发事件
     * @param joinPoint
     * @param
     */
    @Around(value = "doMyLogCut()")
    public Object logInvoke(ProceedingJoinPoint joinPoint) throws Throwable{
        //记录一下时间,
        long beginTime = System.currentTimeMillis();
        Object keys = joinPoint.proceed();
        long time = System.currentTimeMillis() - beginTime;
        LogVo myLogVO = this.getMyLog(joinPoint, keys,null);
        myLogVO.setExecTime(time);
        /**
          运行的时间长 才执行操作
         */
        if(mylogProperties.getRunTime()<=time){
            logService.logHandler(myLogVO);
        }
        return keys;
    }
    /**
     * 异常发生时的通知
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "doMyLogCut()", throwing = "e")
    public void doExceptionMyLog(JoinPoint joinPoint, Throwable e) {
        LogVo myLogVO = this.getMyLog(joinPoint, null,e);
        //出现异常,执行时间为 -1
        myLogVO.setExecTime(-1L);
        // 异常的,一直都进行操作.
        logService.logHandler(myLogVO);
    }
    /**
     * 获取输出日志实体
     * @param joinPoint 触发的连接点
     * @param e 异常对象
     * @return MyLogVO
     */
    private LogVo getMyLog(JoinPoint joinPoint,Object keys,Throwable e){
       // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        // 输出日志VO
        LogVo myLogVO = new LogVo();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            MyLog opLog = method.getAnnotation(MyLog.class);
            if (opLog != null) {
                myLogVO.setModel(opLog.module());
                myLogVO.setOptType(opLog.optType());
                myLogVO.setDescription(opLog.description());
            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            myLogVO.setClassName(className);
            // 获取请求的方法名
            String methodName = method.getName();
            myLogVO.setMethodName(methodName);
            //请求uri
            String uri = request.getRequestURI();
            myLogVO.setReqUrl(uri);
            myLogVO.setReqIp(getIpAddr(request));
            //操作时间点
            myLogVO.setReqTime(getNowDate());
            //异常名称+异常信息
            if(null != e){
                myLogVO.setExcName(e.getClass().getName());
                myLogVO.setExcInfo(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
            }
            //请求的参数,参数所在的数组转换成json
            String params =  Arrays.toString(joinPoint.getArgs());
            myLogVO.setParams(params);
            //返回值
            if(null != keys && Void.class.getName() != keys){
                StringBuilder result =new StringBuilder( JSONObject.toJSONString(keys));
                if(mylogProperties.getResultLength()==0){
                    //表示全部
                    myLogVO.setReturnValue(result.toString());
                }else{
                   String tempResult=result.substring(0,mylogProperties.getResultLength());
                    myLogVO.setReturnValue(tempResult);
                }
            }
            //输出日志
        } catch (Exception ex) {
           // ex.printStackTrace();
        }
        return myLogVO;
    }
    /**
     * 转换异常信息为字符串
     * @param exceptionName
     * @param exceptionMessage
     * @param elements
     * @return
     */
    private String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        if(mylogProperties.getExcFullShow()){
            for (StackTraceElement stet : elements) {
                strbuff.append(stet + "\n");
            }
            return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        }
        return exceptionName+":"+exceptionMessage;
    }
    /**
     * 获取当前的时间
     * @date 2021/10/26 9:29
     * @author zk_yjl
     * @param
     * @return java.lang.String
     */
    private String getNowDate(){
        Date now=new Date();
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(now);
    }
    /**
     * 获取访问者的ip地址
     * 注:要外网访问才能获取到外网地址,如果你在局域网甚至本机上访问,获得的是内网或者本机的ip
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            //X-Forwarded-For:Squid 服务代理
            String ipAddresses = request.getHeader("X-Forwarded-For");
            if (ipAddresses == null || ipAddresses.length() == 0 ||
                    "unknown".equalsIgnoreCase(ipAddresses)) {
                //Proxy-Client-IP:apache 服务代理
                ipAddresses = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 ||
                    "unknown".equalsIgnoreCase(ipAddresses)) {
                //WL-Proxy-Client-IP:weblogic 服务代理
                ipAddresses = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 ||
                    "unknown".equalsIgnoreCase(ipAddresses)) {
                //HTTP_CLIENT_IP:有些代理服务器
                ipAddresses = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ipAddresses == null || ipAddresses.length() == 0 ||
                    "unknown".equalsIgnoreCase(ipAddresses)) {
                //X-Real-IP:nginx服务代理
                ipAddresses = request.getHeader("X-Real-IP");
            }
            //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
            if (ipAddresses != null && ipAddresses.length() != 0) {
                ipAddress = ipAddresses.split(",")[0];
            }
            //还是不能获取到,最后再通过request.getRemoteAddr();获取
            if (ipAddress == null || ipAddress.length() == 0 ||
                    "unknown".equalsIgnoreCase(ipAddresses)) {
                ipAddress = request.getRemoteAddr();
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }
}

这上面就是一个简单的日志切面配置信息。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
2月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
494 5
|
8月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2043 1
|
8月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
968 0
|
8月前
|
SQL Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
762 0
|
8月前
|
Java API 开发者
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——slf4j 介绍
在软件开发中,`System.out.println()`常被用于打印信息,但大量使用会增加资源消耗。实际项目推荐使用slf4j结合logback输出日志,效率更高。Slf4j(Simple Logging Facade for Java)是一个日志门面,允许开发者通过统一方式记录日志,无需关心具体日志系统。它支持灵活切换日志实现(如log4j或logback),且具备简洁占位符和日志级别判断等优势。阿里巴巴《Java开发手册》强制要求使用slf4j,以保证日志处理方式的统一性和维护性。使用时只需通过`LoggerFactory`创建日志实例即可。
605 0
|
4月前
|
机器学习/深度学习 XML Java
【spring boot logback】日志logback格式解析
在 Spring Boot 中,Logback 是默认的日志框架,它支持灵活的日志格式配置。通过配置 logback.xml 文件,可以定义日志的输出格式、日志级别、日志文件路径等。
708 5
|
7月前
|
消息中间件 运维 监控
智能运维,由你定义:SAE自定义日志与监控解决方案
通过引入 Sidecar 容器的技术,SAE 为用户提供了更强大的自定义日志与监控解决方案,帮助用户轻松实现日志采集、监控指标收集等功能。未来,SAE 将会支持 istio 多租场景,帮助用户更高效地部署和管理服务网格。
521 52
32SpringBoot自定义Starter
32SpringBoot自定义Starter
102 0
32SpringBoot自定义Starter
|
设计模式 Java 机器人
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
|
9月前
|
Java Maven 开发者
编写SpringBoot的自定义starter包
通过本文的介绍,我们详细讲解了如何创建一个Spring Boot自定义Starter包,包括自动配置类、配置属性类、`spring.factories`文件的创建和配置。通过自定义Starter,可以有效地复用公共配置和组件,提高开发效率。希望本文能帮助您更好地理解和应用Spring Boot自定义Starter,在实际项目中灵活使用这一强大的功能。
742 17