SpringBoot 通过自定义注解实现AOP切面编程实例

该代码示例展示了如何在Java中使用SpringAOP和自定义注解`@MessageNotice`来实现数据变化的通知机制。`MessageNotice`定义了模块和功能类型,`Module`和`BusinessType`枚举提供了标准的模块和业务操作类型。切面类`MessageNoticeAspect`处理注解的方法,通过Kafka发送通知。此外,还有一个用于处理日志的`WebLogAspect`切面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注解代码

package com.ds.admin.v2.common.annotation;

import com.ds.admin.v2.common.enums.Module;
import com.ds.common.enums.BusinessType;
import com.ds.common.utils.uuid.UUID;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义数据变化通知
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageNotice {

    /**
     * 模块
     * @see Module#name()
     */
    public Module module() default Module.OTHER;

    /**
     * 功能
     * @see BusinessType#name()
     */
    public BusinessType businessType() default BusinessType.OTHER;

    /**
     * uuid
     */
    public String uuid() default "";
}

枚举

package com.ds.admin.v2.common.enums;

/**
 * 标准各模块定义
 */
public enum Module {

    ELEMENT("数据元"),

    DIC("值域"),

    SBR("主索引"),

    STANDARD_MODEL("标化模型"),

    BUSINESS_MODEL("业务模型"),

    MAPPING("链路映射"),

    OTHER("未知");

    Module( String info) {
        this.info  = info;
    }

    private String info;

    public String getInfo() {
        return info;
    }

}


package com.ds.common.enums;

/**
 * 业务操作类型
 *
 * @author lr-app
 */
public enum BusinessType
{
    /**
     * 其它
     */
    OTHER,

    /**
     * 新增
     */
    INSERT,

    /**
     * 修改
     */
    UPDATE,

    /**
     * 删除
     */
    DELETE,

    /**
     * 授权
     */
    GRANT,

    /**
     * 导出
     */
    EXPORT,

    /**
     * 导入
     */
    IMPORT,

    /**
     * 强退
     */
    FORCE,

    /**
     * 生成代码
     */
    GENCODE,

    /**
     * 清空数据
     */
    CLEAN,
}

切面类

package com.ds.admin.v2.common.aspectj;

import com.ds.admin.v2.common.annotation.MessageNotice;
import com.ds.admin.v2.common.domain.dto.MessageNoticeDto;
import com.ds.admin.v2.utils.MessageNoticeUtil;
import com.ds.common.utils.StringUtils;
import com.ds.common.utils.uuid.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Optional;

/**
 * 数据标准改动通知
 */
@Aspect
@Component
@Slf4j
public class MessageNoticeAspect {

    @Resource
    private KafkaTemplate<String, String> kafkaTemplate;

    @Pointcut("@annotation(com.ds.admin.v2.common.annotation.MessageNotice)")
    public void noticePointCut() {
        log.info("執行發送通知方法");
    }


//    @Before("noticePointCut()")
//    public void doBeforeReturning(JoinPoint joinPoint)
//    {
//
//        MessageNotice messageNotice = getAnnotationLog(joinPoint);
//        System.out.println(messageNotice.module().getInfo());
//    }

    @Before("execution(public * com.ds.admin.v2.valuedict.service.impl.DataValueColumnServiceImpl.*(..))")
    public void doBeforeReturning(JoinPoint joinPoint)
    {

        MessageNotice messageNotice = getAnnotationLog(joinPoint);
        System.out.println(messageNotice.module().getInfo());
    }

    /**
     * 出现异常也执行
     *
     * @param joinPoint 切点
     */
    @After("noticePointCut()")
    public void doAfter(JoinPoint joinPoint) {
        MessageNotice messageNotice = getAnnotationLog(joinPoint);
        System.out.println(messageNotice.module().name());

    }

    /**
     * 处理完请求后执行
     * 出现异常不执行
     *
     * @param joinPoint 切点
     */
//    @AfterReturning(pointcut = "noticePointCut()")
//    public void doAfterReturning(JoinPoint joinPoint) {
//        handleLog(joinPoint);
//    }

    protected void handleLog(final JoinPoint joinPoint) {
        MessageNotice messageNotice = getAnnotationLog(joinPoint);
        Optional.ofNullable(messageNotice).ifPresent(f -> send(messageNotice));
    }

