不要小瞧面向切面编程

本文介绍Spring AOP,面向切面编程。在权限验证、保存日志、安全检查和事务控制等多个应用场景之下都会依赖该技术。以下是在自己学习过程中的一些总结,如有错误,还望指正。

面向切面的定义

         面向切面编程(AOP),全称 Aspect Oriented Programming。是基于面向对象编程之上的编程思想。指的是在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的编程方式。

不要小瞧面向切面编程_第1张图片
        动态代理可以非耦合动态插入代码,我们之前介绍过,JDK动态代理相关内容请移步:JDK动态代理

AOP的几个术语

不要小瞧面向切面编程_第2张图片
        切入点:我们真正需要执行日志记录的地方。连接点:每一个方法的每一个位置都是一个连接点。通过使用切入点表达式在众多的连接点中选出我们感兴趣的地方。

案例入门

1. 切面类
@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
	}
}
2. 测试执行
@Test
public void test(){
     
	// 此处传入的是接口类型
	NeedProxyClass npc = ioc.getBean(NeedProxyClass.class);
	npc.method1("low");
}

AOP使用细节

1. IOC容器中保存代理对象

        从ioc容器中拿到目标对象,参数传递的是被代理类的接口,因为接口是被代理类和代理类产生关联的东西。AOP底层是动态代理,容器中保存的组件是它的代理对象(com.sun.proxy.$Proxyxxx)

2. 切入点表达式

’ * ’ :
        可以匹配一个或者多个字符: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(* *.*(..)) 双点不能直接写
切入点表达式还支持:&& 、|| 、!

3. 通知的执行顺序

         正常执行:@Before => @After => @AfterReturning
         异常执行:@Before => @After => @AfterThrowing

4. JoinPoint的使用

        为通知方法加上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引用该方法签名

5. 环绕通知

         环绕通知是其他四个通知的集合体,也是最常用的通知,是最类似动态代理的通知

@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;
}

         环绕通知的执行顺序:
环绕前置 => 普通前置 => 目标方法执行 => 环绕正常返回/出现异常(抛出异常) => 环绕后置 => 普通后置 => 普通返回/异常

6. 多切面执行顺序

不要小瞧面向切面编程_第3张图片
如图,只需注意在一个切面中环绕前置要比普通前置先执行,环绕只影响当前切面,所以如果切面二存在环绕通知,两个切面都存在普通通知,执行顺序为:
切面二环绕前置=>切面二普通前置=>切面一普通前置=>目标方法=>切面一后置=>切面一返回=>切面一环绕返回=>切面一环绕后置=>切面一后置=>切面一返回

你可能感兴趣的:(#,spring框架,java,aop,spring)