前言
最近遇到了这样一个工作场景,需要写一批dubbo接口,再将dubbo接口注册到网关中,但是当dubbo接口异常的时候会给前端返回非常不友好的异常。所以就想要对异常进行统一捕获处理,但是对于这种service接口使用@ExceptionHandler注解进行异常捕获也是捕获不到的,应为他不是Controller的接口。这时就想到了自定义一个注解去实现异常捕获的功能。
Spring实现自定义注解
通过拦截器+AOP实现自定义注解的实现,在这里拦截器充当在指定注解处要执行的方法,aop负责将拦截器的方法和要注解生效的地方做一个织入(通过动态注解生成代理类实现)。
1.引入相关依赖
spring-boot-starter:spring的一些核心基础依赖
spring-boot-starter-aop:spring实现Aop的一些相关依赖
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-aop
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(); } }
至此就实现了通过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_PARAMETER | 1.8版本新增,应用于类型变量) |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention
表明该注解的生命周期
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
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(); } }
运行效果:
通过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配置文件中加入
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
写在最后
@ExceptionHandler注解的使用可参考文章Java实现优雅的参数校验方法详解
到此这篇关于SpringBoot中利用AOP和拦截器实现自定义注解的文章就介绍到这了,更多相关SpringBoot自定义注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!