AOP切面编程
- AOP概述
- AOP的相关名词
- AOP的两种配置
一.AOP概述
1.AOP,也就是面向切面编程。同时也是一种思想,可在不改变程序源码的情况下为程序添加额外的功能。
2.AOP的意图
允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。应用对象只实现业务逻辑即可,而不负责其它的系统级关注点,从而达到降低代码之间的耦合度,图解如下:
图一:业务对象与系统服务(日志,安全,事务)过于紧密,每一个业务除了要关注自己的业务,还要亲自执行一些日志,进行安全控制和参与事务,导致代码高耦合度。
图二:应用AOP切面,使这些与业务无关的逻辑模块化,以声明的方式应用到需要影响的组件上,让业务组件具有更高的内聚性以及更好的关注自身的业务,不用涉及到一些日志,系统服务带来的复杂性,从而达到降低代码之间的耦合度。
3.AOP的实现原理
主要的实现原理:通过动态代理(也就是代理模式,代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用),在不修改源代码快的前提下,添加不同方法逻辑或通知,通过两种代理实现:
- 利用cglib工具包
目标对象没有实现接口使用CGLIB代理。
- 利用 JDK Proxy API
如果目标对象实现接口,使用JDK代理。JDK动态代理特点:
2.1.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们提供目标对象所实现的接口的字节码对象)
2.2.动态代理要求目标对象一定要实现接口,所以也叫做接口代理。
二.AOP的相关名词
-
连接点(Join point)
连接点就是程序执行过程中能够插入的一个点,这个点可以是调用方法时或修改一个字段时,切面代码可以通过这个点插入到程序中,并可以添加新的行为。
-
切点(Poincut)
切点的定义:会匹配通知所要织入得一个或多个连接点,在应用中可以通过明确的包名+类名,或方法名称来指定哪些切点。
<!-- 作用在方法上的表达式execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 修饰符:可以省略 返回类型:*代表任意返回值 包名.类名:com.damo.service.*代表service包下的任意类 包名.类名.方法名(参数):com.damo.service.*.*(..)代表service包下的任意类的任意方法,(..)方法上的参数(不管方法是否有参) --> <aop:pointcut id="pointcut" expression="execution(* com.damo.service.*.*(..))" /> <!--within(包名.类名)--> <aop:pointcut id="pointcut" expression="within(com.damo.service.*))" />
连接点和切点的区别:连接点是满足切点扫描条件的时机点,决定哪些可以成为切点。(例子:一层楼的所有出口都是连接点,我们选择具体那个出口出去才是切点)
-
通知(Advice)
通知的定义:切面的工作就是通知,用于定义切面是什么时候使用和切面需要完成的工作。Spring切面可以应用到5种类型的通知:
- 前置通知(Before):目标方法被调用前调用通知功能
- 后置通知/最终通知(After):目标方法被调用后调用通知功能,无论方法输出返回什么
- 返回通知(After-returning):目标方法成功执行后调用通知
- 异常通知(After-throwing):目标方法抛出异常后调用通知
- 环绕通知(Around):在目标方法执行前后之间调用通知
-
切面(Aspect)
切面的定义:就是需要进行的切面内容,需要进行什么逻辑处理之类的内容。
-
织入
织入是把切面应用到目标对象并创建新的代理的过程,这个过程可以发生在编译器、类转载期、运行期。
<!--spring的aop编程 的织入功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
三.AOP的配置
AOP的配置方式主要有两种方式:
- XML配置
<!--AOP配置-->
<aop:config>
<!--
设置切点
id:切点名称
expression:切点表达式
-->
<aop:pointcut id="pointcut" expression="within(com.damo.service.*))" />
<aop:aspect ref="log">
<!--
method:设置某个方法为切面逻辑方法
pointcut-ref:切点对象ID
returing:方法返回值传给切面
throwing:异常对象传给切面处理
-->
<!--前置通知-->
<aop:before method="before" pointcut-ref="pointcut" />
<!--后置通知-->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="a" />
<!--最终通知-->
<aop:after method="after" pointcut-ref="pointcut" />
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut" />
<!--异常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e" />
</aop:aspect>
</aop:config>
- 纯注解方式
//标注为配置类
@Configuration
//开启包扫描
@ComponentScan(basePackages = "com.mlz")
//首先在配置类中开启AOP注解主持
@EnableAspectJAutoProxy
public class SpringConfig {}
@Component //spring容器管理bean
@Aspect //标记为切面组件类
public class LogAspect {
//@Pointcut设置切入点表达式注解
//设置连接点是类
//@Pointcut("within(cn..*Impl)")
//设置连接点是方法
@Pointcut("execution(* cn..*Impl.*(..))")
public void pointcut(){};
/**
* 方法执行前的切面方法
* @Before 替代前置通知标签aop-before
*/
@Before("pointcut()")
public void before(){
System.out.println("方法执行前,记录日志..........");
}
//返回后通知
@AfterReturning(pointcut = "pointcut()",returning = "obj")
public void afterReturning(Object obj){
System.out.println("方法执行返回后,记录日志..........");
}
//最终通知,不管是否有异常都会执行
@After("pointcut()")
public void after(){
System.out.println("方法执行后,最终通知,记录日志..........");
}
//异常通知
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void afterThrowing(Exception e){
System.out.println("方法执行发生异常,异常通知记录日志..........");
}
/**
* ProceedingJoinPoint:封装了Method对象的连接点
* @param joinPoint
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前置记录日志...............");
Object proceed = joinPoint.proceed();//执行目标方法的逻辑
System.out.println("环绕通知后置记录日志................");
return proceed;//目标方法返回值
}
}