spring之面向切面:AOP(2)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 【1月更文挑战第15天】一、基于注解的AOP1、技术说明2、准备工作3、创建切面类并配置4、各种通知5、切入点表达式语法6、重用切入点表达式7、获取通知的相关信息8、环绕通知9、切面的优先级二、基于XML的AOP1、准备工作2、实现

文章目录

前言

一、基于注解的AOP

1、技术说明

2、准备工作

3、创建切面类并配置

4、各种通知

5、切入点表达式语法

6、重用切入点表达式

7、获取通知的相关信息

8、环绕通知

9、切面的优先级

二、基于XML的AOP

1、准备工作

2、实现

总结


前言

一、基于注解的AOP

1、技术说明

2、准备工作

3、创建切面类并配置

4、各种通知

5、切入点表达式语法

6、重用切入点表达式

7、获取通知的相关信息

8、环绕通知

9、切面的优先级

二、基于XML的AOP

1、准备工作

2、实现


一、基于注解的AOP

1、技术说明

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

2、准备工作

①添加依赖

在IOC所需依赖基础上再加入下面依赖即可:

<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.2</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency><!--log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency></dependencies>

②准备被代理的目标资源

接口:

publicinterfaceCalculator {
intadd(inti, intj);
intsub(inti, intj);
intmul(inti, intj);
intdiv(inti, intj);
}

实现类:

@ComponentpublicclassCalculatorImplimplementsCalculator {
@Overridepublicintadd(inti, intj) {
intresult=i+j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintsub(inti, intj) {
intresult=i-j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintmul(inti, intj) {
intresult=i*j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintdiv(inti, intj) {
intresult=i/j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
}

3、创建切面类并配置

// @Aspect表示这个类是一个切面类@Aspect// @Component注解保证这个切面类能够放入IOC容器@ComponentpublicclassLogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidafterMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
    }
@AfterReturning(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning="result")
publicvoidafterReturningMethod(JoinPointjoinPoint, Objectresult){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }
@AfterThrowing(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing="ex")
publicvoidafterThrowingMethod(JoinPointjoinPoint, Throwableex){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicObjectaroundMethod(ProceedingJoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
Objectresult=null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行result=joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwablethrowable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
        }
returnresult;
    }
}

在Spring的配置文件中配置:

<?xmlversion="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--基于注解的AOP的实现:1、将目标对象和切面交给IOC容器管理(注解+扫描)2、开启AspectJ的自动代理,为目标对象自动生成代理3、将切面类通过注解@Aspect标识--><context:component-scanbase-package="com.atguigu.aop.annotation"></context:component-scan><aop:aspectj-autoproxy/></beans>

执行测试:

publicclassCalculatorTest {
privateLoggerlogger=LoggerFactory.getLogger(CalculatorTest.class);
@TestpublicvoidtestAdd(){
ApplicationContextac=newClassPathXmlApplicationContext("beans.xml");
Calculatorcalculator=ac.getBean( Calculator.class);
intadd=calculator.add(1, 1);
logger.info("执行成功:"+add);
    }
}

4、各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
  • 前置通知
  • 目标操作
  • 后置通知
  • 返回通知或异常通知
  • Spring版本5.3.x以后:
  • 前置通知
  • 目标操作
  • 返回通知或异常通知
  • 后置通知

5、切入点表达式语法

①作用

②语法细节

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
  • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
  • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
  • 例如:*Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
  • 例如:*Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(…)表示参数列表任意
  • 在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
  • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
  • 例如:execution(public int Service.(…, int)) 正确
    例如:execution(
    int *…Service.(…, int)) 错误

6、重用切入点表达式

①声明

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
publicvoidpointCut(){}

②在同一个切面中使用

@Before("pointCut()")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③在不同切面中使用

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

7、获取通知的相关信息

①获取连接点信息

获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidbeforeMethod(JoinPointjoinPoint){
//获取连接点的签名信息StringmethodName=joinPoint.getSignature().getName();
//获取目标方法到的实参信息Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

②获取目标方法的返回值

@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

@AfterReturning(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning="result")
publicvoidafterReturningMethod(JoinPointjoinPoint, Objectresult){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③获取目标方法的异常

@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

@AfterThrowing(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing="ex")
publicvoidafterThrowingMethod(JoinPointjoinPoint, Throwableex){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

8、环绕通知

@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicObjectaroundMethod(ProceedingJoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
Objectresult=null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标方法的执行,目标方法的返回值一定要返回给外界调用者result=joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwablethrowable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
    } finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
    }
returnresult;
}

9、切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用@Order注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

二、基于XML的AOP

1、准备工作

参考基于注解的AOP环境

2、实现

<context:component-scanbase-package="com.atguigu.aop.xml"></context:component-scan><aop:config><!--配置切面类--><aop:aspectref="loggerAspect"><aop:pointcutid="pointCut"expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/><aop:beforemethod="beforeMethod"pointcut-ref="pointCut"></aop:before><aop:aftermethod="afterMethod"pointcut-ref="pointCut"></aop:after><aop:after-returningmethod="afterReturningMethod"returning="result"pointcut-ref="pointCut"></aop:after-returning><aop:after-throwingmethod="afterThrowingMethod"throwing="ex"pointcut-ref="pointCut"></aop:after-throwing><aop:aroundmethod="aroundMethod"pointcut-ref="pointCut"></aop:around></aop:aspect></aop:config>

总结

以上就是spring之面向切面:AOP(2)的相关知识点,希望对你有所帮助。

积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!

相关文章
|
29天前
|
Java 关系型数据库 MySQL
利用Spring AOP技术实现一个读写分离
利用Spring AOP技术实现一个读写分离
32 0
|
2月前
|
监控 Java 开发者
Spring AOP动态代理
Spring AOP动态代理
46 1
|
2月前
|
Java Spring 容器
Spring的AOP失效场景详解
Spring的AOP失效场景详解
119 0
|
2月前
|
设计模式 Java Maven
Spring Aop 底层责任链思路实现-springaopdi-ceng-ze-ren-lian-si-lu-shi-xian
Spring Aop 底层责任链思路实现-springaopdi-ceng-ze-ren-lian-si-lu-shi-xian
36 1
|
3月前
|
XML Java 数据格式
5个点轻松搞定Spring AOP底层实现原理
AOP 也是 Spring 中一个较为重要的内容,相对于传统的 OOP 模式,AOP 有很多让人难以理解的地方,本篇文章将向大家介绍 AOP 的实现方法及其底层实现,内容包括:
47 1
|
23小时前
|
Java 测试技术 开发者
【亮剑】如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
|
18天前
|
Java Spring
代码优雅的转变:基于注解的AOP编程在Spring中的实践
代码优雅的转变:基于注解的AOP编程在Spring中的实践
17 0
|
18天前
|
XML 监控 Java
Spring AOP:解锁切面编程的威力与实践
Spring AOP:解锁切面编程的威力与实践
21 0
Spring AOP:解锁切面编程的威力与实践
|
21天前
|
XML 安全 Java
spring面向切面编程AOP
spring面向切面编程AOP
|
27天前
|
XML Java Maven
Spring之Aop的注解使用
Spring之Aop的注解使用