AOP概述
- 在我们的日常开发中,除了正常业务逻辑外,还可能经常会需要在业务逻辑的特定位置加入日志,以便于调试和问题分析。但是这种插入日志的逻辑和业务逻辑间并不存在连续性和依赖性,这种逻辑侵入随着项目的不断发展,会导致项目越来越来臃肿,同时也更加难以管理。为了解决这个问题,优秀的前辈们推出了AOP(面向切面编程)理念以及很多优秀的AOP框架,其中比较有代表性的就AspectJ,AspectJ通过扩展java语言实现了AOP框架。当然我们的spring框架也实现了自己的AOP框架(Spring AOP),并在spring 2.0后完美的集成了ApectJ。
- AOP有很多种实现方式,在最早的时候AOP的实现还是通过静态代理的方式实现,例如AspectJ通过自己的ajc编译器在程序编译阶段编译生成静态代理类的Class文件以完成相关切面逻辑,不过这种方式不够灵活,每次有新的接口或逻辑需要使用切面功能都需要修改AspectJ中的切面配置,然后重新启动项目,无疑这是很烦的。随着技术的发展,我们有了更多更好的技术实现,这里介绍两只中常用AOP实现技术,也是我们spring中使用的:
(1)jdk动态代理:
java在jdk 1.3后引入了jdk动态代理,可以在运行期间为实现了某个接口的对象动态的是生成代理对象,它是在运行期间通过反射实现的,灵活性很好了,但是较编译生成Class文件的实现方式,性能差了些,另外所有需要进行代理的对象都必须要实现某个接口,这是动态代理最大的硬伤了。
(2)cglib动态字节码增强:
大家都知道,jvm的类加载器并不在乎class文件如何生成,只要是满足规范的class文件都能加载运行。一般我们的java应用程序都是通过javac编译生成class文件,但是只要我们满足class文件规范,我们完全可以使用cglib等字节码工具在程序运行期间动态的构建Class文件。在我们从本地的class文件加载类文件并构建对象时,可以使用cglib动态的生成该对象的代理对象。
静态代理与动态代理
AOP的实现是基于代理模式的,所以我们需要通过了解代理模式来学习AOP,接下来我来介绍下静态代理和动态代理的实现。
1. 静态代理:
我们可以看下下面的代码:
public interface Subject {String getName();
}public class RealSubject implements Subject {public String getName() {return ”东哥真帅!“;}
}public class Proxy implements Subject {private RealSubject realSubject;public Proxy(Subject realSubject){this.realSubject = realSubject;}public String getName() {return realSubject.getName() + ”东哥真是666!“;}
}public class Main {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy. getName();
}
我们可以看到真的是灰常简单,代理对象和原始对象都实现了Subject,代理对象引用了原始对象,并在接口调用时利用了realSubject的getName()方法,只不过在realSubject返回数据的基础上加了些文字,没错这就是最简单的静态代理。但这种模式存在一个致命的问题:getName()函数可能并不仅仅只有Subject接口实现了,其他接口可能也实现了这个函数,例如我再有个Topic接口,它也有这个getName()方法。那么当我的切面需要切到所有实现getName的函数时,我们还需要在为Topic接口实现一套类似上面的代码,这要搞下去的话累都累死了,为了解决这个问题,我们引出了动态代理。我们接下来看下基于jdk和cglib实现的动态代理。
2. 动态代理:
(1)jdk动态代理代码:
public class RequestInvocationHandler implements InvocationHandler {private Object target;public RequestInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("getName")){return method.invoke(target, args) + "东哥真是666!";}return null;}}Subject subject = (Subject) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Subject.class},new RequestInvocationHandler(new RealSubject()));subject.getName();Topic topic = (Topic) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Topic.class},new RequestInvocationHandler(new RealTopic()));topic.getName();
jdk动态代理的实现主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。我们可以看出jdk动态代理是靠实现特定接口,我们在平时开发中不可能所有的对象都实现特定的接口,cglib为我们解决了这个问题。
(2)cglib实现动态代理代码:
public class RequestInvocationHandler implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("getName")) {return methodProxy.invokeSuper(o, objects) + "东哥真是666!";}return null;}}Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Subject.class);enhancer.setCallback(new RequestInvocationHandler());Subject proxy = (Subject) enhancer.create();proxy.getName();
我们可以看到cglib通过继承目标对象,并覆写父类方法来实现动态代理,通过字节码生成技术在运行期间动态的自动帮我们构建代理对象。由于cglib采用继承覆写父类方法实现,所以也存在一定局限,当父类的方法为final、static、private类型时,cglib无法对方法进行代理,只能直接调用目标对象的方法。
AOP组件介绍
在具体了解整个AOP的运行流程前,我们先来看下AOP中几个比较重要的组件,当然这些术语都是参照ApectJ中的概念。
组件名 | 概念 |
---|---|
joinpoint | AOP功能模块需要织入的地方, 也有人称之为系统执行点,在ApectJ中有很多执行点类型,例如方法调用、方法执行、字段设置、类初始化等,但是在Spring Aop目前仅支持方法调用类型的切入点,不过已经能够我们大部分的业务开发了。 |
pointcut | pointcut就是joinpoint的表述方式,通过pointcut的表达式我们就可以寻找到满足条件joinpoint。 |
advice | advice是切入逻辑的载体,简单的说就是我们要织入的逻辑代码,他有很多种类型,包括Before Advice、After Advice等等,不过我们最常用的还是Around Advice,它同时包含了其他几种的功能。 |
aspect(advisor) | aspect是将joinpoint和pointcut模块化封装的AOP概念实体,在我们SpringAop中也称为advisor,且一般一个advisor仅包含一个joinpoint和一个pointcut。 |
weaver | weaver就是织入器,负责将逻辑织入到joinpoint的具体执行者。 |
target object | 被织入的对象 |
Spring AOP织入原理
我们先来看下伪代码:
// 新建织入者weaver
ProxyFactory weaver = new ProxyFactory(new Executable());// 新建根据接口名匹配的advisor(aspect)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 设置pointcut,在这里是需要织入的接口名,"execute"相当于pointcut
advisor.setMappedName("execute");
// 设置Advice,ExecutableAdvice中包含了代码逻辑
advisor.setAdvice(new ExecutableAdvice());// 将advisor传递给weaver
weaver.addAdvisor(advisor);
// weaver通过ProxyFactor获取代理类
Executable proxyObject = weaver.getProxy();proxyObject.execute();
上面的伪代码实际上已经揭示了AOP的完整流程:
(1)通过advisor包装pointcut和advice数据
(2)然后weaver利用advisor内的数据来获取代理对象
通过上面的逻辑分析,我们可以知道,对于SpringAOP的实现,最重要的有两点即pointcut和advice数据的获取和代理对象的生成,那我们接下来就分为这两部分着重讲解下。
(一)pointcut和advice数据的获取
其实上面的伪代码是spring早期的实现方式,如上面代码所示,当时的pointcut只能是方法名或者对象类型(支持正则表达式),而advice需要通过实现特定的接口来实现,advisor也是通过继承特定的系统类实现的,最后还需要将bean的信息以xml的形式保存起来以支持IoC。
当然现在没那么麻烦了,我们看下目前我们的使用方式(基于注解的代理):
@Aspect
@Component
class Aspect {@Pointcut("@annotation(com.xxx.xxx.xxx)")public void servicePointcut() {}@Around("servicePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {xxxxxxxxxxxxxxx;xxxxxxxxxxxxxxx;return xxx;}
}
现在我们有注解支持并且集成了AspectJ,可以使用AspectJ表达式,但底层的实现原理还是一样的。我们现在使用的weaver在注入到容器后,容器会自动寻找容器内所有标识了@Aspect的advisor(aspect)对象,获取到advisor(aspect)对象后,会通过反射获取@Pointcut内的表达式,然后通过AspectJ的类库解析生成pointcut对象,最后ProxyFactory便可以利用这些数据生成代理对象了,其实原理还是一样的,只是加了层封装,更加方便了我们的业务开发。
(二)代理对象的生成
接下来就是最重点的部分了,其实也很简单,需要大家有足够的IoC知识,可以看下我的IoC原理文章:深入了解spring IoC
在获取到了pointcut和advice数据后,weaver便开始了代理对象的构造了。我们在上面的代码中可以看到weaver的类型是ProxyFactory,ProxyFactory是最简单基本的一个织入器实现类,目前我们最经常使用的织入器实现类是AnnotationAwareAspectJAutoProxyCreator,不过实现原理都是一样的:都是基于BeanPostProcessor。我们在IoC原理中介绍过BeanPostProcessor,BeanPostProcessor可以干预Bean的实例化过程,它有点类似于责任链,任何一个bean的构建都需要经过这条链,只有执行完所有BeanPostProcessor后才会返回实例化的对象。AOP实现了BeanPostProcessor,他在目标对象实例化后利用反射或cglib生成了目标对象的动态代理对象(实现逻辑见上面:jdk动态代理代码实现/cglib实现动态代理),然后直接将代理对象返回给了使用方。这样使用方使用的便是经过动态代理后的对象,便实现了AOP的功能。很简单吧!🙃🙃🙃🙃