Spring思维导图,让Spring不再难懂(AOP 篇)

32次阅读

共计 6944 个字符,预计需要花费 18 分钟才能阅读完成。

转载自:1、微信公众号【Java 思维导图】2、若谷先生
一、什么是 AOP?
AOP(Aspect-Oriented Programming,面向方面编程),对 OOP(Object-Oriented Programming,面向对象编程)

【OOP 与 AOP】

概念

AOP(Aspect-Oriented Programming,面向方面编程)
OOP(Object-Oriented Programming,面向对象编程)

方向

OOP 定义从上到下的关系
AOP 定义从左到右的关系

【两个部分】

核心关注点
业务处理的主要流程

横切关注点

与业务主要流程关系不大的部分
经常发生在核心关注点的多处,而各处都是基本相似的功能
如权限认证、日志、事务处理

二、AOP 使用场景
1、AOP 框架种类

AspectJ
JBoss AOP
Spring AOP

2、使用 AOP 场景

性能监控:在方法调用前后记录调用事件,方法执行太长或超时报警。
缓存代理:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
软件破解:使用 AOP 修改软件的验证类的判断逻辑。
记录日志:在方法执行前后记录系统日志。
工作流系统:工作流系统需要将业务代码和流畅引擎代码混合在一起执行,那么可以使用 AOP 将其分离,并动态挂载业务。
权限验证:方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕获。

3、传统编码与 AOP 区别

