Spring 框架的一个关键组件是面向方面的编程(AOP)框架。面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的方面的例子,如日志记录、审计、声明式事务、安全性和缓存等。
在 OOP 中,关键单元模块度是类,而在 AOP 中单元模块度是方面。依赖注入帮助你对应用程序对象相互解耦和 AOP 可以帮助你从它们所影响的对象中对横切关注点解耦。AOP 是像编程语言的触发物,如 Perl,.NET,Java 或者其他。
Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。
也就是说,AOP可以实现在指定程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的变成方式【动态代理】
实现步骤:
1、导入AOP模块:Spring AOP:(spring-aspects)
<!-- 导入AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
2、定义一个业务逻辑类(以MathCalculator类为例);我们希望在业务逻辑运行的时候将日志进行打印(在方法运行之前、在方法运行结束之后,在方法出异常…等情况)
public class MathCalculator {
public int div(int i,int j){
return i/j;
}
}
3、定义一个日志切面类(以LogAspects类为例):切面类里面的方法需要动态感知MathCalculator.div运行到哪里了,然后进行执行。
Spring 方面可以使用下面提到的五种通知工作:代码示例在【4】
通知(对应注解) | 描述 |
---|---|
前置通知(@Before()) | 在一个方法执行之前,执行通知。 |
后置通知(@After()) | 在一个方法执行之后,不考虑其结果,执行通知。(无论方法正常结束还是异常结束都调用) |
返回后通知(@AfterReturning) | 在一个方法执行之后,只有在方法成功完成时,才能执行通知。 |
抛出异常后通知(@AfterThrowing) | 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。 |
环绕通知(@Around()) | 在建议方法调用之前和之后,执行通知。 |
4、给切面类的目标方法标注注解;何时何地运行(注解通知);
/**
* 切面类
*/
public class LogAspects {
//@Pointcut抽取公共点表达式
//1、本类引用:只需要写方法名(比如:LogEnd方法的形式)
//2、如果是其他类引用,需要写全名(比如:com.AOP.LogAspects.pointCut)
@Pointcut("execution(public int com.AOP.MathCalculator.div(int, int))")
public void pointCut(){};
//Before代表切入时机(目标方法运行之前);+切入点表达式(指定在那个方法切入)
@Before("public int com.AOP.MathCalculator.div(int, int)")
//也可以写成@Before("public int com.AOP.MathCalculator.*(..)"),代表切入MathCalculator的所有方法,且不区分参数(任意多参数且任意类型)
public void LogStart(){
System.out.println("除法开始运行....参数列表是:{}");
}
@After("pointCut()")
public void LogEnd(){
System.out.println("除法运行结束....参数列表是:{}");
}
@AfterReturning("pointCut()")
public void LogReturn(){
System.out.println("除法正常返回....运行结果是:{}");
}
@AfterThrowing("pointCut()")
public void LogException(){
System.out.println("除法异常束....异常信息:{}");
}
}
5、将切面类和业务逻辑类(目标方法)都加入到容器中
@Configuration
public class MainConfigOfAOP {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public LogAspects logAspects (){
return new LogAspects();
}
}
6、然后必须告诉Spring那个类是切面类(通过@Aspect注解)
@Aspect注解就是告诉Spring当前类是一个切面类;
7、给配置类中加上注解【@EnableAspectJAutoProxy】:开启基于注解的AOP模式
在Spring中很多的@Enablexxxx;都是开启某一项功能,来替代以前的配置
/**
* 切面类
*/
@Aspect
public class LogAspects {
//@Pointcut抽取公共点表达式
//1、本类引用:只需要写方法名(比如:LogEnd方法的形式)
//2、如果是其他类引用,需要写全名(比如:com.AOP.LogAspects.pointCut)
@Pointcut("execution(public int com.AOP.MathCalculator.div(int, int))")
public void pointCut(){};
//Before代表切入时机(目标方法运行之前);+切入点表达式(指定在那个方法切入)
@Before("execution(public int com.AOP.MathCalculator.div(int, int))")
//也可以写成@Before("public int com.AOP.MathCalculator.*(..)"),代表切入MathCalculator的所有方法,且不区分参数(任意多参数且任意类型)
public void LogStart(JoinPoint joinPoint){
//返回这个连接点的参数
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature()返回连接点上的签名。getName()返回方法名
System.out.println(""+joinPoint.getSignature().getName()+"开始运行....参数列表是:{"+ Arrays.asList(args) +"}");
}
/**
* @param joinPoint:提供了对连接点可用状态和它的静态信息。并且JoinPoint只能放在参数的第一位,某种Spring无法识别
*/
@After("pointCut()")
public void LogEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"运行结束....结果是:{}");
}
//此处的value表示切入表达式
//returning来指定方法的参数谁来封装返回值
@AfterReturning(value = "pointCut()",returning = "result")
public void LogReturn(JoinPoint joinPoint,Object result){
System.out.println(""+joinPoint.getSignature().getName()+"正常返回....运行结果是:{"+result+"}");
}
//同上可得,此处的value表示的是切入点表达式
//throwing是拿来接收异常返回值的
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void LogException(JoinPoint joinPoint,Exception exception){
System.out.println(""+joinPoint.getSignature().getName()+"异常束....异常信息:{"+exception+"}");
}
}
效果如下:
主要可分为三步:
第一:将业务逻辑组件和切面类都加入到容器中;并且告诉Spring那个是切面类(也就是给切面类加上@Aspect注解)
第二:在切面类上每个通知方法上标注通知注解,告诉Spring何时何地运行(此处注意切入点表达式)
/**
* 切面类
*/
@Aspect
public class LogAspects {
//@Pointcut抽取公共点表达式
//1、本类引用:只需要写方法名(比如:LogEnd方法的形式)
//2、如果是其他类引用,需要写全名(比如:com.AOP.LogAspects.pointCut)
@Pointcut("execution(public int com.AOP.MathCalculator.div(int, int))")
public void pointCut(){};
//Before代表切入时机(目标方法运行之前);+切入点表达式(指定在那个方法切入)
@Before("execution(public int com.AOP.MathCalculator.div(int, int))")
//也可以写成@Before("public int com.AOP.MathCalculator.*(..)"),代表切入MathCalculator的所有方法,且不区分参数(任意多参数且任意类型)
public void LogStart(JoinPoint joinPoint){
//返回这个连接点的参数
Object[] args = joinPoint.getArgs();
//joinPoint.getSignature()返回连接点上的签名。getName()返回方法名
System.out.println(""+joinPoint.getSignature().getName()+"开始运行....参数列表是:{"+ Arrays.asList(args) +"}");
}
/**
* @param joinPoint:提供了对连接点可用状态和它的静态信息。并且JoinPoint只能放在参数的第一位,某种Spring无法识别
*/
@After("pointCut()")
public void LogEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"运行结束....结果是:{}");
}
//此处的value表示切入表达式
//returning来指定方法的参数谁来封装返回值
@AfterReturning(value = "pointCut()",returning = "result")
public void LogReturn(JoinPoint joinPoint,Object result){
System.out.println(""+joinPoint.getSignature().getName()+"正常返回....运行结果是:{"+result+"}");
}
//同上可得,此处的value表示的是切入点表达式
//throwing是拿来接收异常返回值的
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void LogException(JoinPoint joinPoint,Exception exception){
System.out.println(""+joinPoint.getSignature().getName()+"异常束....异常信息:{"+exception+"}");
}
}
第三:开启基于注解的AOP模式(@EnableAspectJAutoProxy注解)
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public LogAspects logAspects (){
return new LogAspects();
}
}