Spring-AOP:基于AspectJ注解的AOP
2016-08-03 23:08
435 查看
在Spring框架中实现AOP一般需要借助第三方的jar包。最常用的莫过于AspectJ,这也是Java 社区里最完整最流行的 AOP 框架。除此之外也可以通过cglib等实现AOP,本文主要介绍通过AspectJ注解的方式实现AOP,下一篇将介绍通过AspectJ XML配置文件的方式实现AOP。
2、.将 aop Schema 添加到 <beans> 根元素中.
3、要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>,还需要配置自动扫描的包(和之前的基于注解的方式配置bean一样)
做完上述步骤后,当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类. 因为切面要放到IOC容器中,所以还需要加上@Component,例如:
@Aspect
@Component
public class LoggingAspect {
}这就声明了一个日志切面,接下来就可以在切面中定义不同的通知方法。
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
在介绍五种注解的使用之前,先创建一个接口及其实现类:
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);
}在这个借口中定义了加减乘除四个方法,为了简化起见,这里参数均为int类型。
需要被代理的bean,需要在类的定义上方添加注解:
@Component("arithmeticCalculator")
括号里的字符串是要求IOC容器创建bean时使用的名称。
@Before("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begin with " + args);
}
注解的value属性值的通用写法为:"execution(/*方法的完整路径*/)",可以使用通配符 ‘*’ ,这个值用于指定连接点。参数joinPoint是一个JoinPoint类型的对象,代表当前的目标函数。可以调用一些方法,如:joinPoint.getSignature().getName(); 和 joinPoint.getArgs() 得到函数名和当前传入的参数。
切入点表达式的一些示例写法:
public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int) //com.atguigu.spring.aop.impl包下的ArithmeticCalculator类中所有用public int 修饰且参数为两个整数的方法。
* com.atguigu.spring.aop.impl.ArithmeticCalculator.add(..) //切点表达式表示执行 ArithmeticCalculator 接口的 add() 方法. * 代表匹配任意修饰符及任意返回值, 参数列表中的 .. 匹配任意数量的参数
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 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来. 例如:
execution(* *.add(int, ..)) || execution(* *.sub(int, ..))
“与或非”的使用逻辑和编程语言中的一样。
下面的方法打印出连接点执行结束的信息:
//后置通知:目标方法执行后(无论知否发生异常),执行的通知
@After("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("The method " + joinPoint.getSignature().getName() + " ends");
}
/**返回通知
* 在方法 正常 结束(没有抛出异常)后执行的代码
* 返回通知可以访问到方法的返回值
*/
@AfterReturning(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
returning="result")
public void returningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " end with: " + result);
}
在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称.
必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.
原始的切点表达式需要出现在 pointcut (value)属性中
如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
/*
* 异常通知
* 在目标方法出现异常时执行的代码
* 可以访问到异常对象;且可以指定在出现 特定异常 时再执行代码
* 例如:方法的第二个参数可以写成:NullPointerException e 这样就只有出现空指针异常时才会执行方法体。
*/
@AfterThrowing(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + "occurs exception: " + e);
}
接下来写一个main方法验证一下:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
ArithmeticCalculator ac = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
int res = ac.add(2, 3);
System.out.println("result: " + res);
res = ac.div(12, 0);
System.out.println("result: " + res);
}
}
注意这里的调用除法是我传入的除数为0,这样会出现异常,会执行异常通知而不是返回通知。打印输出如下:
对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
/*
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数。
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法。
* 且环绕通知必须有返回值,返回值即为目标方法的返回值
*/
@Around(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pjd) {
Object result = null;
String methodName = pjd.getSignature().getName();
//执行目标方法
try {
//前置通知
System.out.println("The method " + methodName + " begin 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;
}
Spring 中启用 AspectJ 注解支持的步骤
1、要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar2、.将 aop Schema 添加到 <beans> 根元素中.
3、要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>,还需要配置自动扫描的包(和之前的基于注解的方式配置bean一样)
<!-- 自动扫描的包 --> <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan> <!-- 使AspectJ的注解起作用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
做完上述步骤后,当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
用 AspectJ 注解声明切面
要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类. 因为切面要放到IOC容器中,所以还需要加上@Component,例如:
@Aspect
@Component
public class LoggingAspect {
}这就声明了一个日志切面,接下来就可以在切面中定义不同的通知方法。
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
在介绍五种注解的使用之前,先创建一个接口及其实现类:
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);
}在这个借口中定义了加减乘除四个方法,为了简化起见,这里参数均为int类型。
import org.springframework.stereotype.Component; @Component("arithmeticCalculator") public class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } }
需要被代理的bean,需要在类的定义上方添加注解:
@Component("arithmeticCalculator")
括号里的字符串是要求IOC容器创建bean时使用的名称。
@Before: 前置通知, 在方法执行之前执行
//声明该方法是一个前置通知:在目标方法之前执行@Before("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method " + methodName + " begin with " + args);
}
注解的value属性值的通用写法为:"execution(/*方法的完整路径*/)",可以使用通配符 ‘*’ ,这个值用于指定连接点。参数joinPoint是一个JoinPoint类型的对象,代表当前的目标函数。可以调用一些方法,如:joinPoint.getSignature().getName(); 和 joinPoint.getArgs() 得到函数名和当前传入的参数。
切入点表达式的一些示例写法:
public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int) //com.atguigu.spring.aop.impl包下的ArithmeticCalculator类中所有用public int 修饰且参数为两个整数的方法。
* com.atguigu.spring.aop.impl.ArithmeticCalculator.add(..) //切点表达式表示执行 ArithmeticCalculator 接口的 add() 方法. * 代表匹配任意修饰符及任意返回值, 参数列表中的 .. 匹配任意数量的参数
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 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来. 例如:
execution(* *.add(int, ..)) || execution(* *.sub(int, ..))
“与或非”的使用逻辑和编程语言中的一样。
@After: 后置通知, 在方法执行之后执行
无论连接点正常的返回结果还是出现异常,返回通知都会执行。下面的方法打印出连接点执行结束的信息:
//后置通知:目标方法执行后(无论知否发生异常),执行的通知
@After("execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("The method " + joinPoint.getSignature().getName() + " ends");
}
@AfterRunning: 返回通知, 在方法返回结果之后执行
无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点正常返回的时候记录日志, 应使用返回通知代替后置通知./**返回通知
* 在方法 正常 结束(没有抛出异常)后执行的代码
* 返回通知可以访问到方法的返回值
*/
@AfterReturning(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
returning="result")
public void returningMethod(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " end with: " + result);
}
在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称.
必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.
原始的切点表达式需要出现在 pointcut (value)属性中
@AfterThrowing: 异常通知, 在方法抛出异常之后
只在连接点抛出异常时才执行异常通知,将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
/*
* 异常通知
* 在目标方法出现异常时执行的代码
* 可以访问到异常对象;且可以指定在出现 特定异常 时再执行代码
* 例如:方法的第二个参数可以写成:NullPointerException e 这样就只有出现空指针异常时才会执行方法体。
*/
@AfterThrowing(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + "occurs exception: " + e);
}
接下来写一个main方法验证一下:
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
ArithmeticCalculator ac = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
int res = ac.add(2, 3);
System.out.println("result: " + res);
res = ac.div(12, 0);
System.out.println("result: " + res);
}
}
注意这里的调用除法是我传入的除数为0,这样会出现异常,会执行异常通知而不是返回通知。打印输出如下:
@Around: 环绕通知, 围绕着方法执行
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
/*
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数。
* 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法。
* 且环绕通知必须有返回值,返回值即为目标方法的返回值
*/
@Around(value="execution(public int com.atguigu.spring.aop.impl.ArithmeticCalculator.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pjd) {
Object result = null;
String methodName = pjd.getSignature().getName();
//执行目标方法
try {
//前置通知
System.out.println("The method " + methodName + " begin 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;
}
相关文章推荐
- Intellij IDEA创建Spring项目之基于Aspectj的AOP操作(注解方式)
- SSM框架项目搭建系列(七)—Spring AOP之基于注解的声明式AspectJ
- 10、SSM框架-Spring AOP之基于注解的声明式AspectJ(10)
- SSM搭建-Spring AOP之基于注解的声明式AspectJ(9)
- Spring基于注解@AspectJ的AOP
- day3_Spring_02_基于aspectj的注解aop操作
- Spring AOP的使用 基于全注解AspectJ
- Spring基于注解@AspectJ的AOP
- Spring_Spring与AOP_AspectJ基于注解的AOP实现
- Spring Aop(二)——基于Aspectj注解的Aop简单实现
- Spring(十二)AspectJ框架开发AOP(基于注解)
- Spring Aop开发基于AspectJ注解方式的案例
- Spring AOP使用配置介绍(四):基于@AspectJ注解的aop
- Spring AOP面向切面编程详解(基于XML方式 注解方式 注入Aspectj方式)
- spring之基于aspectj注解aop使用
- Spring Aop(四)——基于Aspectj注解的Advice介绍
- Spring学习-21:Spring的AOP:基于AspectJ的注解开发
- java基于spring注解AOP的异常处理的方法
- spring-redis缓存方案学习三:基于aop的自定义注解开发
- Spring学习4-面向切面(AOP)之aspectj注解方式