1.概述:AOP(面向切面编程)是Spring的第二大核心思想,使用AOP思想可以在不修改业务代码的前提下,将一些与业务无关却是业务共同所调用的功能封装成一个"切面"模块,如:事务处理,日志管理以及权限控制。面向切面编程可以减少代码的冗余度并降低模块间的耦合度。
2.相关概念和通知类型
连接点(Join point):表示在程序中明确定义的点,典型的包括方法调用,异常处理程序块的执行,也就是所有的方法,不管是接口的抽象方法,还是实现类的重写方法。
切点(Pointcut):可能将抽取出来的共同功能添加的方法称为切点,切点是连接点的子集。
通知:抽取出来的共同功能称为通知,以通知在上下文的具体位置分类。
切面:即抽取出来的共同功能和切点,通知+切点。
目标对象(Target):切点所对应的类生成的对象,这种对象不能直接完成最终工作。
织入(Weaving):就是将通知添加到业务方法的过程。
通知的类型:
(1)前置通知(Before):即在切点方法执行前执行。
(2)返回通知(After-returning):切点方法正常执行完执行。
(3)异常通知(After-throwing):切点方法发生异常时执行。
(4)后置通知(After):也是切点方法执行完执行,执行位置相当于异常处理代码块中的finally代码块。
(5)环绕通知(Aroud):可以发生在前四个通知的任意位置,其它通知的任意组合。
3. 案例实现
SpringAop和Aspecj都是对AOP思想的实现,Aspectj的实现简单且方便,Spring框架中也整合了Aspectj技术。首先导入第三方jar包,下载地址:https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
Maven坐标:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
Calcuator类:
public class Calcuator {
public int sum(int a,int b){
System.out.println("业务方法执行中.....");
return a+b;
}
public int multi(int a,int b){
System.out.println("业务方法执行中.....");
return a*b;
}
}
LoggerAspect类:
//日志切面类
public class LogggerAspect {
//前置通知
public void before(){
System.out.println("【前置通知】=======>业务方法开始执行!!");
}
//返回通知
public void afterReturning(){
System.out.println("【返回通知】=======>业务方法执行结束!!");
}
//异常通知
public void throwing(){
System.out.println("【异常通知】========>业务方法出现异常!!");
}
//后置通知
public void after(){
System.out.println("【后置通知】========>业务方法最终执行结束!!");
}
//异常通知
public Object aroundMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("【环绕通知】======>前置通知");
result = joinPoint.proceed();
System.out.println("【环绕通知】======>返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("【环绕通知】======>异常通知");
}finally {
System.out.println("【环绕通知】======>后置通知");
}
return result;
}
}
上面代码中,我们定义了两个类,一个为业务类进行整数计算,另一个为日志切面类,为每个业务方法添加日志功能,接下来,我们通过xml配置文件的方式进行配置。
<!--将类注入IOC容器-->
<bean id="calcuator" class="com.yyx.aop.Calcuator"/>
<bean id="logggerAspect" class="com.yyx.aop.LogggerAspect"/>
<!--设置aop-->
<aop:config>
<!--设置切入点-->
<aop:pointcut id="pointcut" expression="execution(public int com.yyx.aop.Calcuator.*(..))"/>
<!--设置切面,id属性为唯一标识,ref属性引用切面类-->
<aop:aspect id="main" ref="logggerAspect">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwing" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
在配置文件中,第一步需要我们将业务类和切面类交给容器管理,然后通过<aop:config>标签配置aop,首先定义了pointCut切入点,表达式为execution(访问修饰符 返回值 包名.类名.方法名(参数列表)),最后在<aop:aspect>定义了通知的类型以及方法。
测试方法:
运行结果:
注意事项:
切入点表达式:execution(访问修饰符 返回值 包名.类名.方法名(参数列表))
1.访问修饰符可以省略,表示任意修饰符
execution( 返回值 包名.类名.方法名(参数列表))
2.返回值可以使用' * '通配符代替,表示任意类型
execution(* 包名.类名.方法名(参数列表))
3.包名可以使用' * '通配符代替,表示任意名称
execution(* *.类名.方法名(参数列表))
4.包名可以使用' .. '通配符代替,表示任意个数
execution(* *...类名.方法名(参数列表))
5.类名和方法名可以使用' * '通配符代替,表示任意名称
execution(* *...*.*(参数列表))
6.参数列表可以使用' .. '通配符代替,表示任意个数任意类型
execution(* *...*.*(..))
如果需要指明参数: int ========>int,String====>java.lang.String,需要指定完全限定名,通常都只给业务层的所有方法配置aop。
4.环绕通知:
环绕通知可以是其它四个通知的任意组合,如果配置了环绕通知就不需要配置其它的通知了,配置方式如下:
<aop:config>
<aop:pointcut id="pointcut" expression="execution(public int com.yyx.aop.Calcuator.sum(..))"/>
<aop:aspect id="main" ref="logggerAspect">
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
运行结果:
如果需要使用注解配置aop,需要在配置文件中开启自动代理,然后在切面类上和方法上添加相应的注解即可。