三、核心概念
【术语】:
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是 J2EE 应用中一个关于横切关注点的很好列子。在 Spring AOP 中,切面可以使用基于模式或者基于 @Aspect 注解的方式来实现。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在 Spring AOP 中,一个连接点总是表示一个方法的执行。
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如:当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是 AOP 的核心:Spring 缺省使用 Aspect 切入点语法。
引入(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 框架一样,在运行时完成织入。
通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括“around”、“before”和“after”等不同类型的通知。许多 AOP 框架(包括 Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
【通知类型】
前置通知(Before advice):@Before 在某连接点之前执行的通知,但这个通知不能阻止了连接点之前的执行流程(除非它抛出一个异常)。
后置通知(After returning advice):@After 在某连接点正常完成后执行的通知,例如,一个方法么有抛出任何异常,正常返回。
异常通知(After throwing advice):@After-returning 在方法抛出异常退出时执行的通知。
最终通知(After(finally)advice):@After-throwing 当某连接点退出的时候执行的通知(不论是正常退出还是异常退出);
环绕通知(Around advice):@Around 包围一个连接点的通知,如方法调用,这是最强大的一种通知类型。
三、简单例子
1、基于注解的方式
@Aspectj
public class TransactionDemo {
@Pointcut(value=”execution(* com.yangxin.core.service.*.*.*(..))”)
public void point() { //…}

@Before(value=”point()”)
public void before() {
System.out.println(“transaction begin”);
}

@AfterReturning(value = “point()”)
public void after() {
System.out.println(“transaction commit”);
}

@Around(“point()”)
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(“transaction begin”);
joinPoint.proceed();
System.out.println(“transaction commit”);
}
}

在 applicationContext.xml 中配置:
<aop:aspectj-autoproxy />
<bean id=”transactionDemo” class=”com.yangxin.core.transaction.TransactionDemo” />

2、基于 XML 的方式
<aop:config>
<aop:aspect ref=”log”>
<aop:pointcut
expression=”(execution(* spring.ch3.topic1.Chief.*(..)))”
id=”chiefPointCut” />
<aop:before method=”washOven” pointcut-ref=”chiefPointCut” />
<aop:before method=”prepare” pointcut-ref=”chiefPointCut” />
<aop:after method=”after” pointcut-ref=”chiefPointCut” />
</aop:aspect>
</aop:config>

四、Spring AOP 原理

AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异:AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。
aop 开发时,其中需要参与开发的只有 3 个部分:

定义普通业务组件。
定义切入点,一个切入点可能横切多个业务组件。
定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。

五、两种动态代理方式
Spring 默认采取的动态代理机制实现 AOP,当动态代理不可用时(代理类无接口)会使用 CGlib 机制。Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。

1、JDK 动态代理

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。

2、CGLib 动态代理
CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。
六、补充实例

package springMVCmybatis.com.my.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;
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.springframework.core.annotation.Order;

@Aspect
@Order(3)
public class MyAopTest {
@Pointcut(“execution(* springMVCmybatis.addController.addEmp(..))”)
private void pointCutMethod() { //…}

@Pointcut(“execution(* springMVCmybatis.com.my.aop.UserServiceImp.*(..))”)
private void testAOP() { //…}

/**
* 声明前置通知,JoinPont 是 srpring 提供的静态变量,
* 通过 joinPoint 参数可以获得目标方法的类名,方法参数,方法名等信息,这个参数可有可无。
*/
@Before(“pointCutMethod() || testAOP()”)
public void doBefore(JoinPoint joinPoint) {
System.out.println(“@Before: 开始添加 –order=3”);
}

/**
* 声明后置通知,如果 result 的类型与 proceed 执行的方法返回的参数类型不匹配那么就不会执行这个方法
*/
@AfterReturning(pointcut = “pointCutMethod() || testAOP()”, returning = “result”)
public void doAfterReturning(String result) {
System.out.println(“@AfterReturning: 后置通知 –order=3”);
System.out.println(“—” + result + “—“);
}

/**
* 声明例外通知
*/
@AfterThrowing(pointcut = “pointCutMethod() || testAOP()”, throwing = “e”)
public void doAfterThrowing(Exception e) {
System.out.println(“@AfterThrowing: 例外通知 –order=3”);
System.out.println(e.getMessage());
}

/**
* 声明最终通知
*/
@After(“pointCutMethod() || testAOP()”)
public void doAfter() {
System.out.println(“@After: 最终通知 –order=3”);
}

/**
* 声明环绕通知
* 参数必须是 ProceedingJoinPoint,通过该对象的 proceed() 方法来执行目标函数,
* proceed() 的返回值就是环绕通知的返回值,proceedingJoinPoint 是个接口,
* implement JoinPoint, 所以也可以获得目标函数的类名,方法名等参数。
*/
@Around(“pointCutMethod() || testAOP()”)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(“@Around: 进入方法 — 环绕通知 –order=3”);
Object o = pjp.proceed();
System.out.println(“@Around: 退出方法 — 环绕通知 –order=3”);
return o;
}
}

七、切点表达式
1、方法签名表达式
execution(< 修饰符模式 >?< 返回类型模式 >< 方法所在类的完全限定名称模式 >(< 参数模式 >)< 异常模式 >?)
execution(modifiers-pattern? ret-type-pattern fully-qualified-class-name (param-pattern) throws-pattern?)
对比方法的定义来记忆,一个 java 方法的全部定义方式可以表示成下面的方式:
public String springMVCmybatic.com.my.aop.UserServiceImp(String a, int b) throw Exception{}

modifier-pattern?:表示方法的修饰符,可有可无;对应的就是 public
ret-type-pattern:表示方法的返回值;对应的就是 String
fully-qualified-class-name 方法所在类的完全限定名称;对应的就是 springMVCmybatic.com.my.aop.UserServiceImp
param-pattern:表示方法的参数;对应的就是 String a, int b
throws-pattern:表示方法抛出的异常,可有可无;对应的就是 throw Exception

2、&&,||,!(表达式之间可以采用与,或,非的方式来过滤。)
@Around(“pointCutMethod() || testAOP()”)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(“@Around: 进入方法 — 环绕通知 ”);
Object o = pjp.proceed();
System.out.println(“@Around: 退出方法 — 环绕通知 ”);
return o;
}

八、多个切点的执行顺序
上面的例子中,定义了 order=3,重新创建一个切面,定义 order=6,执行的结果是:
@Around: 进入方法 — 环绕通知 –order=3
@Before: 开始添加 –order=3
@Around: 进入方法 — 环绕通知 –order=6
@Before: 开始添加 –order=6
============ 执行业务方法 findUser, 查找的用户是:张三 =============
@Around: 退出方法 — 环绕通知 –order=6
@After: 最终通知 –order=6
@AfterReturning: 后置通知 –order=6
— 张三 —
@Around: 退出方法 — 环绕通知 –order=3
@After: 最终通知 –order=3
@AfterReturning: 后置通知 –order=3
— 张三 —

@Around: 进入方法 — 环绕通知 –order=3
@Before: 开始添加 –order=3
@Around: 进入方法 — 环绕通知 –order=6
@Before: 开始添加 –order=6
============ 执行业务方法 addUser=============
@After: 最终通知 –order=6
@AfterThrowing: 例外通知 –order=6
null
@After: 最终通知 –order=3
@AfterThrowing: 例外通知 –order=3
null
从结果中可以看出 order 越小越先执行,执行完了之后就 order 越小就越后推出。总结为下面的图:

正文完
 0