AOP详解

xiaoxiao2021-02-27  354

什么是AOP?

在我们开发应用程序时,有多个服务不属于应用程序的主业务逻辑的组成部分,如登录、安全性和事务管理。 这些服务对Web 应用程序很重要,需要在应用程序的每个模块中实现。这些服务被视为横切关注点(次要业务逻辑),因为它们横跨应用程序的多个模块

通过在应用程序的多个模块间实现这些关注点,可能会在应用程序代码中出现以下复杂性:

横切关注点的代码在应用程序的多个模块间是重复的。如果需要对这些关注点进行任何更改,那么您需要更新实现这些关注点的每个模块。即使这些横切关注点已有单独的模块,您还需要编写代码以在每个模块中调用这些服务。应用程序的模块还包含与其核心功能(主业务逻辑)无关的代码。

下图显示了横切Web 应用程序中多个点的横切关注点:

日志记录、安全性和事务服务是CourseService、StudentService 和MiscService 模块的横切关注点。

Spring 提供面向方面编程(AOP) 来简化程序结构。 AOP 通过允许您在称为方面的独立模块中表达横切关注点来解决这些关注点问题。 应用程序的所有次要关注点(次要业务逻辑)都被视为方面。 方面将应用程序的次要逻辑与主业务逻辑隔离开。

为什么要使用AOP?

AOP 的以下功能简化了应用程序代码,使其易于理解:

它通过将次要逻辑与主逻辑隔离开增加了模块性。它为您提供了将诸如高速缓存、安全性、持久性和事务之类的横切关注点封装到模块中的优点。它真正地将这些模块与导致大量重用代码的核心应用程序分离开。您可以通过在Java 类中定义方法来实现方面。每个方法都由实现应用程序的次要工作的逻辑组成。

AOP:

使您能够在需要使用方面的应用程序中的各种位置调用这些方法。帮助您避免在应用程序中的多个位置编写相同的代码。能够轻松地删除先前定义的功能,而无需修改应用程序的主逻辑。在引入新功能时,应用程序中将不需要存在单元测试和其他测试阶段。

这些方面有以下好处:

每个关注点的逻辑位于一个位置,而不是分散在整个代码库中。修改程序逻辑变得容易。模块仅包含它们的主关注点的代码,因为它们的次要关注点被移到方面中。这简化了应用程序结构。

如何实现AOP?

了解应用程序中的方面后,您需要通过识别和创建以下组件来实现AOP:

通知连接点切入点目标代理织入

创建通知

通知: 是由方面在特定连接点所执行的操作。 通过创建表示应用程序的次要逻辑的函数来实现。 一个应用程序可以有一个或多个通知。 Spring 提供了以下通知类型:

前置返回后抛出后

前置通知: 在连接点之前执行。 除非抛出异常,它不能中断程序的执行。通过导入包含名为before()的方法的org.springframework.aop.MethodBeforeAdvice接口来实现。

此方法的语法是:

public void before(Method method, Object[] args,Object target ) throws Throwable

method表示通知将应用到的方法; args是类型为Object的数组,此数组包含在调用方法时传递给方法的参数; target表示方法调用的目标(即,对其调用方法的对象)。

返回后通知: 在连接点正常完成后执行。例如,方法在不抛出异常的情况下返回。 通过导入包含名为 afterReturning() 的方法的org.springframework.aop.AfterReturningAdvice接口来实现。 此 方法的语法是:

public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable

与before()方法的参数相似。 returnValue保存从被调用的方法返回的值。

抛出后通知: 在方法抛出异常后执行。 通过导入org.springframework.aop.ThrowsAdvice接口来实现。 此接口包含名为afterThrowing()的方法。 此方法的语法是:

public void afterThrowing(Throwable throwable)

afterThrowing()方法采用一个类型为Throwable的参数

可以通过以下方法创建通知:

使用Java 类使用配置元素

定义切入点

定义一个切入点来引用可以应用通知的位置 切入点:

有助于识别可以应用通知的方法。在Spring 配置文件中定义。可以指应用程序代码中的一个或多个位置。

模式:

指定用于确定通知可以应用到的特定位置或方法。包含将使用正则表达式调用的方法的名称。正则表达式提供灵活的方法来匹配文本的字符串,如特定字符、词语或字符模式。

Spring 使用正则表达式为定义切入点提供org.springframework.aop.support.JdkRegexpMet hodPointcut类。

该类有一个名为pattern的属性,其value属性指定特定 的模式,该模式引用可以应用通知的方法。您可以使用诸如星号(*) 之类的通配符字符来指定模式,它 表示任意类、方法、返回类型或参数。您可以使用双点字符(..) 来指定切入点的模式,该字符表示 零个或多个参数。

创建通知器

现在必须定义哪些通知需要应用到该切入点。 为此,创建将通知与切入点相链接的通知器。 通知器定义了要执行的操作(通知)以及要执行该操作的位置(切入点)。 Spring 提供了org.springframework.aop.support.DefaultPoint cutAdvisor类来创建通知器。

使用通知器bean 的advice和pointcut属性将通知与切入点耦合起来。

您还可以使用Spring 配置元素来定义切入点。 Spring 提供了元素来定义切入点。 此元素包含名为expression的属性,它用于定义执行连接 点方法的表达式。 定义切入点表达式的语法是: expression = “execution(regular_ expression)” execution匹配需要作为连接点执行的方法。 regular_expression是确定需要执行哪个方法的切入点

