什么是AOP?

  • AOP(面向切面编程)通过预编译的方式 和 运行期动态代理的方式来实现程序功能统一维护的一种方式,是OOP(面向对象编程)的延续。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度减低,提高程序的可重用性,同时提高了开发效率。

    • OOP 针对业务处理过程的实体及其属性和行为进行抽象封装,以获取更加清晰高效的逻辑单元划分

    • AOP利用的是一种横切技术,解剖开封装的对象内部,并将哪些影响多个类的公共行为封装到一个可重用模块,这就是所谓的Aspect方面/切面。所谓的切面,简单点所说,就是将哪些与业务无关,却为业务模块所共同调用的行为(方法)提取封装,减少系统的重复代码,以达到逻辑处理过程中各部分之间低耦合的隔离效果

  • AOP采取横向抽取机制,取代了传统的纵向继承体系重复性代码

  • 通过 横切 技术,AOP把软件系统分为两部分:核心关注点横切关注点。业务处理的主要流程是核心关注点(比如:增删改查等. . .),与之关系不大的是横切关注点(比如:权限认证,日志记录等. . .)。横切关注点的一个特点是:它们经常发生在核心关注点的多处,且各处都基本相似。如在增删改操作后,进行日志记录 . . .

AOP能做什么?

  • 权限认证
  • 日志记录
  • 性能统计
  • 事务处理
  • 异常处理
  • . . .

AOP相关术语

  • Joinpoint(连接点):连接点是指可以被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,不提供属性的连接点,也就是说spring aop只能对方法层面进行增强

  • Pointcut(切入点):切入点是指我们要对哪些Joinpoint进行拦截的定义,即真正被拦截的点

  • Advice(增强 / 通知):通知是指拦截到Joinpoint之后要做的事情,如* 对 save方法进行权限认证,权限认证的方法称为通知。通知分为:前置通知、后置通知、异常通知、环绕通知、最终通知

  • Target(目标对象):包含Joinpoint连接点的对象,即被通知(增强)或被代理的对象(类)

  • Weaving(织入):织入是指把增强应用到Target目标对象来创建新的代理对象的过程,如* 将权限认证应用到UserSeviceImpl目标对象的save方法的这个过程

  • Proxy(代理):目标类被织入增强后,会产生一个结果代理类。在Spring中,AOP代理可以是JDK动态代理 或 CGLib代理,并且Spring可以智能识别什么时候使用JDK动态代理,什么时候使用CGLib代理

  • Aspect(切面):切面是指切入点 和 通知的组合

    在执行增删改操作后,都可进行日志记录,所以add、remove 和 edit方法都是JoinPoint连接点。由于需求是在执行删除操作后进行日志记录,所以remove方法是Pointcut切入点,日志记录的方法称为Advice通知。UserServiceImpl类被应用了增强,所以UserServiceImpl被称为Target目标对象。将日志记录应用到UserServiceImpl的remove方法的这个过程被称为Weaving织入。UserServiceImpl目标类被应用增强(通知)后,会产生一个代理类,被称为Proxy。

前面我们已经了解了AOP的基本慨念、AOP相关术语,那么下面就了解一下AOP的底层实现。

Spring AOP底层实现

  • JDK动态代理

  • CGLib代理

在了解AOP底层实现之前,我们回顾一下前面讲过的一句话:AOP采取横向抽取机制,取代了传统的纵向继承体系重复性代码,那么什么是横向抽取,什么是纵向继承呢?

举一个例子,有100个UserDao类,每个类中都有一个save方法,而我们要求在每个save方法执行之前都进行权限校验,那么我们该怎么做呢?

最初,我们是在每个类中都写一个权限校验的方法,然后在save方法中直接调用改方法。或者是直接将权限校验的代码写在save方法中。

但在每一个类中都写一遍权限校验的代码,这样就会显得很麻烦,代码开发量过大,且难以维护。

所以在这时候,人们想到了一个解决办法,编写一个通用的类,并将权限校验的方法放入改类中,然后让这些UserDao类取继承该通用类,然后直接在save方法中调用权限校验的方法。这样代码就显得整洁很多,而且更利于后期的维护。这就是前面提到的 纵向继承

紧接着,在AOP出现后,就通过AOP来进行解决,那么AOP是怎么解决的呢?

AOP采取横向抽取机制,其实就是代理机制,对目标类产生一个代理类,然后再代理类中对目标方法进行增强。

AOP的底层实现有两种代理机制,JDK动态代理 和 CGLib代理。下面我们就先以JDK动态代理为例讲解。

JDK动态代理

由于JDK动态代理只能对实现了接口的类产生代理对象,所以这里先定义接口:

package com.cd4356.spring_aop.jdkProxy;public interface UserDao1 {void save();void delete();
}
package com.cd4356.spring_aop.jdkProxy;public interface UserDao2 {void save();void delete();
}

让目标类实现定义的接口:

