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();
}
}
至此就实现了通过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配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK * 和CGLIB字节码生成的区别?
(1)JDK * 只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
写在最后
@ExceptionHandler注解的使用可参考文章Java实现优雅的参数校验方法详解
来源:https://blog.csdn.net/WX5991/article/details/122179177
猜你喜欢
- 一 介绍本节给知识追寻者给大家带来的是springSecurity入门篇,主要是简述下springSecrurity的启动原理和简单的入门搭
- 本文实例讲述了C#定时关闭窗体的方法,分享给大家供大家参考。具体方法如下:public partial class Form2 : Form
- 本文主要介绍了SpringBoot配置文件中的明文密码如何加密保存,读取以及对于自定义的加密算法加密的参数如何保存和读取。背景为了安全的需要
- 需求: 给定一个URL地址, 例如: http://www.cncounter.com/tools/shorturl.php, 解析对应的I
- 本文实例讲述了Java * 和AOP应用。分享给大家供大家参考,具体如下:一 点睛 * 在AOP(Aspect Orient Progr
- easyExcel简介:Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的
- 一、项目简述本系统主要实现的功能有: 学生以及老师的注册登录,在线考试,错题查询,学生管理,问题管理,错题管理,错题查询,分数查询,试卷管
- 引言float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近
- 本文主要介绍了Maven实战之搭建Maven * 和镜像的方法(图文),分享给大家,具体如下:准备工作安装3.6.*的Nexus理论Nexus
- 这篇文章主要介绍了springboot2.0如何通过fastdfs实现文件分布式上传,文中通过示例代码介绍的非常详细,对大家的学习或者工作具
- 1、IO流1.流和流的分类什么是IO流?I:Input (输入)O: Ouput(输出)IO流的分类?有多种分类方式:一种方式是按照流的方向
- 现假设某个公司采用公用电话来传递数据,数据是四位的整数,在传递过程中是加密的。加密规则是每位数字都加上5,然后再用除以10的余数代替该数字,
- 一、什么是代理?指为一个目标对象提供一个代理对象, 并由代理对象控制对目标对象的引用. 使用代理对象, 是为了在不修改目标对象的基础上,增强
- 代码如下:try { // 创建一个线程 Thread thread = new Thread() {
- 1、在ActionSupport中有一个validate()方法,这个方法是验证方法,它会在execute()方法执行之前执行,所以能够起到
- 1.将本地jar包放入本地仓库。只需执行如下命令即可:mvn install:install-file -Dfile=D:/demo/fib
- 昨天直接在机器上配置了Maven环境,今天顺便把Eclipse等IDE环境配置好。安装IDE Plugins的方法有很多。其一:在线安装,通
- using System;using System.Collections.Generic;using System.Web.Script.
- Java注解是在JDK5时引入的新特性,鉴于目前大部分框架(如spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于
- 概述从今天开始, 小白我将带大家开启 Java 数据结构 & 算法的新篇章.冒泡排序冒泡排序 (Bubble Sort) 是一种简单