AOP(Aspect-Oriented Programming,面向切面编程),能够说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。php
OOP引入封装、继承和多态性等概念来创建一种对象层次结构,用以模拟公共行为的一个集合。当咱们须要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP容许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码每每水平地散布在全部对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其余类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它致使了大量代码的重复,而不利于各个模块的重用。java
而AOP技术则偏偏相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的可操做性和可维护性。git
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;二是采用静态织入的方github
1. 链接点(Join point)面试
链接点是在应用执行过程当中可以插入切面的一个点。这个点能够是类的某个方法调用前、调用后、方法抛出异常后等。spring
2. 通知(Advice)编程
在特定的链接点,AOP框架执行的动做。后端
Spring AOP 提供了5种类型的通知:数组
3. 切点(Poincut)缓存
具体定位的链接点:上面也说了,每一个方法均可以称之为链接点,咱们具体定位到某一个方法就成为切点。
切点与链接点:切点和链接点不是一对一的关系,一个切点匹配多个链接点,切点经过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法做为链接点的查询条件。每一个类都拥有多个链接点,例如 ArithmethicCalculator类的全部方法实际上都是链接点。
4. 切面(Aspect)
切面由切点和通知组成,它既包括了横切逻辑的定义、也包括了链接点的定义。
5. 织入(Weaving)
织入描述的是把切面应用到目标对象来建立新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是若是目标类是接口,则使用JDK动态代理技术,不然使用Cglib来生成代理。
6. 引入(Introduction)
添加方法或字段到被通知的类。 Spring容许引入新的接口到任何被通知的对象。例如,你可使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有经过DelegatingIntroductionInterceptor来实现通知,经过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。
首先新建业务逻辑类,该类实现了基本的除法操做:
public class Calculator { //业务逻辑方法 public int div(int i, int j) { System.out.println("--------"); return i/j; } }
如今须要实现:在div()方法运行以前, 记录一下日志, 运行后也记录一下,运行出异常,也打印一下。
所以可使用AOP来完成日志的功能,新建日志切面类:
在定义切面类的时候,须要注意以下几点:
@Aspect
声明为切面类。@Aspect public class LogAspects { @Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))") public void pointCut(){}; //@before表明在目标方法执行前切入, 并指定在哪一个方法前切入 @Before("pointCut()") public void logStart(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法运行....参数列表是:{"+Arrays.asList(joinPoint.getArgs())+"}"); } @After("pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName()+"除法结束......"); } @AfterReturning(value="pointCut()",returning="result") public void logReturn(Object result){ System.out.println("除法正常返回......运行结果是:{"+result+"}"); } @AfterThrowing(value="pointCut()",throwing="exception") public void logException(Exception exception){ System.out.println("运行异常......异常信息是:{"+exception+"}"); } /*@Around("pointCut()") public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("@Arount:执行目标方法以前..."); Object obj = proceedingJoinPoint.proceed();//至关于开始调div地 System.out.println("@Arount:执行目标方法以后..."); return obj; }*/ }
有了以上操做, 咱们还须要将切面类和被切面的类, 都加入到容器中,注意须要加上@EnableAspectJAutoProxy
开启AOP。
/* * 日志切面类的方法须要动态感知到div()方法运行, * 通知方法: * 前置通知:logStart(); 在咱们执行div()除法以前运行(@Before) * 后置通知:logEnd();在咱们目标方法div运行结束以后 ,无论有没有异常(@After) * 返回通知:logReturn();在咱们的目标方法div正常返回值后运行(@AfterReturning) * 异常通知:logException();在咱们的目标方法div出现异常后运行(@AfterThrowing) * 环绕通知:动态代理, 须要手动执行joinPoint.procced()(其实就是执行咱们的目标方法div,), 执行以前div()至关于前置通知, 执行以后就至关于咱们后置通知(@Around) */ @Configuration @EnableAspectJAutoProxy public class Cap10MainConfig { @Bean public Calculator calculator(){ return new Calculator(); } @Bean public LogAspects logAspects(){ return new LogAspects(); } }
使用JoinPoint能够拿到相关的内容, 好比方法名, 参数
那么方法正常返回, 怎么拿方法的返回值呢?
那么若是是异常呢?定义
下面是测试程序,注意须要使用从IOC容器中取出Bean,不然直接new对象进行操做,AOP、没法生效。
从下面的运行结果能够看到,AOP生效,日志功能正常:
小结: AOP看起来很麻烦, 只要3步就能够了:
1, 将业务逻辑组件和切面类都加入到容器中, 告诉spring哪一个是切面类(@Aspect)
2, 在切面类上的每一个通知方法上标注通知注解, 告诉Spring什么时候运行(写好切入点表达式,参照官方文档)
3, 开启基于注解的AOP模式 @EableXXXX
Spring AOP的实现是基于动态代理,在介绍具体实现细节以前,本节先介绍动态代理的原理。
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制。在某些状况下,一个对象不适合或者不能直接引用另外一个对象,而代理对象能够在二者之间起到中介的做用(可类比房屋中介,房东委托中介销售房屋、签定合同等)。 所谓动态代理,就是实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个一个对象(不肯定性)。若是是本身写代理类的方式就是静态代理(肯定性)。
不少场景都是利用相似机制作到的,好比用来包装 RPC 调用、面向切面的编程(AOP)。
(动态)代理模式主要涉及三个要素:
实现方式: 实现动态代理的方式不少,好比 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其余的实现方式,好比利用字节码操做机制,相似 ASM、CGLIB(基于 ASM)、Javassist 等。 举例,常可采用的JDK提供的动态代理接口InvocationHandler来实现动态代理类。其中invoke方法是该接口定义必须实现的,它完成对真实方法的调用。经过InvocationHandler接口,全部方法都由该Handler来进行处理,即全部被代理的方法都由InvocationHandler接管实际的处理任务。此外,咱们常能够在invoke方法实现中增长自定义的逻辑实现,实现对被代理类的业务逻辑无侵入。
反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。经过反射咱们能够直接操做类或者对象,好比获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至能够运行时修改类定义。
代理模式的形式以下图所示:
代理模式最大的特色就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操做的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操做。Java动态代理其实内部也是经过Java反射机制来实现的,即已知的一个对象,而后在运行时动态调用其方法,这样在调用先后做一些相应的处理。
下面举例说明:
1. 静态代理
若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理 ,这种状况下的代理类一般都是咱们在Java代码中定义的。 一般状况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。
public interface Sell { void sell(); void ad(); }
Vendor的定义以下:
public class Vendor implements Sell{ @Override public void sell() { System.out.println("In sell method"); } @Override public void ad() { System.out.println("ad method"); } }
BusinessAgent的定义以下:
/** * 静态代理,经过聚合来实现,让代理类有一个委托类的引用便可。 * */ public class BusinessAgent implements Sell{ private Sell vendor; public BusinessAgent(Sell vendor) { this.vendor = vendor; } @Override public void sell() { // 一些业务逻辑 System.out.println("before sell"); vendor.sell(); System.out.println("after sell"); } @Override public void ad() { // 一些业务逻辑 System.out.println("before ad"); vendor.ad(); System.out.println("after ad"); } }
由上面的代码能够看到, 经过静态代理,一方面无需修改Vendor的代码就能够加入一些业务处理逻辑;另外一方面,实现了客户端与委托类的解耦。但这种静态代理的局限在于,必须在运行前编写好代理类,若是委托类的方法较多,在添加业务逻辑时的工做量较大,须要对每一个方法单独添加。
2. 动态代理
代理类在程序运行时建立的代理方式被成为 动态代理。 也就是说,这种状况下,代理类并非在Java代码中定义的,而是在运行时根据咱们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优点在于能够很方便的对代理类的函数进行统一的处理,而不用修改每一个代理类的函数。
一样仍是上面的例子,须要在委托类的每一个方法先后加入一些处理逻辑,在动态代理的实现中,首先须要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义以下:
/** * 调用处理程序 * 代理类对象做为proxy参数传入,参数method标识了咱们具体调用的是代理类的哪一个方法,args为这个方法的参数 */ public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args); }
中介类必须实现InvocationHandler接口,做为调用处理器”拦截“对代理类方法的调用。
public class DynamicProxy implements InvocationHandler { // obj为委托对象 private Object object; public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(object, args); System.out.println("after"); return result; } }
在使用时须要动态生成代理类,具体以下:
public class Main { public static void main(String[] args) { /* Static proxy */ Vendor vendor = new Vendor(); BusinessAgent businessAgent = new BusinessAgent(vendor); businessAgent.sell(); businessAgent.ad(); /* Dynamic proxy */ DynamicProxy inter = new DynamicProxy(new Vendor()); //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); // 获取代理实例sell /** * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) * throws IllegalArgumentException * loader:定义了代理类的ClassLoder; * interfaces:代理类实现的接口列表; * h:调用处理器,也就是咱们上面定义的实现了InvocationHandler接口的类实例. */ Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter)); // 经过代理类对象调用代理方法,实际上会转到invoke方法调用 sell.sell(); sell.ad(); } }
总结: 动态代理的原理就是,首先经过newProxyInstance方法获取代理类实例,然后咱们即可以经过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke方法,在invoke方法中咱们调用委托类的相应方法,而且能够添加本身的处理逻辑。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它容许咱们在运行时对字节码进行修改和动态生成。CGLIB经过继承方式实现代理。
来看示例,假设咱们有一个没有实现任何接口的类HelloConcrete:
public class HelloConcrete { public String sayHello(String str) { return "HelloConcrete: " + str; } }
由于没有实现接口该类没法使用JDK代理,经过CGLIB代理实现以下:
// CGLIB动态代理 // 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。 class MyMethodInterceptor implements MethodInterceptor{ ... @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { logger.info("You said: " + Arrays.toString(args)); return proxy.invokeSuper(obj, args); } } // 2. 而后在须要使用HelloConcrete的时候,经过CGLIB动态代理获取代理对象。 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(HelloConcrete.class); enhancer.setCallback(new MyMethodInterceptor()); HelloConcrete hello = (HelloConcrete)enhancer.create(); System.out.println(hello.sayHello("I love you!")); // 输出结果以下 // 日志信息: You said: [I love you!] // HelloConcrete: I love you!
经过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终经过调用create()方法获得代理对象,对这个对象全部非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里咱们能够加入任何逻辑,好比修改方法参数,加入日志功能、安全检查功能等;经过调用MethodProxy.invokeSuper()方法,咱们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。CGLIG中MethodInterceptor的做用跟JDK代理中的InvocationHandler很相似,都是方法调用的中转站。
CGLIB是经过继承(
如上面例子中的enhancer.setSuperclass(HelloConcrete.class)
)实现代理,因为final类型不能有子类,因此CGLIB不能代理final类型,遇到这种状况会抛出异常。
AOP的原理简单来说,利用动态代理,在IOC容器初始化时,建立Bean的代理类;在代理方法被调用时,代理类会拦截方法的调用,并在以前或者以后插入切面方法,以此实现AOP的目标。
接下来会从如下几方面深刻分析AOP的原理:
在以前使用AOP时,为了启用AOP,须要在配置类中,声明@EnableAspectJAutoProxy
的注解,这个注解的功能就是注册AnnotationAwareAspectJAutoProxyCreator
。下面具体分析这个组件是如何注册的。
@w=250
进入@EnableAspectJAutoProxy
的源码中,能够看到该类引入了AspectJAutoProxyRegistrar
,
@Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { //proxyTargetClass属性,默认false,采用JDK动态代理织入加强(实现接口的方式);若是设为true,则采用CGLIB动态代理织入加强 boolean proxyTargetClass() default false; //经过aop框架暴露该代理对象,aopContext可以访问 boolean exposeProxy() default false; }
在AspectJAutoProxyRegistrar
中, 能够看到实现了ImportBeanDefinitionRegistrar接口,这个接口以前也有介绍,能给容器中自定义注册组件。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { /** * Register, escalate, and configure the AspectJ auto proxy creator based on the value * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing * {@code @Configuration} class. */ @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } }
重点关注AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
,这一步将会注册AnnotationAwareAspectJAutoProxyCreator
。下面进入源码,
程序的逻辑很清晰,
综上分析,@EnableAspectJAutoProxy的功能就是,利用其中的AspectJAutoProxyRegistrar给咱们容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件,这是后续建立加强Bean的基础。
AnnotationAwareAspectJAutoProxyCreator的类层次结构以下图所示,
继承关系为:
其中的SmartInstantiationAwareBeanPostProcessor
是Bean的后置处理器,同时也实现了BeanFactoryAware
能够在容器初始化时,将beanFactory传进来进行相关操做。
由上述分析可知,AnnotationAwareAspectJAutoProxyCreator既具备BeanPostProcessor特色, 也实现了BeanFactoryAware接口,方便操做BeanFactory。
上面已经介绍了AOP过程当中,核心的AnnotationAwareAspectJAutoProxyCreator组件,接下来对整个AOP的流程进行梳理,主要分为以下4个步骤:
注册AnnotationAwareAspectJAutoProxyCreator
的BeanDefinition
建立AnnotationAwareAspectJAutoProxyCreator
,并加入到BeanFactory
利用AnnotationAwareAspectJAutoProxyCreator
拦截Bean的初始化,建立加强的Bean
加强Bean的调用过程
IOC容器初始化的入口是以下的refresh()函数,上面1,2,3步骤,分别发生在以下标出的3个函数中,下面分别对这三个函数进行详细介绍。
1. 注册AnnotationAwareAspectJAutoProxyCreator
的BeanDefinition
这一步主要是经过invokeBeanFactoryPostProcessors(beanFactory)
函数,添加AnnotationAwareAspectJAutoProxyCreator
的定义,最终调用的函数以下:
注册的组件类型为AnnotationAwareAspectJAutoProxyCreator.class,组件名称ATUO_PROXY_CREATOR_BEAN_NAME值为internalAutoProxyCreator。
下面是调用栈:
2. 建立AnnotationAwareAspectJAutoProxyCreator
,并加入到BeanFactory
这一步入口是registerBeanPostProcessors(beanFactory)
,进入该函数后,会跳转到以下的核心函数中进行beanPostProcess的实例化。注意到以前提到过,AnnotationAwareAspectJAutoProxyCreator
实现了BeanPostProcess接口,因此能够将其当成一个正常的后置处理器来进行实例化。
从下面的debug信息能够看到,在这一步中,容器须要实例化4个后置处理器,其中最后一个就是咱们关注的AnnotationAwareAspectJAutoProxyCreator
。
整个初始化后置处理器的流程,能够分为以下几步:
1)先获取ioc容器已经定义了的须要建立对象的全部BeanPostProcessor
3)优先注册实现了PriorityOrdered接口的BeanPostProcessor;
4)再给容器中注册实现了Ordered接口的BeanPostProcessor;
5)注册没实现优先级接口的BeanPostProcessor;
后置处理器AnnotationAwareAspectJAutoProxyCreator
实例化完成以后,在接下来的Bean的实例化过程当中,它会去尝试拦截Bean的初始化,若是有须要,则会建立代理加强后的Bean。
3. 利用AnnotationAwareAspectJAutoProxyCreator
拦截Bean的初始化,建立加强的Bean
在以前的例子中,定义了以下的切面类,实现了相关的advice方法。
这是Calculate类,就是须要加强的类。
@w=300
这一步中主要关注这两个Bean的实例化。
这一步的入口是refresh函数中的beanFactory.preInstantiateSingletons()
,下一步进入到getBean-->doGetBean
函数,
接着进入doGetBean-->createBean
函数,
接着进入到createBean
函数,会调用函数Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
试图直接返回proxy对象。
接下来首先分析这个函数,再分析以后正常的初始化流程。createBean
函数是理解整个AOP流程的核心。
进入到函数的实现,能够看到最后会去尝试调用类型为InstantiationAwareBeanPostProcessor
的后置处理器,因为AnnotationAwareAspectJAutoProxyCreator
实现了该接口,因此这个时候会被调用来试图返回proxy对象,可是一般状况下,加强bean不会在这里生成。
但并非说这个AnnotationAwareAspectJAutoProxyCreator
就没有做用,进入到该函数的实现,能够发如今shouldSkip
函数中会去找到全部的Advisor,也就是以前例子中的LogAspects
类,并把这些Advisor放到BeanFactory中,方便后续建立加强的Bean。
在获取到全部的Advisor以后,判断当前bean是否在advisedBeans中(保存了全部须要加强bean)
以及判断当前bean是不是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean,若是是的话就跳过。
回到createBean
函数,下面进入到正常的Bean初始化流程,一步步跟进到initializeBean函数中,能够看到在初始化Bean的先后都会调用对应的后置处理器来完成相应的功能,可是AbstractAutoProxyCreator的实现中,在初始化Bean以前,只是直接返回Bean;可是在初始化完Bean以后,会调用对应的后置处理器,也就是在applyBeanPostProcessorsAfterInitialization
函数中来建立加强的Bean。
下面对该函数进行仔细分析,
接着分析createProxy
函数的实现,下面省略了部分中间调用,在最后的实现中,createAopProxy会根据状况使用jdk代理或者CGLib,从代码中能够看到,当被代理类是接口或者是proxy类时,就会采用jdk动态代理,反之则采用CGLib。
之后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;
注意一点:在createAopProxy时,会判断config.isProxyTargetClass(),这个值默认为false。可是在两个地方进行设置,一个是EnableAspectJAutoProxy注解中,另外一个地方是在createProxy函数中,evaluateProxyInterfaces会去查找目标类的全部interface,若是可用的话,则将其加到proxyFactory中,不然,调用setProxyTargetClass,设置为true。在本例子中,calculate类没有相关接口,因此设置为true。这也是为何在createAopProxy函数中,会进行判断,而不是直接返回jdk动态代理的类。
4. 加强Bean的调用过程
上面对AOP流程进行了梳理,经过代码分析了如何代理生成加强的Bean。这部分介绍在调用加强Bean的方法时,proxy对象是如何拦截方法调用的。
当被加强的Bean在执行时,会进入到下面的拦截执行流程,
首先,根据ProxyFactory对象获取将要执行的目标方法拦截器链:List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
进一步跟进到实现,getInterceptorsAndDynamicInterceptionAdvice
的流程大体以下,主要是为了获取拦截链:
在获得拦截链以后,若是没有拦截器链,直接执行目标方法;若是有拦截器链,把须要执行的目标对象,目标方法,拦截器链等信息传入建立一个 CglibMethodInvocation 对象,并调用 mi.proceed();
来获取执行结果。
注意:拦截器链的触发过程是一个迭代的过程,
从下面的调用栈能够看到,全部的拦截器都会等待下一个拦截器调用完成后,再接着执行。
当在执行Before
方法时,会先执行完before定义好的方法,而后再去执行正常的方法体:
整个拦截的流程能够总结以下图所示:
下面对整个AOP实现流程进行总结:
a. 建立业务逻辑组件和切面组件
b. AnnotationAwareAspectJAutoProxyCreator拦截组件的建立过程
c. 组件建立完以后,判断组件是否须要加强,若是是,则将切面的通知方法,包装成加强器(Advisor);给业务逻辑组件建立一个代理对象(cglib);
a. 获得目标方法的拦截器链(加强器包装成拦截器MethodInterceptor)
b. 利用拦截器的链式机制,依次进入每个拦截器进行执行;
c. 效果:
正常执行:前置通知-》目标方法-》后置通知-》返回通知
出现异常:前置通知-》目标方法-》后置通知-》异常通知
以前介绍的都是标准的Spring AOP实现,经过在运行时对目标类加强,生成代理类。可是利用AspectJ一样能够实现加强,只是后者是编译时加强,并且与Spring框架没有关系,能够独立运行。
下面先简单介绍AspectJ的使用,而后将其与Spring AOP进行对比。
业务组件 SayHelloService package com.ywsc.fenfenzhong.aspectj.learn; public class SayHelloService { public void say(){ System.out.print("Hello AspectJ"); } }
须要来了,在须要在调用say()方法以后,须要记录日志。那就是经过AspectJ的后置加强吧。
LogAspect 日志记录组件,实现对SayHelloService 后置加强 public aspect LogAspect{ pointcut logPointcut():execution(void SayHelloService.say()); after():logPointcut(){ System.out.println("记录日志 ..."); } }
执行命令 ajc -d . SayHelloService.java LogAspect.java 生成 SayHelloService.class 执行命令 java SayHelloService 输出 Hello AspectJ 记录日志
ajc.exe 能够理解为 javac.exe 命令,都用于编译 Java 程序,区别是 ajc.exe 命令可识别 AspectJ 的语法;咱们能够将 ajc.exe 当成一个加强版的 javac.exe 命令.执行ajc命令后的 SayHelloService.class 文件不是由原来的 SayHelloService.java 文件编译获得的,该 SayHelloService.class 里新增了打印日志的内容——这代表 AspectJ 在编译时“自动”编译获得了一个新类,这个新类加强了原有的 SayHelloService.java 类的功能,所以 AspectJ 一般被称为编译时加强的 AOP 框架。
1. 从目标角度讲:
2. 织入(Weaving)
AspectJ利用了下面3种不一样的织入方法:
相比于AspectJ,Spring AOP利用了运行时织入(runtime weaving)。
经过动态织入,切面方法被动态的织入到程序的运行过程当中,一般有JDK动态代理或者CGLIB代理。
Spring AOP倾向于使用JDK动态代理,只要目标对象实现了至少一个接口,Spring将会采用JDK动态代理来建立加强的Bean。
若是目标方法没有实现接口,就会采用CGLIB来实现。
3. 链接点(Join point)
从设计的角度讲,Spring AOP经过代理模式来实现, 例如CGLIB建立目标类的子类(以下图的实例所示),再调父类的目标方法实现AOP。可是,一旦目标父类使用了关键字final,子类没法继承,切入就不能实现。所以不能代理final类型,一样的,也不能代理static方法,由于他们不能被重写。因此一般状况下,Spring AOP只支持方法做为链接点。
AspectJ没有这种限制,在编译期直接将加强方法织入到代码中,也不须要像Spring AOP那样继承目标方法,所以能够支持更多的链接点。
具体比较以下:
总结:
Spring AOP是基于代理的实现方式,在程序运行时建立代理,并经过拦截链来执行切面方法。AspectJ在编译期将切面方法织入到目标类,在运行期没有其余性能损耗,所以性能上相比Spring AOP会快不少。
下表是一个总体的对比:
参考:
本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,马上获取最新文章和价值2000元的BATJ精品面试课程。