如果说 IOC 是 Spring 的核心,那么面向切面编程AOP就是 Spring 另外一个最为重要的核心@mikechen

AOP的定义

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。

面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:

AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

AOP的作用

AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

AOP的应用场景

比如典型的AOP的应用场景:

  • 日志记录
  • 事务管理
  • 权限验证
  • 性能监测

AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。

Spring AOP的术语

在深入学习SpringAOP 之前,让我们先对AOP的几个基本术语有个大致的概念。

AOP核心概念

Spring AOP 通知分类

Spring AOP 织入时期

Spring AOP三种使用方式

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

方式1:使用Spring自带的AOP

  1. public class LogAdvice implements MethodBeforeAdvice, AfterReturningAdvice,MethodInterceptor {
  2. @Override
  3. public void before(Method method, Object[] objects, Object target) throws Throwable {
  4. //前置通知
  5. }
  6.  
  7. @Override
  8. public void afterReturning(Object result, Method method, Object[] objects, Object target) throws Throwable {
  9. //后置通知
  10. }
  11. @Override
  12. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  13. //环绕通知
  14. //目标方法之前执行
  15. methodInvocation.proceed(); //目标方法
  16.  
  17. //目标方法之后执行
  18. return resultVal;
  19. }
  20. }

配置通知时需实现org.springframework.aop包下的一些接口

  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterReturningAdvice
  • 环绕通知:MethodInterceptor
  • 异常通知:ThrowsAdvice

创建被代理对象

  1. <bean id="orderServiceBean" class="com.apesource.service.impl.OrderServiceImpl"/>
  2. <bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/>

通知(Advice)

  1. <bean id="logAdviceBean" class="com.apesource.log.LogAdvice"/>
  2. <bean id="performanceAdviceBean" class="com.apesource.log.PerformanceAdvice"/>

切入点(Pointcut):通过正则表达式描述指定切入点(某些 指定方法)

  1. <bean id="createMethodPointcutBean" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
  2. <!--注入正则表达式:描述那些方法为切入点-->
  3. <property name="pattern" value=".*creat.*"/>
  4. </bean>

Advisor(高级通知) = Advice(通知) + Pointcut(切入点)

  1. <bean id="performanceAdvisorBean" class="org.springframework.aop.support.DefaultPointcutAdvisor">
  2. <!--注入切入点-->
  3. <property name="pointcut" ref="createMethodPointcutBean"/>
  4.  
  5. <!--注入通知-->
  6. <property name="advice" ref="performanceAdviceBean"/>
  7. </bean>

创建自动代理

  1. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  2. <!--Bean名称规则(数组):指定那些bean创建自动代理-->
  3. <property name="beanNames">
  4. <list>
  5. <value>*ServiceBean</value>
  6. <value>*TaskBean</value>
  7. </list>
  8. </property>
  9.  
  10. <!--通知列表:需要执行那些通知-->
  11. <property name="interceptorNames">
  12. <list>
  13. <value>logAdviceBean</value>
  14. <value>performanceAdvisorBean</value>
  15. </list>
  16. </property>
  17. </bean>

方式2:使用Aspectj实现切面(普通POJO的实现方式)

导入Aspectj相关依赖

  1. <!--aop依赖1:aspectjrt -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjrt</artifactId>
  5. <version>1.9.5</version>
  6. </dependency>
  7.  
  8. <!--aop依赖2: aspectjweaver -->
  9. <dependency>
  10. <groupId>org.aspectj</groupId>
  11. <artifactId>aspectjweaver</artifactId>
  12. <version>1.9.5</version>
  13. </dependency>

通知方法名随便起,没有限制

  1. public class LogAspectj {
  2. //前置通知
  3. public void beforeAdvice(JoinPoint joinPoint){
  4. System.out.println("========== 【Aspectj前置通知】 ==========");
  5. }
  6.  
  7. //后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知
  8. public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){
  9. System.out.println("========== 【Aspectj后置通知】 ==========");
  10. }
  11. public void afterAdvice(JoinPoint joinPoint){
  12. System.out.println("========== 【Aspectj后置通知】 ==========");
  13. }
  14.  
  15. //环绕通知
  16. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  17. System.out.println("##########【环绕通知中的前置通知】##########");
  18. Object returnVale = joinPoint.proceed();
  19. System.out.println("##########【环绕通知中的后置通知】##########");
  20. return returnVale;
  21. }
  22.  
  23. /**
  24. * 异常通知:方法出现异常时,执行该通知
  25. */
  26. public void throwAdvice(JoinPoint joinPoint, Exception ex){
  27. System.out.println("出现异常:" + ex.getMessage());
  28. }
  29.  
  30. }

