文章目录
- 一、初识面向切面编程(AOP)
- 1.1 什么是 AOP
- 1.2 AOP的应用场景
- 1.3 Aop 在 Spring 中的作用
- 1.3.1 Aop 的核心概念
- 1.4 使用 Spring 实现 AOP
- 1.4.1 方式一:使用 Spring API 接口实现 AOP 【主要是SpringAPI接口实现】
- 1.4.2 方式二:自定义类来实现 AOP【主要是切面定义】
- 1.4.3 方式三:使用注解实现 AOP
一、初识面向切面编程(AOP)
-
在以往的企业开发过程中,一些已经写完的功能可以会在原本的基础上进行扩展,这个时候就需要去修改原有的代码,将新扩展的内容完善进去。但是这个动作其实是企业级开发的大忌,因为原本好用的代码很可能因为新增的内容导致出现问题。
而解决这个问题会用到代理模式,将要新扩展的功能维护到代理角色中,再用代理角色去调用真实角色来实现功能的扩展,这样做不但将新扩展的功能实现类,原本的功能代码也无需变动,更加稳妥。而这也是 Spring AOP 的实现机制。
1.1 什么是 AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 如下图所示:AOP 就是通过一些方式,在原有业务逻辑不变的情况下将一些扩展功能完成
1.2 AOP的应用场景
-
日志记录: 记录调用方法的入参和结果返参。
-
用户的权限验证: 验证用户的权限放到AOP中,与主业务进行解耦。
-
性能监控: 监控程序运行方法的耗时,找出项目的瓶颈。
-
事务管理: 控制Spring事务,Mysql事务等。
-
AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。
1.3 Aop 在 Spring 中的作用
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
1.3.1 Aop 的核心概念
-
提供声明式事务;允许用户自定义切面
-
横切关注点: 跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
-
切面(ASPECT): 横切关注点被模块化的特殊对象。即,它是一个类。
-
通知(Advice): 切面必须要完成的工作。拦截到连接点之后,对切入点增强的内容。即,它是类中的一个方法。
-
目标(Target): 被代理的目标对象。
-
代理(Proxy): 指生成的代理对象
-
切入点(PointCut): 指需要对那些 JointPoint 进行拦截,即被拦截的连接点
-
连接点(JointPoint): 指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法
-
植入点(Weaving): 指把增强代码应用到目标上,生成代理对象的过程
-
通俗理解
横切关注点:在业务代码种需要新增的业务,即为关注点
切面:将关注点维护成一个类,即新增的业务就是切面
通知:切面中具体的方法,也就是具体需要完成的业务就是通知。
目标:即需要在那段业务新增业务代码,即目标对象。
代理:代理目标对象的对象
切入点:需要通知在那个位置执行新的业务代码
注:目标和代理被 Spring 完成了,InvocationHandler、Proxy
- Spring AOP中,通过 “通知(Advice)” 定义横切逻辑,Spring中支持5种类型的Advice:即AOP在不改变原有代码的情况下,去增加新的功能。
通知类型 | 连接点 |
---|---|
Before(前置通知) | 通知方法在目标方法调用之前执行,前置通知不会影响目标方法的执行,除非此处抛出异常 |
After(后置通知) | 通知方法在目标方法返回或者异常后调用(必然会执行) |
After-Returning(最终通知) | 通知方法在目标方法返回后调用,如果连接点抛出异常,则不会执行 |
After-Throwing(抛出异常后通知) | 通知方法在目标方法抛出异常后调用 |
Around(环绕通知) | 环绕目标方法的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行目标方法或直接返回自定义的返回值又或抛出异常将执行结束。 |
注:目标方法就是连接点
1.4 使用 Spring 实现 AOP
1.4.1 方式一:使用 Spring API 接口实现 AOP 【主要是SpringAPI接口实现】
-
创建 Maven 项目并在 pom.xml 中引入如下依赖(使用 AOP 织入需要此依赖)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
在service包下,定义UserService业务接口和UserServiceImpl实现类
-
UserService 接口
public interface UserService { void add(); void query(); void delete(); void update(); }
-
UserServiceImpl 实现类
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("新增一个用户"); } @Override public void query() { System.out.println("查询用户信息"); } @Override public void delete() { System.out.println("删除一个用户"); } @Override public void update() { System.out.println("修改用户信息"); } }
-
-
在 log 包下定义 一个前置增强和一个后置增强类
-
前置通知增强类
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; //MethodBeforeAdvice : 前置通知 public class BeforeLog implements MethodBeforeAdvice { /* * method:要执行的目标对象的方法 * args:参数 * target:目标对象 * */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了"); } }
-
后置通知增强类
import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; // AfterReturningAdvice : 后置通知 public class AfterLog implements AfterReturningAdvice { /* * returnValue: 返回值 * method:要执行的目标对象的方法 * args:参数 * target:目标对象 * */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue); } }
-
-
最后去 spring 的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置 applicationContext.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="afterLog" class="com.sys.log.AfterLog"/> <bean id="beforeLog" class="com.sys.log.BeforeLog"/> <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/> <!--配置 Spring aop:需要导入aop约束--> <!--实现 aop 的方式一:使用原生 Spring API接口--> <aop:config> <!--配置切入点: execution 表达式:execution(要执行的位置!) execution(* *) :第一个 * 代表返回值的类型任意,第二个 * 代表类,如果为 * 就代表所有类 execution(* com.sys.XXX.*(..)):.*(..):表示任何方法名,括号表示参数,两个点表示任何参数类型--> <aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/> <!--配置 Advice(通知)--> <!--前置加后置约等于环绕通知--> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/> </aop:config> </beans>
- Spring AOP切入点@Pointcut – execution表达式
-
创建 MyTest 测试类
public class MyText { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口 UserService userService = (UserService) context.getBean("userService"); userService.add(); userService.update(); userService.query(); userService.delete(); } }
-
执行结果 :因为接口定义的方法都是 Void 所以没有返回值
-
1.4.2 方式二:自定义类来实现 AOP【主要是切面定义】
-
不使用 Spring 提供的 API 接口,使用自定义的 diy 类,搭配 xml 的 aop 配置实现
-
创建 diy 工具类
public class DiyPointCut { public void before(){ System.out.println("======方法执行前======"); } public void after(){ System.out.println("======方法执行后======"); } }
-
创建 Beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 将 Diy 工具类注册到 SPring 容器种 --> <bean id="diyPointCut" class="com.sys.diy.DiyPointCut"/> <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/> <!-- 配置 aop --> <aop:config> <!--自定义切面,ref 要引用的类--> <aop:aspect ref="diyPointCut"> <!--配置切入点--> <aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/> <!--配置通知 before:前置通知 after:后置通知--> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
-
修改 MyTest,其他代码内容不变
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); // 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口 UserService userService = (UserService) context.getBean("userService"); userService.add(); System.out.println("-------------------------------------------------"); userService.update(); System.out.println("-------------------------------------------------"); userService.query(); System.out.println("-------------------------------------------------"); userService.delete(); } }
-
执行结果
-
1.4.3 方式三:使用注解实现 AOP
-
@Aspect : 被该注解标注的类就是一个切面
-
@Before: 标注切面类中的方法为前置通知
- 参数:需要传入切入点,即 execution 表达式
-
@After: 标注切面类中的方法为后置通知
- 参数:需要传入切入点,即 execution 表达式
-
@Around: 标注切面类中的方法为环绕通知
-
参数:需要传入切入点,即 execution 表达式
-
被标记为环绕通知的方法还需要搭配 proceed() 来完成通知,如果没有该方法,那么切入点对应的方法不执行,也可以理解为这个方法就是执行切入点中的方法用的
- proceed() : 通过这个方法判断切入点中的方法在环绕通知中的那个位置执行
-
-
代码示例:(前置通知和后置通知)
-
新增 Diy 切面类
@Aspect // 将该类标记为一个切面 public class AnnotationPointCut { // 将该方法标记为前置通知 @Before("execution(* com.sys.service.impl.UserServiceImpl.*(..))") public void before(){ System.out.println("=====方法执行前====="); } // 将该方法标记为后置通知 @After("execution(* com.sys.service.impl.UserServiceImpl.*(..))") public void after(){ System.out.println("=====方法执行后====="); } }
-
修改 Beans.xml 开启注解支持
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/> <!--方式三:使用注解--> <bean id="annotationPointCut" class="com.sys.diy.AnnotationPointCut"/> <!--开启aop注解支持! JDK(默认是 proxy-target-class="false") cglib(proxy-target-class="true")--> <aop:aspectj-autoproxy/> </beans>
-
其他代码不变,运行测试类,执行结果
-
-
环绕通知代码示例
-
修改 Diy 切面类
@Aspect // 将该类标记为一个切面 public class AnnotationPointCut { @Around("execution(* com.sys.service.impl.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable{ System.out.println("环绕前"); Signature signature = jp.getSignature();// 获得签名 System.out.println("signature:"+signature); jp.proceed(); //执行方法 System.out.println("环绕后"); } }
-
其他代码不变,运行测试类,执行结果
-