AOP配合自定义注解 打印日志和业务逻辑处理

背景
一个简单的功能,就是对特定的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;

/**
 * @author: xianglong[1391086179@qq.com]
 * @date: 下午8:27 2021/3/22
 * @version: V1.0
 * @review:
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogHistory {

    /**
     * 是否开启日志历史记录功能,默认开启
     * @return
     */
    boolean isOpen() default true;

    /**
     * 记录的类型,为空则记录为未知
     * @return
     */
    ResourceTypeEnum type();

    /**
     * 资源的唯一标识
     * @return
     */
    String uuid() default "";

    /**
     * 非异常操作
     * @return
     */
    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;

/**
 * @author: xianglong[1391086179@qq.com]
 * @date: 22:20 2020-02-14
 * @version: V1.0
 * @review: 自定义AOP切点
 */
@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();
        }
    }

    /**
     * 不想用反射拿的,但是没办法,通用性强【手动滑稽】
     * @param jp
     * @return
     */
    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;
    }

    /**
     * 获取入参数对象的属性id,有则返回,无则赋值返回
     *
     * @return
     */
    private String getFiledForId(JoinPoint jp) {
        Object[] args = jp.getArgs();
        log.info("包名【{}】, 方法名[{}], 入参[{}]", jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), args[0].toString());
        String id = "";
        // 通过反射获取对象中属性名为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());

因为我这里有业务场景所以多加了几个枚举参数,为了方便同学们复制下来直接使用,我都粘贴给你们。
做了些许处理。
/**
 * @author: xianglong[1391086179@qq.com]
 * @date: 下午7:53 2021/1/5
 * @version: V1.0
 * @review:
 */
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;
    }
}

######################################################################

/**
 * @author: xianglong[1391086179@qq.com]
 * @date: 下午7:56 2021/1/5
 * @version: V1.0
 * @review:
 */
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值