注解+AOP实现
# Java Annotation注解的详解
Java注解是一种元数据,它可以用于在类、方法或其他代码结构中声明关于程序元素的信息和标记。在Java中,注解以 @ 符号开头,在编译时或运行时由Java虚拟机(JVM)或其他工具进行处理。
注解可以用于许多不同的用途,包括:
- 编译时检查:注解可以让编译器在编译期间检查程序是否满足一些条件,例如@Deprecated标记弃用的代码,编译器会在编译期间提示有关该代码的警告。
- 运行时处理:注解允许代码在运行时根据指定的元数据来执行特定操作。例如,许多Web框架使用注解对请求处理器和URL进行映射。
- 文档生成:注解可以用于生成文档以描述代码的特定方面,使得文档维护更简单方便。
- 安全性:注解可以用于标记代码中的潜在安全漏洞,帮助开发人员快速找到和修复这些问题。
# @Target指定注解针对的地方
在Java开发中,自定义注解是一种非常常见的技术手段,它可以用于定义元数据,对程序的逻辑进行标注、配置和调度等操作。@Target 是一个元注解(即注解的注解),它用来规定自定义注解可以修饰的程序单元(如类、方法、字段等)。@Target
的类型包括以下几种:
ElementType.ANNOTATION_TYPE
表示自定义的注解可以修饰其他注解,例如 @SuppressWarnings。ElementType.CONSTRUCTOR
表示自定义的注解可以修饰构造函数,用于配置依赖注入等操作。ElementType.FIELD
表示自定义的注解可以修饰类的属性(字段),用于配置依赖注入、序列化等操作。ElementType.LOCAL_VARIABLE
表示自定义的注解可以修饰局部变量,通常用于实现某些特定功能或业务逻辑的处理。ElementType.METHOD
表示自定义的注解可以修饰方法,通常用于配置事务、权限校验、缓存管理等操作。ElementType.MODULE
表示自定义的注解可以修饰 Java 9 中的模块(Module)。ElementType.PACKAGE
表示自定义的注解可以修饰 Java 包(Package)内的所有类。ElementType.PARAMETER
表示自定义的注解可以修饰方法的参数,通常用于配置参数校验、日志打印等操作。ElementType.TYPE
表示自定义的注解可以修饰类、接口(Interface)或枚举类型(Enum),通常用于配置声明周期、依赖注入、AOP等操作。
总之,通过合理使用 @Target,我们可以更加精细地控制自定义注解的使用范围,避免滥用或误用。需要注意的是,多个 @Target 可以同时出现在同一个注解上面,使用大括号括起来即可,例如:@Target({ElementType.METHOD, ElementType.FIELD})
表示该注解可以同时修饰方法和属性。
# @Retention指定注解的保留域
在Java开发中,自定义注解是一种非常常见的技术手段,它可以用于定义元数据,对程序的逻辑进行标注、配置和调度等操作。而 @Retention 是一个元注解(即注解的注解),它用来规定自定义注解的生命周期。
在Java语言中,共有三种 @Retention
类型:
RetentionPolicy.SOURCE
表示自定义注解只在源代码中保留,编译器会在编译时丢弃该注解,不会被包含在编译后生成的 class 文件中。这种类型的注解通常用于对开发者进行提示或注释,对实际的程序并没有实质性的影响。RetentionPolicy.CLASS
(默认值) 表示自定义注解在编译时保留,但不会被加载到 JVM 中运行。当程序运行时,JVM 不会将该注解加载进来,因此在运行期间无法获取该注解及其信息。这种类型的注解通常用于字节码分析工具等场景。RetentionPolicy.RUNTIME
表示自定义注解在编译时与运行时都会保留,可以通过反射机制获取注解以及注解的属性。这种类型的注解通常用于在运行时进行动态处理,例如 AOP 、代理模式 等场景。
需要注意的是,@Retention 和 @Target 一样,均可以出现在自定义注解声明上,用于指定注解的生命周期及使用范围,例如:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
// ...
}
通过合理地控制 @Retention
的属性值,我们可以更好地适应程序的需求,并充分发挥自定义注解的优势和特点。
# 自定义注解
自定义一个注解用于方法,自定义注解的保留域是运行时。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InitDemo {
}
如下是定义一个类,用于使用该注解。
public class InitInvoke {
@InitDemo
public void initDemo(){
System.out.println("使用了@InitDemo 注解");
}
}
通过反射机制,查看InitInvoke的类是否使用了InitDemo
的注解。
public class InitMain {
public static void main(String[] args) throws ClassNotFoundException {
// 通过反射机制获取类
Class aClass = Class.forName("com.juc.aop.InitInvoke");
// 通过类获取所有的方法
Method[] methods = aClass.getMethods();
// 通过stream流过滤是否存在该method
Object[] objects = Arrays.stream(methods)
.filter(method -> method.isAnnotationPresent(InitDemo.class))
.map((Function<Method, Object>) method -> method)
.toArray();
// 打印当前的方法
System.out.println(objects[0]);
}
}
打印结果如下:
public void com.juc.aop.InitInvoke.initDemo()
# 自定义注解 + AOP
自定义注解和面向切面编程(AOP)是两种常用的技术手段,它们的结合可以帮助程序员更好地构建高可维护、高性能且易于扩展的应用程序。
自定义注解在Java开发中被广泛使用,通常用于将元数据添加到代码中并实现复杂的逻辑控制。而AOP则是一种技术框架,在运行时动态地将代码分为多个关注点(Aspect),从而实现对系统中不同层次模块的解耦和通用处理。
使用自定义注解与 AOP 相结合的方式,可以为业务需求和技术层面提供更灵活的解决方案。具体来说,在某些场景下,我们可以通过自定义注解为方法、类或对象打上标记,并结合 AOP 同时实现横向切面的操作,例如:
权限校验
:通过自定义注解标记方法或类,并基于 AOP 实现拦截器进行权限校验;日志记录
:通过自定义注解标记方法或类,并基于 AOP 实现切面服务来记录日志信息;事务管理
:通过自定义注解标记方法或类,并基于 AOP 实现拦截器对事务进行管理。
总之,自定义注解和 AOP 的相互配合,可以有效地降低业务逻辑代码的复杂度,提高程序的可维护性和灵活性。
# 1、创建日志打印注解
定义了一个名为 InvokeLog 的注解,它被 @Target(ElementType.METHOD)
指定为方法级别的注解,即只能在方法上使用。同时,通过 @Retention(RetentionPolicy.RUNTIME)
可知该注解的生命周期为 RUNTIME,表示可以在运行时获取到该注解对象及其属性。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}
# 2、创建一个Aspect切面类
使用Spring AOP框架实现的切面类 InvokeLogAspect ,它通过自定义注解 @InvokeLog
和 @Pointcut
切点,对程序中添加了 @InvokeLog
注解的方法进行切面编程,在方法执行前、后或抛出异常时,记录日志信息。
具体来说:
@Aspect
和@Component
分别表示该类是一个切面类且可被 Spring IOC 容器管理。@Pointcut
定义了一段表达式,用于确定哪些方法需要被拦截(即连接点),这里通过判断方法是否添加了@InvokeLog
注解来决定切入点。@Around
表示这是一个环绕通知,将目标方法包围起来,可以在目标方法执行之前和之后分别执行特定的处理逻辑。其中,ProceedingJoinPoint
对象可以在通知方法中获取执行目标方法的信息。- 在 @Around 方法中,首先获取目标方法的方法名。然后通过
log.info()
方法输出一些相关的日志信息,以便于进行调试和排查问题。 - 最后通过 proceedingJoinPoint.proceed() 方法执行目标方法,并记录方法执行前、后、抛出异常时的日志信息,这样可以更方便地追踪程序运行状态,提高系统的可维护性和可靠性。
@Aspect
@Component
@Slf4j
public class InvokeLogAspect {
// 确认切点
@Pointcut("@annotation(cn.itcast.order.aop.InvokeLog)")
public void pt(){
}
@Around("pt()")
public Object printInvokeLog(ProceedingJoinPoint proceedingJoinPoint){
Object proceed = null;
// 目标方法执行前
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
String methodName = signature.getMethod().getName();
log.info("这是方法执行前: {}",methodName);
try {
proceed = proceedingJoinPoint.proceed();
// 目标方法执行后
log.info("这是方法执行后: {}",methodName);
} catch (Throwable e) {
e.printStackTrace();
// 目标方法执行抛出错误的时候
log.info("这是方法出现异常后:{}",methodName);
}
return proceed;
}
}
# 3、在业务上使用注解
@InvokeLog
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用Feign远程调用
User user = userClient.findById(order.getUserId());
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}