(07Day)
今天我们来看看Spring中AOP的最流行最完整的AOP--->AspectJ
我们要使用AspectJ就一定要先导入AspectJ 的Jar包,AspectJ要导入的jar包为aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar。所以加入AspectJ包后算上spring的基本jar包就一共有以下这些包。
加完jar包后,还需要spring的配置文件中启用AspectJ注解支持。在spring中加入<aop:aspectj-autoproxy></aop:aspectj-autoproxy>这句话启用注解支持。这样当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理。
要在spring中声明AspectJ切面,只需要在spring中将切面注册为Bean实例。在 Spring IOC 容器中初始化 AspectJ 切面之后,IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。
那么什么样的类是切面呢?其实在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类。注意前提是该类是一个已经在IOC容器中注册的Bean实例。那么在切面中每一个方法也就是每一个通知,是怎么知道这个方法要给哪一个类的哪些方法做代理呢?其实很简单就是在每个通知上加上某个通知类型的通知注解,并使用切入点表达式来表示(value的值=“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 类型的方法。
那么AspectJ都有哪几种类型的通知注解呢?AspectJ一共提供了5种类型的通知注解。
- @Before: 前置通知, 在方法执行之前执行
- @After: 后置通知, 在方法执行之后执行
- @AfterRunning: 返回通知, 在方法返回结果之后执行
- @AfterThrowing: 异常通知, 在方法抛出异常之后
- @Around: 环绕通知, 围绕着方法执行
下面我就用一个例子来说明这几种通知的用法:
1)创建一个接口
package com.aop.impl;
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
2)实现接口
package com.aop.impl;
import org.springframework.stereotype.Component;
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
public int add(int i, int j) {
int result = i + j;
return result;
}
public int sub(int i, int j) {
int result = i - j;
return result;
}
public int mul(int i, int j) {
int result = i * j;
return result;
}
public int div(int i, int j) {
int result = i / j;
return result;
}
}
3)声明一个切面
package com.aop.impl;
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
//把这个类声明为一个切面:需要把该类放入到IOC容器中,再声明为一个切面
@Component
@Aspect
public class LogginAspect {
//声明该方法是一个前置通知:在目标方法开始执行之前
/*execution执行括号中的是一个AspectJ的一个表达式:可以加入一些占位符,比如可以把add改成*
* 这样就变成了给ArithmeticCalculatorImpl类的所有方法(参数为int int的)注册前置通知。
* (..)可以表示参数任意
*/
@Before("execution(public int com.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
public void beforMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("方法"+methodName+"开始执行,参数为:"+args);
}
//后置通知:在目标方法执行后(无论是否有异常)执行该方法。
@After("execution(public int com.aop.impl.ArithmeticCalculatorImpl.add(int,int))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("方法"+methodName+"执行结束");
}
/*返回通知:在方法正常结束后执行的代码,同时是可以获得返回值的,在returning = “result”
* 的意思是把返回值放在result中,然后在方法的参数中传入Object result参数,这样就可以
* 获得返回值了。
*/
@AfterReturning(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))",
returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("方法"+methodName+"无异常始执行结束,返回值为:"+result);
}
/*
*异常通知:在目标方法抛出异常的时候会执行的代码,同时可以访问异常对象;切可以指定在出现特定异常
*时在执行通知代码
*/
@AfterThrowing(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))",
throwing="ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("方法"+methodName+"抛出异常,异常为:"+ex);
}
/*
*环绕通知需要携带 ProceedingJoinPoint 类型的参数.
*环绕通知类似于动态代理的全过程,ProceedingJoinPoint类型参数可以决定是否执行目标方法。
*且环绕通知必须要有返回值,返回值即为目标方法的返回值。
*/
@Around(value="execution(public int com.aop.impl.ArithmeticCalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("方法"+methodName+"开始执行,参数为:"+Arrays.asList(pjd.getArgs()));
//执行方法
result = pjd.proceed();
//返回通知
System.out.println("方法"+methodName+"无异常始执行结束,返回值为:"+result);
} catch (Throwable e) {
//异常通知
System.out.println("方法"+methodName+"抛出异常,异常为:"+e);
}
//后置通知
System.out.println("方法"+methodName+"执行结束");
return result;
}
}
4)在spring配置文件中配置扫描路径和启用AspectJ注解支持(注意需要加入aop,beans,context三个命名空间)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 设置自动扫描路径 -->
<context:component-scan base-package="com.aop.impl"></context:component-scan>
<!-- 使Aspjectj 注解起作用:自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
5)测试类
package com.aop.impl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main02 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
int result = arithmeticCalculator.add(3, 5);
System.out.println(result);
}
}
讲完了通过注解来配置AOP,那么如何通过XML来配置呢?其实在正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会.
当使用 XML 声明切面时, 需要在 <beans> 根元素中导入 aop Schema在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例. 切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用.
在 aop Schema 中, 每种通知类型都对应一个特定的 XML 元素. 通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式. method 属性指定切面类中通知方法的名称.