初识 AOP
什么是AoP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程
AOP 是 OOP (面向对象编程) 的延,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合降低,提高程序的可重用性,同时提高了开发的效率。
这样做的好处是:
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
- 减少重复代码,提高开发效率,便于后期维护
AOP底层实现
实际上,AOP的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间, Spring 通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
AOP 相关概念
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP 常用的术语如下:
- Target(目标对象):代理的目标对象。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在 Spring中,这些点指的是方法,因为 Spring 只支持方法类型的连接点。
- PoIntcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
- Advice(通知/增强):所谓通知是指拦馘到 Joinpoint之后所要做的事情就是通知
分类:前置通知、后置通知、异常通知、最终通知、环绕通知。 - Aspect(切面):是切入点和通知(引介)的结合。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。 Spring 采用动态代理织入,而 Aspect 采用编译期织入和类装载期织入。
基于 XML 的 AOP 开发
Maven 相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.10</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
|
创建主体方法和接口
1 2 3 4 5 6 7 8 9 10
| public interface AccountService { public void transfer(); }
public class AccountServiceImpl implements AccountService { @Override public void transfer() { System.out.println("开始转账"); } }
|
创建通知类及方法
1 2 3 4 5 6 7 8 9 10 11
| package com.orginly.advice;
public class TransferAdvice { public void before() { System.out.println("前置通知执行了"); }
public void afterReturning() { System.out.println("后置通知执行了"); } }
|
将目标类和通知类的创建权交给 Spring
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?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="accountService" class="com.orginly.service.impl.AccountServiceImpl"/> <bean id="transferAdvice" class="com.orginly.advice.TransferAdvice"/> </beans>
|
配置 AOP 前置增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <bean id="accountService" class="com.orginly.service.impl.AccountServiceImpl"/>
<bean id="transferAdvice" class="com.orginly.advice.TransferAdvice"/>
<aop:config> <aop:aspect ref="transferAdvice"> <aop:before method="before" pointcut="execution(public void com.orginly.service.impl.AccountServiceImpl.transfer())"/> <aop:after-returning method="afterReturning" pointcut="execution(public void com.orginly.service.impl.AccountServiceImpl.transfer())"/> </aop:aspect> </aop:config>
|
XML 配置 AOP 详解
切点表达式
表达式语法:
1
| execution([修饰符]返回值类型 包名.类名.方法名(参数))
|
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
- 包名与类名之间一个点,代表当前包下的类,两个点
..
表示当的包及其子包下的类 - 参数列表可以使用两个点
..
表示任意个数,任意类型的参数列表
例如:
1 2 3 4 5 6 7 8 9
| execution(public void com.orginly.service.impl.AccountServiceImpl.transfer());
execution(public void com.orginly.service.impl.AccountServiceImpl.transfer(java.lang.String));
execution(void com.orginly.service.impl.AccountServiceImpl.*(..));
execution(* com.orginly.service.impl.*.*(..));
execution(* com.orginly.service..*.*(..));
|
切点表达式抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
1 2 3 4 5 6 7 8 9 10 11 12
| <aop:config> <aop:aspect ref="transferAdvice"> <aop:pointcut id="acService" expression="execution(public void com.orginly.service.impl.AccountServiceImpl.*())"/> <aop:before method="before" pointcut-ref="acService"/> <aop:after-returning method="afterReturning" pointcut-ref="acService" /> </aop:aspect> </aop:config>
|
通知类型详解
通知的配置语法:
1
| <aop:通知类型 method="通知类中方法名" pointcut=“切点表达式"></aop:通知类型>
|
名称 | 标签 | 说明 |
---|
前置通知 | < aop:before > | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | < aop:afterReturning > | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | < aop:afterThrowing > | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | < aop:after > | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
环绕通知 | < aop:around > | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |
注意:通常情况下,环绕通知都是独立使用的
环绕通知
1 2 3 4 5 6 7 8
| <aop:config> <aop:aspect ref="transferAdvice"> <aop:pointcut id="acService" expression="execution(public void com.orginly.service.impl.AccountServiceImpl.*())"/> <aop:around method="around" pointcut-ref="acService"/> </aop:aspect> </aop:config>
|
注意:如果只配置 XML 是不生效的,需要在环绕通知方法中进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public Object around(ProceedingJoinPoint pjp) { Object proceed = null; try { System.out.println("前置通知..."); proceed = pjp.proceed(); System.out.println("后置通知..."); } catch (Throwable e) { e.printStackTrace(); System.out.println("异常通知..."); } finally { System.out.println("最终通知..."); } return proceed; }
|
基于注解 AOP 开发
创建主体方法和接口
1 2 3 4 5 6 7 8 9 10 11
| public interface AccountService { public void transfer(); }
@Service public class AccountServiceImpl implements AccountService { @Override public void transfer() { System.out.println("开始转账"); } }
|
通知类升级为切面类
**在通知类中添加 @Aspect
升级为切面类
1 2 3 4 5 6 7 8 9
| @Component @Aspect public class TransferAdvice {
@Before("execution(* com.orginly.service.impl.AccountServiceImpl.*(..))") public void before() { System.out.println("前置通知执行了"); }
|
开启 AOP 自动代理
1 2 3 4 5
| <context:component-scan base-package="com.orginly"/>
<aop:aspectj-autoproxy />
|
如果是纯注解模式可以在核心配置类增加
1
| @EnableAspectJAutoProxy // 开启 AOP 自动代理
|
抽取切点表达式
1 2 3 4 5 6 7 8 9
| @Pointcut("execution(* com.orginly.service.impl.AccountServiceImpl.*(..))") private void myPointcut() {}
@Before("myPointcut()") public void before() { System.out.println("前置通知执行了"); }
|
通知类型
通知的配置语法:@诵知注解("切点表达式")
名称 | 标签 | 说明 |
---|
前置通知 | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | @AfterThrowing | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | @After | 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行 |
环绕通知 | @Around | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行 |
注意: | | |
当前四个通知组合在一起时,执行顺序如下: | | |
@Before → @Ater → @After → Returning(如果有异常:@AfterThrowing)