例如:

<aop:config> <aop:aspect ref=”sj”><aop:pointcut id=”pointcut” expression=”execution(* book*())”> <aop:before method=”authenticate” pointcut-ref=”pointcut” /> <aop:before method=”checkSeatsAvailability” pointcut-ref=”pointcut” /> <aop:after-returning method=”updateSeats” pointcut-ref=”pointcut” /> <aop:after-throwing method=”rewardGift” pointcut-ref=”pointcut” /> </aop:pointcut> </aop:aspect> </aop:config>

execution(* book*())表达式指定为名称以book开头的方法定义切入点。使用pointcut-ref属性可在多个通知元素间引用切入点。


创建代理

创建通知器后,您需要创建这样的代理,它:

调用通知器

截取对调用对象的调用

代理:

充当通知、目标对象、连接点、切入点和通知器之间的链接。在applicationContext.xml 应用程序上下文文件中创建为bean。

为创建代理bean,Spring 提供org.springframework.aop.config.ProxyFactoryBean类。

您需要将以下参数传递给ProxyFactoryBean类的构造函数以创建代理bean:

target 指定您想要代理的目标对象。interceptorNames 表示类型为String的数组。它指定已在Spring 配置文件中定义的通知器bean 名称的列表。proxyInterfaces 表示类型为String的数组。它指定定义应用程序的主逻辑的界面的名称。

现在,您需要调用代理,如以下代码段所示:

ApplicationContext ctx=new ClassPathXmlApplicationContext("bean.xml"); BookTicketInterface perf = (BookTicketInterface)ctx.getBean("bookProxy"); perf.book("zhang","101");

指定bean.xml是Spring 配置文件,它包含所有bean 定义。 BookTicket类调用在Spring 配置文件中声明的bookProxy代理bean。

实例:用Spring AOP实现机票预订应用程序的次要业务逻辑

机票预订应用程序的SecondaryJob类执行以下功 能: 验证用户。 检查座位可用性。 更新订票后的可用座位数。 奖励礼券(如果机票的价格为1000 美元或以上)。

步骤1:创建方面 ,次要业务逻辑

参考示例: SecondaryJob.java

package book; public class SecondaryJob { /** * 验证用户 * @param user */ public boolean authenticate(String user) { System.out.println("验证用户"); return true; } /** * 检查座位的有效性 * @param seatNo * @return */ public boolean checkSeatsAvailability(String seatNo) { System.out.println("检查座位的有效性"); return true; } /** * 更新座位信息 * @param seatNo * @return */ public boolean updateSeats(String seatNo) { System.out.println("更新座位信息"); return true; } /** * 兑换礼券 * @param seatNo * @return */ public boolean rewardGift(String seatNo) { System.out.println("兑换礼券"); return true; }

步骤2:创建通知 SecondaryJobAdvice.java

package book; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; public class SecondaryJobAdvice implements AfterReturningAdvice, MethodBeforeAdvice { public SecondaryJob secondary; public void setSecondary(SecondaryJob secondary) { this.secondary = secondary; } @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // TODO Auto-generated method stub secondary.authenticate(arg1[0].toString()); secondary.checkSeatsAvailability(arg1[1].toString()); } @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { // TODO Auto-generated method stub secondary.updateSeats(arg2[0].toString()); secondary.rewardGift(arg2[0].toString()); }

步骤3:配置通知 bean.xml

<!--方面, 次要业务逻辑方法bean --> <bean id="sj" class="book.SecondaryJob"></bean> <!-- 通知 --> <bean id="secondaryJobAdvice" class="book.SecondaryJobAdvice"> <property name="secondary" ref="sj"></property> </bean>

步骤4:配置切入点 bean.xml

<!-- 定义切入点 --> <bean id="bookingPointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*book"/> </bean>

步骤5:配置通知器 bean.xml

<!-- 通知器, 将通知和切入点连接起来 --> <bean id="secondaryJobAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="secondaryJobAdvice"/> <property name="pointcut" ref="bookingPointCut"/> </bean>

步骤6:配置代理 bean.xml

<!-- 订票主业务方法所在bean --> <bean id="inst" class="book.BookTicket"/> <!-- 定义代理 --> <bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="inst"/> <property name="interceptorNames" value="secondaryJobAdvisor"/> <property name="proxyInterfaces" value="book.BookTicketInterface"/> </bean>

目标对象是执行应用程序的预订机票主逻辑的BookTicket类。interceptorNames属性引用在Spring 配置文件中创建的通知器bean secondaryJobAdvisor。 proxyInterfaces属性引用声明book()方法的BookTicketInterface接口。

步骤7: 测试运行 我们为主业务方法类BookTicket定义一个接口

package book; public interface BookTicketInterface { public void book(String user,String seat); //订票 }

主业务逻辑方法(订票) BookTicket.java:

package book; public class BookTicket implements BookTicketInterface { /** * 订票 */ public void book(String user,String seat) { System.out.println(user + "已经完成了订票!" + seat); } }

测试类TestBook.java:

package book; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBook { public static void main(String[] args) { // TODO Auto-generated method stub ApplicationContext atx = new ClassPathXmlApplicationContext("bean.xml"); BookTicketInterface perf = (BookTicketInterface)atx.getBean("bookProxy"); perf.book("zhang","101"); } }
转载请注明原文地址: https://www.6miu.com/read-6050.html

最新回复(0)