package com.cd4356.spring_aop.jdkProxy;public class UserDaoImpl1 implements UserDao1 {public void save() {System.out.println("保存用户1");}public void delete() {System.out.println("删除用户1");}
}
package com.cd4356.spring_aop.jdkProxy;public class UserDaoImpl2 implements UserDao2 {public void save() {System.out.println("保存用户2");}public void delete() {System.out.println("删除用户2");}
}

定义一个类,实现InvocationHandler接口,然后调用Proxy类的newProxyInstance方法来创建代理对象。

newProxyInstance方法有三个参数:ClassLoader loader用来指明生成代理对象使用的类加载器;Class<?>[ ] interfaces 用来指明目标类实现的所有接口;InvocationHandler h 用来指明产生的这个代理对象要做的事情。

实现InvocationHandler接口后需要重写invoke方法,代理类调用的所有方法实际上都是通过invoke方法调用的目标方法,invoke方法内部实现了两个逻辑:一个是增强逻辑,一个是执行目标方法。通过method.getName()获取当前调用的是代理对象的哪个方法,如果该方法是要被增强的方法,则进行增强,否则直接执行。

package com.cd4356.spring_aop.jdkProxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 创建JDK动态代理类*/
public class JdkProxy implements InvocationHandler {private Object target;/*** @param target 将目标对象作为参数传入进来*/public JdkProxy(Object target) {this.target = target;}/*** 创建代理* @return*/public Object createProxy(){/*** 通过newProxyInstance方法产生代理对象* @param loader 通过object.getClass().getClassLoader()获取目标对象的类加载器* @param interfaces 通过object.getClass.getClassInterfaces()获取目标对象实现的所有接口* @param h 获取实现的InvocationHandler接口实例* @return 返回指定目标对象的代理对象*/Object proxy = (Object) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);return proxy;}/*** 调用目标类中的所有方法都相当于调用invoke方法* @param proxy 代理对象* @param method 目标方法* @param args 执行方法需要的参数* @return method.invoke(target,args)* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result;/*** 如果目标对象中方法的方法名为save,就进行权限校验,然后执行该方法* 如果方法名不为save,直接执行该方法*/if ("save".equals(method.getName()) || "delete".equals(method.getName())){System.out.println("权限校验. . .");//执行目标方法result = method.invoke(target,args);System.out.println("后置通知. . .\n");return result;}return method.invoke(target,args);}
}
package com.cd4356.spring_aop.jdkProxy;import org.junit.Test;public class SpringDemo {@Testpublic void demo1(){UserDao1 userDao1 =new UserDaoImpl1();// 将目标类对象传入产生代理对象UserDao1 proxy1 = (UserDao1) new JdkProxy(userDao1).createProxy();// 调用代理类的方法proxy1.save();proxy1.delete();UserDao2 userDao2 =new UserDaoImpl2();// 产生代理对象UserDao2 proxy2 = (UserDao2) new JdkProxy(userDao2).createProxy();// 调用代理类的方法proxy2.save();proxy2.delete();}
}

CGLib代理

AOP底层实现的另一种代理机制 CGLib代理。

CGLib代理不要求目标类一定要实现了接口,它采用非常底层的字节码技术,可以为一个类创建子类,从而解决无接口代理问题。

package com.cd4356.spring_aop.cglibProxy;public class UserService {public void save() {System.out.println("保存用户1 . . .");}}

( 使用CGLib代理,需引入cglib依赖 或 spring相关依赖 )

package com.cd4356.spring_aop.cglibProxy;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;/*** 创建JDK动态代理类*/
public class CGLibProxy implements MethodInterceptor {private Object object;/*** @param object 将目标对象作为参数传入进来*/public CGLibProxy(Object object) {this.object = object;}/*** 创建代理* @return*/public Object createProxy(){// 1、创建核心类Enhancer enhancer = new Enhancer();// 2、设置父类enhancer.setSuperclass(object.getClass());// 3、设置回调enhancer.setCallback(this);// 4、生成代理Object proxy = enhancer.create();return proxy;}/*** 方法内部实现了两个逻辑,一个是增强逻辑 ,一个是执行目标方法。* @param proxy 代理对象* @param method 目标方法* @param args 执行方法需要用到的参数* @param methodProxy 代理方法* @return methodProxy.invokeSuper(proxy,args)* @throws Throwable*/public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Object result;/*** 如果目标对象中方法的方法名为save,就进行权限校验,然后执行该方法* 如果方法名不为save,直接执行该方法*/if ("save".equals(method.getName()) || "delete".equals(method.getName())){System.out.println("权限校验. .");//执行目标方法result = methodProxy.invokeSuper(proxy,args);System.out.println("后置通知. .\n");return result;}return methodProxy.invokeSuper(proxy,args);}
}
package com.cd4356.spring_aop.cglibProxy;import org.junit.Test;public class SpringDemo {@Testpublic void demo(){UserService userService =new UserService();// 将目标类对象传入产生代理对象UserService proxy = (UserService) new CGLibProxy(userService).createProxy();// 调用代理类的方法proxy.save();}
}

总结:

  • Spring在运行期,生成动态代理对象,不需要特殊的编译器

  • Spring AOP的底层,是通过JDK动态代理 和 CGLib动态代理技术,为目标对象执行横向织入

  • Spring AOP非常智能,它可以根据目标对象是否实现了接口,自动选择使用哪种代理方式(默认使用JDK动态代理)

    • 如果目标对象实现了接口,Spring使用JDK的java.lang.reflect.Proxy类代理

    • 如果目标对象没有实现接口,Spring使用CGLib库生成目标对象的子类

  • 程序中应优先对接口创建代理,便于程序的解耦 和 维护

  • 标记为final的方法,不能被代理(因为方法被final修饰后,不能被重写)

    • JDK动态代理,是针对接口生成子类,接口中的方法不能被final修饰

    • CGLib代理,是针对目标类产生子类,因此类和方法不能使用final修饰

  • Spring只支持方法的连接点,不提供属性的连接点,即Spring AOP仅对方法层面进行增强

Spring AOP底层实现原理(动态代理)相关推荐

  1. 【Spring AOP】AOP 底层实现原理 —— 动态代理类的创建(JDK、CGlib)、工厂如何加工原始对象

    AOP编程 AOP 编程 AOP 概念 AOP 编程的开发步骤 切面的名词解释 AOP 的底层实现原理 动态代理类的创建 JDK 的动态代理(原理 + 编码) CGlib 的动态代理 Spring 工 ...

  2. 【Spring】AOP底层实现原理 —— 动态代理类的创建(JDK、CGlib)、工厂加工原始对象

    一.AOP概念 AOP (Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发 以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建 ...

  3. Spring 容器AOP的实现原理——动态代理

    本文来自极客学院 Spring 容器AOP的实现原理--动态代理 之前写了一篇关于IOC的博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大 ...

  4. Spring AOP底层实现- JDK动态代理和CGLIB动态代理

    Spring AOP是运行时织入的,那么运行时织入到底是怎么实现的呢?答案就是代理对象. 代理又可以分为静态代理和动态代理. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前, ...

  5. Spring 从入门到精通 (十六) AOP底层如何创建动态代理类

    关键词:Spring | AOP | 创建代理类 | 底层 本专栏通过理论和实践相结合,系统学习框架核心思想及简单原理,原创不易,如果觉得文章对你有帮助,点赞收藏支持博主 ✨ 目录 一.创建对象三要素 ...

  6. 菜鸟学SSH——Spring容器AOP的实现原理——动态代理

    之前写了一篇关于IOC的博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到 ...

  7. Spring AOP的两种动态代理方式的原理和实现(JDK和CGLIB)

    代理机制 Spring 的AOP 部分使用使用JDK动态代理,部分使用CGLIB来为目标对象创建代理.如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理:如果目标对象没有实现任何接口,则会 ...

  8. Spring AOP底层实现原理

    1.spring的AOP底层是由 JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)实现. 2.JDK动态代理:Jdk动态代理只针对于接口操作. 3.CGLIB:可以针对没有接口的java ...

  9. mybatis接口中的方法重载_MyBatis底层实现原理: 动态代理的运用

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:祖大俊 my.oschina.net/zudajun/blog/666223 一日小 ...

最新文章

  1. 为什么平头哥做芯片如此迅猛?
  2. [Unity3D]总结使用Unity 3D优化游戏运行性能的经验
  3. R密度聚类之DBSCAN模型
  4. 浮点数内存表示---记录一道题目
  5. iOS---------- MBProgressHUD (1.0.0)的变动
  6. 整理了一份 Docker系统知识,从安装到熟练操作看这篇就够了 | 原力计划
  7. jquery/js 点击空白区域隐藏某一个层/元素
  8. java 内存调优_JVM内存模型以及性能调优
  9. Ubuntu下实现双屏独立切换
  10. 二叉搜索树+快速排序 查到文本中出现频率最多的100个词 【留学生作业】
  11. ODBC、OLE DB、 ADO的区别
  12. 三角矩阵的逆矩阵怎么求_「线性代数」求可逆矩阵P,使得相似矩阵对角化
  13. php实现 三角形_HTML纯CSS绘制三角形(各种角度)
  14. Foxmail中配置Gmail实现gmail客户端收(转)
  15. Beaglebone Black 和树莓派
  16. 微信小程序的注册流程
  17. 历年苹果秋季发布会产品
  18. 大学如何开始学编程?
  19. 连自律都做不到 还谈什么梦想
  20. 名创优品在香港上市:市值170亿港元 10元店也有大生意

热门文章

  1. 小汤学编程之JavaScript学习day05——DOM、事件
  2. 跟ASP.NET MVC一起使用jQuery
  3. 大一的一些心得与近况
  4. 技能拓展笔记-React(一)
  5. 替换Jar包中的一个文件 Replace a file in a JAR
  6. UICamera(NGUI Event system)原理
  7. Flutter TextField 设置默认值和光标位置
  8. Mybatis 插入数据后返回自增主键ID
  9. Mr.J-- jQuery学习笔记(二十五)--监听DOM加载
  10. Jmeter plugins 之 Perfmon Metrics Collector(服务器性能监控)