AOP

1.引言

我们知道,J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统?通过对J2EE容器运行机制的分析,我们可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。这些功能机制是每个应用系统都需要的,而且其设计开发有一定难度,同时对运行的稳定性和快速性要求颇高,必须经过长时间调试和运行经验积累而成,因此可以从具体应用系统中分离出来,形成一个通用的框架平台(J2EE容器服务器产品),如Tomcat、JBoss、Websphere、WebLogic等。从J2EE系统划分为J2EE容器和J2EE应用系统两个方面,我们已经看到一种分散关注的思路(separation of concerns)。

分散关注:将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。AOP就是这种实现分散关注的编程方法,它将“关注”封装在“切面”中。

2.AOP与OOP的关系

AOP是面向切面的编程,但它具体的含义是什么?AOP出来之后是不是要取代原来的OOP? --> AOP是Aspect Oriented Program的首字母缩写,是面向切面编程的意思。我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去(给不同的类设计不同的方法),这在软件设计中往往称为职责分配。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。但是人们也发现,在分散了代码的同时也增加了代码的重复性:比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容(也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来)。有人也许会说,那好办啊,我们可以将这段代码写一个独立的类独立的方法里,然后再在这两个类中调用,但是这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。从技术上来说,AOP基本上是通过代理机制实现的

3.概述

AOP是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP算是这种目标的一种实现。举例:某一个操作在各个模块中都有涉及,这个操作就可以看成“横切”存在于系统当中。在许多情况下,这些操作都是与业务逻辑相关性不强或者不属于逻辑操作的必须部分,而面向对象的方法很难对这种情况做出处理。AOP则将这些操作与业务逻辑分离,使程序员在编写程序时可以专注于业务逻辑的处理,而利用AOP将贯穿于各个模块间的横切关注点自动耦合进来。AOP的核心思想就是将应用程序中的业务逻辑处理部分同对其提供支持的通用服务(即所谓的“横切关注点”)进行分离,这些“横切关注点”贯穿了程序中的多个纵向模块的需求。

使用AOP机制进行开发,首先要将需求分解成一般关注点和横切关注点(即将核心模块级的业务关注点和系统级的横切关注点分离);然后各自独立的实现这些关注点;最后用工具(即proxy)将业务逻辑代码和横切关注点代码编织到一起,形成最终的程序。

4.AOP与Spring

Spring框架中所提供的AOP支持,是基于动态Proxy实现的,在目标对象的方法调用前后插入相应的处理代码。AOP代理可以是基于JDK动态代理,也可以是基于CGLIB代理。Spring默认使用的是基于JDK动态代理实现,这样任何的接口都能被代理。基于Spirng框架的应用程序开发,程序员会有一种自然的倾向性来实现面向接口编程而不是类,业务对象通常也是实现一个或者多个接口,这也是一种良好的编程习惯。Spring也可以基于CGLIB实现AOP代理,这样所代理的是类而不是接口。如果一个业务对象没有实现某一个接口,那么CGLIB将被使用。

5.名词解释

(1)切面(aspect):横切多个对象形成的横切关注点。如日志记录/事务管理/权限管理。由Spring的advisor、struts2的interceptor、servlet的filter实现。

(2)通知(advice):切面的实际实现。advice在pointcut插入到应用系统中。许多AOP框架包括Spring都是以拦截器/拦截器链做通知模型。通知类型主要有:

Around通知:包围一个pointcut的通知,这是最强大的通知。around通知在方法调用前后完成自定义的行为。

Before通知:在一个pointcut之前执行的通知。

AfterReturning通知:在pointcut正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。

Throws通知:在方法抛出异常时执行的通知。

(3)切入点(Pointcut):advice被插入的地点(即拦截点,我们可以在拦截点前后插入advice)。

Spring只支持方法拦截,也就是说,只能在方法的前后进行拦截,而不能在属性前后进行拦截;

Spring支持四种切入点(拦截类型):目标方法调用前(前置拦截的类必须实现MethodBeforeAdvice接口,实现其中的before方法),目标方法调用后(后置拦截的类必须实现AfterReturningAdvice接口,实现其中的afterReturning方法),目标方法调用前后(前后拦截的类必须实现MethodInterceptor接口,实现其中的invoke方法。前后拦截是唯一可以控制目标方法是否被真正调用的拦截类型,也可以控制返回对象。而前置拦截或后置拦截不能控制,它们不能印象目标方法的调用和返回),以及目标方法抛出异常(throw)。Spring拦截器的缺点:不能对于特定方法进行拦截,而只能对某个类的全部方法作拦截。

