AOP相关术语
说起“面向切面编程”,这已经不是一个新鲜的词儿了。它的本质就是在不影响原有业务逻辑的情况下做一些逻辑增强。比方说请求接口前后的日志打印、权限校验、登录验证等等场景就是很经典的AOP的使用场景。
下表阐述了AOP中一些术语的概念:
名词 | 解释 |
Joinpoint(连接点) | 指得是可以加入增强逻辑的点。也就是说在方法执行的开始、返回之前、返回之后和抛出异常时这几个点。连接点是一些候选点。 |
Pointcut(切入点) | 通俗意义上来讲,就是定义横切逻辑会影响哪些方法和横切方法的标识。 |
Advice(通知/增强) | 指的是切面类中用于逻辑增强的方法。其分类有前置通知、后置通知、异常通知、最终通知和环绕通知几类。 |
Target(目标对象) | 即被代理的对象,或者被增强的对象。 |
Proxy(代理) | 指的是目标类被AOP织入增强后产生的代理对象。 |
Weaving(织入) | 指的是把增强应用到目标对象来创建一个新的代理对象的过程。Spring采用动态代理的方式来织入,而AspectJ采用编译期织入和类装载期织入。 |
Aspect(切面) | 指的是增强代码所关注的方面。通俗来讲就是如果一个类中有增强逻辑,那么这个类就是一个切面类。 |
其实概念还是很绕的,但是目的就是为了所定要在那个地方插入什么增强逻辑。所以总结来说就是:
Aspect切面 = 切入点 + 增强 = 切入点(锁定方法)+ 方位点(锁定方法中的特殊时机)+ 横切逻辑。
Spring中的AOP的代理选择
Spring实现AOP用的是动态代理技术。默认情况下,Spring会根据被代理的对象是否实现了接口来选择动态代理技术。如果实现了接口,就采用JDK动态代理技术。没有的话,使用CGLIB动态代理技术。不过,我们可以通过配置来让Spring强制使用CGLIB动态代理。
Spring中AOP的配置方式
Spring中支持三种AOP的配置方式,如下:
- 使用xml配置。
- 使用注解结合xml的方式配置。
- 使用纯注解方式配置。
Spring中AOP的使用
xml配置模式
Spring是模块化开发的框架,使用AOP就引入相关的依赖:
<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.rubin.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public *
com.rubin.service.impl.TransferServiceImpl.updateAccountByCardNo(com.rubin.pojo.Account))">
</aop:before>
<!--配置方法正常执行,返回后通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(public *
com.rubin.service.impl.TransferServiceImpl.updateAccountByCardNo(com.rubin.pojo.Account))">
</aop:after-returning>
<!--配置异常时通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(public *
com.rubin.service.impl.TransferServiceImpl.updateAccountByCardNo(com.rubin.pojo.Account))">
</aop:after-throwing>
<!--配置最终通知-->
<aop:after method="afterPrintLog" pointcut="execution(public *
com.rubin.service.impl.TransferServiceImpl.updateAccountByCardNo(com.rubin.pojo.Account))">
</aop:after>
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut="execution(public *
com.rubin.service.impl.TransferServiceImpl.updateAccountByCardNo(com.rubin.pojo.Account))">
</aop:around>
</aop:aspect>
</aop:config>
对于示例配置,这里做一点补充。我们的一个aop:aspect
里面可以定义多个切入点(通过aop:pointcut
定义),也可以定义多个通知(示例中只有一个前置通知)。我们每个通知都可以引用上面定义的切入点(通过pointcut-ref
)或者直接定义pointcut
的表达式。
另外一个补充点是配置文件约束。配置文件约束说白了就是加入Spring对应模块的约束文件的引用地址,加入之后,我们在配置文件中就可以添加对应的配置,也会出现相应的提示。比如我们加入了aop的约束,我们在配制aop的时候,就会出现对应的标签提示。我们加入了springmvc的约束,相应的该配置文件就可以使用springmvc相对应的配置标签。约束的格式都一样,就是把后缀换成Spring对应的模块即可。
我们也可以手动切换代理方式,即手动指定Spring的AOP代理方式固定为CGLIB,配置方式如下:
<aop:config proxy-target-class="true">
或
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP
的支持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-
autoproxy>
切入点表达式
切入点表达式,也称之为AspectJ切入点表达式。指的是遵循特定语法结构的字符串,其作用是用于符合其特定语法格式的连接点方法进行增强。它是AspectJ表达式的一部分。
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分,开始⽀持AspectJ切入点表达式。
表达式的格式为:访问修饰符(可省略) 返回值 包名.类名.方法名(参数列表)。全匹配方式示例:public void com.rubin.demo.TestDemo.hello(java.lang.String)
。其中访问修饰符可以省略,即也可以配制为:void com.rubin.demo.TestDemo.hello(java.lang.String)
。返回值可以为*
,表示所有类型的返回值,即配置为:* com.rubin.demo.TestDemo.hello(java.lang.String)
。包名也可以使用*
,标识任意包名,但是有几个包要写几个*
,即:* *.*.*.TestDemo.hello(java.lang.String)
。也可以使用*..
来表示当前包以及所有子包,即:* ..TestDemo.hello(java.lang.String)
。也可以使用*
来表示任意类名和方法名,即:* *..*.*(java.lang.String)
。参数列表,对象类型要使用全限定类名,基本类型例如int就直接写int。参数列表也可以使用*
来表示一个任意参数类型,但是必须要有参数(多个参数可以使用(*,*,*)或者(*,int,boolean)等格式定义个数和类型),而使用..
标识任意个数任意参数类型,也就是说可以没有参数。所以通配的配置为:* *..*.*(..)
。
关于详细的切入点表达式介绍可以参考官网:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn。
xml+注解模式
此种方式是比较常用的一种使用方式。一般来讲,我们是使用xml配置文件来开启AOP注解扫描。使用AOP相关注解来定义我们的切面类。
xml中开启AOP注解扫描:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
定义切面类,示例如下:
@Component
@Aspect
public class LogUtil {
/**
* 我们在xml中已经使用了通用切入点表达式,供多个切面使用,那么在注解中如何使用呢?
* 第⼀步:编写⼀个方法
* 第⼆步:在方法使用@Pointcut注解
* 第三步:给注解的value属性提供切入点表达式
* 细节:
* 1.在引用切入点表达式时,必须是方法名+(),例如"pointcut()"。
* 2.在当前切面中使用,可以直接写方法名。在其他切面中使用必须是全限定方法名。
*/
@Pointcut("execution(* com.rubin.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;
}
}
注解模式
注解模式其实就是我们使用注解来替换掉上一小节中xml配置的AOP注解扫描配置。配置类如下:
@Configuration
@ComponentScan("com.rubin")
// 开启spring对注解AOP的支持
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
文章评论