自定义注解,切面

1.定义注解FlushRedis

1.1 注解定义

@Target(ElementType.METHOD) // 注解用来修饰方法
@Retention(RetentionPolicy.RUNTIME) // 运行时
@Documented // 文档
public @interface FlushRedis {
}

1.2 使用注解

@Transactional(rollbackFor = Exception.class)
@Override
@FlushRedis // 使用注解一定要放到public修饰的方法上,并且是别人调用的时候才会生效,this自己调用方法不会生效,事务的注解也是这个思路
public void updateResGroupSwitch(AlarmDescDetailsDto request) {
	// 业务逻辑
}

1.3 定义AOP切面

注解的功能是方法返回结果后自动执行某些代码

@Slf4j
@Aspect // 定义切面
@Component
public class FlushRedisAspect {

    @Autowired
    ResGroupDaoService resGroupDaoService;

    @Autowired
    AlarmRuleNewDaoService alarmRuleNewDaoService;

    AlarmRuleNewConverter middleDataConvert = new AlarmRuleNewConverter();
    AlarmRuleFlinkConverter converter = new AlarmRuleFlinkConverter();

    @Pointcut("@annotation(delta.service.annotation.FlushRedis)") // 以应用了FlushRedis注解的为切面点
    public void flushPointCut() {

    }

    @Before("flushPointCut()") // 切入点之前执行的
    private void before() throws Exception {

    }

    @AfterReturning(pointcut = "flushPointCut()") // 注解修饰的方法返回结果后执行
    private void afterReturning(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod(); // 获取修饰的方法
        FlushRedis flushAnnotation = method.getAnnotation(FlushRedis.class); // 判断方法是否有FlushRedis注解
        if (flushAnnotation == null) {
            return;
        }
		// 获取切入点的方法的参数
        Object[] args = joinPoint.getArgs();
        try {
		   // 转成真正代码里的数据类型
            AlarmDescDetailsDto alarmDescDetailsDto = (AlarmDescDetailsDto) args[0];
            String resGroupCode = alarmDescDetailsDto.getDefinition();
			// 执行业务逻辑
            syncToRedis(resGroupCode);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public void syncToRedis(String resGroupCode) {
	    // 事务提交后才执行某些动作,这是注册了一个同步的动作,提交完事务后自动改执行afterCommit方法
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
				// 提交完事务后执行的动作

            }
        });
    }
}

2.自定义带属性的注解

2.1 定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLog {

    /**
     * 操作类型
     * @return
     */
    OperatorType type() default OperatorType.UPDATE;

    /**
     * 实体名称,操作的是哪个实体
     * @return
     */
    String entityName() default "";
}

2.2 注解中的字段要用EPEL表达式,取到方法中的变量内容

Spel.java工具类

package delta.main.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * @description
 * @date 2023-06-07 4:30 PM
 */
public class SpelUtil {
    /**
     * 用于SpEL表达式解析.
     */
    private static SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    public static String generateKeyBySpEL(String spELString, JoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spELString);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }

}

2.3 切面中解析注解内容(用SpelUtil工具类)

package delta.main.aspect;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jd.common.web.LoginContext;
import delta.api.domain.customization.ModifyLog;
import delta.api.enums.OperatorType;
import delta.common.utils.JsonUtils;
import delta.main.configuration.ApplicationRunner;
import delta.main.controller.annotation.OperatorLog;
import delta.main.utils.SpelUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @description
 * @date 2023-02-03 3:59 下午
 */
@Slf4j
@Aspect
@Component
public class OperatorLogAspect {


    private static final Logger operatorLogger = LoggerFactory.getLogger(ModifyLog.class);
    private static final String EMPTY = "";
    private static int FIRST_INDEX = 0;

    private static final String DEFAULT_ENTITY = "DEFAULT_ENTITY";

    @Pointcut("@annotation(delta.main.controller.annotation.OperatorLog)")
    public void OperatorLogPointCut() {

    }

    @Before("OperatorLogPointCut()")
    private void before() throws Exception {

    }