使用Aspectj实现切面,使用Spring AOP进行配置

  1. <!--业务组件bean-->
  2. <bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/>
  3.  
  4. <!--日志Aspect切面-->
  5. <bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/>
  6.  
  7. <!--使用Aspectj实现切面,使用Spring AOP进行配置-->
  8. <aop:config>
  9. <!--配置切面-->
  10. <!--注入切面bean-->
  11. <aop:aspect ref="logAspectjBean">
  12. <!--定义Pointcut:通过expression表达式,来查找 特定的方法(pointcut)-->
  13. <aop:pointcut id="pointcut"
  14. expression="execution(* com.apesource.service.impl.*.create*(..))"/>
  15.  
  16. <!--配置"前置通知"-->
  17. <!--在pointcut切入点(serviceMethodPointcut)查找到 的方法执行"前",
  18. 来执行当前logAspectBean的doBefore-->
  19. <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
  20.  
  21. <!--配置“后置通知”-->
  22. <!--returning属性:配置当前方法中用来接收返回值的参数名-->
  23. <aop:after-returning returning="returnVal"
  24. method="afterReturningAdvice" pointcut-ref="pointcut"/>
  25. <aop:after method="afterAdvice" pointcut-ref="pointcut"/>
  26.  
  27. <!--配置"环绕通知"-->
  28. <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
  29.  
  30. <!--配置“异常通知”-->
  31. <!--throwing属性:配置当前方法中用来接收当前异常的参数名-->
  32. <aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/>
  33. </aop:aspect>
  34.  
  35. </aop:config>

方式3:使用Aspectj实现切面(基于注解的实现方式)

  1. //声明当前类为Aspect切面,并交给Spring容器管理
  2. @Component
  3. @Aspect
  4. public class LogAnnotationAspectj {
  5. private final static String EXPRESSION =
  6. "execution(* com.apesource.service.impl.*.create*(..))";
  7.  
  8. //前置通知
  9. @Before(EXPRESSION)
  10. public void beforeAdvice(JoinPoint joinPoint){
  11. System.out.println("========== 【Aspectj前置通知】 ==========");
  12. }
  13.  
  14. //后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知
  15. @AfterReturning(value = EXPRESSION,returning = "returnVal")
  16. public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){
  17. System.out.println("========== 【Aspectj后置通知】 ==========");
  18. }
  19.  
  20. //后置通知
  21. @After(EXPRESSION)
  22. public void afterAdvice(JoinPoint joinPoint){
  23. System.out.println("========== 【Aspectj后置通知】 ==========");
  24. }
  25.  
  26. //环绕通知
  27. @Around(EXPRESSION)
  28. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  29. System.out.println("##########【环绕通知中的前置通知】##########");
  30. Object returnVale = joinPoint.proceed();
  31. System.out.println("##########【环绕通知中的后置通知】##########");
  32. return returnVale;
  33. }
  34.  
  35. // 异常通知:方法出现异常时,执行该通知
  36. @AfterThrowing(value = EXPRESSION,throwing = "ex")
  37. public void throwAdvice(JoinPoint joinPoint, Exception ex){
  38. System.out.println("********** 【Aspectj异常通知】执行开始 **********");
  39. System.out.println("出现异常:" + ex.getMessage());
  40. System.out.println("********** 【Aspectj异常通知】执行结束 **********");
  41. }
  42.  
  43. }
  44. <!-- 自动扫描器 -->
  45. <context:component-scan base-package="com.apesource"/>
  46.  
  47. <!--配置Aspectj的自动代理-->
  48. <aop:aspectj-autoproxy/>

Spring AOP的实现原理

Spring的AOP实现原理其实很简单,就是通过动态代理实现的。

Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。

  • JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
  • CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

JDK动态代理

Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。

JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口。

我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。

如下源码所示:

  1. /**
  2. * 动态代理
  3. *
  4. * @author mikechen
  5. */
  6. public class JdkProxySubject implements InvocationHandler {
  7.  
  8. private Subject subject;
  9.  
  10. public JdkProxySubject(Subject subject) {
  11. this.subject = subject;
  12. }
  13.  
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16.  
  17. System.out.println("before 前置通知");
  18. Object result = null;
  19.  
  20. try {
  21. result = method.invoke(subject, args);
  22. }catch (Exception ex) {
  23. System.out.println("ex: " + ex.getMessage());
  24. throw ex;
  25. }finally {
  26. System.out.println("after 后置通知");
  27. }
  28. return result;
  29. }
  30. }

然后JDK动态代理需要使用的第二个组件就是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。

生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。

  1. public class Main { public static void main(String[] args) {
  2. //获取InvocationHandler对象 在构造方法中注入目标对象
  3. InvocationHandler handler = new JdkProxySubject(new RealSubject());
  4.  
  5. //获取代理类对象
  6. Subject proxySubject = (Subject)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Subject.class}, handler);
  7.  
  8. //调用目标方法
  9. proxySubject.request(); proxySubject.response();
  10.  
  11. }

运行结果:

  1. before 前置通知
  2. 执行目标对象的request方法......
  3. after 后置通知
  4. before 前置通知
  5. 执行目标对象的response方法......
  6. after 后置通知

JDK动态代理优缺

优点

JDK动态代理是JDK原生的,不需要任何依赖即可使用;

通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;

缺点

如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;

JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。

JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLib代理

