您的位置:首页 > 编程语言 > ASP

Spring-AOP:基于AspectJ注解的AOP

2016-08-03 23:08 435 查看
在Spring框架中实现AOP一般需要借助第三方的jar包。最常用的莫过于AspectJ,这也是Java 社区里最完整最流行的 AOP 框架。除此之外也可以通过cglib等实现AOP,本文主要介绍通过AspectJ注解的方式实现AOP,下一篇将介绍通过AspectJ XML配置文件的方式实现AOP。

 Spring 中启用 AspectJ 注解支持的步骤

1、要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

2、.将 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring框架 AOP aspectj