1. AOP基础概念
AOP(Aspect-Oriented Programming)即面向切面编程,与我们熟悉的OOP面向对象变成不同,面向对象变成的基础单位为类:Class,通过继承、封装、多态等着力于构建一个垂直方向上的结构,强调类之间的层次,就像用Class一层层构建的高楼大厦,每个Class或对象就像大厦中的一个个房间。
但是,当我们想对大量房间装相同的新设备时就会非常麻烦,必须在每个Class中添加类似的代码以达到目的。这里,我们就引入了AOP面向切面编程的概念,还是前面比喻的大厦,假如我们想要为每个Class增加日志记录的功能,就像是为Class构建的大厦的房间中添加摄像头监控设备,按OOP的做法,就必须在所有的Class中添加类似的记录日志代码,但是利用AOP,我们更加关注横向,也即忽略不同Class的层次(继承等),将每个Class拆分为一个个joinpoint(在spring中指方法),也即将大厦拆分为一个个房间中更小的单位,可以统一为所有符合要求的房间安装一个总监控系统,而不用我们分别去每个房间安装设备。
- Aspect Aspect(切面)由pointcut(切点)和advise(增强)组成,pointcut描述了我们关注或者需要做advise的jointpoint(连接点),一起构成了Aspect(切面)的概念。
- joinpoint joinpoint(连接点),在spring中,joinpoint代表方法的执行点,所有的方法都可以认为是joinpoint。
- pointcut pointcut(切点),我们已经知道可以将所有的方法看作joinpoint,但是大多数时候我们不需要在所有的方法上(也即joinpoint)使用advise(增强),所以就需要对我们增强的目标点进行描述,而pointcut可以对增强目标进行描述、限定,例如制定对某个包中以service结尾的方法使用增强。需要注意的是,pointcut并不是一个特殊的joinpoint,而是一种描述或者定义,描述了哪些joinpoint需要进行增强。
- advise advise(增强),将advise代码织入目标joinpoint(即满足pointcut限定的joinpoint)中,在目标joinpoint执行前、后、抛出异常时调用我们的advise增强中对应的代码。
2. @AspectJ
@AspectJ是一种使用Java注解完成AOP的编码风格。与普通spring中利用xml方式配置AOP不同,@AspectJ 方式通过在AOP类上标注注解的方式完成AOP的配置。 下面简单介绍下在spring中如何使用@AspectJ 方式配置AOP:
- 引入支持@AspectJ的jar包: 在maven的配置文件中需要加入:
<!--spring Aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
复制代码
- spring配置支持@AspectJ : 在spring的xml配置文件中添加:
<!-- 扫描aop包下所有使用注解的类型 -->
<context:component-scan base-package="com.moyuzai.servlet.aop"/>
<!--支持aspectj-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
复制代码
不要忘记添加aop的命名空间。
- 编写AOP类
首先我们编写一个日志增强类,对某些方法进行增强:在方法执行前打印出所执行方法的名称、参数等信息,在方法返回值后打印返回值,当有异常抛出时打印异常信息:
@Component
@Aspect
public class LogAdvise {
Logger logger = LoggerFactory.getLogger(this.getClass());
//within(com.moyuzai.servlet.controller.*)表示controller包下所有的连接点joinpoint都是pointcut,这里的joinpoint可以认为是所有的方法,
// 而pointcut只是对符合我们想要增强(advise)的joinpoint的一种描述,描述什么样的joinpoint才是我们想要增强对象。
@Pointcut("within(com.moyuzai.servlet.controller.*)")
public void logPointCut(){
}
//增强(advise)
@Before("logPointCut()")
public void logMethodInvokeParam(JoinPoint joinPoint){
logger.info("执行方法:{}, 参数:{}",joinPoint.getSignature().toShortString(),joinPoint.getArgs());
}
@AfterReturning(pointcut = "logPointCut()",returning = "retVal")
public void logMethodInvokeResult(JoinPoint joinPoint,Object retVal){
logger.info("方法:{} 的返回值:{}",joinPoint.getSignature().toShortString(),joinPoint.getArgs());
}
@AfterThrowing(pointcut = "logPointCut()",throwing = "exception")
public void logMethodInvokeException(JoinPoint joinPoint,Exception exception){
logger.info("方法:{}出现异常:{}",joinPoint.getSignature().toShortString(),exception.getMessage());
}
}
复制代码
这样,当“com.moyuzai.servlet.controller”包下所有的方法得到执行的前后就会调用LogAdvise中的增强方法,起到了记录日志的作用。