    @AfterReturning(pointcut = "OperatorLogPointCut()")
    private void afterReturning(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        OperatorLog annotation = method.getAnnotation(OperatorLog.class);
        if (annotation == null) {
            return;
        }
        String entityName = StrUtil.isNotBlank(annotation.entityName()) ? annotation.entityName() : DEFAULT_ENTITY;
		// 如果注解中的属性是不为空,并且以#开头的,用工具类进行解析转换出来真正的值并赋值
        if (StrUtil.isNotBlank(annotation.entityName()) && annotation.entityName().startsWith("#")) {
            entityName = SpelUtil.generateKeyBySpEL(annotation.entityName(), joinPoint);
        }
        Context context = new Context()
                .setOperatorType(annotation.type())
                .setArgs(joinPoint.getArgs())
                .setMethod(method)
                .setEntityName(entityName);
        try {
            // arg1:方法参数
            // arg2:方法对象
            saveLog(context);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }


    @Accessors(chain = true)
    @Data
    class Context {
        Object[] args;
        Method method;
        OperatorType operatorType;
        String entityName;
    }

    public void saveLog(Context context) {
        defaultLogConsumer.accept(context);
    }

    private ModifyLog buildModifyLog() {
        return ModifyLog.builder()
                .traceId(IdUtil.randomUUID())
                .appName(ApplicationRunner.APP_NAME)
                .erp(LoginContext.getLoginContext().getPin())
                .modifyTime(System.currentTimeMillis())
                .build();
    }

    private Consumer<Context> defaultLogConsumer = context -> {
        List<String> args = Arrays.stream(context.args)
                .map(arg -> isConvertJSON(arg) ? JsonUtils.toJsonString(arg) : arg.toString())
                .collect(Collectors.toList());
        ModifyLog modifyLog = buildModifyLog();
        modifyLog.setEntityName(context.entityName);
        modifyLog.setOperate(context.operatorType.name());
        modifyLog.setBefore(EMPTY);
        modifyLog.setAfter(JsonUtils.toJSONString(args));
        log.info("生成操作记录:{}", JsonUtils.toJSONString(modifyLog));
        operatorLogger.info(JsonUtils.toJSONString(modifyLog));
    };

    private boolean isConvertJSON(Object o) {
        try {
            JsonUtils.toJsonString(o);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

2.4 使用注解

@ApiOperation(value = "删除告警定义")
// 使用注解,指定type字段为....
@OperatorLog(type = OperatorType.DELETE,entityName = "#alarmDescDefinition")
@DeleteMapping("/alarm-desc/details/{alarmDescDefinition}")
public Result delResGroup(@PathVariable String alarmDescDefinition) {
	Assert.notNull(alarmDescDefinition, "请求格式异常");
	Assert.isTrue(hasResGroupPermission(alarmDescDefinition), INVALID_RESGROUP_PERMISSION_MSG);
	resGroupManageServices.stream().findAny().orElseThrow(() -> new RuntimeException(INVALID_RESGROUP_MANAGE_SERVICE_MSG)).removeResGroup(alarmDescDefinition);
	//        syncAlarmRuleCacheJob.asyncFlush();
	return Result.codeMsg(0, "ok");
}

3.ump监控的注解定义及如何生效的

3.1 注解定义

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface JProfiler {
    String jKey();

    /** @deprecated */
    @Deprecated
    String jAppName() default "";

    JProEnum[] mState() default {JProEnum.TP};

    Class<? extends Throwable>[] errorIncludes() default {Throwable.class};

    Class<? extends Throwable>[] errorExcludes() default {};
}

3.2 注解应用


    @JProfiler(
            jKey = "delta.main.processor.NotifyScheduleProcessor.process",
            mState = {JProEnum.TP, JProEnum.FunctionError}
    )
    public void process(ProcessContext<NotificationModel> context) {
    }

3.3 如何是如何生效的

源码包里有一个切面

@Aspect
public class JAnnotation {
	public JAnnotation() {
	}

	@Pointcut("@annotation(com.xxx.ump.annotation.JProfiler)")
	public void JAnnotationPoint() {
	}
	// Around 之前之后执行
	@Around("JAnnotationPoint()")
	public Object execJAnnotation(ProceedingJoinPoint jp) throws Throwable {
		JProfilerAnnoInstance annoInstance = null;
		boolean hasError = false;
		CallerInfo callerInfo = null;

		try {
			Method method = ((MethodSignature)jp.getSignature()).getMethod();
			annoInstance = (JProfilerAnnoInstance)this.methodJProfilerMap.createIfAbsent(method, jp, VALUE_FACTORY);
			if (annoInstance != null) {
				callerInfo = Profiler.registerInfo(annoInstance.key); // 注册key
			}
		} catch (JAnnotationNotFoundException var11) {
			LOGGER.warn("JAnnotation aspect a method: {} by joinPoint: {} can't found @JProfiler annotation! ", var11.getMethod(), var11.getJoinPoint());
		} catch (Throwable var12) {
			LOGGER.warn("JAnnotation aspect by joinPoint: {} error! ", jp, var12);
		}

		Object var15;
		try {
			var15 = jp.proceed();
		} catch (Throwable var13) {
			if (callerInfo != null && this.isIncludedThrowableForFunctionError(annoInstance, var13.getClass())) {
				Profiler.functionError(callerInfo);  // 标记错误,及异常与注解中的过滤
			}

			throw var13;
		} finally {
			if (callerInfo != null) {
				Profiler.registerInfoEnd(callerInfo); // 提交数据
			}

		}

		return var15;
	}
}

4.自定义注解在数据库事务提交后才执行

@Slf4j
@Aspect
@Component
@Order(1) // 核心是Order要制定,不指定的话数据库事务未提交,就进来
public class FlushRedisAspect {
  @Pointcut("@annotation(delta.service.annotation.FlushRedis)")
    public void flushPointCut() {

    }

    @Before("flushPointCut()")
    private void before() throws Exception {

    }


    @AfterReturning(pointcut = "flushPointCut()")
    private void afterReturning(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        FlushRedis flushAnnotation = method.getAnnotation(FlushRedis.class);
        if (flushAnnotation == null) {
            return;
        }
        Object[] args = joinPoint.getArgs();
        Object arg1 = args[0];
        Consumer executor = getExecutor(arg1);
        execute(executor, arg1);

    }
}
posted @ 2023-02-03 18:45  SpecialSpeculator  阅读(574)  评论(0编辑  收藏  举报