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