AOP:在程序运行期间,动态的将某段代码切入到指定方法运行时的指定时机运行,其实就是动态代理。

Spring提供了对AOP很好的支持,使用时需要导入spring-aspects包。

业务逻辑类:要求在业务方法运行时打印日志

public class MathCalculator {

	public int div(int i,int j){
		return i/j;
	}
}

切面类:类上需要注解@Aspect,切面类中的方法需要动态感知业务方法的执行情况

@Aspect 
public class AopAspect {
	
	@Before("public int com.bdm.aop.MathCalculator.div(int, int)")
	public void logStart() {
		System.out.println("计算开始");
	}

	@After("public int com.bdm.aop.MathCalculator.*(int, int)")
	public void logEnd() {
		System.out.println("计算结束");
	}

	@AfterReturning("public int com.bdm.aop.MathCalculator.*(..)")
	public void logReturn() {
		System.out.println("计算结果");
	}

	@AfterThrowing("public int com.bdm.aop.MathCalculator.div(..)")
	public void logException() {
		System.out.println("计算异常");
	}
}

切面类中的方法也称为通知方法:

    前置通知(@Before):在目标方法运行之前运行

    后置通知(@After):在目标方法运行之后运行,即使出现异常也会运行

    返回通知(@AfterReturning):在目标方法正常返回之后运行

    异常通知(@AfterThrowing):在目标方法运行出现异常之后运行

    环绕通知(@Around):动态代理,手动推进目标方法的运行

在使用这些注解的时候必须配合切入点表达式,来指明在哪些方法执行之前、之后、返回时、异常时执行,在写切入点表达式时可以使用通配符*表示所有方法或者类,入参可以使用..表示所有参数类型

如果切入点表达式一样的话,则可以使用一个方法抽取切入点表达式,注意该方法的名称可以任意,而且方法体内无需写任何内容,只需要使用@PointCut注解并将切入点表达式标明即可:

@Aspect public class AopAspect {
	
	@Pointcut("execution(public int com.bdm.aop.MathCalculator.*(..))")
	public void pointCut(){}
	
	@Before("pointCut()")
	public void logStart() {
		System.out.println("计算开始");
	}

	@After("com.bdm.aop.AopAspect.pointCut()")
	public void logEnd() {
		System.out.println("计算结束");
	}

	@AfterReturning("pointCut()")
	public void logReturn() {
		System.out.println("计算结果");
	}

	@AfterThrowing("com.bdm.aop.AopAspect.pointCut()")
	public void logException() {
		System.out.println("计算异常");
	}
}

本类中使用时可直接写方法名,其他类也想使用此切入点表达式时则需要使用该方法的全类名

容器会将标注有@Aspect注解的类认为是切面类

使用Spring的切面需要开启Spring的切面自动代理,只需要在配置类中加注解@EnableAspectJAutoProxy,Spring中有很多@EnableXxx注解,用来开启一些功能

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {

	@Bean
	public MathCalculator cal(){
		return new MathCalculator();
	}
	@Bean
	public AopAspect aspect(){
		return new AopAspect();
	}

}

配置bean怎么区分哪个bean是切面类呢,它会看哪个类上有@Aspect注解,另外切面方法的执行仅对Spring容器中的bean起作用,对于我们自己new出来的对象是不起作用的,原因也很简单,我们自己创建的bean并没有被spring管理,也就没有为其设置切面方法等。

通过JoinPoint对象获取调用目标方法时的信息,比如方法名、参数等,使用returning指定用通知方法的哪个入参接收返回值,使用throwing指定用哪个入参接收异常,另外如果使用JoinPoint,则必须将其放在切面方法入参的第一个位置,否则会报错:

@Aspect
public class AopAspect {
	
	@Pointcut("execution(public int com.bdm.aop.MathCalculator.*(..))")
	public void pointCut(){}
	
	@Before("pointCut()")
	public void logStart(JoinPoint point) {
		String name = point.getSignature().getName();
		Object[] args = point.getArgs();
		System.out.println(name + "方法计算开始,入参:" + Arrays.asList(args));
	}

	@After("com.bdm.aop.AopAspect.pointCut()")
	public void logEnd(JoinPoint point) {
		String name = point.getSignature().getName();
		System.out.println(name + "方法计算结束");
	}

	@AfterReturning(value="pointCut()",returning="obj")
	public void logReturn(JoinPoint point,Object obj) {
		String name = point.getSignature().getName();
		System.out.println(name +"方法的计算结果:" + obj.toString());
	}

	@AfterThrowing(value="com.bdm.aop.AopAspect.pointCut()",throwing="exception")
	public void logException(Exception exception) {
		System.out.println("计算异常:"+ exception.getMessage());
	}
}

总结:

1、将切面类(@Aspect标注的类)纳入到配置类中,业务类对象也必须由Spring容器创建

2、切面方法加上通知注解(@Before、@After、@AfterReturning、@AfterThrowing、@Round),并写上切入点表达式,告诉切面方法在执行哪些方法前、后等时点执行这些切面方法

3、启用基于注解的AOP模式:配置类上加注解@EnableAspectJAutoProxy