写点什么

Spring 之 AOP

作者:andy
  • 2022-10-27
    北京
  • 本文字数:4495 字

    阅读完需:约 15 分钟

一、AOP


在整个项目开发中,业务层的业务处理会涉及到以下功能:


  • 操作日志的记录

  • 调用数据层的操作

  • 当数据层进行更新操作时,需要进行事务控制处理

  • 数据库的开启和关闭

  • 过滤器是否可以算作是 AOP 的应用呢?


最初的这些处理是交由代理模式实现的。但是因为静态代理模式自身的局限性,动态代理受限于硬编码,开发复杂,很难做出合理的扩展。为了解决切面以及代理模式的问题,spring 提出了 AOP 的设计思想。随着引入 AspectJ 框架技术,Spring 的 AOP 技术得到更好的发展,得到了更为合理的规划。


OOP 建立了对象层次结构,定义了从上到下的关系。但是对分散在各个层次中的水平代码,无法提供较好的处理。AOP 技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。


使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。


对于切入点,不需要进行硬编码,而是通过配置文件配置切入到需要处理的业务层上(即具体的业务层方法上)。借助于 AOP 技术,程序没有了明确的代理模式程序。


Spring 中的 AOP 就相当于一个超级大代理,比直接使用 Java 动态代理和 CGLIB 代理要简单许多。



AOP 配置说明


横切关注点

纵向执行程序中,哪些方法需要拦截,拦截之后做什么处理,这些方法可以称为横切关注点。


切面(Aspect)

横切关注点的模块化或者说是抽象化,事务管理就是 J2EE 应用中一个关于横切关注点的很好的例子。


切入点(Pointcut)

对连接点进行拦截的定义。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是 AOP 的核心:Spring 缺省使用 AspectJ 切入点语法。


连接点(Joinpoint)

程序纵向执行过程中某个特定具体的点,比如某方法调用的时候或者处理异常的时候。在 Spring AOP 中,一个连接点总是表示一个方法的执行。


通知(Advice)

