1 前言
Spring框架两大核心技术为IOC和AOP,今天就来讲解AOP的具体使用,AOP为Aspect Oriented Programming的缩写,被称为面向切面编程。AOP主要用于处理共同逻辑,例如日志记录、方法执行性能统计、事务控制等等。AOP的相关概念有:方面、目标、切入点、通知。
2 相关概念
2.1 方面
指封装共通处理的组件,该组件被作用到其他目标组件方法上,通俗的讲就是一个普通的JavaBean。
2.2 目标
是指被一个或多个切面所作用的对象,也即动态代理中的目标对象。
2.3 切入点
用于指定哪些方法使用方面功能,也即被作用的对象里的实际方法。
2.4 通知
用于指定作用的时机,即方法执行前后还是返回值后执行。通知主要有:before、after、after-throwing、after-returning、around五类。
3 具体使用
Spring框架中主要用两种方式实现AOP:基于XML配置、基于注解方式。
3.1 基于XML配置
AOP实现依赖spring-aop相关jar,所以需引入相关jar包,并引入aop命名空间,如图
①定义方面
<!-- 定义一个方面Bean,用于记录日志信息 -->
<bean id="myAspect" class="com.pengmm.aop.MyAspect"/>
具体的com.pengmm.aop.MyAspect代码如下
package com.pengmm.aop;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 方面:记录日志信息<p>
* 任何advice方法第一个参数都可以为:JoinPoint joinPoint 封装了参数信息;<p>
* around advice 第一个参数为:ProceedingJoinPoint,它是JoinPoint的子类,封装了参数和返回值信息
* @author pengmm
*/
public class MyAspect {
/**
* 前置通知
*
* @param joinPoint
* 封装参数信息
* @return
*/
public void mybefore(JoinPoint joinPoint) {
System.out.println("========我是一个前置通知========");
}
/**
* 无论正确返回与否,都将执行,通常用于关闭或释放资源<p>
* <aop:after method="myafter" pointcut-ref="myPointcut" />
*/
public void myafter() {
System.out.println("========我是一个后置通知========");
}
/**
* 环绕通知<br>
* <aop:around method="myaround" pointcut-ref="myPointcut" />
*
* @param joinPoint
* 封装参数和返回值
*/
public void myaround(ProceedingJoinPoint joinPoint) {
Logger logger = Logger.getLogger(MyAspect.class);
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getName();
try {
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
logger.debug("------->方法[" + className + "." + methodName + "]执行时间为:" + (endTime - startTime) + "ms");
}
/**
* 后置返回值后通知,retval必须与xml声明的returning值相同<br>
* <code>
* <aop:after-returning method="myafterReturning"
* pointcut-ref="myPointcut" returning="retVal" />
* </code>
*
* @param retVal
* 返回值,指定类型Object表示可以返回任何类型
*/
public void myafterReturning(JoinPoint joinPoint,Object retVal) {
}
/**
* 当返回异常时通知<br/>
* <code>
* <aop:after-throwing method="myafterThrowing"
pointcut-ref="myPointcut" throwing="exception"/>
</code> <br/>
Exception:指定异常的类型,比如指定IllegalArgumentException,则只有发生参数异常时才能执行通知
*
* @param exception
* 异常信息,此变量必须同throwing定义的值
*/
public void myafterThrowing(Exception exception) {
System.out.println(exception.getMessage());
}
}
②每个aop配置都定义在<aop:config>标签下
<aop:config>
<!-- 定义方面,可直接引用上面定义的方面 -->
<aop:aspect ref="myAspect">
<!-- 定义切入点,即设置作用于哪些类中的哪些方法 -->
<aop:pointcut id="myPointcut"
expression="execution(* com.pengmm.service.*Impl.*(..)) and args(..)" />
<!-- 定义各种通知 -->
<aop:before method="mybefore" pointcut-ref="myPointcut" />
<aop:after method="myafter" pointcut-ref="myPointcut" />
<aop:after-throwing method="myafterThrowing"
pointcut-ref="myPointcut" throwing="exception"/>
<aop:after-returning method="myafterReturning"
pointcut-ref="myPointcut" returning="retVal" />
<aop:around method="myaround" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
③测试,定义了环绕通知,输出了自定义的通知内容。
3.2 基于注解方法
①开启自动代理注解
<!-- 开启aop自动代理注解 -->
<aop:aspectj-autoproxy />
②编写一个普通的类,通过注解声明
package com.pengmm.aop;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @Aspect 注册该类为一个方面Bean
* @Component 把该Bean交给IOC容器管理,也可基于XML注入该Bean
*/
@Aspect
@Component
public class MyAnnotationAspect {
/**
* @Around定义一个环绕通知
* 配置切入点:execution(* com.pengmm.service.*Impl.*(..)) and args(..)
* @param joinPoint 封装方法参数和返回值信息
*/
@Around("execution(* com.pengmm.service.*Impl.*(..)) and args(..)")
public void myaround(ProceedingJoinPoint joinPoint) {
Logger logger = Logger.getLogger(MyAspect.class);
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getName();
try {
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
logger.debug("------->方法[" + className + "." + methodName + "]执行时间为:" + (endTime - startTime) + "ms");
}
}
③测试,执行结果同“基于XML配置”测试结果