需要自定义注解的一些场景:
- 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优;
- 关键方法在幂等性前置校验(基于本地消息表);
- 类似于Spring-Retry模块,提供关键方法多次调用重试机制;
- 提供关键方法自定义的快速熔断、服务降级等职责;
- 关键方法的共性入参检验;
- 关键方法在执行后裔的扩展行为,例如记录日志、启动其他任务等‘
- ……..
关键词:共性需求、关键方法
首先需要添加aspectj依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
自定义注解
//记录接口的操作日志
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OperationLog {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Target(ElementType.METHOD)
public @interface ParamCheck {
}
切面类
@Aspect
@Component
@Slf4j
public class OperationLogAspect {
@AfterReturning(pointcut = "@annotation(com.yu.annotation.OperationLog)")
public void saveSysLog(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
OperationLog operation = method.getAnnotation(OperationLog.class);
if (operation != null) {
String value = operation.value();
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
//获取参数
List<Object> allparams = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
//打印日志
log.info(className + "." + methodName + "参数信息:" + allparams.toString() + " "+value);
}
}
}
@Aspect
@Component
@Slf4j
public class ParamCheckAspect {
@Around("@annotation(com.yu.annotation.ParamCheck)")
public Object paramCheckAspect(ProceedingJoinPoint point) throws Throwable {
log.info("开始检查参数");
Object[] args = point.getArgs();
for (Object arg : args) {
Assert.notNull(arg, "参数异常!");
}
long start = System.currentTimeMillis();
Object proceed = point.proceed();
long end = System.currentTimeMillis();
log.info(point.getSignature().getName() + " 方法的执行时间:" + (end - start) + "ms");
return proceed;
}
}
使用
@ParamCheck //自定义的参数检验注解
public String getResultby2Param(String a, String b) throws InterruptedException {
Thread.sleep(100);
return a + b;
}
@OperationLog//自定义的参数检验注解
public String simpleMethod(int a, String b , char c){
return a + b + c;
}
测试
@ParameterizedTest
@DisplayName("自定义参数检验注解测试")
@CsvSource({"i,", ",love", "yo,u"})
void paramCheck(String a, String b) throws InterruptedException {
String s = service.getResultby2Param(a, b);
System.out.println(s);
}
@ParameterizedTest
@DisplayName("自定义打印日志注解测试")
@CsvSource({"99,cccc,c", "22,love,e", "33333,yo,u"})
@OperationLog(value = "llllll")
void operationLog(int a, String b, char c) {
String s = service.simpleMethod(a, b, c);
}
其本质是AOP(动态代理)+反射
//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTransactional {
}
//动态代理必须要实现接口
public interface ITest {
public void manipulateDB1() throws Exception;
public void manipulateDB2() throws Exception;
}
//为ITest的实现类创建代理
class MyProxyHandler implements InvocationHandler {
private ITest iTest;
public MyProxyHandler(ITest iTest) {
this.iTest = iTest;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
System.out.println("事务开始!");
Object invoke = null;
try {
invoke = method.invoke(iTest, args);
System.out.println("事务提交");
} catch (Exception e) {
System.out.println("操作失败,事务回滚!");
throw new Exception();
}
return invoke;
}
public ITest getProxy() {
return (ITest) Proxy.newProxyInstance(iTest.getClass().getClassLoader(), iTest.getClass().getInterfaces(), this);
}
}
//使用注解
public class Test implements ITest {
@Override
@MyTransactional//自定义注解,支持事务
public void manipulateDB1() throws Exception {
System.out.println("111111111模拟操作数据库!");
int i = 0;
// int a = 8 / i;
UnitTest.map.get("manipulateDB2").manipulateDB2();
}
@Override//自定义注解,支持事务
@MyTransactional
public void manipulateDB2() throws Exception {
System.out.println("2222222222模拟操作数据库!");
//模拟异常
int i = 0;
// int a = 8 / i;
}
}
//测试
class UnitTest {
static ITest test = new Test();
//模拟IOC容器
static Map<String, ITest> map = new HashMap<>();
static {
Class<? extends ITest> clzz = test.getClass();
Method[] methods = clzz.getMethods();
for (Method method : methods) {
MyTransactional annotation = method.getAnnotation(MyTransactional.class);
//有注解的方法,容器中则保存代理方法,否则保存原始方法
if (annotation != null) {
MyProxyHandler myProxy = new MyProxyHandler(test);
map.put(method.getName(), myProxy.getProxy());
} else {
map.put(method.getName(), test);
}
}
}
public static void main(String[] args) throws Exception {
//测试,模拟@Resource注解,根据name取组件
map.get("manipulateDB1").manipulateDB1();
System.out.println("------------");
new Test().manipulateDB1();
}
}