Spring框架(使用@AspectJ注解开发AOP)
目录,更新ing,学习Java的点滴记录
目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录
Spring知识
第一篇---->初识Spring
第二篇---->深入SpringIoC容器(一)
第三篇---->深入SpringIoC容器(二)
第四篇---->依赖注入的方式
第五篇---->基于xml装配Bean
第六篇---->基于注解装配Bean
第七篇---->Spring Bean之间的关系
第八篇---->SpringBean的作用域
第九篇---->Spring 加载属性(properties)文件
第十篇---->Spring表达式(SpEL)
第十一篇---->Spring在xml中配置组件扫描
第十二篇—>认识SpringAOP及底层原理
第十三篇—>使用@AspectJ注解开发AOP
第十四篇—>使用xml配置开发AOP
5 使用@AspectJ注解开发Spring AOP
5.1 AOP简介及准备环境
- AspectJ:Java 社区里最完整最流行的 AOP 框架.在 Spring2.0 以上版本中, 可以使用基于AspectJ 注解的形式实现AOP
- 准备环境
1) 要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectjweaver.jar 和 spring-aspects.jar,spring-aop.jar
jar包资料:链接:https://pan.baidu.com/s/1i3LPve0Z3ap5oBRDTELCXw 提取码:bk13
2) 将 aop命名空间及对应xsd文件添加到 根元素中.(具体命名空间查询—>Spring配置文件命名空间),要在 Spring IOC 容器中启用 AspectJ 注解支持
, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>
3) 当 Spring IOC 容器侦测到 Bean 配置文件中的 aop:aspectj-autoproxy 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理. - 其他说明
&esmp;在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例.
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解
5.2 其他知识
- 使用注解@AspectJ开发AOP的时候的时候还需要了解@AspectJ注解的使用,各种通知的注解,切入点表达式等,这里就不单独拿出来挨个介绍了,直接通过后面的这个案例,用到之后立即说明,这样理解起来也会更加直观
5.3 选择切点
- Spring是方法级别的AOP框架,而我们主要也是以某个类的某个方法作为切点,用动态代理的理论来说,就是要拦截哪个方法织入对应AOP通知.
- 首先建立一个User类,然后创建一个打印用户信息的接口及其实现类
- 这时候我会将实现类中的
printUser方法作为AOP的切点
,那么底层动态代理的作用就是要为UserServiceImpl生成代理对象,然后拦截printUser()方法.于是就产生了各种AOP通知方法
5.4 创建切面
- 选择好了切点,那么我们来创建切面,对于
动态代理而言,它就如同是一个拦截器
,在Spring中只要使用@AspectJ注解一个类,那么SpringIOC容器就会认为这是一个切面
- 创建UserAspect切面
- 上面画框的就是AspectJ关于通知的注解,看名字我不说都能猜到了吧,这里没有添加环绕通知,可能是因为这个太强了,后面再抽出来讲哈哈哈
- 各种通知的对应注解解释
- 强调一下
返回通知
在返回通知中, 只要将returning 属性
添加到 @AfterReturning 注解中, 就可以访问连接点的返回值(被调用方法的返回值). 该属性的值即为用来传入返回值的参数名称.
必须在通知方法的签名中添加一个同名参数
. 在运行时, Spring AOP 会通过这个参数传递返回值.
原始的切点表达式需要出现在pointcut 属性
中
- 再强调一下
异常通知
:
只在连接点抛出异常时才执行异常通知
将 throwing 属性添加到 @AfterThrowing 注解中
, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
如果只对某种特殊的异常类型感兴趣
, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行
- 参考这个表,研究上面的切面类代码就容易多了.还有一个需要注意的知识点—>注解中用到的切入点表达式,作用是告诉SpringAOP,需要拦截什么类的什么方法.
5.5 连接点
- 下面谈谈Spring是如何判断什么方法是需要拦截的,毕竟你项目中设计的方法不可能都需要被拦截,这就涉及到连接点的问题.
- 切入点表达式解释:execution(* com.aop.aspectj.UserServiceImpl.printUser(…))
- 通过这种描述,全限定名 com.aop.aspectj.UserServiceImpl的类的printUser方法被拦截了,它就会按照AOP的通知的规则把方法织入到流程中.
- 切入点表达式的时候非常灵活,下面几个例子,大家可以过一遍
- AspectJ还包括很多指示器,可以更为具体的使用切入点表达式
- 拿within举个例子,下面的表达式就只能匹配到com.aop.aspectj.UserServiceImpl类下的printUser方法
- 其中,&&表示并且的含义,在xml中配置时需要使用and代替,运算符||可以用or代替,非运算符!可以用not代替
- 讨论完了切入点表达式,现在我们回到切面类UserAspect类中,可以发现各种通知的切入点表达式都是一样的,重复代码多写好几遍,就应该有办法将其统一简化一下,这里我们引入@Pointcut注解
- 这样就可以做到重复使用一个简单表达式取代多次写复杂表达式了
5.6 测试AOP
- 对Spring的Bean进行配置,采用Java注解方式
@EnableAspectJAutoProxy代表启用AspectJ框架的自动代理,这个时候Spring会生成动态代理对象,然后就可以使用AOP,而getUserAspect方法可以生成一个切面实例 - 对Spring的Bean进行配置,也可以采用xml方式
第一行作用等同于@EnableAspectJAutoProxy - 以上两种方式都可以产生动态代理对象,从而组织切面,把各种通知放入流程中
- 测试代码
-
强调注意点!!!!
,关于上图中代码获取Bean的方式,目前测试只能使用这种写法,有些小伙伴可能想到了下面这种写法,但是运行时,发现报错了,显示找不到UserServiceImpl类型的Bean
- 但是当你打开UserServiceImpl类的代码后发现明明添加了@Component注解,该注解不是可以将被注解的类变成Bean放入SpringIOC容器中吗?为啥我明明注解了反而获取不到了呢?其实你忘记了AOP底层很重要的知识点–>动态代理
- 下面让我们通过断点调试的方式,看一下UserSerive userSerive = ctx.getBean(UserSerive.class);拿到的究竟是什么对象
- 哇,真相大白了,原来拿到的是一个代理对象,看来Spring底层是帮我们对UserServiceImpl类进行了转换,用JDK动态代理的方式生成了新的代理对象,那么奇怪的问题又来了–>我用UserServiceImpl获取不到对象现在明白了,那么为啥UserService为啥就可以呢?看来你对我本篇文章先讲的动态代理案例中断点调试的截图没有仔细看哦,
采用JDK的动态代理,生成的代理对象拥有和被代理对象相同的接口
,而getBean可以使用多态获取Bean示例,你传入一个UserService接口类型,那么就能匹配到UserService接口实现类的Bean实例.
5.7 环绕通知
- 环绕通知是SpringAOP
最强大的通知
,他可以同时实现前置通知和后置通知.它保留了调度被代理对象原有方法的功能,强大又灵活,但可控性不强,如果不需要大量改变业务逻辑,一般而言并不需要使用它. - 下面在UserAspect接口中引入,并运行测试类执行
- 但是需要注意的是有些通知之间执行顺序是不一定的,如下图(有兴趣的同学可以看一下第十三篇,文章中有xml格式开发AOP的通知的执行顺序,和本次注解的顺序是不相同的)
5.8 织入
- 织入是生成代理对象的过程.在前面提到的代码示例中,选择的切点所在的类都是拥有接口的类,而实际上即使没有接口,Spring也能提供AOP的功能,所以
是否拥有接口不是使用SpringAOP的一个强制要求
.之前说动态代理的时候也提到过,使用JDK动态代理,必须拥有接口,而使用CGLib则不需要.于是Spring中存在一个规则:`当类的实现存在接口的时候,Spring将提供JDK动态代理,从而织入各个通知.当类不存在接口的时候没有办法使用JDK动态代理,Spring会采用CGLib来生成代理对象. - 我们现在重新修改上述代码,将接口实现类取消实现接口,并修改Test类获取Bean的方式,看看得到的Bean对象是否为CGLib生成的代理对象
- 修改代码和设置断点
- 断点调试
- 这里可以很明显看出,在对没有接口实现的类实现代理时,Spring底层为我们使用了CGlib的代理模式
- 动态代理对象是由SpringIOC容器根据描述生成的,一般不需要修改,对于我们来说,你只需要明白AOP中的约定就可以轻松使用AOP了,Spring中建议使用接口编程.好处是实现定义和实现分离,更加灵活.
5.9 给通知传递参数
- 之前使用各种通知的时候都没有分析过参数的传递,只是在各类通知的注解中声明了一个切入点表达式去匹配切点,实际开发中有时还是用的到传递被拦截方法的参数到通知中去的.
- 修改切点的方法,让方法带有形参
- 现在将这个方法作为切点,使用切面进行拦截
- 通过使用args就可以实现参数的传递的功能,其他的通知传递参数的方式与该示例是一致的.
5.10 引入
- SpringAOP只是通过动态代理技术,把各类通知织入到它所约定的流程中,而实际上,我们有时候会
希望通过引入其他类的方法来得到更好的实现
,这时候就可以通过引入来操作了 - 以之前的代码为例,要求printUser方法在打印User时,需要要求用户不为空是才打印,这时我们可以引入一个检测当前User是否为空的类.
- 定义一个检测User是否为空的接口及其实现类,该类非常简单,这里只是使用一个浅显易懂的例子帮助大家理解引入
- 现在需要修改切面类了,我们需要给它添加一个新的属性
- 开始测试被拦截的方法,现在执行的时候会先判断打印的User是否为空,如果不为空才会打印
- 使用强制转换后可以把UserService转化为UserCheck接口对象,然后就可以使用check方法了,而UserCheck调用的方法check,显然就是通过UserCheckImpl来实现的.
- 它的底层实现原理就是依赖于动态代理,由于上面代码中 的UserServiceImpl是实现了接口的,所以Spring在运行时会选择JDK动态代理模式,
- 而JDK的动态代理可以将代理对象挂载到多个接口下面,所以说,只要SpringAOP让代理对象挂载到UserService和UserCheck接口下,那么就可以把对应的Bean通过强制转换,让其在UserService和UserCheck直接互相转换了.
- 下面通过断点测试验证一下
- 调试结果中很明显看出确实挂在了两个接口,所以肯定能够互相转换,进而可以调用引入的方法,这样就能通过引入功能,在原有基础上增强Bean了.
- 同样的,之前提到过如果被代理对象没有实现接口,Spring底层依旧会自动选用CGLib进行动态代理,CGLib中有Enhancer对象,他也有interfaces的属性,同样运行代理对象挂载到多个接口之下.