一、AOP介绍
1.定义
Spring框架的AOP(Aspect-Oriented Programming)是一种编程范式,用于实现横切关注点的模块化。AOP通过将与业务逻辑无关的功能(例如日志记录、事务管理、安全性等)从主要业务逻辑中分离出来,提供了一种更加灵活和可维护的方式来处理横切关注点。
2.关键要点
在Spring框架中,AOP的实现依赖于面向切面编程的概念。以下是Spring框架中AOP的关键要点:
1. 切面(Aspect):切面是一个模块,它封装了与特定关注点相关的行为。例如,一个日志记录切面可以定义在方法执行前后打印日志。在Spring中,切面由通知和切点组成。
2. 通知(Advice):通知定义了在切点上执行的具体操作。Spring支持以下几种通知类型:
- 前置通知(Before Advice):在目标方法执行之前执行。
- 后置通知(After Advice):在目标方法执行之后(无论是否发生异常)执行。
- 返回通知(After Returning Advice):在目标方法正常返回后执行。
- 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
- 环绕通知(Around Advice):在目标方法执行前后都执行,并可以控制目标方法的执行。
3. 切点(Pointcut):切点定义了在应用程序中哪些方法将被通知所影响。它使用表达式来匹配方法的签名或注解等信息。通过定义切点,可以精确地确定哪些方法将被通知所拦截。
4. 连接点(Joinpoint):连接点是在应用程序中可能被通知的点。在Spring中,连接点通常是方法调用。通知可以在连接点之前、之后或者环绕连接点执行。
5. 织入(Weaving):织入是将切面应用到目标对象中的过程。Spring支持两种织入方式:
- 编译时织入(AspectJ编译器):在编译阶段将切面织入目标类中。
- 运行时织入(Spring AOP):通过代理对象在运行时将切面织入目标对象中。
在传统的面向对象编程中,业务逻辑通常分散在各个类和方法中。然而,某些功能(如日志记录、事务管理、安全性等)在多个类和方法中都可能存在,这导致了代码的冗余性和重复性。
AOP的目标是通过将这些横切关注点从核心业务逻辑中剥离出来,以便将其模块化并集中处理。通过AOP,可以将这些横切关注点作为独立的切面(Aspect)来实现,然后将它们织入(Weaving)到应用程序的目标对象中。
3.实现AOP的好处
①模块化:AOP允许将关注点的逻辑独立封装为切面,使代码更具模块化和可维护性。
②代码重用:通过将通用的横切关注点逻辑提取到切面中,可以在多个类和方法中重用它们,避免了代码的重复编写。
③可维护性:将横切关注点与核心业务逻辑分离,使代码更易于理解、修改和维护。
④ 解耦和聚焦:AOP允许开发人员将关注点的逻辑与核心业务逻辑解耦,使代码更加聚焦于核心功能。
⑤横切关注点的集中处理:通过AOP,可以将横切关注点的逻辑集中处理,提高代码的可读性和可管理性。
总而言之,AOP的目的是提供一种更加灵活和可维护的方式来处理与核心业务逻辑无关的功能,从而提高代码的可重用性、可维护性和模块化性。
二、切入点表达式
1.作用
切入点表达式用于确定在应用程序中哪些方法应该被拦截和执行切面逻辑,通过表达式的方式定位一个或多个具体的连接点。
2.语法细节
①切入点表达式的语法格式
execution([权限修饰符] [返回值类型] [简单类名/全类名][方法名]([参数列表]))
1.可以使用“*”代替表达式中的权限修饰符和返回值类型、返回值类型,类名,表示任意
2.可以用“..”代替参数列表,表示匹配任意数量、任意类型的参数。
3.若目标类、接口与该切面类在同一个包中可以省略包名
②举例说明
@Before(value="execution(* com.sina.spring.ArithmeticCalculator.*(..))")
含义 :ArithmeticCalculator 接口中声明的所有方法。
第一个“ * ”代表任意修饰符及任意返回值。 第二个“ * ”代表任意方法。
“..”匹配任意数量、任意类型的参数。
@Before(value="execution(public * ArithmeticCalculator.*(..)) ")
含义 :ArithmeticCalculator接口的所有公有方法
@Before(value="execution(public double ArithmeticCalculator.*(..)) ")
含义:ArithmeticCalculator接口中返回 double 类型数值的公有方法
@Before(value="execution(public double ArithmeticCalculator.*(double, ..))")
含义:ArithmeticCalculator接口中第一个参数为double类型的返回 double 类型数值的公有方法
“..”匹配任意数量、任意类型的参数。
@Before(value="execution(public double ArithmeticCalculator.*(double, double)) ")
含义:ArithmeticCalculator接口中参数类型为double,double类型,返回 double 类型数值的公有方法
3.注意事项
①切入表达式也可以指向类的方法,这时切入表达式会对该类/对象生效
②切入表达式也可以指向接口的方法,这时切入表达式会对实现了接口的类/对象生效
③切入表达式也可以对没有实现接口的类进行切入
三、切入点表达式的重用
1.作用
切入点表达式重用是指在不同的切面中重复使用相同的切入点表达式
通过重用切入点表达式,可以避免在不同的切面中重复编写相同的表达式,提高代码的可维护性和可读性。在Spring框架中,可以将切入点表达式定义为一个单独的方法或属性,然后在多个切面中引用该表达式,实现切入点表达式的重用。
例如,假设有两个切面A和B,它们都需要拦截某个包下的所有Service类的方法。可以将这个切入点表达式定义为一个方法或属性,然后在切面A和B中引用该表达式,避免重复编写相同的表达式。
2.实现
这种切入点表达式的重用可以通过Spring的@Pointcut注解来实现。通过定义一个带有@Pointcut注解的方法或属性来表示切入点表达式,然后在各个切面中使用@Pointcut注解的value属性引用该表达式,即可实现切入点表达式的重用。
@Component
@Aspect
public class AopAspect {
//定义切入点
@Pointcut(value = "execution(public int SmartDog.getSum(int ,int ))")
public void dogPointCut(){}
@Before(value = "dogPointCut()")
public void f1(JoinPoint joinPoint){
System.out.println("方法执行开始-日志-方法名-"+joinPoint.getSignature().getName()+"-参数 "
+ Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "dogPointCut()",returning = "res")
public void f2(JoinPoint joinPoint,Object res){
System.out.println("方法执行正常结束-日志-方法名-"+joinPoint.getSignature().getName()
+"-结果 result= " + res);
}
}
四、连接点JoinPoint
常用方法
joinPoint.getSignature().getName();//获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName();//获取目标方法所属类的简单类名
joinPoint.getSignature().getDeclaringTypeName();//获取目标方法所属类的类名
joinPoint.getSignature().getModifiers();//获取目标方法声明类型(public、private、 protected)
Object[] args = joinPoint.getArgs();//获取传入目标方法的参数,返回一个数组
joinPoint.getTarget();//获取被代理的对象
joinPoint.getThis();//获取代理对象自己
五、切面类
@Aspect :表示这个类是一个切面类
@Component :表示要加入到 IOC 容器
@Before:前置通知,在目标方法执行之前执行。
@After:后置通知,在目标方法执行之后(无论是否发生异常)执行。
@AfterReturning:返回通知,在目标方法正常返回后执行。
@AfterThrowing:异常通知,在目标方法抛出异常后执行。
@Aspect
@Component
public class AopAspect {
@Before(value = "execution(public int SmartDog.getSum(int ,int ))")
public void f1(JoinPoint joinPoint){
System.out.println("方法执行开始-日志-方法名-"+joinPoint.getSignature().getName()+"-参数 "
+ Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "execution(public int SmartDog.getSum(int ,int ))" ,returning = "res")
public void f2(JoinPoint joinPoint,Object res){
System.out.println("方法执行正常结束-日志-方法名-"+joinPoint.getSignature().getName()
+"-结果 result= " + res);
}
@AfterThrowing(value = "execution(public int SmartDog.getSum(int ,int ))",throwing = "e")
public void f3(JoinPoint joinPoint, Throwable e){
System.out.println("方法执行异常结束-日志-方法名-"+joinPoint.getSignature().getName()+"-异常类型"+e);
}
@After(value = "execution(public int SmartDog.getSum(int ,int ))")
public void f4(){
System.out.println("结束执行计算" + System.currentTimeMillis());
}
}