应用切面
DI能够让相互协作的软件组件保持松散耦合,而面向切面编程 (aspect-oriented programming,AOP)允许你把遍布应用各处的功能 分离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点的分离这一项技术。系统由许多不同的组件组成,每一个组件各负责一块特定功能。 除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。 诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。
如果将这些关注点分散到多个组件中去,那么代码将会带给我们双重的复杂性:
- 实现系统关注点功能的代码将会重复出现在多个组件中。这意味着如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只 是调用它的方法,但方法的调用还是会重复出现在各个模块中。
- 组件会因为那些与自身核心业务无关的代码而变得混乱。一个向地址簿增加地址条目的方法应该只关注如何添加地址,而不应该关注它是不是安全的或者是否需要支持事务。
例如下图所示的核心功能与关注点之间的映射关系:
借助AOP,我们可以将这种复杂的映射变得简单!
如上图所示,我们利用AOP来将核心业务逻辑与关注点相分离。使得关注点(日志模块、安全模块、事务模块等)呈现一种覆盖状,而核心业务逻辑部分根本不知道关注点覆盖层的存在。
那么我们该如何实现一个切面呢?
~书接上回:https://blog.youkuaiyun.com/keq_keq/article/details/88565863
我们继续回到骑士这个案例:
每一个人都熟知骑士所做的任何事情,这是因为吟游诗人用诗歌记载 了骑士的事迹并将其进行传唱。假设我们需要使用吟游诗人这个服务 类来记载骑士的所有事迹。程序清单1.9展示了我们会使用的 Minstrel类。
//吟游诗人是中世纪的音乐记录器
package sia.knights;
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("Fa la la, the knight is so brave!");//before expedition
}
public void singAfterQuest() {
stream.println("Tee hee hee, the brave knight " +
"did embark on a quest!"); //after expedition
}
}
BraveKnight调用Minstrel的传统方法:
package sia.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest,Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throw QuestException{
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
这样实现完成后,好像并没有什么问题,我们只需要在配置层声明Minstrel bean并将其注入到BraveKnight的构造器之中。
……wait! 这样岂不是变成了骑士支配吟游诗人?吟游诗人作为一个自发宣传骑士事迹的存在,难道不应该是独立存在并且行动的?
所以这便暗中契合了我们AOP的思想:你在背后默默奉献,而他什么都不知道!
要将Minstrel抽象为一个切面,在一个 Spring配置文件中声明它。
Minstrel被声明为一个切面:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="sia.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="sia.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="sia.knights.Minstrel"> <!--声明minstrel bean-->
<constructor-arg value="#{T(System).out}" />
</bean>
<aop:config> <!-- 定义切面 -->
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/> <!-- 定义切点 -->
<aop:before pointcut-ref="embark"
method="singBeforeQuest"/> <!-- 声明前置通知 -->
<aop:after pointcut-ref="embark"
method="singAfterQuest"/> <!-- 声明后置通知 -->
</aop:aspect>
</aop:config>
</beans>
这里使用了Spring的aop配置命名空间把Minstrel bean声明为一个 切面。首先,需要把Minstrel声明为一个bean,然后 在<aop:aspect>元素中引用该bean。为了进一步定义切面,声明 (使用<aop:before>)在embarkOnQuest()方法执行前调 用Minstrel的singBeforeQuest()方法。这种方式被称为前置通 知(before advice)。同时声明(使用<aop:after>) 在embarkOnQuest()方法执行后调用singAfter Quest()方 法。这种方式被称为后置通知(after advice)。
在这两种方式中,pointcut-ref属性都引用了名字为embank的切 入点。该切入点是在前边的<pointcut>元素中定义的,并配 置expression属性来选择所应用的通知。表达式的语法采用的是 AspectJ的切点表达式语言。
现在,Minstrel可以被应用到BraveKnight中, 而BraveKnight不需要显式地调用它。
实际上,BraveKnight完 全不知道Minstrel的存在。
必须还要指出的是,尽管我们使用Spring魔法把Minstrel转变为一 个切面,但首先要把它声明为一个Spring bean。能够为其他Spring bean做到的事情都可以同样应用到Spring切面中,例如为它们注入依赖。
至此,用spring魔法来歌颂骑士就告一段落了!
内容参考自《spring实战》第四版