前言:
AOP是Spring框架除了IOC之外的另一个核心概念。
AOP:Aspect Oriented Programming,意为面向切面编程。
这是一个新的概念,我们知道Java是面向对象编程(OOP):指将所有的一切都看做对象,通过对象与对象之间相互作用来解决问题的一种编程思想。
AOP是对OOP的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。将不同方法的同一位置抽象成一个切面对象,对该切面对象进行编程就是AOP。
AOP的优点:
1.降低模块之间的耦合度。
2.使系统容易扩展。
3.更好的代码复用。
4.非业务代码更加集中,不分散,便于统一管理。
5.业务代码更简洁纯粹,没有其他代码的影响。
说概念太空泛,不好理解,我们还是通过代码来直观感受什么是AOP。
1.创建一个计算器接口 Cal。
定义四个方法:加,减,乘,除。
2.创建接口实现类CalImpl。
实现四个方法。
3.在测试方法中创建CalImpl对象,调用方法。
以上这段代码很简单,现添加功能:在每一个方法执行的同时,打印日志信息:该方法的参数列表和该方法的计算结果。
这个需求很简单,我们只需要在每一个方法体中,运算执行之前打印参数列表,运算结束之后打印计算结果即可,对代码做出如下修改。
再次运行代码,成功打印日志信息。
功能已经实现了,但是我们会发现这种方式业务代码和打印日志代码的耦合性非常高,不利于代码后期的维护。
如果需求改变,需要对打印的日志内容作出修改,那么我们就必须修改4个方法中的所有相关代码,如果是100个方法呢?每次就需要手动去改100个方法中的代码。
换个角度去分析,会发现4个方法中打印日志信息的代码基本相同,那么有没有可能将这部分代码提取出来进行封装,统一维护呢?同时也可以将日志代码和业务代码完全分离开,解耦和。
按照这思路继续向下走,我们希望做的事情是把这4个方法的相同位置(业务方法执行前后)提取出来,形成一个横切面,并且将这个横切面封装成一个对象,将所有的打印日志代码写到这个对象中,以实现与业务代码的分离。
这就是AOP的思想。
如何实现?
使用动态代理的方式来实现。
我们希望CalImpl只进行业务运算,不进行打印日志的工作,那么就需要有一个对象来替代CalImpl进行打印日志的工作,这就是代理对象。
代理对象首先应该具备CalImpl的所有功能,并在此基础上,扩展出打印日志的功能。
1.删除CalImpl方法中所有的打印日志代码,只保留业务代码。
2.创建MyInvocationHandlerl类,并实现InvocationHandler接口,成为一个动态代理类。
bind方法是MyInvocationHandlerl类提供给外部调用的方法,传入需要被代理的对象,bind方法会返回一个代理对象。
bind方法完成了两项工作:
1.将外部传进来的被代理对象保存到成员变量中,因为业务方法调用时需要用到被代理对象。
2.通过Proxy.newProxyInstance方法创建一个代理对象。
解释一下Proxy.newProxyInstance方法的参数:
(1)我们知道对象是JVM根据运行时类来创建的,此时需要动态创建一个代理对象,可以使用被代理对象的运行时类来创建代理对象:obj.getClass().getClassLoader()获取被代理对象的运行时类。
(2)同时代理对象需要具备被代理对象的所有功能,即需要拥有被代理对象的所有接口,所以传入obj.getClass().getInterfaces()。
(3)this指当前MyInvocationHandler对象。
以上全部是反射(reflect)的知识点。
invoke方法:method是描述被代理对象的所有方法的对象,agrs是描述被代理对象方法的参数列表的对象。
method.invoke(this.obj, args)是通过反射机制来调用被代理对象的方法,即业务方法。
所以在method.invoke(this.obj, args)前后添加打印日志信息,就等同于在被代理对象的业务方法前后添加打印日志信息,并且已经做到了分类,业务方法在被代理对象中,打印日志信息在代理对象中。
测试方法中执行代码。
成功,并且现在已经做到了代码分离,CalImpl类中只有业务代码,打印日志的代码写在MyInvocationHandler类中。
以上就是通过动态代理实现AOP的过程,我们在使用Spring框架的AOP时,并不需要这么复杂,Spring已经对这个过程进行了封装,让开发者可以更加便捷的使用AOP进行开发。
接下来我们就来学习Spring框架的AOP如何使用。
在Spring框架中,我们不需要创建动态代理类,只需要创建一个切面类,Spring底层会自动根据切面类以及目标类生成一个代理对象。
1.创建切面类 LoggerAspect
LoggerAspect类名处添加两个注解:
1.@Aspect:表示该类是切面类。
2.@Component:将该类注入到IOC容器。
分别来说明类中的4个方法注解的含义。
1.@Before:表示before方法执行的时机。
即CalImpl所有方法在执行之前会首先执行LoggerAspect类中的before方法。
after方法同理,
表示CalImpl所有方法执行之后会执行LoggerAspect类中的after方法。
afterReturn方法表示CalImpl所有方法在return之后会执行LoggerAspect类中的afterReturn方法。
afterThrowing方法表示CalImpl所有方法在抛出异常时会执行LoggerAspect类中的afterThrowing方法。
所以我们就可以根据具体需求,选择在before,after,afterReturn,afterThrowing方法中添加相应代码。
2.目标类也需要添加@Component注解。
3.spring.xml中进行配置。
2.添加aop:aspectj-autoproxy注解,Spring容器会结合切面类和目标类自动生成动态代理对象,Spring框架的AOP底层就是通过动态代理的方式完成AOP。
4.测试方法执行如下代码:
从IOC容器中获取代理对象,执行方法。
成功。结合代码,回过头来说几个概念更好理解。
切面:横切关注点被模块化的特殊对象。
CalImpl所有方法中需要加入日志的部分,抽象成一个切面对象LoggerAspect。
通知:切面对象完成的工作。
LoggerAspect对象打印日志的操作。
目标:被通知的对象,即被横切的对象。
CalImpl对象。
代理:切面通知目标混合之后的内容。
连接点:程序要执行的某个特定位置。
切面方法要插入业务代码的具体位置。
切点:AOP通过切点定位到连接点。
源码:
链接:https://pan.baidu.com/s/1dUs9cE
密码:wbh6
专业 热爱 专注
致力于最高效的Java学习
Java大联盟
领取专属 10元无门槛券
私享最新 技术干货