首先不做过多的源码解释(其实是源码我也看不太明白,目前只会使用)。下面来介绍spring aop应该怎么用注解实现。
所需依赖:pom.xml
<dependencies>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!--spring-aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
/**
* AOP : 面向切面编程 [动态代理]
* 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式
*/
所以我们需要准备两个类,一个是业务类,即运行某个方法的类,另一个是切面类,即要把什么代码切入到业务类方法中执行(执行位置和时间可以选择)。
这种最典型的例子就是,业务类在运行一个方法时,需要输出打印一些信息,在运行方法前,运行完成后,正常运行得到结果并打印(或者异常错误并打印出错误)。这些代码都可以写在业务类方法中,但耦合性太高。
业务类:MathCalculator.java
package com.sixteen.aop;
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i / j;
}
}
很简单的一个方法,传入两个int类型i,j,并返回i/j的结果。
切面类应该怎么写呢?切面类需要动态的知道业务类方法进行到什么步骤了,才能做出相应的代码调用。
通知方法
就可以做到动态感知了。
/**
* 此处的div是业务类MathCalculator中的div方法
* 通知方法:
* 前置通知(@Before):在目标方法(div)运行前执行
* 后置通知(@After): 在目标方法(div)运行结束后执行(无论方法正常还是异常结束)
* 返回通知(@AfterReturning):在目标方法(div)正常返回后执行
* 异常通知(@AfterThrowing):在目标方法(div)异常返回后执行
* 环绕通知(@Around):动态代理,手动推进目标方法进行(joinPoint.procced())
*/
这样就可以知道写一个切面类大致怎么写了。
切面类:LogAspect.java
package com.sixteen.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
/**
* 切面类
* @Aspect : 告诉spring当前类是一个切面类
*/
@Aspect//切面类注解
public class LogAspect {
//抽取公共的切入点表达式
// 1. 本类引用: @Before、@After等注解 如```@Before("pointCut()")```
// 里面写加了@Pointcut注解的方法名
// 2. 其他的切面引用
// @Before("com.sixteen.aop.LogAspect.pointCut()")
@Pointcut("execution(public * com.sixteen.aop.MathCalculator.*(..))")
/**
* public修饰符(可省略)
* * 返回任意类型值
* com.sixteen.aop.MathCalculator哪个类
* * 类下所有方法
* (..)参数任意
*/
public void pointCut(){}
@Before(value = "pointCut()")
public void logStart(JoinPoint point){
Object[] args = point.getArgs();//获取方法参数
// point.getSignature().getName() 可以获取方法名
System.out.println(point.getSignature().getName()+"开始运行...@Before...接收参数为:{"+ Arrays.asList(args)+"}");
}
@After(value = "pointCut()")
public void logEnd(JoinPoint point){
System.out.println(point.getSignature().getName()+"结束...@After...");
}
@AfterReturning(pointcut = "pointCut()",returning = "data")
// JoinPoint point 必须在方法内第一个参数位置,否则spring无法识别会报错
// returning = 指定返回值的变量名,以Object类型接收
public void logReturn(JoinPoint point,Object data){
System.out.println(point.getSignature().getName()+"方法正常结束...@AfterReturning...返回值为:{"+data+"}");
}
@AfterThrowing(pointcut = "pointCut()",throwing = "exception")
// throwing = 指定异常的变量名,以Exception类型接收
public void logException(JoinPoint point,Exception exception){
System.out.println("方法抛出异常...@AfterThrowing...异常:"+exception);
}
}
@Aspect
是一个注解告诉spring这是一个切面类而不是其他的类,spring是无法区分切面类和业务类的所以需要加上该注解,@Pointcut
是抽取公共切入点的注解,也可以不用,如果不用的话,那么像这样的注解中@After(value = "pointCut()")
,value就不是写pointCut()了,而是切入点表达式,切入点表达式的写法可以参考spring官方文档,也可百度查询资料,此处不做过多解释。
@Pointcut("execution(public * com.sixteen.aop.MathCalculator.*(..))")
/**
* public修饰符(可省略)
* * 返回任意类型值
* com.sixteen.aop.MathCalculator哪个类
* * 类下所有方法
* (..)参数任意
*/
public void pointCut(){}
这个方法的目的就是抽取公共的切入点表达式,要应用该切入点表达式的时候就在需要切入点表达式的地方使用该方法的方法名。eg: @AfterReturning(pointcut = "pointCut()",returning = "data")
,@After(value = "pointCut()")
JoinPoint point
这个参数必须作为通知注解方法的第一个参数,否则会报错,通过这个point
可以获取到业务类执行的方法的一些信息,如方法名,方法接收参数。
而要得到业务类执行方法返回值和异常,就需要这样@AfterReturning(pointcut = "pointCut()",returning = "data")
@AfterThrowing(pointcut = "pointCut()",throwing = "exception")
指定参数的名字,然后再在切面类方法中传入。(这里我可能将的不太清楚,可以直接看上面的代码实现)
在业务类和切面类都准备好之后,就需要将这两个类注册到IOC容器中。
配置类:SpringAopConfig.java
@Configuration
@EnableAspectJAutoProxy//开启注解aop
public class SpringAopConfig {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public LogAspect logAspect(){
return new LogAspect();
}
}
向其他注解版配置类一样加一个@Configuration
把此类声明为配置类,而@EnableAspectJAutoProxy
注解就是告诉spring开启注解版的aop。
ps:因为都是用@Bean进行bean注册(用其他方式注册也一样),spring是无法自动区分哪个是业务类哪个是切面类的,所以在切面类上千万不要忘记加@Aspect
注解。