    /**
     * 环绕通知:灵活自由的在目标方法中切入代码
     */
    @Around("noticePointCut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取目标方法的名称
        String methodName = joinPoint.getSignature().getName();
        // 获取方法传入参数
        Object[] params = joinPoint.getArgs();
//        MessageNotice lingyejun = getDeclaredAnnotation(joinPoint);
        MessageNotice lingyejun = getAnnotationLog(joinPoint);
        System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
        // 执行源方法
        joinPoint.proceed();
        // 模拟进行验证
        if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth success");
        } else {
            System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth failed");
        }
    }

    /**
     * 获取方法中声明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public MessageNotice getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 反射获取目标类
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法对应的参数类型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根据类、方法、参数类型(重载)获取到方法的具体信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        // 拿到方法定义的注解信息
        MessageNotice annotation = objMethod.getDeclaredAnnotation(MessageNotice.class);
        // 返回
        return annotation;
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private MessageNotice getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(MessageNotice.class);
        }
        return null;
    }

    /**
     * 消息发送
     */
    private void send(MessageNotice messageNotice) {
        MessageNoticeUtil.getMessage(messageNotice.uuid())
                .forEach(f -> {
                    String topic = StringUtils.isNotBlank(f.getTopic()) ? f.getTopic() : kafkaTemplate.getDefaultTopic();
                    kafkaTemplate.send(topic,
                        MessageNoticeDto.builder()
                                .messageId(IdUtils.fastSimpleUUID())
                                .module(messageNotice.module().name().toLowerCase())
                                .businessType(messageNotice.businessType().name().toLowerCase())
                                .object(f)
                                .build().toString());
                        });
        MessageNoticeUtil.clear();

    }
}

注解使用在方法上

package com.ds.admin.v2.valuedict.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ds.admin.v2.common.annotation.MessageNotice;
import com.ds.admin.v2.common.enums.Module;
import com.ds.admin.v2.sbr.domian.vo.SbrQueryModelColumnAndSort;
import com.ds.admin.v2.sbr.enums.DataValueExportEnums;
import com.ds.admin.v2.utils.MessageNoticeUtil;
import com.ds.admin.v2.utils.MyPageUtil;
import com.ds.admin.v2.valuedict.domain.vo.DictColumnInsertReqVo;
import com.ds.admin.v2.valuedict.domain.vo.DictColumnVo;
import com.ds.admin.v2.valuedict.enums.CodeCatalog;
import com.ds.admin.v2.valuedict.model.DataValue;
import com.ds.admin.v2.valuedict.model.DataValueColumn;
import com.ds.admin.v2.valuedict.mapper.DataValueColumnMapper;
import com.ds.admin.v2.valuedict.service.DataValueColumnService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ds.admin.v2.valuedict.service.DataValueService;
import com.ds.common.enums.BusinessType;
import com.ds.common.exception.CustomException;
import com.ds.common.utils.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 值域字段信息 服务实现类
 * </p>
 *
 * @author standard
 * @since 2022-10-12
 */
@Service
public class DataValueColumnServiceImpl extends ServiceImpl<DataValueColumnMapper, DataValueColumn> implements DataValueColumnService {

    @Resource
    private DataValueService dataValueService;

    @Override
    public List<DictColumnVo> getColumName(Integer dataValueId) {
        LambdaQueryWrapper<DataValueColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DataValueColumn::getDataValueId, dataValueId).orderByAsc(DataValueColumn::getSeqNo);
        List<DataValueColumn> dataValueColumns = this.list(queryWrapper);

        //是否展示
        DataValue dataValue = dataValueService.getById(dataValueId);
        String field = DataValueExportEnums.getField(dataValue.getCodeCatalogId());
        List<SbrQueryModelColumnAndSort> list = JSON.parseArray(field, SbrQueryModelColumnAndSort.class);

