自研框架-AOP模块
前言
容器是OOP的高级工具:
以低耦合低侵入的方式打通从上到下的开发通道
按部就班填充代码逻辑实现业务功能,每层逻辑都可无缝替换
OOP将业务程序分解成各个层次的对象,通过对象联动完成业务
无法很好地处理分散在各业务里的通用系统需求
系统需求
码农才去关系的需求
添加日志信息:为每个方法添加统计时间
添加系统权限校验:针对某些方法进行限制
软件工程中有个基本原则:关注点分离 Concern Separation
不同的问题交给不同的部分去解决,每部分专注解决自己的问题
Aspect Oriented Programming就是其中一种关注点分离的技术
通用化功能的代码实现即切面Aspect
Aspect之于AOP,就相当于Class之于OOP,Bean之于Spring
框架搭建图:
AOP的子民们
切面Aspect:将横切关注点逻辑进行模块化封装的实体对象
通知Advice:好比是Class里面的方法,还定义了织入逻辑的时机
连接点Joinpoint,允许使用Advice的地方(对于SpringAOP来讲默认只支持方法级别的Joinpoint)
切入点Pointcut:定义一系列规则对Joinpoint进行筛选
目标对象Target:符合Pointcut条件,要被织入横切逻辑的对象
AOP是OOP里的"寄生虫"
织入:将Aspect模块化的横切关注点集成到OOP里
织入器︰完成织入过程的执行者,如ajc
Spring AOP则会使用一组类来作为织入器以完成最终的织入操作
Advice的种类
BeforeAdvice:在JoinPoint前被执行的Advice(该方法并不能阻止JoinPoint的执行,除非抛出异常)
AfterAdvice: 就好比try..catch..finaly里面的finaly(无论Joinpoint能否正常执行,都会执行到)
AfterReturningAdvice:在Joinpoint执行流程正常返回后被执行(如果Joinpoint抛出异常,则不会执行)
AfterThrowingAdvice:在Joinpoint执行过程中抛出异常才会触发(如果trycatch了,是不会到这的)
AroundAdvice:在Joinpoin前和后都执行,最常用的Advice(常用)
Introduction——引入型Advice
为目标类引入新接口,而不需要目标类做任何实现
使得目标类在使用的过程中转型成新接口对象,调用新接口的方法
注:
这种方式的操作,对于代码的阅读理解是比较复杂的
使用方法:
首先声明好一个接口,一个实现类
public interface LittleUniverse { void burningup(); }
public class LittleUniverseImpl implements LittleUniverse { @Override public void burningup() { System.out.println("燃烧吧小宇宙"); } }
然后在Aspect中添加上如下代码:
@DeclareParents(value = "com.imooc.controller..*",defaultImpl = com.imooc.introduction.impl.LittleUniverseImpl.class) public LittleUniverse littleUniverse;//这样子我们便赋予了每个Controller都能变身为littleUniverse的权力
此时我们在容器中getBean获取到了Controller之后,便可以将他强转类型并调用接口的方法。
public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Entrance.class); HiController hiController = (HiController)applicationContext.getBean("hiController"); ((LittleUniverse)hiController).burningup(); }
SpringAOP 的 实 现 原 理
在学习SpringAOP内部的实现原理之前,首先我们要了解代理模式
通常代理模式由(抽象主题)(被代理角色)(代理角色)三个模块完成
抽象主题:可以是抽象类或者抽象接口(用户只需要只需要面向抽象主题即可)
代理类:实现或者继承自抽象主题(实例化的时候使用代理类)(SpringAOP需要做的便是生成这么个代理类,替换掉真正的实现类来提供服务)
被代理类:实现或者继承自抽象主题
代码演示:
首先我们声明一个(个人支付)接口及其接口实现类 (被代理对象)
public interface toCPayment { void pay(); }
public class toCPaymentImpl implements toCPayment { @Override public void pay() { System.out.println("以用户的名义进行支付"); } }
然后我们对于(个人支付)接口的实现类定义一个类(代理对象)
public class AlipayToC implements toCPayment { toCPayment toCPayment; public AlipayToC(toCPayment toCPayment){ this.toCPayment = toCPayment; } @Override public void pay() { beforPay(); toCPayment.pay(); afterPay(); } private void afterPay() { System.out.println("支付给慕课网"); } private void beforPay() { System.out.println("从招行取款"); } }
这时候我们去调用支付方法
public class ProxyDemo { public static void main(String[] args) { toCPayment toCProxy = new AlipayToC(new toCPaymentImpl()); toCProxy.pay(); } }
执行结果:
从招行取款
以用户的名义进行支付
支付给慕课网
-----------------------
上面这种代理模式是静态的代理模式,如果我们要添加多一个(企业支付)接口,即使支付前后的方法一样,只是里头的pay()方法改变,那么我们又要重新按照刚刚的流程走一步。这样也是相对比较麻烦的
这种模式的思路是没有问题的,只是相关的实现方式需要改进。
寻求改进:
在改进前我们重新回顾一下ClassLoader(类加载器)
溯源ClassLoader
通过带有包名的类来获取对应class文件的二进制字节流
根据读取的字节流,将代表的静态存储结构转化为(方法区的)运行时数据结构
生成一个代表该类的Class对象,作为方法区该类的数据访问入口
在复习完ClassLoader之后呢,我们可以根据ClassLoader对切入点进行改进:
根据一定规则去改动或者生成新的字节流,将切面逻辑织入其中(既然字节流能定义类的行为,我们将切面逻辑织入其中,使其动态生成天生织入了切面逻辑的类。)
行之有效的方案就是取代被代理类的动态代理机制
根据接口或者目标类,计算出代理类的字节码并加载到JVM中去
SpringAOP的实现原理之JDK动态代理
JDK1.3之后,java就为我们引入了JDK的动态代理机制
静态代理,需要在编译前实现,编译完成后,代理类是一个实际的Class文件
JDK动态代理,是在运行时动态生成的,编译完成后并没有实际的Class文件,而是在运行时动态生成类的字节码,并加载到JVM中(要求【被代理的类】必须实现接口)(并不要求【代理对象】去实现接口,所以可以复用代理对象的逻辑)
(采用JDK动态代理相比静态代理,逻辑可复用)
其中涉及两个比较重要的类:
java.lang.reflect.InvocationHandler:并非代理类,只是用来统一管理横切逻辑的Aspect。
java.lang.reflect.Proxy:真正的代理类是由Proxy创建出来的。该类最关键的方法就是newProxyInstance方法,该方法最终会返回创建好的动态代理实例。
代码演示:
首先创建管理横切逻辑的,将被包装类通过构造函数传递进来。
public class AlipayInvocationHandler implements InvocationHandler { private Object targetObject; public AlipayInvocationHandler(Object targetObject){ this.targetObject = targetObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforPay(); Object result = method.invoke(targetObject,args); afterPay(); return result; } private void afterPay() { System.out.println("支付给慕课网"); } private void beforPay() { System.out.println("从招行取款"); } }
创建工具类,去创建出真正的Proxy
public class jdkDynamicProxyUtil { public static <T>T newProxyInstance(T targetObject, InvocationHandler handler){ ClassLoader classLoader = targetObject.getClass().getClassLoader(); Class<?>[] interfaces = targetObject.getClass().getInterfaces(); return (T)Proxy.newProxyInstance(classLoader,interfaces,handler); } }
最后main方法调用
public class ProxyDemo { public static void main(String[] args) { toCPayment toCPayment = new toCPaymentImpl();//首先创建被代理的类 InvocationHandler handler = new AlipayInvocationHandler(toCPayment); toCPayment toCProxy = jdkDynamicProxyUtil.newProxyInstance(toCPayment,handler); toCProxy.pay(); ToBPayment toBPayment = new ToBPaymentImpl(); InvocationHandler handlerToB = new AlipayInvocationHandler(toBPayment); ToBPayment toBProxy =jdkDynamicProxyUtil.newProxyInstance(toBPayment,handlerToB); toBProxy.pay(); } }
SpringAOP的实现原理之CGLIB动态代理
cgLib代码生成库:Code Generation Library
不要求被代理类实现接口
内部主要封装了ASM java字节码操控框架(转化字节码产生新类)
动态生成子类以覆盖非final的方法,绑定钩子回调自定义拦截器
CGLib库中,和动态代理最相关的就是net.sf.cglib.proxy.Proxy类。
(1)在net.sf.cglib.proxy包下有个类叫做Callback,这个类就是一个比较核心的底层接口了,看名字就知道该类是用来提供回调实现了,该类只是一个空接口(标记),主要干活的是MethodInterceptor类
(2)MethodInterceptor类中有一个intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)方法,方法的前三个参数跟JDK动态代理的invork方法一样
1.动态代理对象 2.被代理的方法实例 3.被代理方法实例里需要的参数数组。第四个参数则是动态代理Method的对象
(3)有了上面这些类,还需要个核心类来创建出最终代理对象(Enhancer),该类的核心方法create(),既可以用动态的方式调用,也可以通过静态(static)的方式调用。
代码演示:
首先我们创建被代理对象类:
public class CommonPayment { public void pay(){ System.out.println("个人或者公司都可以走这个支付通道"); } }
然后创建切面Aspect类
public class AlipayMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { beforPay(); Object result = methodProxy.invokeSuper(o,args); afterPay(); return result; } private void afterPay() { System.out.println("支付给慕课网"); } private void beforPay() { System.out.println("从招行取款"); } }
然后针对cglib创建一个工具类
public class CglibUtil { public static <T>T createProxy(T targetObject, MethodInterceptor methodInterceptor){ return (T)Enhancer.create(targetObject.getClass(),methodInterceptor); } }
最后main方法执行
public static void main(String[] args) { //CGLib动态代理 CommonPayment commonPayment = new CommonPayment(); MethodInterceptor methodInterceptor = new AlipayMethodInterceptor(); CommonPayment commonPaymentProxy = CglibUtil.createProxy(commonPayment,methodInterceptor); commonPaymentProxy.pay(); //哪怕是切换了代理对象也不需要对应的去创建接口 toCPayment toCPayment = new toCPaymentImpl(); toCPayment toCProxy = CglibUtil.createProxy(toCPayment,methodInterceptor); toCProxy.pay(); }
总结:
实现机制:
JDK动态代理:基于反射机制实现,要求业务类必须实现接口
CGLIB:基于ASM机制实现,生成业务类的子类作为代理类
优势:
JDK动态代理:
JDK原生,在JVM里运行较为可靠
平滑支持JDK版本的升级
CGLIB:
被代理对象无需实现接口,能实现代理类的无侵入
在SpringAOP中的底层机制中靠的就是CGLIB和JDK动态代理共存
默认策略:Bean实现了接口则使用JDK,否则使用CGLIB
AspectJ框架
AspectJ框架是Eclipse托管给apache基金会的一个开源项目,提供了完整的AOP解决方案。AspectJ是AOP的java实现版本,定义了AOP的语法,可以说是对java的扩展。
AspectJ提供了两套强大的机制:
1.定义切面语法以及切面语法的解析机制
2.提供了强大的织入工具
Spring-AOP与AspectJ框架 所支持的织入点:
如图所示,我们可以看出SpringAOP只支持方法上的织入点,而AspectJ可以说是支持了所有的连接点(所以我们可以称AspectJ是一套AOP的完整解决方案)
既然AspectJ这么强大,为什么Spring没有完整复用呢。因为在方法上的织入已经可以满足大多数的业务,而且在AspectJ的学习与使用上也远比Spring复杂。
SpringAOP的脉络分析图: