1. 快速入门
1.1 基本说明
AOP(aspect oriented programming) ,面向切面编程
切面类中声明通知方法:
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
1.2 快速入门
参考视频:B站 三更草堂视频
1.3 细节说明
1)切面类方法命名
- showBeginLog()
- showSuccessEndLog()
- showExceptionLog()
- showFinallyEndLog()
2)切入表达式更多的配置
使用模糊配置
@Before(value="execution(* com.hspedu.aop.proxy.SmartDog.*(..))")
这个就表示这个切面方法作用与 com.hspedu.aop.proxy.SmartDog 类的函数
3)所有访问权限
@Before(value="execution(* *.*(..))")
- 第一个 * 表示 任意方法权限及任意返回值
- 第二个 * 表示 任意路径
- 第三个 * 表示 任意方法
- (..) 表示任意数量,任意类型的参数
所有包的下所有有类的所方法,都会被执行该前置通知方法
4)使用基于注解的AOP需要在配置文件里开启
<aop:aspectj-autoproxy/>
5)开启了基于注解的后获取对象
这个时候去获取 ioc 容器里的对象的话,在底层被转换为 代理对象
我们获取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型
2. AOP-切入表达式
2.1 具体使用
1)作用
通过表达式的方式来定位一个或多个具体的切入点
2)语法细节
格式: execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]))
若目标类、接口 与切面类在同一个包下,可以省略包名
2.2 注意事项和细节
1)表达式指向类
切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
2)表达式指向接口
切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
3)表达式指向没有实现接口的类
切入表达式也可以对没有实现接口的类,进行切入
@Component
class Car {
public void run() {
System.out.println("car run");
}
}
切面类:
@Before(value = "execution(public void com.hspedu.spring.aop.aspectj.Car.run())")
public static void showBeginLog3(JoinPoint joinPoint) {
//获取方法签名
Signature signature = joinPoint.getSignature();
System.out.println("方法执行前--方法名--" + signature.getName());
}
测试:
@Test
public void carTestProxy() {
//得到spring容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
Car car = ioc.getBean(Car.class);
System.out.println(car.getClass());
car.run();
}
不一样的时,这此取出来的 car 对象的运行类型:
class com.hspedu.spring.aop.aspectj.Car$$EnhancerBySpringCGLIB$$8e9f1fa7
这个时候 就是 Spring 的 CGlib 方案:取出的时 Car 对象的子类
参考:jdk的Proxy与spring的CGlib
3. 切入方法细节
3.1 AOP-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(); 获取代理对象自己
3.2 返回通知获知结果
如何在返回通知方法获取返回结果?
**
* returning = "res", Object res 名称保持一致
* @param joinPoint
* @param res 调用 getSum() 返回的结果
*/
@AfterReturning(value="execution(public float com.hspedu.spring.aop.joinpoint.SmartDog.getSum(float, float))",returning = "res")
public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
System.out.println("返回通知" + "--结果是--" + res );
}
3.3 AOP-异常通知中获取异常
@AfterThrowing(value="execution(public float
com.hspedu.spring.aop.joinpoint.SmartDog.getSum(float, float))",throwing = "throwable")
public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
System.out.println("异常通知 -- 异常信息--" + throwable);
}
3.4 环绕通知(了解)
环绕通知可以完成其它四个通知要做的事情
@Aspect //表示这个类是一个切面类
@Component //需要加入到 IOC 容器
public class SmartAnimalAspect {
//=====环绕通知 start=====
//@Around(value = "execution(* *.*(..))")
@Around(value="execution(public float
com.hspedu.spring.aop.joinpoint.SmartDog.getSum(float, float))")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);
System.out.println("AOP 环绕通知--" + methodName + "开始了--参数有:
" + argList);
//在环绕通知中一定要调用 joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("AOP 环绕通知" + methodName + "结束了--结果是:"
+ result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("AOP 环绕通知" + methodName + "抛异常--异常对象:" +
throwable);
} finally {
//4.相当于最终通知完成的事情
System.out.println("AOP 后置通知" + methodName + "最终结束...");
}
return result;
}
环绕通知和动态代理完成的事情相似
3.5 切入点表达式重用
为了统一管理切入点表达式,可以使用切入点表达式重用技术
在切面类上添加:
//=====AOP-切入点表达式重用 start ======
/*
* 这样定义的一个切入点表达式,就可以在其它地方直接使用
*/
@Pointcut(value="execution(public float
com.hspedu.spring.aop.joinpoint.SmartDog.getSum(float, float))")
public void myPointCut() {
}
这样其他切面方法就可以简写:
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) { //前置方法
Signature signature = joinPoint.getSignature();
//得到方法名.
String method_name = signature.getName();
//得到参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知" + "--调用的方法是 " + method_name + "--参数是
--" + Arrays.asList(args));
}
3.6 AOP-切面优先级问题
如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制?
@order(value=n)
通过设定 order 的value 值来控制, n 值越小,优先级越高
@Aspect //表示这个类是一个切面类
@Order(value = 2)
@Component //需要加入 IOC 容器
public class SmartAnimalAspect2 {
/**
内部是切面方法
*/
}
/****************************/
@Aspect //表示这个类是一个切面类
@Order(value = 1)
@Component //需要加入 IOC 容器
public class SmartAnimalAspect {
/**
内部是切面方法
*/
}
输出的信息顺序,类似 Filter 的过滤链式调用机制
3. 基于 XML 配置 AOP
前面是通过注解来配置 aop 的,在 spring 中,我们也可以通过 xml 的方式来配置 AOP
1)准备对象
去掉切面方法的所有注解
2)配置 xml
<!-- 配置 SmartAnimalAspect bean -->
<bean id="smartAnimalAspect" class="com.hspedu.spring.aop.xml.SmartAnimalAspect"/>
<!--配置 SmartDog-->
<bean class="com.hspedu.spring.aop.xml.SmartDog" id="smartDog"/>
<aop:config>
<!-- 配置统一切入点 -->
<aop:pointcut expression="execution(public float
com.hspedu.spring.aop.xml.SmartDog.getSum(float, float))" id="myPointCut"/>
<aop:aspect ref="smartAnimalAspect" order="1">
<!-- 配置各个通知对应的切入点 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut"
returning="res"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut"
throwing="throwable"/>
<aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
<!-- 还可以配置环绕通知 -->
<!-- <aop:around method=""/> -->
</aop:aspect>
</aop:config>