CGLIB组成结构

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截,如下图所示Cglib与Spring等应用的关系:

  • 最底层的是字节码Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于ASM之上的是CGLIBGroovyBeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于CGLIBGroovyBeanShell之上的就是HibernateSpring AOP这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序

所以,Cglib的实现是在字节码的基础上的,并且使用了开源的ASM读取字节码,对类实现增强功能的。

以上

作者简介

陈睿|mikechen,10年+大厂架构经验,《BAT架构技术500期》系列文章作者,分享十余年BAT架构经验以及面试心得!

阅读mikechen的互联网架构更多技术文章合集

Java并发|JVM|MySQL|Spring|Redis|分布式|高并发|架构师

Spring AOP全面详解(超级详细)的更多相关文章

  1. Spring系列25:Spring AOP 切点详解

    本文内容 Spring 10种切点表达式详解 切点的组合使用 公共切点的定义 声明切点@Poincut @Poincut 的使用格式如下: @Poincut("PCD") // 切 ...

  2. Java注解最全详解(超级详细)

    Java注解是一个很重要的知识点,掌握好Java注解有利于学习Java开发框架底层实现.@mikechen Java注解定义 Java注解又称Java标注,是在 JDK5 时引入的新特性,注解(也被称 ...

  3. spring框架 AOP核心详解

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

  4. spring原理案例-基本项目搭建 02 spring jar包详解 spring jar包的用途

    Spring4 Jar包详解 SpringJava Spring AOP: Spring的面向切面编程,提供AOP(面向切面编程)的实现 Spring Aspects: Spring提供的对Aspec ...

  5. spring事务管理(详解和实例)

    原文地址: 参考地址:https://blog.csdn.net/yuanlaishini2010/article/details/45792069 写这篇博客之前我首先读了<Spring in ...

  6. Spring jar包详解

    Spring jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spri ...

  7. Spring——jar包详解(转)

    Spring——jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spr ...

  8. spring事务配置详解

    一.前言 好几天没有在对spring进行学习了,由于这几天在赶项目,没有什么时间闲下来继续学习,导致spring核心架构详解没有继续下去,在接下来的时间里面,会继续对spring的核心架构在继续进行学 ...

  9. (转)Spring JdbcTemplate 方法详解

    Spring JdbcTemplate方法详解 文章来源:http://blog.csdn.net/dyllove98/article/details/7772463 JdbcTemplate主要提供 ...

随机推荐

  1. 产品揭秘】来也Lead 2022产品亮点解读-RPA学习天地

    2022年4月26日,来也举行新品发布会.作为技术人员,花里胡哨的我且不说,我且说技术相关.整体架构"概念"整个平台覆盖了智能自动化的全生命周期包含:业务理解.流程创建.随处运行. ...

  2. Mesokurtic,Leptokurtic, Platykurtic介绍

    [原文地址1] [原文地址2] 简要介绍 这三个东西其实是把峰度(Kurtosis)分成了三种类别,峰度也就是评测一个分布的尾部与正态分布的尾部有多不同的定量测量值(如下图所示). 对于一个正态分布的 ...

  3. 拥抱Spring全新OAuth解决方案

    以下全文 Spring Authorization Server 简称为: SAS 背景 Spring 团队正式宣布 Spring Security OAuth 停止维护,该项目将不会再进行任何的迭代 ...

  4. CSS(九):background(背景属性)

    通过CSS背景属性,可以给页面元素添加背景样式. 背景属性可以设置背景颜色.背景图片.背景平铺.背景图像固定等. background-color(背景颜色) background-color属性定义 ...

  5. 【物联网串口服务器通信经验教程】Modbus网关协议转换

    在前面的文章中,我们已经详细地介绍了Modbus网关的几种主要类型,今天,就让我们来介绍一下其中简单协议转换的处理过程. 简单协议转换是最常规.最普遍的Modbus网关功能,也是数据处理效率最高Mod ...

  6. ExtJS 布局-Accordion布局(Accordion layout)

    更新记录: 2022年6月2日 开始. 2022年6月3日 发布. 1.说明 accordion(手风琴)布局一次仅显示一个子组件,内置支持 折叠 和 展开.当需要堆叠多个子组件,并每次只显示一次时, ...

  7. 慢到不能忍?别忍了,Ubuntu 21.10 APT 源修改为华为云镜像源

    更新记录 2022年4月15日:本文迁移自Panda666原博客,原发布时间:2021年3月29日. 2022年4月15日:将源改为华为云,华为云更方便.Ubuntu从20.04更新到21.10. 切 ...

  8. JS:表达式

    js代码的形式: 1.直接量 2.表达式 3.语句 1; "a"; true; null; var a; function fn(){}; b; var c = 20; var f ...

  9. 从零打造一个Web地图引擎

    说到地图,大家一定很熟悉,平时应该都使用过百度地图.高德地图.腾讯地图等,如果涉及到地图相关的开发需求,也有很多选择,比如前面的几个地图都会提供一套js API,此外也有一些开源地图框架可以使用,比如 ...

  10. Spring Boot:整合knife4j

    前言 这玩意就swagger的升级版,但是用起来比swagger舒服些,界面也看着好看. knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger- ...