本文介绍Spring AOP,面向切面编程。在权限验证、保存日志、安全检查和事务控制等多个应用场景之下都会依赖该技术。以下是在自己学习过程中的一些总结,如有错误,还望指正。
面向切面编程(AOP),全称 Aspect Oriented Programming。是基于面向对象编程之上的编程思想。指的是在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的编程方式。
动态代理可以非耦合动态插入代码,我们之前介绍过,JDK动态代理相关内容请移步:JDK动态代理
切入点:我们真正需要执行日志记录的地方。连接点:每一个方法的每一个位置都是一个连接点。通过使用切入点表达式在众多的连接点中选出我们感兴趣的地方。
@Aspect
@Componet
public class AspectClass {
/**
@Before:目标方法执行之前 前置通知
@After:目标方法结束之后 后置通知
@AfterReturning:在目标方法正常返回之后 返回通知
@AfterThrowing:在目标方法抛出异常之后 异常通知
@Around:环绕通知 环绕通知
对比动态代理:
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
*/
/**
指明要切入那个类的那个方法
execution(访问修饰符 返回值类型 方法签名)
/
@Before("execution(public String cn.joncy.MyNeedProxyClass.method1(String))")
public static void methodStart(){
do something
}
/*
切某个类下的所有方法 *
*/
@AfterReturning("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodReturn(){
do something
}
@AfterThrowing("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodException(){
do something
}
@After("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodEnd(){
do something
}
}
@Test
public void test(){
// 此处传入的是接口类型
NeedProxyClass npc = ioc.getBean(NeedProxyClass.class);
npc.method1("low");
}
从ioc容器中拿到目标对象,参数传递的是被代理类的接口,因为接口是被代理类和代理类产生关联的东西。AOP底层是动态代理,容器中保存的组件是它的代理对象(com.sun.proxy.$Proxyxxx)
’ * ’ :
可以匹配一个或者多个字符:execution(public String cn.joncy.MyNeed*y.*(String ))
匹配任意一个参数:第一个是String 第二个随意:execution(public String cn.joncy.MyNeed*.*(String ,*))
只能匹配一层路径
权限位置要么写上,要么不要写,不能写*
‘..’:
匹配任意多个参数,任意类型参数
匹配任意多层路径:execution(public String cn.*.MyNeed*.*(String ,)) 一层
execution(public String cn..MyNeed*.*(String ,)) 多层
最精确匹配:
execution(public String cn.joncy.MyNeedProxyClass.method1(String))
最模糊匹配:
execution(* *(..))
execution(* *.*(..)) 双点不能直接写
切入点表达式还支持:&& 、|| 、!
正常执行:@Before => @After => @AfterReturning
异常执行:@Before => @After => @AfterThrowing
为通知方法加上JoinPoint参数,该参数封装了当前目标方法的详细信息
//获取方法参数
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.asList(args));
//获取方法名
Signature signature joinPoint.getSignature();
String name signature.getName();
接收返回值结果和异常信息
@AfterReturning(value="execution(public String cn.joncy.MyNeedProxyClass.*(String))",returning="result")
// result的类型可以变得更加具体 比如变为int String等
public static void methodReturn(JoinPoint joinPoint,Object result){
do something
}
@AfterThrowing(value="execution(public String cn.joncy.MyNeedProxyClass.*(String))",throwing="exception")
// 此处是最大范围的异常,可以缩小比如接数组越界或空指针异常等
public static void methodException(JoinPoint joinPoint,Exception exception){
do something
}
Spring对通知方法的访问修饰符、返回值类型要求不严格,唯一有要求的就是通知方法的参数,因为Spring需要知道这些参数都是什么,才能帮我们调用
可以抽取可重用的切入点表达式,此表达式空实现,其他通知方法的切入点表达式的value引用该方法签名
环绕通知是其他四个通知的集合体,也是最常用的通知,是最类似动态代理的通知
@Around(execution(public String cn.joncy.MyNeedProxyClass.method1(String)))
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjg.getArgs();
Object proceed = null;
try{
// 此处添加内容 == 前置通知
proceed = pjp.proceed(args);
// 此处添加内容 == 返回通知
}catch(Exception e){
// 此处添加内容 == 异常通知
e.printStack();
}finally{
// 此处添加内容 == 后置通知
}
// 这个值决定了执行方法的最终返回值
return proceed;
}
环绕通知的执行顺序:
环绕前置 => 普通前置 => 目标方法执行 => 环绕正常返回/出现异常(抛出异常) => 环绕后置 => 普通后置 => 普通返回/异常
如图,只需注意在一个切面中环绕前置要比普通前置先执行,环绕只影响当前切面,所以如果切面二存在环绕通知,两个切面都存在普通通知,执行顺序为:
切面二环绕前置=>切面二普通前置=>切面一普通前置=>目标方法=>切面一后置=>切面一返回=>切面一环绕返回=>切面一环绕后置=>切面一后置=>切面一返回