自定义注解,切面
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);
}
}
原创:做时间的朋友