文章目录
代码沿用 Spring–AOP简介
一. bean的实际类型
AOP底层就是动态代理,容器中保存的组件是他的代理对象:$Proxy23,不是本类类型。
代理对象和目标对象是用同一个接口,所以获取时一定要用接口类型
@Test
public void test() {
//从IOC容器中拿到目标对象,如果想要用类型,一定要用接口类型,不要用本类MyCalculator.class
Calculator bean = ioc.getBean(Calculator.class);
System.out.println(bean);
System.out.println(bean.getClass());
}
若没有接口就是本类类型,cglib帮我们创建了代理对象
@Service
public class MyCalculator //implements Calculator{
public int add(int i, int j) {
int result=i+j;
return result;
}
public int sub(int i, int j) {
int result=i-j;
return result;
}
public int mul(int i, int j) {
int result=i*j;
return result;
}
public int div(int i, int j) {
int result=i/j;
return result;
}
}
二. 切入点表达式
固定格式:execution(访问权限服 返回值类型 方法全类名(参数表))
通配符:
*
:
- 匹配一个或多个字符:
execution(public int com.jess.aop.MyCal*.*(int,int))
- 匹配任意一个参数:第二个参数任意
execution(public int com.jess.aop.MyCalculator.*(int,*))
- 只能匹配一层路径
- 权限位置不能使用*,不写就行:public可不写
..
:
- 匹配任意多个参数,任意参数类型:
execution(public int com.jess.aop.MyCalculator.*(..))
- 匹配任意多层路径:
execution(public int com.jess..MyCalculator.*(int,int))
- 最模糊:不推荐
execution(* *.*(..))
- 最精确:
execution(public int com.jess.aop.MyCalculator.add(int,int))"
表达式中还可以使用 && 、||、!
例如:execution(* *.*(..)) && execution(public int com.jess.aop.MyCalculator.add(int,int))"
抽取可重用的切入点表达式:
- 随便声明一个没有返回的空方法
- 给方法标注注解
- 在其他方法上使用
@Pointcut("execution(public int com.jess.aop.MyCalculator.*(int,int))")
public void MyPoint(){}
@Before("MyPoint()")
三. 通知方法的执行顺序
在上一篇博客中代码执行结果为
先执行了finally块中的 @After,在执行try中的@AfterReturning或者catch中@AfterThrowing
正常执行顺序:
- @Before:前置通知
- @After:后置通知
- @AfterReturning
异常执行通知:
- @Before:前置通知
- @After:后置通知
- @AfterThrowing
四. JoinPoint获取目标方法的信息
在通知方法运行时,拿到目标方法的详细信息
在通知方法的参数列表上写一个参数JoinPoint
JoinPoint joinPoint:封装了当前目标方法的详细信息
注意:JoinPoint 选用 org.aspectj.lang 包下的
@Before("execution(public int com.jess.aop.MyCalculator.*(int,int))")
public static void logStrat(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();//获取方法签名
String name = signature.getName();//获取方法名
Object[] args = joinPoint.getArgs(); //获取方法参数
System.out.println(name+"方法开始执行,使用的参数列表:"+ Arrays.asList(args));
}
五. throwing、returning
在通知方法的参数列表上写一个参数 Object result
并在 @AfterReturning后添加一个属性returning,值为你设定的Object参数名
throwing同
@AfterReturning(value = "execution(public int com.jess.aop.MyCalculator.*(int,int))",returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result){
System.out.println("方法执行完成,结果为:"+result);
}
@AfterThrowing(value = "execution(public int com.jess.aop.MyCalculator.*(int,int))",throwing = "exception")
public static void logException(JoinPoint joinPoint,Exception exception){
System.out.println("方法出现异常:"+exception);
}
异常类型指定后,通知方法只接受此类异常,所以一般把异常的范围写大
@AfterThrowing(value = "execution(public int com.jess.aop.MyCalculator.*(int,int))",throwing = "exception")
public static void logException(JoinPoint joinPoint,NullPointerException exception){
System.out.println("方法出现异常:"+exception);
}
六. Spring对通知方法的约束
- spring对通知方法要求不严格(访问权限符,返回值)
- 唯一要求参数列表不能乱写:通知方法是spring利用反射调用的,每次方法调用得绑定这个方法的参数列表的值,每一个参数spring都得知道是什么。
七. 环绕通知
Spring中最强大的通知方法
@Around:动态代理
环绕通知就是其余4种通知的四合一
@Around("MyPoint()")
public Object MyAround(ProceedingJoinPoint pjp){
Object[] args = pjp.getArgs()
//利用反射调用目标方法,就是method.invoke()
Object proceed = pjp.proceed(args);
//反射的返回值一定要返回
return proceed;
}
关闭其他通知,只使用环绕通知:
其他通知和环绕通知一起使用:
异常已经被环绕通知获取了,普通通知获取不到了
若在环绕通知获取异常后(catch中)将异常抛出,普通通知就能捕获异常了
throw new RuntimeException(e);
环绕通知优先执行
前置通知顺序不一定,后续的通知顺序是一定的
八. 多切面运行顺序
@Aspect
@Component
public class ValidateAspect {
//内容与LogUtils相同
}
若要人为的改变顺序可以添加注解@Order(1)
,数值越小,优先级越高
环绕只是影响当前切面
九. 基于配置的AOP
不使用注解,配置xml
<bean id="myCalculator" class="com.jess.aop.MyCalculator"></bean>
<bean id="validateAspect" class="com.jess.aop.ValidateAspect"></bean>
<bean id="logUtils" class="com.jess.aop.LogUtils"></bean>
<!--告诉spring哪个是切面类:aop名称空间-->
<aop:config>
<!--指定切面-->
<aop:aspect ref="logUtils">
<!-- 指定切面何时何地运行-->
<aop:pointcut id="mypoint" expression="execution(* com.jess.aop.MyCalculator.*(..))"/>
<aop:before method="logStrat" pointcut="execution(* com.jess.aop.MyCalculator.*(..))"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"></aop:after-throwing>
<aop:after method="logEnd" pointcut-ref="mypoint"></aop:after>
</aop:aspect>
<aop:aspect ref="validateAspect"></aop:aspect>
</aop:config>
十. AOP的应用
- 保存日志到数据库
- 做权限验证
- 做安全检查
- 做事务控制