SpringBoot中利用AOP和 * 实现自定义注解

作者:WX7251 时间:2022-09-14 00:26:53 

前言

最近遇到了这样一个工作场景,需要写一批dubbo接口,再将dubbo接口注册到网关中,但是当dubbo接口异常的时候会给前端返回非常不友好的异常。所以就想要对异常进行统一捕获处理,但是对于这种service接口使用@ExceptionHandler注解进行异常捕获也是捕获不到的,应为他不是Controller的接口。这时就想到了自定义一个注解去实现异常捕获的功能。

Spring实现自定义注解

通过 * +AOP实现自定义注解的实现,在这里 * 充当在指定注解处要执行的方法,aop负责将 * 的方法和要注解生效的地方做一个织入(通过动态注解生成代理类实现)。

1.引入相关依赖

spring-boot-starter:spring的一些核心基础依赖

spring-boot-starter-aop:spring实现Aop的一些相关依赖

<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-aop</artifactId>
       </dependency>

2.相关类

1.自定义注解类

@Target({ElementType.TYPE})  //说明了Annotation所修饰的对象范围,这里,的作用范围是类、接口(包括注解类型) 或enum
@Retention(RetentionPolicy.RUNTIME)  //自定义注解的有效期,Runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented //标注生成javadoc的时候是否会被记录
public @interface EasyExceptionResult {
}

2. * 类

/**
* MethodInterceptor是AOP项目中的 * (注:不是 * * ),
* 区别与HandlerInterceptor拦截目标时请求,它拦截的目标是方法。
*/
public class EasyExceptionIntercepter implements MethodInterceptor {
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
       AnnotatedElement element=invocation.getThis().getClass();
       EasyExceptionResult easyExceptionResult=element.getAnnotation(EasyExceptionResult.class);
       if (easyExceptionResult == null) {
           return invocation.proceed();
       }
       try {
           return invocation.proceed();
       } catch (Exception rpcException) {
           //不同环境下的一个异常处理
           System.out.println("发生异常了");
           return null;
       }
   }
}

3.切点切面类

MethodInterceptor的实现类能作为切面的执行方式是应为Interceptor的父类是Advice。

@Configuration
public class EasyExceptionAdvisor {

/**
    * 放在最后执行
    * 等待ump/日志等记录结束
    *
    * @return {@link DefaultPointcutAdvisor}对象
    */
   @Bean
   @Order(Integer.MIN_VALUE)
   public DefaultPointcutAdvisor easyExceptionResultAdvisor() {
       DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
       //针对EasyExceptionResult注解创建切点
       AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(EasyExceptionResult.class, true);
       EasyExceptionIntercepter interceptor = new EasyExceptionIntercepter();
       advisor.setPointcut(annotationMatchingPointcut);
       //在切点执行interceptor中的invoke方法
       advisor.setAdvice(interceptor);
       return advisor;
   }

}

4.自定义注解的使用

@Service
@EasyExceptionResult  //自定义异常捕获注解
public class EasyServiceImpl {

public void testEasyResult(){
       throw new NullPointerException("测试自定义注解");
   }

}

5.效果

@SpringBootApplication
public class JdStudyApplication {

public static void main(String[] args) {
       ConfigurableApplicationContext context=SpringApplication.run(JdStudyApplication.class, args);
       EasyServiceImpl easyService=context.getBean(EasyServiceImpl.class);
       easyService.testEasyResult();
   }

}

SpringBoot中利用AOP和 * 实现自定义注解

至此就实现了通过spring实现自定义注解。

Java实现自定义注解

虽然通过Spring实现了自定义注解但是还有办法让我们不通过Spring也能实现自定义注解,毕竟注解是早于Spring的。

JDK中有一些元注解,主要有@Target,@Retention,@Document,@Inherited用来修饰注解,如下为一个自定义注解。

@Target({ElementType.TYPE})  //说明了Annotation所修饰的对象范围,这里,的作用范围是类、接口(包括注解类型) 或enum
@Retention(RetentionPolicy.RUNTIME)  //自定义注解的有效期,Runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented //标注生成javadoc的时候是否会被记录
public @interface EasyExceptionResult {
}

@Target

表明该注解可以应用的java元素类型

Target类型描述
ElementType.TYPE应用于类、接口(包括注解类型)、枚举
ElementType.FIELD应用于属性(包括枚举中的常量)
ElementType.METHOD应用于方法
ElementType.PARAMETER应用于方法的形参
ElementType.CONSTRUCTOR应用于构造函数
ElementType.LOCAL_VARIABLE应用于局部变量
ElementType.ANNOTATION_TYPE应用于注解类型
ElementType.PACKAGE应用于包
ElementType.TYPE_PARAMETER1.8版本新增,应用于类型变量)
ElementType.TYPE_USE1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)

@Retention

表明该注解的生命周期

