经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot中利用AOP和拦截器实现自定义注解
来源:jb51  时间:2022/6/27 14:13:42  对本文有异议

前言

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

Spring实现自定义注解

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

1.引入相关依赖

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

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

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-aop</artifactId>
  8. </dependency>

2.相关类

1.自定义注解类

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

2.拦截器类

  1. /**
  2. * MethodInterceptor是AOP项目中的拦截器(注:不是动态代理拦截器),
  3. * 区别与HandlerInterceptor拦截目标时请求,它拦截的目标是方法。
  4. */
  5. public class EasyExceptionIntercepter implements MethodInterceptor {
  6. @Override
  7. public Object invoke(MethodInvocation invocation) throws Throwable {
  8. AnnotatedElement element=invocation.getThis().getClass();
  9. EasyExceptionResult easyExceptionResult=element.getAnnotation(EasyExceptionResult.class);
  10. if (easyExceptionResult == null) {
  11. return invocation.proceed();
  12. }
  13. try {
  14. return invocation.proceed();
  15. } catch (Exception rpcException) {
  16. //不同环境下的一个异常处理
  17. System.out.println("发生异常了");
  18. return null;
  19. }
  20. }
  21. }

3.切点切面类

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

  1. @Configuration
  2. public class EasyExceptionAdvisor {
  3. /**
  4. * 放在最后执行
  5. * 等待ump/日志等记录结束
  6. *
  7. * @return {@link DefaultPointcutAdvisor}对象
  8. */
  9. @Bean
  10. @Order(Integer.MIN_VALUE)
  11. public DefaultPointcutAdvisor easyExceptionResultAdvisor() {
  12. DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
  13. //针对EasyExceptionResult注解创建切点
  14. AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(EasyExceptionResult.class, true);
  15. EasyExceptionIntercepter interceptor = new EasyExceptionIntercepter();
  16. advisor.setPointcut(annotationMatchingPointcut);
  17. //在切点执行interceptor中的invoke方法
  18. advisor.setAdvice(interceptor);
  19. return advisor;
  20. }
  21. }

4.自定义注解的使用

  1. @Service
  2. @EasyExceptionResult //自定义异常捕获注解
  3. public class EasyServiceImpl {
  4. public void testEasyResult(){
  5. throw new NullPointerException("测试自定义注解");
  6. }
  7. }

5.效果

  1. @SpringBootApplication
  2. public class JdStudyApplication {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext context=SpringApplication.run(JdStudyApplication.class, args);
  5. EasyServiceImpl easyService=context.getBean(EasyServiceImpl.class);
  6. easyService.testEasyResult();
  7. }
  8. }

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

Java实现自定义注解

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

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

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

@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实现

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

  1. public static void main(String[] args) {
  2. Class easyServiceImplClass=EasyServiceImpl.class;
  3. //判断该对象是否有我们自定义的@EasyExceptionResult注解
  4. if(easyServiceImplClass.isAnnotationPresent(EasyExceptionResult.class)){
  5. final EasyServiceImpl easyService=new EasyServiceImpl();
  6. //cglib的字节码加强器
  7. Enhancer enhancer=new Enhancer();
  8. 将目标对象所在的类作为Enhaner类的父类
  9. enhancer.setSuperclass(EasyServiceImpl.class);
  10. 通过实现MethodInterceptor实现方法回调,MethodInterceptor继承了Callback
  11. enhancer.setCallback(new MethodInterceptor() {
  12. @Override
  13. public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  14. try{
  15. method.invoke(easyService, args);
  16. System.out.println("事务结束...");
  17. }catch (Exception e){
  18. System.out.println("发生异常了");
  19. }
  20. return proxy;
  21. }
  22. });
  23. Object obj= enhancer.create();;
  24. EasyServiceImpl easyServiceProxy=(EasyServiceImpl)obj;
  25. easyServiceProxy.testEasyResult();
  26. }
  27. }

运行效果:

通过JDk动态代理实现

  1. public class EasyServiceImplProxy implements InvocationHandler {
  2. private EasyServiceImpl target;
  3. public void setTarget(EasyServiceImpl target)
  4. {
  5. this.target = target;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. // 这里可以做增强
  10. System.out.println("已经是代理类啦");
  11. try{
  12. return method.invoke(proxy, args);
  13. }catch (Exception e){
  14. System.out.println("发生异常了");
  15. return null;
  16. }
  17. }
  18. /**
  19. * 生成代理类
  20. * @return 代理类
  21. */
  22. public Object CreatProxyedObj()
  23. {
  24. return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
  25. }
  26. }

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实现优雅的参数校验方法详解

到此这篇关于SpringBoot中利用AOP和拦截器实现自定义注解的文章就介绍到这了,更多相关SpringBoot自定义注解内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号