AOP 的 helloWorld
1. 加入 jar 包
* com.springsource.net.sf.cglib-2.2.0.jar
* com.springsource.org.aopalliance-1.0.0.jar
* com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
* spring-aspects-4.0.0.RELEASE.jar
2. 在 Spring 的配置文件中加入 aop 的命名空间
3. 基于注解的方式来使用 AOP
<!--在配置文件中配置自动扫描的包-->
<context:component-scan base-package="Spring扫描的包如(com.xxx.xxx)"></context:component-scan>
<!-- 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时,会自动为与 AspectJ 切面匹配的 Bean 创建代理 -->
4. 编写切面类:
- 4.1 一个一般的 Java 类
- 4.2 在其中添加要额外实现的功能.
public class LoggingAspect {
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
}
5. 配置切面
5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
5.2 声明是一个切面: 添加 @Aspect
@Aspect
@Component
public class LoggingAspect {
5.3 声明通知: 即额外加入功能对应的方法.
AspectJ 支持 5 种类型的通知注解:
* @Before: 前置通知, 在方法执行之前执行
* @After: 后置通知, 在方法执行之后执行
* @AfterRunning: 返回通知, 在方法返回结果之后执行
* @AfterThrowing: 异常通知, 在方法抛出异常之后
* @Around: 环绕通知, 围绕着方法执行
5.3.1 前置通知: @Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")
* @Before 表示在目标方法执行之前,执行 @Before 标记的方法(括号中的)的方法体.
* @Before 括号里面的是切入点表达式。
@Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinPoint){
5.4. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.
String methodName = joinPoint.getSignature().getName();
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
5.5. @After 表示后置通知: 在被调用方法执行之后执行的代码.
5.6 无论连接点是正常返回还是抛出异常, 后置通知都会执行.如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知
@After("execution(* com.atguigu.spring.aop.*.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
5.7 返回通知
/**
* 在方法法正常结束受执行的代码
* 返回通知时可以访问到方法的返回值的!
* 原始的切点表达式需要出现在 pointcut 属性中
* returning属性的值,即为用来传入返回值的参数名称.
*/
@AfterReturning(pointcut = "execution(* *.*(..))",returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends with " + result);
}
5.8 异常通知
/**
* 在目标方法出现异常时会执行的代码.
* 将 throwing 属性添加到 @AfterThrowing 注解中,可以访问到异常对象;
* 且可以指定在出现特定异常时在执行通知代码(可以将参数声明为其他异常的参数类型)
*/
@AfterThrowing(pointcut = "execution(* *.*(..))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " occurs excetion:" + e);
}
5.9环绕通知
/**
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}
}
6.关于AspectJ 切入点表达式
execution (* *.*(..))
第一个 * 代表匹配任意修饰符及任意返回值,
第二个 * 代表任意类的对象,
第三个 * 代表任意方法,
参数列表中的 .. 匹配任意数量的参数
execution (* com.atguigu.spring.ArithmeticCalculator.*(..))
匹配 ArithmeticCalculator 中声明的所有方法
第一个 * 代表任意修饰符及任意返回值.
第二个 * 代表任意方法. .. 匹配任意数量的参数.
若目标类与接口与该切面在同一个包中, 可以省略包名.
execution (public * ArithmeticCalculator.*(..))
匹配 ArithmeticCalculator 接口的所有公有方法.
execution (public double ArithmeticCalculator.*(..))
匹配 ArithmeticCalculator 中返回 double 类型数值的公有方法
execution (public double ArithmeticCalculator.*(double, ..))
匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数
execution (public double ArithmeticCalculator.*(double, double)):
匹配参数类型为 double, double 类型的方法.
6.1 在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来.
execution (* *.add(..) || * *.sub(..)):
匹配 1.任意修饰符及任意返回值 2.任意类的对象 3.匹配任意数量的参数 的 add与sub方法.
7. 指定切面的优先级
切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.
实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.
若使用 @Order 注解, 序号出现在注解中
/**
* 可以使用 @Order 注解指定切面的优先级, 值越小优先级越高
*/
@Order(2)
@Aspect
@Component
public class LoggingAspect {
@Order(1)
@Aspect
@Component
public class VlidationAspect {
8.通知的调用顺序
1.前置通知 2.后置通知 3.返回(异常)通知
9.重用切入点定义
①在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的
②切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public.
③在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.
④其他通知可以通过方法名称引入该切入点.
/**
* 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
* 使用 @Pointcut 来声明切入点表达式.
* 后面的其他通知直接使用方法名来引用当前的切入点表达式.
*/
@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){ }
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint){}
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinPoint){}
@AfterReturning(value="declareJointPointExpression()",returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){}
@AfterThrowing(value="declareJointPointExpression()",throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){}