AOP
1. 概念:AOP(Aspect Oriented Programming),译为面向切面编程,其本质是:在运行时,动态地将增强方法添加到待被增强类的指定方法上。
2. 原因:面向对象编程要求将不同功能代码封装到不同的类和方法中,以降低代码复杂程度,增加代码可重用性。但是在使用过程中,这种方式又增加了代码间的耦合程度,不利于协同开发。举例说明,日志打印功能被封装到一个打印类Printer的打印方法print()中,现在多个类中要使用日志打印功能,方式一在各个类中分别自己写日子打印,这样降低了代码重用性;方式二把打印类Printer的打印方法print()集成到各个类中,这无疑增加了类间的耦合。那有没有即可重用又不耦合的方式呢?那就是AOP技术。
这里讲解Spring常用的动态AOP,也即动态代理的方式,其他AOP可以参考(https://www.cnblogs.com/xiaoxiao7/p/6057724.html)。接下来是我自己的理解,有不对的地方请指正。
3. 相关概念:
-
切面:之前学JAVA是面向对象的,对象之间的相互关联操作,这里的切面是指从对象的切面看,我们的目标是类的方法,需要被增强的是方法,提供增强功能的也是方法,所以切面编程可以说是,类间方法的关联编程,它不同于方法间的引用(这样会有耦合),是一种多个方法拼接式的功能增强。
-
切面类和待被增强类:待被增强类是我们的核心类,是把切面类的一些方法“拼接”到待被增强类的待被增强方法前后,得到一个和待被增强类有相同接口的代理类——已被增强类,如下图。已被增强类的使用方法和待被增强类相同,因为其实现了相同的接口。
-
连接点和切入点:待被增强类的所有方法都可以被称为连接点,如果一个连接点方法被增强,就被称为切入点。
-
织入:拼接的过程就是织入,织入的结果就是已被增强类,或者称为代理类(动态代理的说法)。
- Advice(通知/增强):直译通知不如翻译为增强,也就是切面类的可用来拼接的方法,如上图的methodA1和methodA3。根据Advice的位置和执行方式不同,可以被分为四类,如下图所示(这里的拼接方式和上面的不一样了,需要注意):
- 前置通知
- 后置通知
- 异常通知
- 最终通知
- 环绕通知:手动构建以上四个通知
通过,这个try-catch-finally模型可以简便记住这四种Advice的功能的不同。
3. Spring AOP方法:
- xml配置方式:
<!-- 配置srping的IOC,把service和Loggr对象配置进来-->
<bean id="Service" class="com.AccountServiceImpl"></bean>
<bean id="Logger" class="com.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="Logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="print" pointcut="execution(* com.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
-
注解的方式:
-
@Aspect//类前,表示当前类是一个切面类
-
@Pointcut(“execution(* com.itheima.service.impl..(…))”)
private void pt1(){}//filed,声明执行表达式,指定被增强的方法 -
@Before(“pt1()”)//方法前,前置通知
-
@AfterReturning(“pt1()”)//方法前,后置通知
-
@AfterThrowing(“pt1()”)//方法前,异常通知
-
@After(“pt1()”)//最终通知
-
@Around(“pt1()”)//环绕通知
-
另外:
1.环绕通知需要在切面类中手动配置以下代码:
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
//这里手动插入前置通知
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
//这里手动插入后置通知
return rtValue;
}catch (Throwable t){
//这里手动插入异常通知
throw new RuntimeException(t);
}fi nally {
//这里手动插入最终通知
}
}
}
2.切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)