背景
一个简单的功能,就是对特定的service打印入参。按一般的做法我们可能会像下面这样打印
@Override
public void updateConfDict ( MqConfDictBO mqConfDictBO) {
log. info ( "修改小类状态【{}】, id【{}】" , mqConfDictBO. getStatus ( ) , mqConfDictBO. getId ( ) ) ;
. . .
}
这种打印日志的方式相对灵活,但同时灵活的劣势就是不够规范。所以我们可以采用aop的方式对方法进行切面增强
aop
aop的大家可以自行谷歌:https://blog.youkuaiyun.com/q982151756/article/details/80513340
思路
大概思路就是aop切我们的方法,让后获取方法的参数进行打印,我们再稍微扩展一下,切面的同时加一个自定义注解,通过自定义注解去细粒度化这个功能,这样我们可以将灵活和规范二者结合使用,当然对于某些方法可能不太喜欢用规范日志输出,理解。
代码
自定义注解代码,我们加一个参数控制注解的是否开启,扩展,我们也可以将这个开启参数放到配置里面,统一管理。当然还是要看自己实际需求。
import java. lang. annotation. Documented;
import java. lang. annotation. ElementType;
import java. lang. annotation. Retention;
import java. lang. annotation. RetentionPolicy;
import java. lang. annotation. Target;
@Documented
@Target ( { ElementType. METHOD, ElementType. TYPE} )
@Retention ( RetentionPolicy. RUNTIME)
public @interface LogHistory {
boolean isOpen ( ) default true ;
ResourceTypeEnum type ( ) ;
String uuid ( ) default "" ;
ActionEnum action ( ) ;
ActionEnum errorAction ( ) ;
}
切面代码
import com. cmft. cloud. bo. MqHisBO;
import com. cmft. cloud. enums. ActionEnum;
import com. cmft. cloud. enums. AuditStsEnum;
import com. cmft. cloud. enums. ResourceTypeEnum;
import com. cmft. cloud. service. HistoryService;
import com. cmft. cloud. utils. StringUtil;
import lombok. extern. slf4j. Slf4j;
import org. aspectj. lang. JoinPoint;
import org. aspectj. lang. annotation. Aspect;
import org. aspectj. lang. annotation. Before;
import org. aspectj. lang. annotation. Pointcut;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. stereotype. Component;
import java. lang. reflect. Field;
@Slf4j
@Aspect
@Component
public class MyAspect {
@Pointcut ( "execution(* com.cmft.cloud.service.impl.*.*(..)) && @annotation(log)" )
public void pointCut ( LogHistory log) {
}
@Before ( "pointCut(lg)" )
public void before ( JoinPoint jp, LogHistory lg) {
log. info ( "包名【{}】, 方法名[{}], 入参[{}]" , jp. getSignature ( ) . getDeclaringTypeName ( ) , jp. getSignature ( ) . getName ( ) , jp. getArgs ( ) [ 0 ] . toString ( ) ) ;
if ( ! lg. isOpen ( ) || jp. getArgs ( ) . length != 1 ) {
return ;
}
ActionEnum action = null;
if ( lg. type ( ) == ResourceTypeEnum. RESOURCE_TYPE_1 || lg. type ( ) == ResourceTypeEnum. RESOURCE_TYPE_4) {
action = getAuditResult ( jp, lg) ;
} else {
action = lg. action ( ) ;
}
}
private ActionEnum getAuditResult ( JoinPoint jp, LogHistory lg) {
Object arg = jp. getArgs ( ) [ 0 ] ;
Class< ? > aClass = arg. getClass ( ) ;
ActionEnum actionEnum = ActionEnum. ACTION_ENUM_0;
try {
Field field = aClass. getDeclaredField ( "auditSts" ) ;
field. setAccessible ( true ) ;
Integer auditSts = ( Integer) field. get ( arg) ;
if ( "0" . equals ( auditSts) ) {
actionEnum = lg. errorAction ( ) ;
}
} catch ( NoSuchFieldException | IllegalAccessException e) {
e. printStackTrace ( ) ;
}
return actionEnum;
}
private String getFiledForId ( JoinPoint jp) {
Object[ ] args = jp. getArgs ( ) ;
log. info ( "包名【{}】, 方法名[{}], 入参[{}]" , jp. getSignature ( ) . getDeclaringTypeName ( ) , jp. getSignature ( ) . getName ( ) , args[ 0 ] . toString ( ) ) ;
String id = "" ;
Class< ? > aClass = args[ 0 ] . getClass ( ) ;
try {
Field field = aClass. getDeclaredField ( "id" ) ;
field. setAccessible ( true ) ;
try {
id = ( String) field. get ( args[ 0 ] ) ;
if ( StringUtil. isEmpty ( id) ) {
id = StringUtil. getTimestampId ( ) ;
log. info ( "申请工单生成uuid[{}]" , id) ;
field. set ( args[ 0 ] , id) ;
}
return id;
} catch ( IllegalAccessException e) {
e. printStackTrace ( ) ;
}
} catch ( NoSuchFieldException e) {
e. printStackTrace ( ) ;
}
return id;
}
}
当然,我们如果就只是日志输出就只看这句话就好了:log.info("包名【{}】, 方法名[{}], 入参[{}]", jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), jp.getArgs()[0].toString());
因为我这里有业务场景所以多加了几个枚举参数,为了方便同学们复制下来直接使用,我都粘贴给你们。
做了些许处理。
public enum ResourceTypeEnum {
RESOURCE_TYPE_0 ( "0" , "容器申请单" ) ,
RESOURCE_TYPE_1 ( "1" , "容器审核单" ) ,
RESOURCE_TYPE_2 ( "2" , "容器实例单" ) ,
RESOURCE_TYPE_3 ( "3" , "虚拟机申请单" ) ,
RESOURCE_TYPE_4 ( "4" , "虚拟机审核单" ) ,
RESOURCE_TYPE_5 ( "5" , "虚拟机实例单" ) ;
private String code;
private String msg;
ResourceTypeEnum ( String code, String msg) {
this . code = code;
this . msg = msg;
}
public static String getMsgByCode ( String code) {
for ( ResourceTypeEnum resourceTypeEnum : ResourceTypeEnum. values ( ) ) {
if ( code. equals ( resourceTypeEnum. code) ) {
return resourceTypeEnum. msg;
}
}
return null;
}
public String getCode ( ) {
return code;
}
public void setCode ( String code) {
this . code = code;
}
}
######################################################################
public enum ActionEnum {
ACTION_ENUM_0 ( "0" , "审核通过" ) ,
ACTION_ENUM_1 ( "1" , "审核驳回" ) ,
ACTION_ENUM_2 ( "2" , "申请资源" ) ,
ACTION_ENUM_3 ( "3" , "删除资源" ) ,
ACTION_ENUM_4 ( "4" , "创建成功" ) ,
ACTION_ENUM_5 ( "5" , "创建失败" ) ,
ACTION_ENUM_6 ( "6" , "未知操作" ) ;
private String code;
private String msg;
ActionEnum ( String code, String msg) {
this . code = code;
this . msg = msg;
}
public static String getMsgByCode ( String code) {
for ( ActionEnum actionEnum : ActionEnum. values ( ) ) {
if ( code. equals ( actionEnum. code) ) {
return actionEnum. msg;
}
}
return null;
}
public String getCode ( ) {
return code;
}
public void setCode ( String code) {
this . code = code;
}
public String getMsg ( ) {
return msg;
}
public void setMsg ( String msg) {
this . msg = msg;
}
}
######################################################################
怎么使用?
@Override
@LogHistory ( type = ResourceTypeEnum. RESOURCE_TYPE_4, action = ActionEnum. ACTION_ENUM_0, errorAction = ActionEnum. ACTION_ENUM_1)
public void audit ( Dto user) {
}
总结
如果对aop和反射有了解的同学,读代码应该大概知道我要做什么功能,这个aop里面的反射涉及一些反射获
取属性和赋值的操作。感兴趣的同学可以谷歌了解学习。总体上我们的思路就是切一个service里面有自定义
注解的方法,然后将方法的包名、方法名、入参打印出来的操作。其次还附带了一些其他的业务逻辑。
jar
plugins {
id 'org.springframework.boot' version '2.2.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'io.freefair.lombok' version '5.0.0-rc6' apply false
}
group 'com.cas'
version '0.0.1-SNAPSHOT'
repositories {
maven {
url 'https://maven.aliyun.com/repository/public/'
}
mavenCentral ( )
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'com.alibaba:druid-spring-boot-starter:1.1.10'
implementation 'mysql:mysql-connector-java:8.0.13'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
implementation "org.projectlombok:lombok:1.18.12"
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
testImplementation 'mysql:mysql-connector-java:8.0.13'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform ( )
}
github
这里面有类似的功能,学习的同学可以直接点击查看,方便帮忙点个关注,收藏,点赞。
地址:https://github.com/xianglong123/cas-druid