文章目录
AOP
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善;
实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(例如,在对象运行时动态织入一些扩展功能或控制对象执行)。
1 环境准备
1.1 工程及接口创建
- 第1步:创建普通
SpringBoot
工程 _07springaop; - 第2步:创建计算器接口
Calculator
,并定义 加减乘除 4 个方法;packagecn.tedu._07springaop.aop;publicinterfaceCalculator{intadd(int m,int n);intsub(int m,int n);intmul(int m,int n);intdiv(int m,int n);}
- 第3步:创建实现类
CalculatorImpl
,并实现这 4 个方法,要带有日志功能;packagecn.tedu._07springAop.aop;publicclassCalculatorImplimplementsCalculator{@Overridepublicintadd(int m,int n){System.out.println("[日志]add方法开始...");int result = m + n;System.out.println("[日志]add方法结束...);return result;}@Overridepublicintsub(int m,int n){System.out.println("[日志]sub方法开始...);int result = m - n;System.out.println("[日志]sub方法结束...);return result;}@Overridepublicintmul(int m,int n){System.out.println("[日志]mul方法开始...);int result = m * n;System.out.println("[日志]mul方法结束...);return result;}@Overridepublicintdiv(int m,int n){System.out.println("[日志]div方法开始...");int result = m / n;System.out.println("[日志]div方法结束...");return result;}}
1.2 工程存在的问题
1.2.1 问题
附加功能分散在各个业务功能方法中,不利于统一维护
1.2.2 解决思路
- 解耦:将附加功能代码从业务功能代码中抽取出来;
- 难点:要抽取的代码在方法内部,使用之前技术不太好解决,而AOP技术可以解决这个难题。
2 AOP面向切面编程
2.1 AOP概述
AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善;
在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.2 AOP原理分析
- 问题思考假如现在有一个业务对象,这个对象已经实现了一些核心业务功能,但是我们希望在核心业务的基础上在添加一些拓展业务,而且要求不能对目标业务对象中的实现进行修改(遵循OCP原则-对扩展开放,对修改关闭),请问如何实现?
- 解决思路为目标类创建子类或者为目标类创建兄弟类,对目标业务进行功能拓展。这种方式可以实现,但如果需要进行业务拓展的类有很多,我们每个类都要基于目标类型进行子类或兄弟类的创建,工作量会比较大;AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图:其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):- 第一种方式:借助JDK官方API(Proxy)为目标对象类型创建其兄弟类型对象;- 第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象.
3 基于注解的AOP
基于注解的AOP是一种AOP的实现方式,它通过在Java类、方法、参数等上添加注解的方式来实现切面的定义和应用,相比于传统的XML配置方式更加便捷和灵活.
3.1 入门示例
- 第1步:添加依赖
<!--引入aop依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
- 第2步:准备被代理的目标资源接口
packagecn.tedu.aop;publicinterfaceCalculator{intadd(int m,int n);intsub(int m,int n);intmul(int m,int n);intdiv(int m,int n);}
实现类packagecn.tedu._07springAop.aop;importorg.springframework.stereotype.Component;@ComponentpublicclassCalculatorImplimplementsCalculator{@Overridepublicintadd(int m,int n){int result = m + n;System.out.println("方法内部:"+ result);return result;}@Overridepublicintsub(int m,int n){int result = m - n;System.out.println("方法内部:"+ result);return result;}@Overridepublicintmul(int m,int n){int result = m * n;System.out.println("方法内部:"+ result);return result;}@Overridepublicintdiv(int m,int n){int result = m / n;System.out.println("方法内部:"+ result);return result;}}
- 第3步:创建切面类并配置
LogAspect
用于进行切入点的定义,功能增强方法的定义,在方法内部做日志业务增强,关键代码如下:packagecn.tedu._07springAop.aop;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.springframework.stereotype.Component;/** * Aspect注解:表示这个类是一个切面类 */@Aspect@ComponentpublicclassLogAspect{@Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")publicvoidbeforeMethod(JoinPoint joinPoint){System.out.println("LogAspect类中:前置通知,方法开始记录日志了...");}}
- 第4步:测试类中进行测试
@SpringBootTestclassApplicationTests{@TestvoidcontextLoads(){ApplicationContext context =newAnnotationConfigApplicationContext("cn.tedu._07springAop");Calculator calculator = context.getBean(Calculator.class);int addResult = calculator.add(10,20);}}
- 第5步:执行结果
3.2 使用流程
- 第1步:添加
aop
依赖并刷新Maven
;<!--引入aop依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
- 第2步:创建切面类(选择通知的方式);注意添加 @Aspect 注解,表示该类为一个切面类.
3.3 切入点表达式
在 AOP 中,切入点表达式指定了哪些方法需要被植入增强逻辑。它是一个表达式,用于匹配目标对象中的方法,并提供切入点的精确定义
- 切入点表达式语法 - 权限修饰符:* 表示权限修饰符不限;- 方法返回值:* 表示返回值不限;- 包名部分: … 表示包名任意,包的层次和深度任意;- 类名部分: * 表示类名任意;- 方法名部分: 表示方法名任意;- 方法参数列表部分:(…) 表示参数列表任意;
3.4 练习
- 项目背景假设你有一个
OrderService
接口,其中有一个方法placeOrder
,无返回值。这个方法在执行时,需要先进行订单数量的检查,如果订单数量大于0,则继续执行订单创建,否则返回异常信息,具体异常为:IllegalArgumentException
- 操作步骤- 第1步:创建接口
OrderService
和接口方法placeOrder
[需要有一个int类型的参数orderNumber];- 第2步:创建实现类OrderServiceImpl
,实现该方法,打印:订单创建成功
;- 第3步:创建切面类OrderCheckAspect
,前置通知beforeMethod
,进行订单数量的校验- 第4步:创建测试类进行测试 - 要求- 请使用AOP来实现上述功能;- 给出切面的定义和通知的定义。
3.5 通知类型
- 前置通知:
@Before
在被代理的目标方法前执行 - 返回通知:
@AfterReturning
在被代理的目标方法成功结束后执行 - 后置通知:
@After
在被代理的目标方法最终结束后执行 - 异常通知:
@AfterThrowing
在被代理的目标方法异常结束后执行 - 环绕通知:
@Around
使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
问题:返回通知 和 后置通知 的区别是什么?
- 执行时机返回通知是在目标方法执行后返回结果时执行的通知,只有目标方法正常返回时才会执行,抛异常则不会执行。后置通知则是在目标方法执行后执行的通知,无论目标方法是否抛出异常,后置通知都会执行。
- 访问权限返回通知可以获取并修改目标方法的返回值,后置通知无法访问目标方法的返回值。
通知类型示例代码
packagecn.tedu._07springAop.aop;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.*;importorg.springframework.stereotype.Component;importjava.util.Arrays;/**
* Aspect注解:表示这个类是一个切面类
*/@Aspect@ComponentpublicclassLogAspect{@Before("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")publicvoidbeforeMethod(JoinPoint joinPoint){/*
getSignature(): 获取连接点签名信息;
getName(): 获取连接点名称;
getArgs(): 获取连接点参数;
*/String methodName = joinPoint.getSignature().getName();String args =Arrays.toString(joinPoint.getArgs());System.out.println("LogAspect类中:前置通知,方法开始记录日志了..."+ methodName +":"+ args);}@After("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")publicvoidaftherMethod(JoinPoint joinPoint){System.out.println("LogAspect类中:后置通知,方法开始记录日志了...");}@AfterReturning(value ="execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", returning ="result")publicvoidafterReturningMethod(JoinPoint joinPoint,Object result){System.out.println("LogAspect类中:返回通知,方法开始记录日志了...");}@AfterThrowing(value ="execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))", throwing ="ex")publicvoidafterThrowingMethod(JoinPoint joinPoint,Throwable ex){System.out.println("LogAspect类中:异常通知,方法开始记录日志了...");}@Around("execution(public int cn.tedu._07springAop.aop.CalculatorImpl.*(..))")publicObjectaroundMethod(ProceedingJoinPoint joinPoint){Object result =null;try{System.out.println("环绕通知:目标对象方法执行之前");//目标对象方法的执行
result = joinPoint.proceed();System.out.println("环绕通知:目标对象方法执行之后");}catch(Throwable throwable){
throwable.printStackTrace();System.out.println("环绕通知:目标对象方法出现异常");}finally{System.out.println("环绕通知:目标对象方法执行完毕");}return result;}}
版权归原作者 极客编程坊 所有, 如有侵权,请联系我们删除。