        List<DictColumnVo> dictColumnVos = dataValueColumns.stream().map(dvc -> {
            DictColumnVo dcv = new DictColumnVo();
            BeanUtils.copyProperties(dvc, dcv);
            dcv.setShowFlag(false);
            for(SbrQueryModelColumnAndSort sort : list){
                if (sort.getNameEn().equals(dcv.getNameEn())) {
                    dcv.setShowFlag(true);
                    dcv.setNameCn(sort.getNameCn());
                    if(StringUtils.isNotBlank(sort.getNameEn())){
                        dcv.setNameEn(sort.getNameEn());
                    }
                }
            }
            dcv.setColumnId(dvc.getId());
            return dcv;
        }).collect(Collectors.toList());
        return dictColumnVos;
    }

    @Override
    public Page<DictColumnVo> getColumNamePages(Page<DictColumnVo> page,  Integer dataValueId) {
        List<DictColumnVo> dictColumnVos = this.getColumName(dataValueId);
        return MyPageUtil.getMyPage(page, dictColumnVos);
    }

    @Override
    @MessageNotice(module = Module.DIC,businessType = BusinessType.INSERT)
    public String addColumnName(DictColumnInsertReqVo dictColumnInsertReqVo) {
        DataValue dataValue = dataValueService.getById(dictColumnInsertReqVo.getDataValueId());
//        if(dataValue.getCodeCatalogId().equals(CodeCatalog.SINGELVALUECODECATALOG.getId())) throw new CustomException("单值表字段不能修改");
//        DataValueColumn dataValueColumn = new DataValueColumn();
//        BeanUtils.copyProperties(dictColumnInsertReqVo,dataValueColumn);
//        this.save(dataValueColumn);
//        MessageNoticeUtil.putMessage(new Object(){}.getClass().getEnclosingMethod(),dataValueColumn);
        return "sss";
    }

    @Override
    @MessageNotice(module = Module.DIC,businessType = BusinessType.UPDATE)
    public String updateColumnName(DictColumnInsertReqVo dictColumnInsertReqVo) {
        DataValue dataValue = dataValueService.getById(dictColumnInsertReqVo.getDataValueId());
//        if(dataValue.getCodeCatalogId().equals(CodeCatalog.SINGELVALUECODECATALOG.getId())) throw new CustomException("单值表字段不能修改");
//        DataValueColumn dataValueColumn = this.getById(dictColumnInsertReqVo.getId());
//        if(dataValueColumn == null) throw new CustomException("值域字段不存在");
//        BeanUtils.copyProperties(dictColumnInsertReqVo,dataValueColumn);
//        this.updateById(dataValueColumn);
//        MessageNoticeUtil.putMessage(new Object(){}.getClass().getEnclosingMethod(),dataValueColumn);
        return "sss";
    }

    @Override
    @MessageNotice(module = Module.DIC,businessType = BusinessType.DELETE)
    public void deleteColumnName(List<Integer> columnIds) {
        List<DataValueColumn> dataValueColumns = this.listByIds(columnIds);
        if(StringUtils.isEmpty(dataValueColumns)) throw new CustomException("值域字段不存在");
        this.removeByIds(columnIds);
        MessageNoticeUtil.putMessage(new Object(){}.getClass().getEnclosingMethod(),dataValueColumns.toArray());
    }
}

统一日志处理

package com.ds.admin.v2.common.aspectj;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.ds.admin.v2.common.annotation.MessageNotice;
import com.ds.admin.v2.common.domain.dto.MessageNoticeDto;
import com.ds.admin.v2.utils.MessageNoticeUtil;
import com.ds.common.utils.StringUtils;
import com.ds.common.utils.uuid.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 统一日志处理切面
 * Created by 石磊
 */
@Aspect
@Component
@Order(1)
public class WebLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
    @Pointcut("execution(public * com.ds.admin.v2.valuedict.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息(通过Logstash传入Elasticsearch)
        WebLog webLog = new WebLog();
        Object result = joinPoint.proceed();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation log = method.getAnnotation(ApiOperation.class);
            webLog.setDescription(log.value());
        }
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteUser());
        Map<String,Object> logMap = new HashMap<>();
        logMap.put("spendTime",webLog.getSpendTime());
        logMap.put("description",webLog.getDescription());
        LOGGER.info("{}", JSONUtil.parse(webLog));
        return result;
    }
}
package com.ds.admin.v2.common.aspectj;

import lombok.Data;

/**
 * Controller层的日志封装类
 * Created by macro on 2018/4/26.
 */
@Data
public class WebLog {
    private String description;
    private String username;
    private Long startTime;
    private Integer spendTime;
    private String basePath;
    private String uri;
    private String url;
    private String method;
    private String ip;
    private Object parameter;
    private Object result;
    //省略了getter,setter方法
}

参考1
参考2
参考3
统一日志处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值