第二章 引入必备依赖(LomBok、Swagger、fastjon、logback、ELK)
1、整合Lombok 。
<!--Lombok 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。 自动生成get set 方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2、 引入依赖knife4j(swagger)
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍!.
<!-- 1. swagger-bootstrap-ui 目前改名为 knife4j -->
<!-- 2. 实现 swagger-bootstrap-ui 的自动化配置 -->
<!-- 3. 因为 knife4j-spring 已经引入 Swagger 依赖,所以无需重复引入 -->
<!-- knife4j knife4j是为Java MVC框架生成Api文档的增强解决方案。-->
<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文件,创建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提供的增强注解,Ui提供了例如动态参数、参数过滤、接口排序等增强功能,如果你想使用这些增强功能就必须加该注解,否则可以不用加。
增强功能
1、给接口添加作者。
添加作者需要使用knife4j提供的增强注解@ApiOperationSupport
接口代码示例如下:
@ApiOperationSupport(author = "xiaoymin@foxmail.com")@ApiOperation(value = "写文档注释我是认真的")@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版本中,收到开发者反馈希望能在Controller上增加作者的注解
2、显示自定义的MD文档。(意义不大)
3、正式使用环境,屏蔽掉swagger。
knife4j.production=true
4、接口排序。
目前提供的排序规则主要有2种,分别是:
Controller之间的tags分组排序
Controller下的接口排序
Controller之间的排序主要有两种方式,排序的规则是倒序,但是排序的最小值必须大于0
推荐第一种
第一种,使用@ApiSupport注解中的属性order,代码示例如下:
@Api(tags = "2.0.3版本-20200312")@ApiSupport(order = 284)@RestController@RequestMapping("/api/nxew203")public class Api203Constroller {
}
第二种情况,使用knife4j提供的增强注解@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下的具体接口,排序规则是使用Knife4j提供的增强注解@ApiOperationSupport中的order字段,代码示例如下:
@ApiOperationSupport(order = 33)@ApiOperation(value = "忽略参数值-Form类型")@PostMapping("/ex")public Rest<LongUser> findAll(LongUser longUser) {
Rest<LongUser> r=new Rest<>();
r.setData(longUser);
return r;}
5、开启调试动态请求参数
个性化设置功能里,可以开启对参数的动态调试,可以手动添加参数。
6、解决参数排序的问题
由于springfox-swagger2的实现的问题,程序只能配置为按照英文字母排序。不过我们可以通过编写插件实现字段按类变量定义顺序排序。
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的实现的问题,程序只能配置为按照英文字母排序。不过我们可以通过编写插件实现字段按类变量定义顺序排序。
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 修复AutoType 的安全问题。
2.3.2 实现功能
实现 javaBean、 jsonString、jsonObject、jsonArray、javalist数组之间转换。
4、引入依赖logback
spring已经引入logback2.3.1 不需要再次引入依赖。
关于日志文件是否应该存储到数据库进行结构化分析的问题,建议采用ELK解决。
添加配置文件
在resources下添加 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <!--
说明:
1、日志级别及文件
日志记录采用分级记录,级别与日志文件名相对应,不同级别的日志信息记录到不同的日志文件中
例如:error级别记录到log_error_xxx.log或log_error.log(该文件为当前记录的日志文件),而log_error_xxx.log为归档日志,
日志文件按日期记录,同一天内,若日志文件大小等于或大于2M,则按0、1、2...顺序分别命名
例如log-level-2013-12-21.0.log
其它级别的日志也是如此。
2、文件路径
若开发、测试用,在Eclipse中运行项目,则到Eclipse的安装路径查找logs文件夹,以相对路径../logs。
若部署到Tomcat下,则在Tomcat下的logs文件中
3、Appender
FILEERROR对应error级别,文件名以log-error-xxx.log形式命名
FILEWARN对应warn级别,文件名以log-warn-xxx.log形式命名
FILEINFO对应info级别,文件名以log-info-xxx.log形式命名
FILEDEBUG对应debug级别,文件名以log-debug-xxx.log形式命名
stdout将日志信息输出到控制上,为方便开发测试使用
-->
<contextName>logback</contextName>
<!--生成日志目录,dubug模式生成在项目根目录,运行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日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
而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日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
而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日志,当前写的日志文件路径为file节点指定,可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
而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 默认配置为PatternLayoutEncoder-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<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"/>
<!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 -->
<root level="INFO">
<appender-ref ref="FILEERROR" />
<appender-ref ref="FILEWARN" />
<appender-ref ref="FILEINFO" />
<!-- 生产环境将请stdout,testfile去掉 -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
5、AOP统一处理切面日志
6、ELK记录结构化日志