AOP
介绍
- AOP:面向切面编程,无入侵式编程一种编程范式,指导开发者如何组织程序结构
- OOP:面向对象
- 作用:在不惊动原始设计的基础上为其做功能增强
概念定义
Aspect
(切面):描述通知与切入点的对应关系(执行位置和共性之间的关系)- Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point
(连接点):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等- 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- 在SpringAOP中,理解为方法的执行
Pointcut
(切入点):匹配连接点的式子- 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
Advice
(增强 / 通知):在切入点处执行的操作,也就是共性功能- Advice 定义了在
Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 - 通知写入通知类中
- 在SpringAOP中,功能最终以方法的形式呈现
- Advice 定义了在
Target
(目标对象):织入Advice
的目标对象.。Weaving
(织入):将Aspect
和其他对象连接起来, 并创建Advice
d object 的过程
入门案例
例1:在接口执行前输出当前系统时间
-
导入坐标(pom.xml)
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
制作连接点方法(原始操作,Dao接口与实现类)
@Component("UserService") @Repository public class UserServiceImpl { public void save(){ System.out.println(System.currentTimeMillis()); System.out.println("service save"); } public void update(){ System.out.println("service update"); } }
-
制作共性功能
public class MyAdvice { public void method() { System.out.println(System.currentTimeMillis());//共性功能 } }
-
定义切入点
//class MyAdvice //@Pointcut("execution()"")括号内表示这个方法为切入点 @Pointcut("execution(void spring.configTest.UserServiceImpl.update())") private void pt(){ }
-
绑定切入点与增强的关系
//class MyAdvice //@Before()表示当发现切入点时,在切入点执行之前使用增强 @Before("pt()") public void method() { System.out.println(System.currentTimeMillis()); }
-
让Spring识别AOP
-
在通知类前加入
@Component
和@Aspect
@Component @Aspect public class MyAdvice { ... }
-
在配置类前加入
@EnableAspectJAutoProxy
@Configuration @ComponentScan(basePackages = "spring.configTest") @EnableAspectJAutoProxy public class SpringConfig { ... }
-
- 运行结果:
userService.save()
与userService.update()
一致
AOP工作流程&核心概念
-
工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
-
匹配失败,创建对象
-
匹配成功,创建原始对象(目标对象)的代理对象
-
-
获取bean执行方法
-
获取bean,调用方法并执行,完成操作
-
获取的ben是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
-
-
-
核心概念
-
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
-
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
-
-
SpringAOP的本质:代理模式
AOP切入点表达式
-
切入点:要进行增强的方法
-
切入点表达式:要进行增强的方法的描述方法
-
通知的类型
@Before
:切入点方法运行前运行@After
:切入点方法运行后运行@Around
:前后都运行- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
@AfterReturning
:正常执行完毕后运行@AfterThrowing
:抛出异常后运行
-
切入点表达式标准格式
execution (public User com.UserService.findById(int))
- 动作关键字:描述切入点的行为动作,例如execution:表示执行到指定切入点
- 访问修饰符:public,private等,可以省略
- 返回值:使用
*
匹配任意类型 - 目标类/接口(需要完整包名):省略时匹配任意类型,
..
匹配包及其子包的所有类,*
- 方法名:使用
*
表示通配符以匹配任意方法,xxx*
匹配名称以xxx
开头的方法 - 参数:可以匹配多个,
(..)
匹配有任意数量参数的方法,括号内*
表示任意类型属性 - 异常名:方法定义中抛出指定异常,省略时匹配任意类型
-
类型匹配语法
+
:匹配任何数量字符;..
:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。+
:匹配指定类型的子类型**;仅能作为后缀放在类型模式后边。
-
示例
- java.lang.String 匹配String类型;
- java.*.String 匹配java包下的任何“一级子包”下的String类型;
- 如匹配java.lang.String,但不匹配java.lang.ss.String
- java..* 匹配java包及任何子包下的任何类型;
- java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
- java.lang.Number+ 匹配java.lang包下的任何Number的子类型;
获取通知数据
-
获取切入点方法的参数
-
JoinPoint:适用于前置、后置、返回后、抛出异常后通知
-
ProceedJointPoint:适用于环绕通知
//public class UserImpl public String test2(int id, String pwd) { System.out.println("test2"); return id+pwd; }
//public class MyAdvice @Pointcut("execution(* spring.configTest.UserImpl.test2(..))") private void pt(){} @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ Object[] args = pjp.getArgs(); System.out.println("around"); System.out.println(Arrays.toString(args)); return pjp.proceed(args); } @After("pt()") public void after(JoinPoint jp) throws Throwable{ Object[] args = jp.getArgs(); System.out.println("after"); System.out.println(Arrays.toString(args)); }
>>around >>[10, 22] >>test2 >>after >>[10, 22] >>update
-
-
获取切入点方法返回值
-
返回后通知
-
环绕通知
-
例(test2函数与上文一致)
//public class MyAdvice //returning = 的意思是,如果此函数有返回值,则将返回值填入ret中 @Pointcut("execution(* spring.configTest.UserImpl.test2(..))") private void pt(){ } @AfterReturning(value = "pt()",returning = "ret") public void afterReturning(Object ret){ System.out.println(ret); }
>>test2 >>1022
-
-
获取切入点方法运行异常信息(了解即可)
- 抛出异常后通知
- 环绕通知