ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【Spring基础系列5】Spring AOP基础(上)

【Spring基础系列5】Spring AOP基础(上) 主要讲解AOP基础知识然后附加简单的示例。前言学习Spring AOP时开始以为AOP的知识会很多但是和IOC知识比起来发现少很多但是如果放到一篇文章感觉还是有点长就分上-下两个部分来讲解这块知识。这两天更新的有点慢感觉身体不太好可能是早上起太早了效率不太高周末会好好休息一下把状态调整过来。关于AOPAOP概述如果说 IoC 是 Spring 的核心那么面向切面编程就是 Spring 最为重要的功能之一了。面向切面编程AOP和面向对象编程OOP类似也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架它的使用有效减少了系统间的重复代码达到了模块间的松耦合目的。AOP 的全称是“Aspect Oriented Programming”即面向切面编程它将业务逻辑的各个部分进行隔离使开发人员在编写业务逻辑时可以专心于核心业务从而提高了开发效率。AOP 采取横向抽取机制取代了传统纵向继承体系的重复性代码其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。目前最流行的 AOP 框架有两个分别为 Spring AOP 和 AspectJSpring AOP 使用纯 Java 实现不需要专门的编译过程和类加载器在运行期间通过代理方式向目标类植入增强的代码。AspectJ 是一个基于 Java 语言的 AOP 框架从 Spring 2.0 开始Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言提供了一个专门的编译器在编译时提供横向代码的植入。面向切面编程首先在面向切面编程的思想里面把功能分为核心业务功能和周边功能所谓的核心业务比如登陆增加数据删除数据都叫核心业务所谓的周边功能比如性能统计日志事务管理等等周边功能在 Spring 的面向切面编程AOP思想里即被定义为切面。在面向切面编程AOP的思想里面核心业务功能和切面功能分别独立进行开发然后把切面功能和核心业务功能 编织 在一起这就叫AOP。AOP能够将那些与业务无关却为业务模块所共同调用的逻辑或责任例如事务处理、日志管理、权限控制等封装起来便于减少系统的重复代码降低模块间的耦合度并有利于未来的可拓展性和可维护性。AOP基础知识基础概念为了更好地理解 AOP就需要对 AOP 的相关术语有一些了解这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect它们的含义如下表所示。上面的讲解有点抽象我们再进一步进行讲解切面(Aspect)Aspect 声明类似于 Java 中的类声明事务管理是AOP一个最典型的应用。在AOP中切面一般使用 Aspect 注解来使用在XML中可以使用 aop:aspect 来定义一个切面。连接点(Join Point)一个在程序执行期间的某一个操作就像是执行一个方法或者处理一个异常。在Spring AOP中一个连接点就代表了一个方法的执行。通知(Advice)在切面中(类)的某个连接点(方法)采取的动作会有四种不同的通知方式around(环绕通知)before(前置通知)after(后置通知) exception(异常通知)return(返回通知)。许多AOP框架包括Spring将建议把通知作为为拦截器并在连接点周围维护一系列拦截器。切入点(Pointcut)表示一组连接点通知与切入点表达式有关并在切入点匹配的任何连接点处运行(例如执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心Spring默认使用AspectJ切入点表达式语言。介绍(Introduction)introduction可以为原有的对象增加新的属性和方法。例如你可以使用introduction使bean实现IsModified接口以简化缓存。目标对象(Target Object)由一个或者多个切面代理的对象。也被称为切面对象。由于Spring AOP是使用运行时代理实现的因此该对象始终是代理对象。AOP代理(AOP proxy)由AOP框架创建的对象在Spring框架中AOP代理对象有两种JDK动态代理和CGLIB代理织入(Weaving)是指把增强应用到目标对象来创建新的代理对象的过程它(例如 AspectJ 编译器)可以在编译时期加载时期或者运行时期完成。与其他纯Java AOP框架一样Spring AOP在运行时进行织入。什么还是有点蒙圈可以看看下面这位大神的总结言简意赅如果还不懂那楼哥也没办法了。切入点Pointcut在哪些类哪些方法上切入where通知Advice在方法执行的什么实际when:方法前/方法后/方法前后做什么what:增强的功能切面Aspect切面 切入点 通知通俗点就是在什么时机什么地方做什么增强织入Weaving把切面加入到对象并创建出代理对象的过程。由 Spring 来完成通知的分类前置通知(Before Advice): 在目标方法被调用前调用通知功能相关的类org.springframework.aop.MethodBeforeAdvice后置通知(After Advice): 在目标方法被调用之后调用通知功能相关的类org.springframework.aop.AfterReturningAdvice返回通知(After-returning): 在目标方法成功执行之后调用通知功能异常通知(After-throwing): 在目标方法抛出异常之后调用通知功能相关的类org.springframework.aop.ThrowsAdvice环绕通知(Around): 把整个目标方法包裹起来在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptorAOP织入的三种时期编译期: 切面在目标类编译时被织入这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面的。类加载期: 切面在目标类加载到 JVM 时被织入这种方式需要特殊的类加载器( ClassLoader )它可以在目标类引入应用之前增强目标类的字节码。运行期: 切面在应用运行的某个时期被织入。一般情况下在织入切面时AOP容器会为目标对象动态创建一个代理对象Spring AOP 采用的就是这种织入方式。AOP简单示例学习语言最好的方式就是实战其它的介绍我们就先不进行直接看一个具体的使用示例我们先定义一个实现类Component(customerDao) public class CustomerDaoImpl { public void add() { System.out.println(添加客户...); } public void update() { System.out.println(修改客户...); } public void delete() { System.out.println(删除客户...); } public void find() { System.out.println(修改客户...); } }然后再定义一个切面类Component public class MyAspect { // 前置通知 public void myBefore(JoinPoint joinPoint) { System.out.println(前置通知方法名称: joinPoint.getSignature().getName()); } // 后置通知 public void myAfterReturning(JoinPoint joinPoint) { System.out.println(后置通知方法名称 joinPoint.getSignature().getName()); } // 环绕通知 public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println(环绕开始); // 开始 Object obj proceedingJoinPoint.proceed(); // 执行当前目标方法 System.out.println(环绕结束); // 结束 return obj; } // 异常通知 public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println(异常通知出错了); } // 最终通知 public void myAfter() { System.out.println(最终通知); } }下面这个非常重要就是在applicationContext.xml文件中增加相应配置!-- 用于注解扫描 -- context:component-scan base-packagecom.java.spring.aop.xml2 / context:component-scan base-packagecom.java.spring.aop.customer / !-- 使切面开启自动代理 -- aop:aspectj-autoproxy/aop:aspectj-autoproxy !--AOP 编程 -- aop:config aop:aspect refmyAspect !-- 配置切入点通知最后增强哪些方法 -- aop:pointcut expressionexecution (* com.java.spring.aop.customer.*.* (..)) idmyPointCut / !--前置通知关联通知 Advice和切入点PointCut -- aop:before methodmyBefore pointcut-refmyPointCut / !--后置通知在方法返回之后执行就可以获得返回值returning 属性 -- aop:after-returning methodmyAfterReturning pointcut-refmyPointCut returningreturnVal / !--环绕通知 -- aop:around methodmyAround pointcut-refmyPointCut / !--抛出通知用于处理程序发生异常可以接收当前方法产生的异常 -- !-- *注意如果程序没有异常则不会执行增强 -- !-- * throwing属性用于设置通知第二个参数的名称类型Throwable -- aop:after-throwing methodmyAfterThrowing pointcut-refmyPointCut throwinge / !--最终通知无论程序发生任何事情都将执行 -- aop:after methodmyAfter pointcut-refmyPointCut / /aop:aspect /aop:config这里必须要开启“使切面开启自动代理”否则AOP不生效因为我都是通过注解扫描自动注入所以不要忘记“用于注解扫描”中的内容因为我的CustomerDaoImpl和MyAspect分别放到xml2和customer这两个包下面所以需要扫描这两个包。对于下面这个我估计很多同学看到这个第一时间处于蒙圈状态execution (* com.java.spring.aop.customer.*.* (..))这个其实是Spring AOP表达式的写法任意公共方法的执行 execution(public * *(..)) ##public可以省略, 第一个* 代表方法的任意返回值 第二个参数代表任意包类方法 ..表示任意参数 任何一个以“get”开始的方法的执行 execution(* get*(..)) UserService接口的任意方法 execution(* com.einblatt.service.UserService.*(..)) 定义在com.einblatt.service包里的任意方法的执行 execution(* com.einblatt.service.*.*(..)) #第一个 .* 代表任意类, 第二个 .* 代表任意方法 定义在service包和所有子包里的任意类的任意方法的执行 execution(* com.einblatt.service..*.*(..)) # ..* 代表任意包或者子包 定义在com.einblatt包和所有子包里的UserService类的任意方法的执行 execution(* com.einblatt..UserService.*(..))我们再回顾一下上面的这个表达式第一个“星号”表示任意返回值com.java.spring.aop.custome是需要加强对象的包路径第二个“星号”表示这个包路径下所有的文件第三个“星号”表示文件中类的所有的方法最后的两个“..”表示可以是任意参数。编辑器不能直接打“*”符号会被当做排版符号我就用“星号”代替。对于applicationContext.xml文件中的其它内容我们配置了切面myAspect定义切面的切入点myPointCut然后针对该切入点配置了前置、后置、环绕、抛出异常和最终通知等功能所以我们执行切入点中的方法时都会通过切面中的方法进行增强下面的示例public class XMLTest { Test public void test() { ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml); CustomerDaoImpl customerDao (CustomerDaoImpl) applicationContext.getBean(customerDao); // 执行方法 customerDao.add(); } } // 输出 // 前置通知方法名称:add // 环绕开始 // 添加客户... // 最终通知 // 环绕结束 // 后置通知方法名称add我们的核心方法输出的是“添加客户...”这里AOP给我们增加了前置通知、环绕通知、最终通知和后置通知大家可以看一下调用顺序下面测试一下抛出通知我们修改CustomerDaoImpl内容如下Component(customerDao) public class CustomerDaoImpl { public void add() throws Exception { System.out.println(添加客户...); throw new Exception(抛出异常测试); } // ... }还是通过上面的示例测试输出结果如下前置通知方法名称:add 环绕开始 添加客户... 最终通知 异常通知出错了可以看到抛出异常加强输出“异常通知出错了”但是后面的“环绕结束”和“后置通知”就没有了。通过这个简单的示例大家应该多AOP有一个初步的了解这个示例其实是基于AspectJ的XML实现方式。其实JAVA中实现AOP的方式有很多种下面我们对现有的实现方式进行一个整体的介绍让大家有一个宏观的认识。AOP的两种实现方式AOP 采用了两种实现方式静态织入(AspectJ 实现)和动态代理(Spring AOP实现)静态织入(AspectJ 实现)又可以分为基于XML的声明式基于Annotation的声明式。上面的示例是AspectJ基于XML的声明式动态代理(Spring AOP实现)又可以分为JDK动态代理和CGLib 动态代理。所以准确来说实现AOP其实有4种实现姿势。AspectJAspectJ 是一个采用Java 实现的AOP框架它能够对代码进行编译(一般在编译期进行)让代码具有AspectJ 的 AOP 功能。AspectJ 是目前实现 AOP 框架中最成熟功能最丰富的语言。ApectJ 主要采用的是编译期静态织入的方式。在这个期间使用 AspectJ 的 acj 编译器(类似 javac)把 aspect 类编译成 class 字节码后在 java 目标类编译时织入即先编译 aspect 类再编译目标类。Spring AOP 实现Spring AOP 是通过动态代理技术实现的而动态代理是基于反射设计的。Spring AOP 采用了两种混合的实现方式JDK 动态代理和 CGLib 动态代理分别来理解一下JDK动态代理Spring AOP的首选方法。每当目标对象实现一个接口时就会使用JDK动态代理。目标对象必须实现接口CGLIB代理如果目标对象没有实现接口则可以使用CGLIB代理。因为文章篇幅原因AOP的4种实现姿势还有3种未给出示例我将会在下一篇文章中给出完整的示例描述。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进