前言
一、AOP(面向切面)简介(了解AOP)
1. 了解AOP中重要的概念及术语
2. AOP的一些关键功能
3. AOP的作用领域
二、AOP的使用(面向切面)
1. AOP解决的问题
2. AOP使用中的关键性概念
三、AOP的通知类型及案例测试
1.通知类型
案例业务功能部署
BookBizImpl接口实现类
PriceException价格异常类
Spring-context.xml配置Bean元素
测试类Demo1代码(测试方法)
测试输出结果
注意事项
2. 通知案例
2.1 前置通知
MyMethodBeforeAdvice(前置通知类)
Spring-context.xml配置前置通知的上下文
测试类代码Demo1
测试效果
2.2 后置通知
MyAfterReturningAdvice (后置通知类)
Spring-context.xml配置后置通知的上下文
测试类Demo1代码
测试输出结果
2.3 环绕通知
MyMethodInterceptor(环绕通知类)
Spring-context.xml配置环绕通知的上下文
测试类Demo1代码
测试输出结果
2.4 异常通知
MyThrowsAdvice (异常通知类代码)
Spring-context.xml配置环绕通知的上下文
测试类Demo1代码
测试输出结果
2.5 过滤通知
Spring-context.xml配置过滤通知的上下文
测试Demo1代码
测试输出结果
四、总结
五、扩展
1. 如何在项目中去使用AOP
1.1 实现步骤
1.2 使用时的注意事项
2. AOP与OOP的区别及各自的优势
2.1 区别:
2.2 各自的优势:
OOP的优势:
AOP的优势:
在Spring框架中,面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,用于增强应用程序的模块化性和可重用性。AOP通过在程序执行过程中动态地将额外的行为织入到代码中,可以解耦和横切系统的关注点。
在传统的面向对象编程中,我们将应用程序的功能和业务逻辑以对象的形式进行封装和组织。然而,在某些情况下,我们需要处理跨多个对象、模块或层的关注点,如日志记录、事务管理、安全性等。这些关注点被称为横切关注点(cross-cutting concerns),它们与应用程序的核心逻辑相交织在一起,导致代码的重复和耦合。
AOP通过引入切面(Aspect)来解决这个问题。切面是横切关注点的模块化封装,它包含了横切关注点的逻辑和代码。在Spring中,切面可以通过使用基于注解或XML配置的方式定义。切面会被织入到应用程序的目标对象中,在目标对象的特定连接点(Join Point)上执行相应的行为。
切点(Pointcut):切点指定了在应用程序中哪些连接点应该被拦截和处理。连接点可以是方法的执行、方法的调用、对象的创建与初始化等。切点可以使用表达式或模式进行定义,以匹配特定的连接点。
通知(Advice):通知指定了在切点上要执行的逻辑。通知可以是在切点之前、之后或周围执行。在Spring中,常见的通知类型有前置通知、后置通知、返回通知和异常通知。
切面(Aspect):切面是通知和切点的组合。它是代码模块化的单元,它封装了特定关注点的逻辑和行为。切面定义了在哪些切点上应该执行特定的通知。
织入(Weaving):织入是将切面应用到目标对象中的过程。它可以在编译时、类加载时或运行时发生。Spring使用代理织入和字节码织入两种主要的织入方式。
引入(Introduction):引入允许我们向现有的类添加新方法或属性。它允许我们在不修改原始代码的情况下向现有类添加新的行为。
切面优先级(Aspect Ordering):如果有多个切面应用于同一个切点,可以通过指定优先级来定义它们的执行顺序。
切面的横切关注点条件(Aspect Pointcut Conditions):可以使用条件表达式定义在何时应用切面。这允许我们根据特定的条件选择性地应用切面。
异常处理(Exception Handling):AOP允许我们在方法抛出异常时自动捕获并处理异常。
事务管理(Transaction Management):AOP可以用于实现声明式事务管理,将事务相关的代码从业务逻辑中分离出来,使得代码更加清晰和可维护。
领域 | 说明 |
日志记录 | 通过AOP可以实现自动的日志记录,记录方法的调用、参数、返回值等,方便系统的监控和问题排查。 |
事务管理 | AOP可以实现声明式事务管理,将事务处理的代码从业务逻辑中分离出来,使得代码更加简洁和易于维护。 |
安全性 | 通过AOP可以实现对系统的安全性进行管理,例如进行身份验证、权限检查等。 |
缓存管理 | AOP可以实现对方法的结果进行缓存,在方法执行前先检查缓存,提高系统的性能和响应速度。 |
异常处理 | 通过AOP可以实现统一的异常处理,将异常捕获并进行处理,避免异常的冒泡传播。 |
性能监控 | AOP可以用于对方法执行时间、资源消耗等进行监控和统计,以便进行性能优化和调整。 |
面向切面工程改变了原有代码块的执行模式,原有代码是从上至下执行代码。而我们面向切面工程是代码从上至下执行,当代码执行到连接点时,如果它有前置通知则先执行前置通知再执行目标对象,因此一个完整的业务链就执行完成了;如果说执行到连接点时,它没有前置对象则先执行目标对象,再看看下面的连接点有没有后置通知,若有则执行后置通知,因此这个完整的业务就完成了。
解决了客户项目的需求改变,造成了在原有没必要改变的代码,则需要去改变它原有的代码。例如:在一个图书项目中的书籍管理的增删改的三个功能,本身只需要完成增删改三个功能实现即可,这时如果要求需要添加日志功能,那么需要在原有的代码基础上去修改添加日志功能,则受牵连的方法就三个(add、update、delete)。
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。
目标(Target):被通知(被代理)的对象。(作用:完成具体的业务逻辑)
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) (作用:完成切面编程)
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知)。
例子:外科医生+护士。 说明:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的。
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
注:可以将切入点看作是连接点的一个集合。
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
通知类型 | 说明 |
前置通知(Before) | 在目标方法执行前执行的通知。 |
后置通知(After) | 在目标方法执行后(无论是否抛出异常)执行的通知。 |
异常通知(After Throwing) | 在目标方法抛出异常后执行的通知。 |
环绕通知(Around) | 在目标方法执行前后执行的自定义行为。 |
过滤通知(Throws Advice) | 在目标方法执行前后执行的自定义行为。 |
IBookBiz接口类
package com.yx.AOP.biz;
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
package com.yx.AOP.biz.Impl;
import com.yx.AOP.biz.IBookBiz;
import com.yx.AOP.exception.PriceException;
public class BookBizImpl implements IBookBiz {
public BookBizImpl() {
super();
}
public boolean buy(String userName, String bookName, Double price) {
// 通过控制台的输出方式模拟购书
if (null == price || price <= 0) {
throw new PriceException("book price exception");
}
System.out.println(userName + " buy " + bookName + ", spend " + price);
return true;
}
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
package com.yx.AOP.exception;
public class PriceException extends RuntimeException {
public PriceException() {
super();
}
public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public PriceException(String message, Throwable cause) {
super(message, cause);
}
public PriceException(String message) {
super(message);
}
public PriceException(Throwable cause) {
super(cause);
}
}
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
bookBiz.buy("木易","斗破苍穹",25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
成功打印出方法内的输出语句,说明案例部署成功没有问题。
记得修改配置项目的jdk,要求与自身电脑安装的JKD版本一致,否则有一些的类的方法编写会报错。(步骤如下)
package com.zking.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 买书、评论前加系统日志
* @author Administrator
*
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
// 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去
String target = arg2.getClass().getName();//方法对象类
String methodName = arg0.getName();//方法名
String args = Arrays.toString(arg1);//方法参数
System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了");
}
}
com.yx.AOP.biz.IBookBiz
methodBeforeAdvice
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
// IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
bookBiz.buy("木易","斗破苍穹",25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
目的:实现买书返利的功能
package com.zking.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
/**
* 买书返利
* @author Administrator
*
*/
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
String target = arg3.getClass().getName();//方法对象类
String methodName = arg1.getName();//方法名
String args = Arrays.toString(arg2);//方法参数
System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0);
}
}
com.yx.AOP.biz.IBookBiz
myAfterReturningAdvice
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
// IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
bookBiz.buy("木易","斗破苍穹",25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
package com.yx.AOP.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.Arrays;
/**
* 环绕通知
* 包含了前置和后置通知
*
* @author Administrator
*
*/
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
String target = arg0.getThis().getClass().getName();//方法类对象
String methodName = arg0.getMethod().getName();//方法名
String args = Arrays.toString(arg0.getArguments());//方法参数
System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了");
// arg0.proceed()就是目标对象的方法
Object proceed = arg0.proceed();//方法返回结果
System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed);
return proceed;
}
}
com.yx.AOP.biz.IBookBiz
methodInterceptor
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
// IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
bookBiz.buy("木易","斗破苍穹",25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
package com.yx.AOP.advice;
import com.yx.AOP.exception.PriceException;
import org.springframework.aop.ThrowsAdvice;
/**
* 出现异常执行系统提示,然后进行处理。价格异常为例
* @author Administrator
*异常通知
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(PriceException ex) {
System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!");
}
}
com.yx.AOP.biz.IBookBiz
throwsAdvice
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
// IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
bookBiz.buy("木易","斗破苍穹",-25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
com.yx.AOP.biz.IBookBiz
methodBeforeAdvice
methodInterceptor
throwsAdvice
regexpMethodPointcutAdvisor
将后置通知的配置注释换成过滤通知。
package com.yx.AOP.Demo;
import com.yx.AOP.biz.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 君易--鑨
* @site www.yangxin.com
* @company 木易
* @create 2023-08-17 18:15
* 测试类
*/
public class Demo1 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring-context.xml");
// IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy");
bookBiz.buy("木易","斗破苍穹",25d);
bookBiz.comment("木易","太精彩了!!!");
}
}
AOP是一个面向切面编程(Aspect-Oriented Programming,AOP)的编程范式,程序一般是由上到下执行,但是AOP面向切面不是。AOP的程序执行,首先当程序执行到目标对象的目标方法时,
1.1 实现步骤
引入AOP框架:选择适合你项目的AOP框架,比如Spring框架的AOP模块或AspectJ框架。根据项目的需求和技术栈选择相应的框架,并将其添加到项目的依赖中。
定义切面:在项目中创建切面类,这个类将包含横切关注点的逻辑。你可以使用注解或XML配置方式创建切面。
定义切点:在切面类中定义切点,选择你感兴趣的目标方法或类。切点使用切点表达式来匹配方法和类,你可以使用切点表达式指定匹配的条件,如方法名、类名、包名等。
编写通知:在切面类中编写通知代码,根据你的需求选择适当类型的通知,如
@Before
、@After
、@Around
等。通知是在切点上执行的代码片段,你可以在通知中添加需要的逻辑,如日志记录、事务管理、权限检查等。将切面织入目标对象:通过配置文件或注解等方式将切面织入到目标对象中。这可以在Spring框架中使用
元素进行自动代理,或者使用
@Aspect
注解标记切面类。测试和调试:在项目中验证AOP是否按预期工作。执行一些被切点匹配的方法,观察通知是否按照定义的切面逻辑执行。
1.2 使用时的注意事项
- 了解AOP框架的具体用法和配置方式,参考相关文档和示例。
- 将AOP的关注点分离出来,确保切面的逻辑单一和职责清晰。
- 根据项目需求选择合适的切点表达式,不要使用过于宽泛的表达式,以免匹配到不相关的方法或类。
- 进行适当的测试和调试,验证切面的行为是否符合预期。
- 关注切面的性能影响,确保AOP操作的效率和可扩展性。
2.1 区别:
关注点不同:OOP关注对象及其行为,将代码组织成对象的集合,通过封装、继承和多态等机制实现代码的模块化。而AOP关注横切关注点,这些关注点通常不是特定的类或对象所特有的,而是跨越多个对象或层次的通用功能。
抽象级别不同:OOP在语言级别上提供了类、继承、多态等概念和机制,以支持面向对象的设计和编程。而AOP是对OOP的一种补充,它在更高的抽象级别上考虑系统中的横切关注点,通过横切关注点的抽象描述和通用切面逻辑来增强系统的功能。
关注点分离:OOP通过封装将相关逻辑组织在对象中,实现了关注点的局部化。而AOP通过将横切关注点从业务逻辑中分离出来,实现了关注点的集中化和复用。
2.2 各自的优势:
OOP的优势:
- 模块化和可维护性:OOP通过封装、继承和多态等机制,实现了代码的模块化,使得代码易于理解、扩展和维护。
- 重用性:OOP提倡代码的重用,可以通过继承和组合等方式实现代码的复用,减少代码冗余。
- 可扩展性:OOP的继承和多态机制使得系统易于扩展和演化,可以通过添加新的子类或实现新的接口来引入新功能。
AOP的优势:
- 关注点分离:AOP将横切关注点从业务逻辑中分离出来,减少了代码的重复性,增加了代码的可维护性和可重用性。
- 横向扩展:AOP可以轻松地在不修改目标对象代码的情况下,添加、修改或删除横切逻辑,改变系统的行为,提高了系统的灵活性。
- 增强系统功能:AOP可以通过在运行时动态地织入切面,增加新的功能和行为,如日志记录、安全检查、事务管理等。
感谢老铁们的阅读与支持,希望老铁能够三连一波,这无疑是对我最大的支持。请敬请期待下期博客的分享,我们下期再见。