目录
一、Aop介绍
( 1 ) 是什么
( 2 ) 作用
( 3 ) 概念和机制
( 4 ) 特点
二、日志通知
( 1 ) 前置通知
( 2 ) 后置通知
( 3 ) 环绕通知
( 4 ) 异常通知
( 5 ) 过滤通知
给我们带来收获
AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的一个重要特性。AOP通过将与核心业务逻辑无关的横切关注点(如事务管理、日志记录、安全性检查等)独立出来,从而实现了代码的模块化和重用。
在传统的面向对象编程中,通常通过将功能逻辑嵌入到核心业务逻辑中来实现某些功能。然而,这种方式会导致代码的重复和耦合,使得应用程序的维护和扩展变得困难。
而AOP的思想是将这些横切关注点与核心业务逻辑进行解耦。在Spring框架中,AOP通过切面(Aspect)来实现。切面是一个跨越多个类的模块化单元,它定义了横切关注点的行为。
在Spring中,通过使用AOP,你可以将横切关注点抽象为一个切面,并将其应用到多个类或方法上。当程序执行到被切入的方法时,切面的代码将自动执行,完成预定义的功能。
AOP在Spring框架中具有以下几个作用:
- 1. 代码解耦:AOP通过将横切关注点(如日志记录、事务管理、安全性检查等)与核心业务逻辑进行解耦,将这些功能从具体的业务代码中抽离出来。这样可以使业务代码更加清晰和简洁,减少了代码的重复和耦合。
- 2. 横切关注点的集中处理:通过定义切面,将多个类中的相同横切关注点集中处理。例如,多个方法需要进行日志记录,可以将日志记录的代码逻辑抽取到切面中,将切面应用到需要的方法上。这样,无需在每个方法中重复编写日志记录的代码,提高了代码的重用性和可维护性。
- 3. 代码重用与维护:AOP可以将横切关注点作为独立的模块进行开发和维护。这些模块可以在多个不同的业务场景中重用,减少了开发和维护的工作量。例如,将事务管理的逻辑抽取为切面,可以应用到不同的方法或类中,实现统一的事务管理,提高了代码的复用性和可维护性。
- 4. 增强系统功能:通过AOP可以增强系统的功能,如安全性检查、性能监控、缓存管理等。这些非功能性需求可以通过切面的方式统一添加到系统中,而不需要修改核心的业务逻辑。这样可以更灵活地对系统进行功能的增强和定制。
- 5. 可插拔式框架扩展:AOP可以为框架提供可插拔式的扩展能力。例如,Spring框架本身就使用AOP来实现事务管理、缓存管理等功能。开发人员可以基于Spring的AOP机制,为自己的框架增加新的功能,实现可插拔式的框架扩展。
- 6. 代码的可测试性:AOP可以使代码更易于进行单元测试和集成测试。通过将横切关注点抽取为切面,可以在测试过程中更方便地对关注点进行模拟或验证。这样可以提高测试的覆盖率和测试代码的可读性。
总而言之,AOP在Spring框架中的作用是通过解耦、集中处理横切关注点、增强系统功能、提供框架扩展能力等方面,使得代码更具重用性、可维护性和灵活性。它使开发人员可以更好地管理非核心功能,提高开发效率和系统的可靠性。
在Spring框架中,AOP的主要概念和机制包括切面(Aspect)、切点(Pointcut)、通知(Advice)、织入(Weaving)和引入(Introduction)等。
- 1. 切面(Aspect):切面是横切关注点的模块化单元,它由切点和通知组成。切面定义了在特定切点插入的代码逻辑,即在何处和何时执行通知。
- 2. 切点(Pointcut):切点用于定义切面将会生效的具体位置。通过切点表达式,可以选择需要被切入的方法或类。例如,通过表达式"execution(* com.example.service.*.*(..))"可以选择所有com.example.service包下的方法。
- 3. 通知(Advice):通知是切面内部的代码逻辑,它决定了在切点何处执行切面的逻辑。Spring定义了多种通知类型,包括:
- 前置通知(Before):在切点之前的代码逻辑执行。
- 后置通知(After):在切点之后的代码逻辑执行(无论目标方法是否发生异常)。
- 返回通知(After-returning):在切点之后的代码逻辑执行(只有目标方法正常返回时)。
- 异常通知(After-throwing):在切点之后的代码逻辑执行(只有目标方法发生异常时)。
- 环绕通知(Around):在切点之前和之后的代码逻辑执行,可以完全控制目标方法的执行。
- 过滤通知(Filtering advice)是一种特殊类型的通知,它用于过滤切点上要执行的切面逻辑。过滤通知可以控制切面是否应该在特定的条件下执行。
- 4. 织入(Weaving):织入是将切面与目标对象的代码进行整合的过程。在Spring中,有两种织入方式:
- 编译时织入(Compile-time weaving):在编译阶段,将切面织入目标类的字节码中。
- 运行时织入(Runtime weaving):在运行时,动态地将切面织入目标对象中。
- 5. 引入(Introduction):引入是一种特殊的通知类型,它允许为目标对象添加新的方法或属性,而不需要修改目标对象的源码。通过引入,可以在不改变类继承关系的情况下,为目标对象添加额外的功能。
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
- 目标(Target):被通知(被代理)的对象
- 代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知)
- 切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
- 适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
总结小知识 :
以上这些概念和机制一起协作,通过配置来实现AOP的功能。在Spring中,通过使用XML配置、注解或Java配置来定义切面、切点、通知等,并将切面应用到目标对象上。Spring框架在运行时会自动识别切点,并按照配置的通知类型执行切面的逻辑。
总而言之,Spring框架的AOP通过切面、切点、通知等概念和机制,提供了一种机制来管理横切关注点的代码逻辑。这使得开发人员能够在不修改核心业务逻辑的情况下,实现横切关注点的复用和集中处理,提高了代码的模块化、重用性和可维护性。
AOP(Aspect-Oriented Programming)是一种编程范式,具有以下几个特点:
- 1. 横切关注点:AOP通过横切关注点的概念,将与核心业务逻辑无关的横切功能(如日志记录、事务管理、安全性检查等)从业务代码中分离出来,形成独立的模块。这样可以实现功能的重用和集中管理,减少了代码冗余和耦合。
- 2. 切面模块化:AOP将横切关注点抽象为切面,切面是横切关注点的模块化单元,它包含了切点和通知。切点表示切面将会生效的具体位置,而通知表示在切点处执行的代码逻辑。通过切面的定义和组织,可以更好地管理横切关注点,并灵活地将切面应用到不同的目标对象上。
- 3. 代码解耦和重用:AOP的一个主要目标是解耦,通过将横切关注点与核心业务逻辑进行解耦,可以减少代码间的耦合度。切面可以在多个类和方法中复用,使得横切功能可以统一管理和维护。这样可以提高代码的重用性,减少代码的冗余,同时也使得业务逻辑更加清晰和简洁。
- 4. 面向切面编程:AOP是一种面向切面的编程范式,它允许开发人员通过定义切面来增强系统功能,而无需修改核心业务逻辑。通过切面的织入,可以在不修改原有代码的情况下,实现对系统的功能增强和定制。
- 5. 运行时动态代理:AOP通常在运行时对目标对象进行动态代理,即在程序运行时动态地将切面的逻辑织入到目标对象中。这样可以灵活地控制切面的应用范围,并在运行时根据需要进行切换。Spring框架通过动态代理技术实现AOP,提供了对JDK动态代理和CGLIB动态代理的支持。
- 6. 增强系统功能:AOP可以为系统增加额外的功能,如安全性检查、性能监控、缓存管理等。这些功能可以作为切面的通知部分,通过配置的方式将其应用到目标对象上。通过AOP的方式增强系统功能,不仅可以避免在每个方法中重复编写这些功能逻辑,而且还可以将其与核心业务逻辑分离,提高了代码的可维护性和可测试性。
总而言之,AOP在软件开发中具有横切关注点、切面模块化、代码解耦和重用、面向切面编程、运行时动态代理以及增强系统功能等特点。它能够提高代码的模块化程度、可维护性和灵活性,帮助开发人员更好地管理和扩展横切关注点的代码逻辑。
首先根据以下项目结构创建项目的接口,类,配置文件(可以根据自己的习惯进行更改)
IBookBiz 接口
package com.CloudJun.aop.biz.impl;
/**
* @author CloudJun
* @create 2023-08-17 14:20
*/
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
在exception包内创建一个PriceException的一次类
package com.CloudJun.aop.exception;
/**
* @author CloudJun
* @create 2023-08-17 14:26
* 异常类
*/
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);
}
}
BookBizImpl 接口实现类
package com.CloudJun.aop.biz.impl;
import com.CloudJun.aop.exception.PriceException;
/**
* @author CloudJun
* @create 2023-08-17 14:22
*/
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);
}
}
spring-aop.xml 配置文件
创建完以接口及类后可能还会出现方法报错及导包报错,查看了项目结构对比导包确实没有问题的情况下,进行以下操作方可恢复:
选中 File 在 点击Settings 查询Java Compiler后选中,如图操作:
Demo 测试类 (测试类中字符串纯属为了搞笑,没有其他意思,嘿嘿嘿。。。)
package com.CloudJun.aop.demo;
import com.CloudJun.aop.biz.impl.IBookBiz;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author CloudJun
* @create 2023-08-17 14:26
*/
public class Demo {
public static void main(String[] args) {
//加载spring核心配置文件(建模),获取spring上下文对象及上下文对象中可以获取任何javabean的对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/spring-aop.xml");
IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
bookBiz.buy("坤坤","金鸡读立图",19.9);
bookBiz.comment("坤坤","让我看出坤拳");
}
}
测 试 结 果 :
要进行前置通知,在advice包内创建一个 MyMethodBeforeAdvice 前置通知类
package com.CloudJun.aop.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 买书、评论前加系统日志 * @author CloudJun */ 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+")被调用了"); } }
在将配置文件修改为以下代码 ( spring-aop.xml )
com.CloudJun.aop.biz.impl.IBookBiz
myMethodBeforeAdvice
在测试类(Demo)中将
IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz");
这一行代码修改为
IBookBiz bookBiz = (IBookBiz) context.getBean("proxyFactoryBean");
如图:
再进行测试,开启服务测试代码,如果如图:
由图我们可看出在方法前会先执行前置通知(相当于进行了方法调用的记录)
在advice包内创建一个后置通知的类为 MyAfterReturningAdvice 的后置通知类
package com.CloudJun.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
/**
* 列如场景:买书返利
* @author CloudJun
*
*/
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);
}
}
再在 spring-aop.xml 配置文件中增加通知配置即可
com.CloudJun.aop.biz.impl.IBookBiz
myMethodBeforeAdvice
myAfterReturningAdvice
进行测试,结果为:
由图可以看出在调用方法后会执行后置通知(相当于进行了方法后的调用的记录)
在advice包内创建一个环绕通知的类为 MyMethodInterceptor 的环绕通知类
package com.CloudJun.aop.advice;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 环绕通知
* 包含了前置和后置通知
* @author CloudJun
*
*/
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;
}
}
再在 spring-aop.xml 配置文件中增加通知配置即可
com.CloudJun.aop.biz.impl.IBookBiz
myMethodBeforeAdvice
myAfterReturningAdvice
methodInterceptor
进行测试,结果为:
由图可以看出在调用方法前后(环绕)都会执行环绕通知(相当于进行了方法前后的调用的记录)
在advice包内创建一个环绕通知的类为 MyThrowsAdvice 的异常通知类
package com.CloudJun.aop.advice;
import com.CloudJun.aop.exception.PriceException;
import org.springframework.aop.ThrowsAdvice;
/**
* 出现异常执行系统提示,然后进行处理。价格异常为例
* @author CloudJun
*
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(PriceException ex) {
System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!");
}
}
注意 : 在改通知类中 afterThrowing 方法名称是不能修改的,必须是方法名为afterThrowing
再在 spring-aop.xml 配置文件中增加通知配置即可
com.CloudJun.aop.biz.impl.IBookBiz
myMethodBeforeAdvice
myAfterReturningAdvice
methodInterceptor
myThrowsAdvice
为了演示错误,我们将测试类中的bookBiz.buy("坤坤","金鸡读立图",19.9);
修改为
bookBiz.buy("坤坤","金鸡读立图",-19.9);
进行测试,结果为:
在执行代码出现异常时给予提示,说明代码的问题所在,还可以在异常类(MyThrowsAdvice )
中的afterThrowing方法里面直接进行异常的处理,让代码执行不进行报错。
将Demo测试类中的bookBiz.buy("坤坤","金鸡读立图",-19.9);修改回原来的代码。
以下代码:
bookBiz.buy("坤坤","金鸡读立图",19.9);
要求: 后置通知必须要在买完书之后才会出现通知(买书返利)。
只需要修改spring-aop.xml 配置文件即可
com.CloudJun.aop.biz.impl.IBookBiz
myMethodBeforeAdvice
advisor
methodInterceptor
myThrowsAdvice
进行测试,结果为:
由图中可以看出只有买书的方法有后置通知(买书返利)
而给书的评论的方法就没有后置通知(买书返利)
说明已经进行了筛选
学习Spring中的AOP(面向切面编程)可以带来以下收获:
模块化:AOP允许将横切关注点(例如日志记录、事务管理等)从核心业务逻辑中分离出来,将其作为一个独立的模块进行开发和维护。这样可以提高代码的可读性和可维护性,使系统更加模块化和灵活。
代码重用:AOP通过将通用的横切关注点抽象为切面,并将其应用到多个不同的类和模块中,可以实现代码的重用。这样可以减少重复的代码编写,提高开发效率。
面向横切关注点编程:AOP将关注点从传统的面向对象编程的纵向结构转变为横向结构,使得系统的结构更加清晰。通过将横切关注点抽象为切面,可以将其应用到系统的多个模块中,提高系统的可扩展性和可维护性。
提高系统的性能:AOP可以在运行时动态地将切面织入到目标对象中,从而实现对目标对象的增强。例如,可以通过AOP实现缓存、事务管理等功能,从而提高系统的性能和可靠性。
实现安全性控制:AOP可以用于实现安全性控制,通过在切面中定义安全性的规则和权限检查,可以对系统的访问进行控制。这样可以提高系统的安全性,防止未授权的访问和恶意操作。
日志记录和调试:AOP可以用于实现日志记录和调试功能,通过在切面中定义日志记录的规则和操作,可以自动记录系统的运行日志和调试信息。这样可以方便排查和解决问题,提高系统的可靠性和可维护性。
提供性能监控:AOP可以用于实现性能监控,通过在切面中定义性能监控的规则和操作,可以自动收集系统的性能数据和指标。这样可以及时发现和解决性能问题,提高系统的性能和响应能力。
总结来说,学习Spring中的AOP可以帮助我们解耦业务逻辑、提供声明式事务管理、实现安全性控制、日志记录和调试以及性能监控等功能。这些收获可以提高系统的可维护性、可测试性、安全性和性能,使我们的开发工作更加高效和稳定。