PointCut的定义相当于更加细化地规定了哪些方法被哪些拦截器所拦截,而并非所有的方法都被所有的拦截器所拦截。在ProxyFactoryBean的属性中,interceptorNames属性的对象详细定义了 PointCut和Advice的对应关系,比如常见的基于名字的切入点匹配(NameMatchMethodPointcutAdvisor类)和基于正则表达式的切入点匹配(RegExpPointcutAdvisor类)。这些切入点都属于”静态切入点“,因为他们只在代理创建的时候被创建一次,而不是每次运行都创建。

PointCut的概念是AOP的关键,使AOP区别于其它使用拦截的技术。PointCut使Advice独立于OO的层次选定目标,转而更细致的在method层面选定目标。例如,提供声明式事务管理的around通知可以被应用到跨越多个对象的一组方法上。

(4)目标对象(Target Object):被通知(被代理)对象。

(5)代理对象(Proxy Object):将advice应用到目标对象后创建的对象。

(6)织入(Weaving):将切面应用到目标对象从而创建一个代理对象的过程。

6.举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。使用传统的编程习惯,我们会创建一个接口,所有的访问类继承这个接口,如下:

  1. public interface BaseServlet {
  2.   abstract void locked();
  3.   abstract void accessDataObject();
  4.   abstract void unlocked();
  5. }

缺点:accessDataObject()方法需要有“锁”状态之类的相关代码;继承于BaseServlet接口的每个类都要重复实现locked()和unlocked()方法,重复代码很多。

仔细研究这个应用的“锁”,它其实有下列特性:“锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作;“锁”行为其实是和具体访问类的主要功能可以独立、区分开来的;“锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:

 

因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect。在这个应用中,“锁”的aspect应该有以下职责:提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。

### AOP 概念及核心思想 Aspect-Oriented Programming (AOP) 是一种编程范式,旨在将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来[^1]。这些横切关注点通常包括日志记录、性能统计、事务管理、安全性检查等,它们会影响多个模块或类的功能,但在传统的面向对象编程中往往被分散地插入到业务代码中,导致代码重复和难以维护。AOP 提供了一种机制,允许开发者把这些横切关注点集中定义为独立的模块,称为“切面”(Aspect)。 #### 核心概念 以下是 AOP 的几个关键术语: - **切面(Aspect)**:切面是横切关注点的模块化实现,通常由通知(Advice)和切入点(Pointcut)组成。 - **通知(Advice)**:定义了切面在何执行以及执行的具体行为。常见的通知类型包括 `Before`(方法执行前)、`After`(方法执行后)、`Around`(环绕方法执行)等。 - **切入点(Pointcut)**:定义了切面在哪些连接点(Join Point)上生效。连接点通常是方法调用或异常抛出的位置。 - **连接点(Join Point)**:程序执行过程中的特定点,例如方法调用或对象实例化。 - **目标对象(Target Object)**:被切面所影响的对象。 - **理对象(Proxy)**:通过 AOP 动态创建的对象,用于拦截对目标对象的调用并应用切面逻辑。 ### AOP 的实现方式 AOP 可以通过静态织入(Static Weaving)或动态理(Dynamic Proxy)来实现。静态织入通常在编译完成,而动态理则在运行生成理对象[^2]。Spring AOP 使用动态理技术,在运行为需要增强的目标对象创建理对象,并在理对象上调用方法插入切面逻辑。 #### Spring AOP 的使用方法 以下是一个基于 Spring AOP 的简单示例,展示如何定义切面和应用通知: ```java import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { // 定义切入点,匹配所有以 "execute" 开头的方法 @Before("execution(* com.example.service.*.execute*(..))") public void logBeforeMethod() { System.out.println("Logging before method execution"); } } ``` 在这个例子中: - `@Aspect` 注解标识这是一个切面类。 - `@Before` 注解定义了一个前置通知,表示在指定的切入点之前执行。 - 切入点表达式 `execution(* com.example.service.*.execute*(..))` 表示匹配 `com.example.service` 包下所有以 `execute` 开头的方法。 ### 优势与应用场景 AOP 的主要优势在于能够降低代码耦合度,提高代码的可维护性和可扩展性[^3]。它适用于以下场景: - **日志记录**:在方法执行前后记录日志信息。 - **性能监控**:统计方法的执行间或资源消耗。 - **事务管理**:统一管理事务的开启、提交和回滚。 - **安全性检查**:验证用户权限或限制访问。 - **异常处理**:集中捕获和处理异常。 ### 注意事项 虽然 AOP 提供了强大的功能,但也需要注意以下几点: - 过度使用 AOP 可能会导致代码难以调试,因为切面逻辑可能隐藏在业务代码之外。 - 切入点表达式的复杂性可能增加维护难度。 - 动态理可能会引入一定的性能开销,尤其是在高频调用的场景下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值