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


猜你喜欢
- 本文是Neward & Associates的总裁Ted Neward为developerworks独家撰稿“你不知道5个……”系列
- 前言今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,
- 本文实例为大家分享了java排列组合算法的具体代码,供大家参考,具体内容如下package BeanUtil;import java.uti
- 提出问题下面所给代码编译时正常,但是执行时会出错,请指出程序在执行时能够执行到编号为(1)(2)(3)的代码行中的哪一行。using Sys
- OSS.Http项目对于.Net Standard标准库的支持已经迁移完毕,OSS开源系列两个最底层的类库已经具备跨运行时支持的能力。由于O
- 前言插入排序狭义上指的是简单插入排序(选择集合,比较大小,插入元素),广义上还应该包括希尔排序(分治思想)及其两种实现方式,最激动人心的是
- 本文实例为大家分享了SpringBoot整合BCrypt实现密码加密的具体代码,供大家参考,具体内容如下一. 首先在pom依赖中加入依赖:&
- LockSupport类用于创建锁和其他同步类的基本线程阻塞原语,此类与使用它的每个线程关联一个许可。如果获得许可,将立即返回对park的调
- 很多时候需要先判断当前用户的网络,才会继续之后的一些处理逻辑。但网络类型获取这一块,我用我自己的的手机调试时遇到一些问题,这里记录一下。一加
- 可空类型用途主要是从数据库读取数据有可能为空,而不是插入使用,插入数据都要进行验证,如果要插入数据库的null,则使用DBNull.valu
- 前言SpringBoot引入neo4j <dependency> &nb
- 线程状态图线程共包括以下5种状态。1. 新建状态(New)  
- 本文为大家分享了C#多线程之线程控制,供大家参考,具体内容如下方案一:调用线程控制方法.启动:Thread.Start();停止:Threa
- 故事背景故事发生在几个星期前,自动化平台代码开放给整个测试团队以后,越来越多的同事开始探索平台代码。为了保障自动化测试相关的数据和沉淀能不被
- 原由移动开发中,随着项目不断的跌代,需求越来越复杂后。项目工程也越来越庞大。那么此时的分module的开发,则是必然的选择了。在最终的组件化
- 本文实例汇总了C#中@的用法,对C#程序设计来说有不错的借鉴价值。具体如下:一 字符串中的用法1.学过C#的人都知道C# 中字符串常量可以以
- 一、问题描述LBS位置服务是android应用中重要的功能,应用越来越广泛,下面我们逐步学习和实现lbs相关的应用如定位、地图、导航等,首先
- 你好,今天我要和大家分享一些东西,举例来说这个在JavaScript中用的很多。我要讲讲回调(callbacks)。你知道什么时候用,怎么用
- 什么是不可变对象?String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。众所周知, 在Java中, String类
- Java中的StringUtils引入及使用pom.xml中引入依赖<!-- https://mvnrepository.com/ar