切面的某个特定的连接点上执行的动作,其中包括了“around”、“before”和“after”等不同类型的通知。许多 AOP 框架(包括 Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。


引入(Introduction)

给一个类型,声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring 允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个 bean 实现 IsModified 接口,以便简化缓存机制。


目标对象(Target Object)

被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然 Spring AOP 是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。


AOP 代理(AOP Proxy)

AOP 框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在 Spring 中,AOP 代理可以是 JDK 动态代理或者 CGLIB 代理。


织入(Weaving)

把通知加入到切面涉及的目标对象上。这些可以在编译时(例如使用 AspectJ 编译器),类加载时和运行时完成。Spring 和其他纯 Java AOP 框架一样,在运行时完成织入。


切入点操作有以下几种主要方式:

  • 前置通知(Before Advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常),也就是在业务方法执行之前调用。

  • 后置通知(After Advice):在某连接点正常完成后执行的通知,也即在业务方法调用完成之后执行


  1. 后置返回通知(After Returning Advice)在业务方法执行完返回结果时调用

  2. 后置异常通知(After Throwing Advice):在方法抛出异常退出时执行的通知

  3. 后置最终通知(After Finally Advice):不管是否出现异常,都要在方法执行完后调用


  • 环绕通知(Round Advice):在业务方法执行前和执行后调用,包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。


面向切面编程的配置方法配置可以通过 xml 文件来进行,大概有四种方式:


1,配置 ProxyFactoryBean,显式地设置 advisors, advice, target 等

2,配置 AutoProxyCreator,这种方式下,还是如以前一样使用定义的 bean,但是从容器中获得的其实已经是代理对象

3,通过aop:config来配置

4,通过<aop: aspectj-autoproxy>来配置,使用 AspectJ 的注解来标识通知及切入点


二、AOP 实现


对于配置相关的信息,都在容器启动时设置好的了,之后的过程只是具体调用实现而已。


通过 ProxyFactory 提供的方法,根据配置切面下的 target 对象,通过 getProxy()方法来获取代理对象。target 对象如果是接口,则生成 JDKProxy 对象,反之,则生成 CGLIB 代理对象。


通过代理对象,获取可以应用到目标对象的方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行 joinpoint;如果没有,则直接反射执行 joinpoint。



三、AOP 配置


2.1、AOP 基础配置


定义 AOP 程序处理类


package org.fuys.own.test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Componentpublic class AOPPorxy {	private Logger logger = LoggerFactory.getLogger(AOPPorxy.class);	public void sayHiBefore(){		logger.info("This is complexible world. You need to pay your attention to yourself.");	}	public void sayGoodByeAfter(){		logger.info("Time to say goodbye.");	}}
复制代码


配置 applicationContext.xml 文件


  • 配置 aop 规范


xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"
复制代码


  • 将编写的 aop 程序处理方法映射到业务处理类的方法上


<aop:config>		<aop:pointcut expression="execution(public * org.fuys.own..*.*(..))" id="defaultPointcut"/>		<aop:aspect ref="AOPProxy">			<aop:before method="sayHiBefore" pointcut-ref="defaultPointcut"/>			<aop:after method="sayGoodByeAfter" pointcut-ref="defaultPointcut"/>		</aop:aspect>	</aop:config>
复制代码


定义 AOP 切点的表达式语法使用 AspectJ 的格式要求,结构 execution(public * org.fuys.own..*.*(..))组成解释如下:


  • execution():指的是切入点的语法定义

  • 内部组成为:execution(修饰符? 返回值类型 操作类型? 名称(参数) 异常)

  • 修饰符一般使用 public、private,可以省略

  • *:方法的返回值,*号表示任意的数据类型

  • org.fuys.own..:切入点匹配的包名称 org..own..:类似 org.fuys.own.vo.

  • *.*:表示类中的所有方法,格式:类.方法,“*.*”表示所有类中的所有方法

  • (..):描述参数


问题:


程序曾经运行正常,但是出现了类找不到的异常?

则可是尝试 eclipse 重新配置 JRE 系统库。


2.2、AOP 深入配置


2.2.1、前置通知的参数传递


需要注意的是,切点的方法定义有参数,这样才可进行前置通知的参数传递,犹如拦截器。配置文件如下:


<aop:before method="sayHiBeforeWithArg" pointcut="execution(public * org.fuys.own..*.*(..)) and args(id)" arg-names="id"/>
复制代码


2.2.2、后置返回通知


方法执行结束后,获取方法放回值。


<aop:after-returning method="sayGoodBysAfterReturning" pointcut-ref="defaultPointcut" returning="v" arg-names="v"/>
复制代码


2.2.3、后置异常通知


当出现异常时,该方式执行。


<aop:after-throwing method="sayAfterthrowing" pointcut-ref="defaultPointcut" throwing="e" arg-names="e"/>
复制代码


2.2.4、环绕通知


环绕通知中将由开发者手工控制代码的具体调用,可以在调用前后进行自己的处理。一个环绕通知就相当于之前配置的前置通知和后置返回通知了,非常方便。


Java 类


	/**	 * 环绕通知	 * @param point 传递参数的对象	 * @throws Throwable 	 */	public void arroundAdvice(ProceedingJoinPoint point) throws Throwable{		// 通过ProceedingJoinPoint对象进行参数的传递		logger.info("++++++++ args is " + Arrays.toString(point.getArgs()));		Object obj = point.proceed(point.getArgs());		logger.info("Result is " + obj);	}
复制代码


配置文件信息


<aop:around method="arroundAdvice" pointcut-ref="defaultPointcut"/>
复制代码


2.3、基于 Annotation 的 AOP 配置


配置文件添加 AOP 的 Annotation 支持


<aop:aspectj-autoproxy/>
复制代码


Java 类



package org.fuys.own.test;import java.util.Arrays;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Componentpublic class AOPAnnotation { private Logger logger = LoggerFactory.getLogger(AOPAnnotation.class); @Before(value="execution(public * org.fuys.own..*.*(..))") public void sayBefore(){ logger.info(" ****** before advice ****** "); } @Before(value="execution(public * org.fuys.own..*.*(..)) and args(id)",argNames="id") public void sayBeforeWithArg(Object obj){ logger.info(" -----> before advice with arg <----- " + obj); } @After(value="execution(public * org.fuys.own..*.*(..))") public void sayAfter(){ logger.info(" ****** after advice ****** "); } @AfterReturning(value="execution(public * org.fuys.own..*.*(..))",returning="r",argNames="r") public void sayAfterReturning(Object obj){ logger.info(" -----> after returning advice <----- " + obj); } @AfterThrowing(value="execution(public * org.fuys.own..*.*(..))",throwing="e",argNames="e") public void sayAfterthrow(Exception e){ logger.error(" >>>> after throwing <<<<" + e.toString()); } /** * 环绕通知 * @param point 传递参数的对象 * @throws Throwable */ @Around("execution(public * org.fuys.own..*.*(..))") public void sayArroundAdvice(ProceedingJoinPoint point) throws Throwable{ // 通过ProceedingJoinPoint对象进行参数的传递 logger.info("++++++++ args is " + Arrays.toString(point.getArgs())); Object obj = point.proceed(point.getArgs()); logger.info("Result is " + obj); }}
复制代码


问题:AOP 可以做什么?


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
Spring之AOP_andy_InfoQ写作社区