第二章 引入必備依賴(LomBok、Swagger、fastjon、logback、ELK)
1、整合Lombok 。?
<!--Lombok 是一種 Java? 實(shí)用工具,可用來幫助開發(fā)人員消除 Java 的冗長,尤其是對于簡單的 Java 對象(POJO)。它通過注解實(shí)現(xiàn)這一目的。? 自動生成get set 方法-->
<dependency>
? ? <groupId>org.projectlombok</groupId>
? ? <artifactId>lombok</artifactId>
</dependency>
2、 引入依賴knife4j(swagger)
?
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強(qiáng)解決方案,前身是swagger-bootstrap-ui,取名knife4j是希望她能像一把匕首一樣小巧,輕量,并且功能強(qiáng)悍!.
?
<!-- 1. swagger-bootstrap-ui 目前改名為 knife4j -->
<!-- 2. 實(shí)現(xiàn) swagger-bootstrap-ui 的自動化配置 -->
<!-- 3. 因?yàn)?knife4j-spring 已經(jīng)引入 Swagger 依賴,所以無需重復(fù)引入 -->
<!--? ? knife4j knife4j是為Java MVC框架生成Api文檔的增強(qiáng)解決方案。-->
<dependency>
? ? <groupId>com.github.xiaoymin</groupId>
? ? <artifactId>knife4j-spring</artifactId>
? ? <version>2.0.4</version>
</dependency>
<dependency>
? ? <groupId>com.github.xiaoymin</groupId>
? ? <artifactId>knife4j-spring-ui</artifactId>
? ? <version>2.0.4</version>
</dependency>
?
?
配置文件
新建Swagger的配置文件SwaggerConfiguration.java文件,創(chuàng)建springfox提供的Docket分組對象,代碼如下:
@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfiguration {
? ? @Bean(value = "defaultApi2")
? ? public Docket defaultApi2() {
? ? ? ? Docket docket=new Docket(DocumentationType.SWAGGER_2)
? ? ? ? ? ? ? ? .apiInfo(apiInfo())
? ? ? ? ? ? ? ? //分組名稱
? ? ? ? ? ? ? ? .groupName("2.X版本")
? ? ? ? ? ? ? ? .select()
? ? ? ? ? ? ? ? //這里指定Controller掃描包路徑
? ? ? ? ? ? ? ? .apis(RequestHandlerSelectors.basePackage("com.swagger.bootstrap.ui.demo.new2"))
? ? ? ? ? ? ? ? .paths(PathSelectors.any())
? ? ? ? ? ? ? ? .build();
? ? ? ? return docket;
? ? }
}
以上有兩個注解需要特別說明,如下表:
注解
說明
@EnableSwagger2
該注解是Springfox-swagger框架提供的使用Swagger注解,該注解必須加。
@EnableKnife4j
該注解是knife4j提供的增強(qiáng)注解,Ui提供了例如動態(tài)參數(shù)、參數(shù)過濾、接口排序等增強(qiáng)功能,如果你想使用這些增強(qiáng)功能就必須加該注解,否則可以不用加。
增強(qiáng)功能
?
1、給接口添加作者。
添加作者需要使用knife4j提供的增強(qiáng)注解@ApiOperationSupport
?
接口代碼示例如下:
@ApiOperationSupport(author = "xiaoymin@foxmail.com")@ApiOperation(value = "寫文檔注釋我是認(rèn)真的")@GetMapping("/getRealDoc")public Rest<RealDescription> getRealDoc(){
? ? Rest<RealDescription> r=new Rest<>();
? ? try {
? ? ? ? TimeUnit.SECONDS.sleep(1);
? ? } catch (InterruptedException e) {
? ? ? ? e.printStackTrace();
? ? }
? ? r.setData(new RealDescription());
? ? return r;}
?
在文檔中顯示效果如下:
?
在2.0.3版本中,收到開發(fā)者反饋希望能在Controller上增加作者的注解
2、顯示自定義的MD文檔。(意義不大)
?
3、正式使用環(huán)境,屏蔽掉swagger。
?
knife4j.production=true
?
4、接口排序。
?
目前提供的排序規(guī)則主要有2種,分別是:
Controller之間的tags分組排序
Controller下的接口排序
?
Controller之間的排序主要有兩種方式,排序的規(guī)則是倒序,但是排序的最小值必須大于0
?
推薦第一種
第一種,使用@ApiSupport注解中的屬性order,代碼示例如下:
@Api(tags = "2.0.3版本-20200312")@ApiSupport(order = 284)@RestController@RequestMapping("/api/nxew203")public class Api203Constroller {
? ? }
?
第二種情況,使用knife4j提供的增強(qiáng)注解@ApiSort,代碼示例如下:
@Api(tags = "2.0.2版本-20200226")@ApiSort(286)@RestController@RequestMapping("/api/nxew202")public class Api202Controller {
? ??
? ? }
第三種,使用注解@Api中的屬性position,代碼示例如下:
@Api(tags = "2.0.2版本-20200226",position = 286)@RestController@RequestMapping("/api/nxew202")public class Api202Controller {
? ??
? ? }
tag下接口排序
針對Controller下的具體接口,排序規(guī)則是使用Knife4j提供的增強(qiáng)注解@ApiOperationSupport中的order字段,代碼示例如下:
?
@ApiOperationSupport(order = 33)@ApiOperation(value = "忽略參數(shù)值-Form類型")@PostMapping("/ex")public Rest<LongUser> findAll(LongUser longUser) {
? ? Rest<LongUser> r=new Rest<>();
? ? r.setData(longUser);
? ? return r;}
?
5、開啟調(diào)試動態(tài)請求參數(shù)
個性化設(shè)置功能里,可以開啟對參數(shù)的動態(tài)調(diào)試,可以手動添加參數(shù)。
6、解決參數(shù)排序的問題
由于springfox-swagger2的實(shí)現(xiàn)的問題,程序只能配置為按照英文字母排序。不過我們可以通過編寫插件實(shí)現(xiàn)字段按類變量定義順序排序。
?
package com.example.demo.config;
import static springfox.documentation.schema.Annotations.findPropertyAnnotation;
import static springfox.documentation.swagger.schema.ApiModelProperties.findApiModePropertyAnnotation;
import java.lang.reflect.Field;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;
/*
由于springfox-swagger2的實(shí)現(xiàn)的問題,程序只能配置為按照英文字母排序。不過我們可以通過編寫插件實(shí)現(xiàn)字段按類變量定義順序排序。
yzw 2020-10-27
?*/
@Component
public class CustomApiModelPropertyPositionBuilder implements ModelPropertyBuilderPlugin {
? ? private Log log = LogFactory.getLog(getClass());
? ? @Override
? ? public boolean supports(DocumentationType delimiter) {
? ? ? ? return SwaggerPluginSupport.pluginDoesApply(delimiter);
? ? }
? ? @Override
? ? public void apply(ModelPropertyContext context) {
? ? ? ? Optional<BeanPropertyDefinition> beanPropertyDefinitionOpt = context.getBeanPropertyDefinition();
? ? ? ? Optional<ApiModelProperty> annotation = Optional.absent();
? ? ? ? if (context.getAnnotatedElement().isPresent()) {
? ? ? ? annotation = annotation.or(findApiModePropertyAnnotation(context.getAnnotatedElement().get()));
? ? ? ? }
? ? ? ? if (context.getBeanPropertyDefinition().isPresent()) {
? ? ? ? annotation = annotation.or(findPropertyAnnotation(context.getBeanPropertyDefinition().get(), ApiModelProperty.class));
? ? ? ? }
? ? ? ? if (beanPropertyDefinitionOpt.isPresent()) {
? ? ? ? ? ? ? ? BeanPropertyDefinition beanPropertyDefinition = beanPropertyDefinitionOpt.get();
? ? ? ? ? ? ? ? if (annotation.isPresent() && annotation.get().position() != 0) {
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? AnnotatedField field = beanPropertyDefinition.getField();
? ? ? ? ? ? ? ? Class<?> clazz = field.getDeclaringClass();
? ? ? ? ? ? ? ? Field[] declaredFields = clazz.getDeclaredFields();
? ? ? ? ? ? ? ? Field declaredField;
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? declaredField = clazz.getDeclaredField(field.getName());
? ? ? ? ? ? ? ? } catch (NoSuchFieldException | SecurityException e) {
? ? ? ? ? ? ? ? log.error("", e);
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? int indexOf = ArrayUtils.indexOf(declaredFields, declaredField);
? ? ? ? ? ? ? ? if (indexOf != -1) {
? ? ? ? ? ? ? ? context.getBuilder().position(indexOf);
? ? ? ? ? ? ? ? }
? ? ? ? ?}
? ? }
}
?
?
3、 引入依賴fastjson
2.3.1 引入依賴
<!--阿里巴巴 fastjson-->
? ? ? <dependency>
? ? ? ? ?<groupId>com.alibaba</groupId>
? ? ? ? ?<artifactId>fastjson</artifactId>
? ? ? ? ?<version>1.2.62</version>
? ? ? </dependency>
最新版本 v1.2.72? 修復(fù)AutoType 的安全問題。
2.3.2 實(shí)現(xiàn)功能
實(shí)現(xiàn) javaBean、 jsonString、jsonObject、jsonArray、javalist數(shù)組之間轉(zhuǎn)換。
?
4、引入依賴logback
?
spring已經(jīng)引入logback2.3.1 不需要再次引入依賴。?
關(guān)于日志文件是否應(yīng)該存儲到數(shù)據(jù)庫進(jìn)行結(jié)構(gòu)化分析的問題,建議采用ELK解決。
添加配置文件
在resources下添加? logback-spring.xml?
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <!--
?說明:
?1、日志級別及文件
?日志記錄采用分級記錄,級別與日志文件名相對應(yīng),不同級別的日志信息記錄到不同的日志文件中
?例如:error級別記錄到log_error_xxx.log或log_error.log(該文件為當(dāng)前記錄的日志文件),而log_error_xxx.log為歸檔日志,
?日志文件按日期記錄,同一天內(nèi),若日志文件大小等于或大于2M,則按0、1、2...順序分別命名
?例如log-level-2013-12-21.0.log
?其它級別的日志也是如此。
?2、文件路徑
?若開發(fā)、測試用,在Eclipse中運(yùn)行項(xiàng)目,則到Eclipse的安裝路徑查找logs文件夾,以相對路徑../logs。
?若部署到Tomcat下,則在Tomcat下的logs文件中
?3、Appender
?FILEERROR對應(yīng)error級別,文件名以log-error-xxx.log形式命名
?FILEWARN對應(yīng)warn級別,文件名以log-warn-xxx.log形式命名
?FILEINFO對應(yīng)info級別,文件名以log-info-xxx.log形式命名
?FILEDEBUG對應(yīng)debug級別,文件名以log-debug-xxx.log形式命名
?stdout將日志信息輸出到控制上,為方便開發(fā)測試使用
?-->
?<contextName>logback</contextName>
? ? <!--生成日志目錄,dubug模式生成在項(xiàng)目根目錄,運(yùn)行jar模式生成目錄在jar的同級目錄-->
?<property name="LOG_PATH" value="logs" />
? ? <!-- 日志記錄器,日期滾動記錄 -->
?<appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
? ? ? ? <!-- 正在記錄的日志文件的路徑及文件名 -->
?<file>${LOG_PATH}/log_error.log</file>
? ? ? ? <!-- 日志記錄器的滾動策略,按日期,按大小記錄 -->
?<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
? ? ? ? ? ? <!-- 歸檔的日志文件的路徑,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定,可以將此文件與file指定文件路徑設(shè)置為不同路徑,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
?而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
?<fileNamePattern>${LOG_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
? ? ? ? ? ? <!-- 除按日志記錄之外,還配置了日志文件不能超過2M,若超過2M,日志文件會以索引0開始,
?命名日志文件,例如log-error-2013-12-21.0.log -->
?<timeBasedFileNamingAndTriggeringPolicy
?class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
? ? ? ? ? ? ? ? <maxFileSize>2MB</maxFileSize>
? ? ? ? ? ? </timeBasedFileNamingAndTriggeringPolicy>
? ? ? ? ? ? <!--只保留最近n天的日志-->
?<maxHistory>7</maxHistory>
? ? ? ? ? ? <!--用來指定日志文件的上限大小,那么到了這個值,就會刪除舊的日志-->
?<totalSizeCap>1GB</totalSizeCap>
? ? ? ? ? ? <!--啟動清除超過上限的歷史日志-->
?<cleanHistoryOnStart>true</cleanHistoryOnStart>
? ? ? ? </rollingPolicy>
? ? ? ? <!-- 追加方式記錄日志 -->
?<append>true</append>
? ? ? ? <!-- 日志文件的格式 -->
?<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
? ? ? ? ? ? <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
? ? ? ? ? ? <charset>utf-8</charset>
? ? ? ? </encoder>
? ? ? ? <!-- 此日志文件只記錄info級別的 -->
?<filter class="ch.qos.logback.classic.filter.LevelFilter">
? ? ? ? ? ? <level>error</level>
? ? ? ? ? ? <onMatch>ACCEPT</onMatch>
? ? ? ? ? ? <onMismatch>DENY</onMismatch>
? ? ? ? </filter>
? ? </appender>
? ? <!-- 日志記錄器,日期滾動記錄 -->
?<appender name="FILEWARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
? ? ? ? <!-- 正在記錄的日志文件的路徑及文件名 -->
?<file>${LOG_PATH}/log_warn.log</file>
? ? ? ? <!-- 日志記錄器的滾動策略,按日期,按大小記錄 -->
?<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
? ? ? ? ? ? <!-- 歸檔的日志文件的路徑,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定,可以將此文件與file指定文件路徑設(shè)置為不同路徑,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
?而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
?<fileNamePattern>${LOG_PATH}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
? ? ? ? ? ? </fileNamePattern>
? ? ? ? ? ? <!-- 除按日志記錄之外,還配置了日志文件不能超過2M,若超過2M,日志文件會以索引0開始,
?命名日志文件,例如log-error-2013-12-21.0.log -->
?<timeBasedFileNamingAndTriggeringPolicy
?class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
? ? ? ? ? ? ? ? <maxFileSize>2MB</maxFileSize>
? ? ? ? ? ? </timeBasedFileNamingAndTriggeringPolicy>
? ? ? ? ? ? <!--只保留最近n天的日志-->
?<maxHistory>7</maxHistory>
? ? ? ? ? ? <!--用來指定日志文件的上限大小,那么到了這個值,就會刪除舊的日志-->
?<totalSizeCap>1GB</totalSizeCap>
? ? ? ? ? ? <!--啟動清除超過上限的歷史日志-->
?<cleanHistoryOnStart>true</cleanHistoryOnStart>
? ? ? ? </rollingPolicy>
? ? ? ? <!-- 追加方式記錄日志 -->
?<append>true</append>
? ? ? ? <!-- 日志文件的格式 -->
?<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
? ? ? ? ? ? <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
? ? ? ? ? ? <charset>utf-8</charset>
? ? ? ? </encoder> <!-- 此日志文件只記錄info級別的 -->
?<filter class="ch.qos.logback.classic.filter.LevelFilter">
? ? ? ? ? ? <level>warn</level>
? ? ? ? ? ? <level>debug</level>
? ? ? ? ? ? <onMatch>ACCEPT</onMatch>
? ? ? ? ? ? <onMismatch>DENY</onMismatch>
? ? ? ? </filter>
? ? </appender>
? ? <!-- 日志記錄器,日期滾動記錄 -->
?<appender name="FILEINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
? ? ? ? <!-- 正在記錄的日志文件的路徑及文件名 -->
?<file>${LOG_PATH}/log_info.log</file>
? ? ? ? <!-- 日志記錄器的滾動策略,按日期,按大小記錄 -->
?<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
? ? ? ? ? ? <!-- 歸檔的日志文件的路徑,例如今天是2013-12-21日志,當(dāng)前寫的日志文件路徑為file節(jié)點(diǎn)指定,可以將此文件與file指定文件路徑設(shè)置為不同路徑,從而將當(dāng)前日志文件或歸檔日志文件置不同的目錄。
?而2013-12-21的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
?<fileNamePattern>${LOG_PATH}/info/log-info-%d{yyyy-MM-dd}.%i.log
? ? ? ? ? ? </fileNamePattern>
? ? ? ? ? ? <!-- 除按日志記錄之外,還配置了日志文件不能超過2M,若超過2M,日志文件會以索引0開始,
?命名日志文件,例如log-error-2013-12-21.0.log -->
?<timeBasedFileNamingAndTriggeringPolicy
?class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
? ? ? ? ? ? ? ? <maxFileSize>2MB</maxFileSize>
? ? ? ? ? ? </timeBasedFileNamingAndTriggeringPolicy>
? ? ? ? ? ? <!--只保留最近n天的日志-->
?<maxHistory>7</maxHistory>
? ? ? ? ? ? <!--用來指定日志文件的上限大小,那么到了這個值,就會刪除舊的日志-->
?<totalSizeCap>1GB</totalSizeCap>
? ? ? ? ? ? <!--啟動清除超過上限的歷史日志-->
?<cleanHistoryOnStart>true</cleanHistoryOnStart>
? ? ? ? </rollingPolicy>
? ? ? ? <!-- 追加方式記錄日志 -->
?<append>true</append>
? ? ? ? <!-- 日志文件的格式 -->
?<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
? ? ? ? ? ? <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
? ? ? ? ? ? <charset>utf-8</charset>
? ? ? ? </encoder>
? ? ? ? <!-- 此日志文件只記錄info級別的 -->
?<filter class="ch.qos.logback.classic.filter.LevelFilter">
? ? ? ? ? ? <level>info</level>
? ? ? ? ? ? <onMatch>ACCEPT</onMatch>
? ? ? ? ? ? <onMismatch>DENY</onMismatch>
? ? ? ? </filter>
? ? </appender>
? ? <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
? ? ? ? <!--encoder 默認(rèn)配置為PatternLayoutEncoder-->
?<encoder>
? ? ? ? ? ? <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
? ? ? ? ? ? <charset>utf-8</charset>
? ? ? ? </encoder>
? ? ? ? <!--此日志appender是為開發(fā)使用,只配置最底級別,控制臺輸出的日志級別是大于或等于此級別的日志信息-->
?<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
? ? ? ? ? ? <level>debug</level>
? ? ? ? </filter>
? ? </appender>
? ? <logger name="org.springframework" level="WARN" />
? ? <logger name="org.hibernate" level="WARN" />
? ? <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
? ? <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="TRACE"/>
? ? <logger name="org.hibernate.SQL" level="DEBUG"/>
? ? <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>
? ? <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>
? ? <!-- 生產(chǎn)環(huán)境下,將此級別配置為適合的級別,以免日志文件太多或影響程序性能 -->
?<root level="INFO">
? ? ? ? <appender-ref ref="FILEERROR" />
? ? ? ? <appender-ref ref="FILEWARN" />
? ? ? ? <appender-ref ref="FILEINFO" />
? ? ? ? <!-- 生產(chǎn)環(huán)境將請stdout,testfile去掉 -->
?<appender-ref ref="STDOUT" />
? ? </root>
</configuration>
?
5、AOP統(tǒng)一處理切面日志
?
?
6、ELK記錄結(jié)構(gòu)化日志