生命周期类型描述
RetentionPolicy.SOURCE编译时被丢弃,不包含在类文件中
RetentionPolicy.CLASSJVM加载时被丢弃,包含在类文件中,默认值
RetentionPolicy.RUNTIME由JVM 加载,包含在类文件中,在运行时可以被获取到

@Document

表明该注解标记的元素可以被Javadoc 或类似的工具文档化

@Inherited

表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解

通过Cglib实现

在我们定义好注解之后就需要考虑如何将注解和类绑定到一起,在运行期间达到我们想要的效果,这里就可以引入 * 的机制,将注解想要做的操作在方法执行前,类编译时就进行一个织入的操作如下。  

public static void main(String[] args) {
       Class easyServiceImplClass=EasyServiceImpl.class;
       //判断该对象是否有我们自定义的@EasyExceptionResult注解
       if(easyServiceImplClass.isAnnotationPresent(EasyExceptionResult.class)){
           final EasyServiceImpl easyService=new EasyServiceImpl();
           //cglib的字节码加强器
           Enhancer enhancer=new Enhancer();
           将目标对象所在的类作为Enhaner类的父类
           enhancer.setSuperclass(EasyServiceImpl.class);
           通过实现MethodInterceptor实现方法回调,MethodInterceptor继承了Callback
           enhancer.setCallback(new MethodInterceptor() {
               @Override
               public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                   try{
                       method.invoke(easyService, args);
                       System.out.println("事务结束...");
                   }catch (Exception e){
                       System.out.println("发生异常了");
                   }
                   return proxy;
               }
           });

Object obj= enhancer.create();;
           EasyServiceImpl easyServiceProxy=(EasyServiceImpl)obj;
           easyServiceProxy.testEasyResult();
       }

}

运行效果:

SpringBoot中利用AOP和 * 实现自定义注解

通过JDk * 实现

public class EasyServiceImplProxy implements InvocationHandler {

private EasyServiceImpl target;

public void setTarget(EasyServiceImpl target)
   {
       this.target = target;
   }

@Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       // 这里可以做增强
       System.out.println("已经是代理类啦");
       try{
           return  method.invoke(proxy, args);
       }catch (Exception e){
           System.out.println("发生异常了");
           return null;
       }
   }

/**
    * 生成代理类
    * @return 代理类
    */
   public Object CreatProxyedObj()
   {
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
   }
}

Cglib和JDK * 的区别

java * 是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

而cglib * 是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

1、如果目标对象实现了接口,默认情况下会采用JDK的 * 实现AOP 

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 

3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK * 和CGLIB之间转换

如何强制使用CGLIB实现AOP?

(1)添加CGLIB库,SPRING_HOME/cglib/*.jar

(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

JDK * 和CGLIB字节码生成的区别?

(1)JDK * 只能对实现了接口的类生成代理,而不能针对类

(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法

因为是继承,所以该类或方法最好不要声明成final 

写在最后

@ExceptionHandler注解的使用可参考文章Java实现优雅的参数校验方法详解

来源:https://blog.csdn.net/WX5991/article/details/122179177

标签:SpringBoot,AOP, , ,自定义,注解
0
投稿

猜你喜欢

  • 浅谈自定义校验注解ConstraintValidator

    2023-07-06 03:10:53
  • Java continue break制作简单聊天室程序

    2022-12-20 01:23:01
  • springmvc与mybatis集成配置实例详解

    2021-06-16 22:10:27
  • java多线程并发中使用Lockers类将多线程共享资源锁定

    2021-11-14 11:08:37
  • Java运行时数据区概述详解

    2023-10-08 07:00:10
  • Java中的break和continue关键字的使用方法总结

    2022-07-13 11:50:46
  • java实现快速打字游戏

    2022-11-05 11:16:38
  • java 数据结构单链表的实现

    2022-07-24 09:45:33
  • Java枚举学习之定义和基本特性详解

    2022-07-23 20:29:44
  • Java实现限定时间CountDownLatch并行场景

    2023-06-05 01:47:27
  • JDK源码之Vector与HashSet解析

    2021-09-06 10:47:23
  • IDEA Error:java:无效的源发行版:13的解决过程

    2023-11-25 10:07:19
  • Java 你知道什么是耦合、如何解(降低)耦合

    2022-03-23 08:44:19
  • c#处理3种json数据的实例

    2023-06-23 19:12:47
  • java基础--自己动手实现一个LRU

    2023-06-25 18:21:04
  • java.lang.NoClassDefFoundError错误解决办法

    2021-12-29 03:52:27
  • JAVA得到数组中最大值和最小值的简单实例

    2023-03-18 01:19:39
  • IDEA搭建dubbo项目的过程及存在的问题

    2023-10-15 17:56:53
  • Java制作智能拼图游戏原理及代码

    2022-08-02 21:46:57
  • Java 超详细讲解数据结构中的堆的应用

    2021-08-19 08:10:53
  • asp之家 软件编程 m.aspxhome.com