SpringBoot注解+AOP实现

SpringBoot注解+AOP实现

Java Annotation注解的详解

​ Java注解是一种元数据,它可以用于在类、方法或其他代码结构中声明关于程序元素的信息和标记。在Java中,注解以 @ 符号开头,在编译时或运行时由Java虚拟机(JVM)或其他工具进行处理。

注解可以用于许多不同的用途,包括:

  1. 编译时检查:注解可以让编译器在编译期间检查程序是否满足一些条件,例如@Deprecated标记弃用的代码,编译器会在编译期间提示有关该代码的警告。
  2. 运行时处理:注解允许代码在运行时根据指定的元数据来执行特定操作。例如,许多Web框架使用注解对请求处理器和URL进行映射。
  3. 文档生成:注解可以用于生成文档以描述代码的特定方面,使得文档维护更简单方便。
  4. 安全性:注解可以用于标记代码中的潜在安全漏洞,帮助开发人员快速找到和修复这些问题。

@Target指定注解针对的地方

在Java开发中,自定义注解是一种非常常见的技术手段,它可以用于定义元数据,对程序的逻辑进行标注、配置和调度等操作。@Target 是一个元注解(即注解的注解),它用来规定自定义注解可以修饰的程序单元(如类、方法、字段等)。@Target 的类型包括以下几种:

  1. ElementType.ANNOTATION_TYPE 表示自定义的注解可以修饰其他注解,例如 @SuppressWarnings。
  2. ElementType.CONSTRUCTOR 表示自定义的注解可以修饰构造函数,用于配置依赖注入等操作。
  3. ElementType.FIELD 表示自定义的注解可以修饰类的属性(字段),用于配置依赖注入、序列化等操作。
  4. ElementType.LOCAL_VARIABLE 表示自定义的注解可以修饰局部变量,通常用于实现某些特定功能或业务逻辑的处理。
  5. ElementType.METHOD 表示自定义的注解可以修饰方法,通常用于配置事务、权限校验、缓存管理等操作。
  6. ElementType.MODULE 表示自定义的注解可以修饰 Java 9 中的模块(Module)。
  7. ElementType.PACKAGE 表示自定义的注解可以修饰 Java 包(Package)内的所有类。
  8. ElementType.PARAMETER 表示自定义的注解可以修饰方法的参数,通常用于配置参数校验、日志打印等操作。
  9. ElementType.TYPE 表示自定义的注解可以修饰类、接口(Interface)或枚举类型(Enum),通常用于配置声明周期、依赖注入、AOP等操作。

总之,通过合理使用 @Target,我们可以更加精细地控制自定义注解的使用范围,避免滥用或误用。需要注意的是,多个 @Target 可以同时出现在同一个注解上面,使用大括号括起来即可,例如:@Target({ElementType.METHOD, ElementType.FIELD}) 表示该注解可以同时修饰方法和属性。

@Retention指定注解的保留域

在Java开发中,自定义注解是一种非常常见的技术手段,它可以用于定义元数据,对程序的逻辑进行标注、配置和调度等操作。而 @Retention 是一个元注解(即注解的注解),它用来规定自定义注解的生命周期。

在Java语言中,共有三种 @Retention 类型:

  1. RetentionPolicy.SOURCE 表示自定义注解只在源代码中保留,编译器会在编译时丢弃该注解,不会被包含在编译后生成的 class 文件中。这种类型的注解通常用于对开发者进行提示或注释,对实际的程序并没有实质性的影响。
  2. RetentionPolicy.CLASS(默认值) 表示自定义注解在编译时保留,但不会被加载到 JVM 中运行。当程序运行时,JVM 不会将该注解加载进来,因此在运行期间无法获取该注解及其信息。这种类型的注解通常用于字节码分析工具等场景。
  3. 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;
    }

image-20230503135102244

猜你喜欢

转载自blog.csdn.net/weixin_46532941/article/details/130477849