AOP 术语
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。 |
Pointcut(切⼊点) | 它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
Advice(通知/增强) | 它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。 |
Target(⽬标对象) | 它指的是代理的⽬标对象。即被代理对象。 |
Proxy(代理) | 它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。 |
Weaving(织⼊) | 它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
Aspect(切⾯) | 它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager 就是⼀个切⾯。 |
连接点
:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
切⼊点
:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法
Advice增强
:
第⼀个层次:指的是横切逻辑
第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
Aspect切⾯:切⾯概念是对上述概念的⼀个综合
Aspect切⾯= 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑
众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码
Spring中AOP的代理选择
Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
Spring中AOP的配置⽅式
在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。
第⼀类:使⽤XML配置
第⼆类:使⽤XML+注解组合配置
第三类:使⽤纯注解配置
Spring中AOP实现
1. XML 模式
Spring是模块化开发的框架,使⽤aop就引⼊aop的jar
- 坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency
- AOP 核⼼配置
<!--
Spring基于XML的AOP配置前期准备:
在spring的配置⽂件中加⼊aop的约束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
Spring基于XML的AOP配置步骤:
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型
⼊⻔案例采⽤前置通知,标签为aop:before
-->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public * com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
- 细节
- 关于切⼊点表达式
上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?我们往下看。-
概念及作⽤
切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。 -
关于AspectJ
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。 -
切⼊点表达式使⽤示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表) 全匹配⽅式: public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 访问修饰符可以省略 void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 返回值可以使⽤*,表示任意返回值 * com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个 * ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 包名可以使⽤..表示当前包及其⼦包 * ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 类名和⽅法名,都可以使⽤.表示任意类,任意⽅法 * ...(com.lagou.pojo.Account) 参数列表,可以使⽤具体类型 基本类型直接写类型名称 : int 引⽤类型必须写全限定类名:java.lang.String 参数列表可以使⽤*,表示任意参数类型,但是必须有参数 * *..*.*(*) 参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型 * *..*.*(..) 全通配⽅式: * *..*.*(..)
-
- 改变代理⽅式的配置
在前⾯我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。
但是我们都知道,⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种-
使⽤aop:config标签配置
<aop:config proxy-target-class="true">
-
使⽤aop:aspectj-autoproxy标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持--> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
-
- 五种通知类型
-
前置通知
配置⽅式:aop:before标签<!-- 作⽤: ⽤于配置前置通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定前置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:before method="printLog" pointcut-ref="pointcut1"> </aop:before>
执⾏时机
前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
细节
前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。 -
正常执⾏时通知
配置⽅式<!-- 作⽤: ⽤于配置正常执⾏时通知 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定后置通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after-returning method="afterReturningPrintLog" pointcutref="pt1"></aop:after-returning
-
异常通知
配置⽅式<!-- 作⽤: ⽤于配置异常通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定异常通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
执⾏时机
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
细节
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。 -
最终通知
配置⽅式<!-- 作⽤: ⽤于指定最终通知。 出现位置: 它只能出现在aop:aspect标签内部 属性: method:⽤于指定最终通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
执⾏时机
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
细节
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。 -
环绕通知
配置⽅式<!-- 作⽤: ⽤于配置环绕通知。 出现位置: 它只能出现在aop:aspect标签的内部 属性: method:⽤于指定环绕通知的⽅法名称 pointcut:⽤于指定切⼊点表达式 pointcut-ref:⽤于指定切⼊点表达式的引⽤ --> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
特别说明
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。
-
- 关于切⼊点表达式
2. XML+注解模式
-
XML 中开启 Spring 对注解 AOP 的⽀持
<!--开启spring对注解aop的⽀持--> <aop:aspectj-autoproxy/>
-
示例
/**
* 模拟记录⽇志
*/
@Component
@Aspect
public class LogUtil {
/**
* 我们在xml中已经使⽤了通⽤切⼊点表达式,供多个切⾯使⽤,那么在注解中如何使⽤呢?
* 第⼀步:编写⼀个⽅法
* 第⼆步:在⽅法使⽤@Pointcut注解
* 第三步:给注解的value属性提供切⼊点表达式
* 细节:
* 1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。
* 2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。
*/
@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:"+ Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义返回值
Object rtValue = null;
try{
//前置通知
System.out.println("前置通知");
//1.获取参数
Object[] args = pjp.getArgs();
//2.执⾏切⼊点⽅法
rtValue = pjp.proceed(args);
//后置通知
System.out.println("后置通知");
}catch (Throwable t){
//异常通知
System.out.println("异常通知");
t.printStackTrace();
}finally {
//最终通知
System.out.println("最终通知");
}
return rtValue;
}
}
3. 注解模式
在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
在配置类中使⽤如下注解进⾏替换上述配置
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}