SpringAop实操之记录关键业务请求

栏目: Java · 发布时间: 5年前

内容简介:AOP,中文名称,切面。在不影响业务代码情况下,实现想要的功能,是个真炫酷的事。这不,最近来了个需求 。业务需求:希望记录多个关键业务的请求及操作情况!这本身是ok的,但是这些需求规律性太强,咱们不能硬干,否则就一个字low。aop是个好东西,记录日志是必须的。

AOP,中文名称,切面。在不影响业务代码情况下,实现想要的功能,是个真炫酷的事。这不,最近来了个需求 。

业务需求:希望记录多个关键业务的请求及操作情况!这本身是ok的,但是这些需求规律性太强,咱们不能硬干,否则就一个字low。

aop是个好东西,记录日志是必须的。

记录数据也一样的,那么也是可以用aop来实现的,这里借助注解一起解决问题吧。

因为是关键业务,所以意味着不是所有业务,那么自然地就想到了,可以使用过滤的方式,也就是使用注解,如果有注解那么就代表要记录数据,否则不记录。

记录过程需要做什么呢?

首先,因为是记录业务数据,那么我们可以抽象出一个方法出来,也就是 xxx.addRec()。也就是就切面里只需调用记录方法即可。

那么还剩下几个问题,怎样找到这个方法?参数的处理怎么办?需要使用规范限制吗?

找到这个方法,我们可以使用反射调用,当然需要注解进行调用的类和方法名称(如果固定死则无需标注),用反射的好处就是自由,想怎么写就怎么写,要么失败要么成功。但是low!

参数判定,可以根据输入的类,进行if..else.. 判定,然后直接处理相应参数即可。这样可以很自由,想怎么写参数就怎么写参数。但是这样的话,这个切面就和业务代码完全耦合在一起了,还多了n多无谓的判断。很low!

那么问题来了,怎样才不low呢?

几个理论可以借鉴下:

1. 面向接口编程而非面向类编程;

2. 使用枚举值进行注解统一规划;

3. 使用泛型,进行参数上下限处理;

4. 使用模板方法模式封装参数;

5. 使用线程池;

让我们细看下~

面向接口编程,即规定所有业务处理类都实现一个公共的接口,从而使方法不至于凌乱,定义一个清晰的接口,更易于理解含义。各实现类只需关注自己的逻辑即可。

使用枚举值进行注解参数的限定,可以使所有业务操作都在一处进行罗列,且都符合必须的规范。另外,当哪天发现参数无法满足某些需求时,可方便地在该枚举中加入相应参数以完成需求。

使用泛型,将参数限定一定范围内,如要求入库参数必须继承某个基类以实现统一参数处理,也防止了传入任意参数而必须做相应转换的性能消耗。

使用模板方法模式进行参数封装,因入参中都要求继承一个基类,也就是具有共性的参数,所以应具备自动处理公共参数的能力,但是不可越权处理各业务实现的处理,应让实现类有能力自行处理个性化参数(实现类也可以不处理)。交由实现类处理个性化参数时,应使其具有绝对的能力,不应限制其发挥。

使用线程池技术,将额外的工作交给额外的线程,从而使主业务不爱影响。

具体代码撸一遍:

1. 注解开启新篇章,无注解,不工作。

@Component
public class UserTestService {

    // 添加注解,代表需要进行相应的逻辑处理
    @BizRecordTrans(bizType = BizRecordTypeEnum.USER_ADD_FLOW)
    public ResponseEntity<Boolean> addUser(UserlDto terminalReq) {
        ResponseEntity<Boolean> ret = ResponseBuilder.buildResponse();
        // 完成自己的业务...
        return ret;
    }
}

2. 切面配置,做一个幕后的老兵。

@Component
@Aspect
public class BizRecordAop {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Around("execution(* com.xxx.dubbo.*.*(..))) and @annotation(com.xxx.spring.annotation.BizRecordTrans)")
    public Object deal(ProceedingJoinPoint pjp) throws Throwable {
        Object retVal = null;
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        Class<?> classTarget = pjp.getTarget().getClass();
        Class<?>[] argTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method objMethod = classTarget.getMethod(methodName, argTypes);
        BizRecordTrans bizRecordTrans = objMethod.getAnnotation(BizRecordTrans.class);
        BizRecordTypeEnum bizRecordTypeEnum = null;
        if (BizRecordTrans != null) {
            bizRecordTypeEnum = BizRecordTrans.bizType();
        }
        try {
            retVal = pjp.proceed();
        } catch (Exception e) {
            // ignore
            throw e;
        } finally {
            if (bizRecordTrans != null) {
                Class<? extends SvcBaseEntity> bizRecEntityCls = bizRecordTypeEnum.getBizRecEntityCls();
                SvcBaseEntity recEntity = (SvcBaseEntity) bizRecEntityCls.newInstance();
                if (args[0] instanceof BaseDto) {
                    BaseDto baseDto = (BaseDto) args[0];
                    fillBaseFields(recEntity, baseDto);
                }
                try {
                    ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) SpringContextsUtil.getBean("threadPoolTaskExecutor");
                    executor.execute(() -> {
                        BizRecBaseService<BaseEntity> recService = (BizRecBaseService<SvcBaseEntity>) SpringContextsUtil
                            .getBean(bizRecordTypeEnum.getHandlerBeanName());
                        recService.fitOwnParams(recEntity, args);
                        recService.addRecord(recEntity);
                    });
                } catch (Exception e) {
                    logger.error("发生异常", e);
                }
            }
        }
        return retVal;
    }

    /**
     * 填充公共参数
     */
    private void fillBaseFields(BaseEntity baseEntity, BaseDto base) {
        baseEntity.setUserId(base.getUserId());
        baseEntity.setSeqId(base.getSeqId());
        baseEntity.setAddIp(base.getAddIp());
    }
}

3. 写一个基础接口,让业务去实现。

public interface BizRecordBaseService<T extends BaseEntity> {
    /**
     * 执行业务方法
     */
    public Integer addRecord(T bizRecord);

    /**
     * 个性化参数填充方法,选择性实现
     */
    default void fixOwnParams(T bizRecord, Object[] rawData) {
        System.out.println("可以不实现");
    }
}

4. 写一个基础类,让所有其他业务实体继承以实现公共参数的封装。

@Data
public class SvcBaseEntity {
    /**
     * ID 
     */
    private Long id;
    
    /**
     * 用户ID 
     */
    private Long userId;

    /**
     * 请求序列id
     */
    private String seqId;

    /**
     * 添加ip
     */
    private String addIp;
    
}

5. 打一个组合拳,搞定。

原理浅显,易懂。性能嘛,也ok,无需太担心。

嘿! 每一个奇怪的业务,都是一次实践的机会。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法谜题

算法谜题

Anany Levitin、Maria Levitin / 赵勇、徐章宁、高博 / 人民邮电出版社 / 2014-1-1

算法是计算机科学领域最重要的基石之一。算法谜题,就是能够直接或间接地采用算法来加以解决的谜题。求解算法谜题是培养和锻炼算法思维能力一种最有效和最有乐趣的途径。 本书是一本经典算法谜题的合集。本书包括了一些古已有之的谜题,数学和计算机科学有一部分知识就发源于此。本书中还有一些较新的谜题,其中有一部分谜题被用作知名IT企业的面试题。全书可分为4个部分,分别是概览、谜题、提示和答案。概览介绍了算法......一起来看看 《算法谜题》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具