在软件开发中, 散布于应用中多处的功能被称为横切关注点,通常来讲, 这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中),例如日志、 安全和事务管理。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP) 所要解决的问题。
横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)
通知(Advice)
它必须要完成的工作。 在AOP术语中, 切面的工作被称为通知。通知定义了切面是什么以及何时调用。 除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。 它应该应用在某个方法被调用之前? 之后? 之前和之后都调用? 还是只在方法抛出异常时调用
Spring切面可以应用5种类型的通知:
前置通知(Before) : 在目标方法被调用之前调用通知功能;
后置通知(After) : 在目标方法完成之后调用通知, 此时不会关
心方法的输出是什么;
返回通知(After-returning) : 在目标方法成功执行之后调用通
知;
异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方
法调用之前和调用之后执行自定义的行为。
连接点(Join point)
我们的应用可能也有数以千计的时机应用通知。 这些时机被称
为连接点。 连接点是在应用执行过程中能够插入切面的一个点。 这个
点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码
可以利用这些点插入到应用的正常流程之中, 并添加新的行为。
切点(Poincut)
如果说通知定义了切面的“什么”和“何时”的话, 那么切点就定义了“何处”。 切点的定义会匹配通知所要织入的一个或多个连接点。 我们通常使用明确的类和方法名称, 或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 有些AOP框架允许我们创建动态的切点, 可以根据运行时的决策(比如方法的参数值) 来决定是否应用通知。
切面(Aspect)
切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。 例如, 我们可以创建一个Auditable通知类, 该类记录了对象最后一次修改时的状态。 这很简单, 只需一个方法, setLastModified(Date), 和一个实例变量来保存这个状态。 然后, 这个新方法和实例变量就可以被引入到现有的类中, 从而可以在无需修改这些现有的类的情况下, 让它们具有新的行为和状态。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。 切面在指定的连接点被织入到目标对象中。 在目标对象的生命周期里有多个点可以进行织入:编译期: 切面在目标类编译时被织入。 这种方式需要特殊的编译器。 AspectJ的织入编译器就是以这种方式织入切面的。类加载期: 切面在目标类加载到JVM时被织入。 这种方式需要特殊的类加载器(ClassLoader) , 它可以在目标类被引入应用之前增强该目标类的字节码。 AspectJ 5的加载时织入(load-timeweaving, LTW) 就支持以这种方式织入切面。运行期: 切面在应用运行的某个时刻被织入。 一般情况下, 在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的
4.2.1 编写切点
我们定义一个Performance接口
假设我们想编写Performance的perform()方法触发的通知
这个表达式能够设置当perform()方法执行时触发通知的调用
现在假设我们需要配置的切点仅匹配concert包。 在此场景下, 可以使用within()指示器来限制匹配
因为“&”在XML中有特殊含义, 所以在Spring的XML配置里面描述切点时, 我们可以使用and来代替“&&”。 同样, or和not可以分别用来代替“||”和“!”。
4.2.2 在切点中选择bean
4.3.1 定义切面
Audience类, 它定义了我们所需的一个切面
@Aspect 注解 声明为一个切面类和POJO。
通过@Pointcut注解声明频繁使用的切点表达式
{erformance方法重复了N次
Audience类依然是一个POJO。 我们能够像使用其他的Java类那样调用它的方法, 它的方法也能够独立地进行单元测试, 这与其他的Java类并没有什么区别。 Audience只是一个Java类, 只不过它通过注解表明会作为切面使用而已。
像其他的Java类一样, 它可以装配为Spring中的bean
在JavaConfig中启用AspectJ注解的自动代理
在XML中, 通过Spring的aop命名空间启用AspectJ自动代理
4.3.2 创建环绕通知
它能够让你所编写的逻辑将被通知的目标方法完全包装起来。 实际上就像在一个通知方法中同时编写前置通知和后置通知。
4.3.3 处理通知中的参数
为了记录每个磁道所播放的次数, 我们创建了TrackCounter类,它是通知playTrack()方法的一个切面。 下面的程序清单展示了这个切面
需要关注的是切点表达式中的args(trackNumber)限定符。 它表明传递给playTrack()方法的int类型参数也会传递到通知中去。 参数的名称trackNumber也与切点方法签名中的参数相匹配。
这个通知方法是通过@Before注解和命名切点trackPlayed(trackNumber)定义的。 切点定义中的
参数与切点方法中的参数名称是一样的, 这样就完成了从命名切点到通知方法的参数转移
4.4 在XML中声明切面
第一个需要注意的事项是大多数的AOP配置元素必须在<aop:config>元素的上下文内使用
基于AspectJ注解的通知中, 当发现这种类型的重复时, 我们使用@Pointcut注解消除了这些重复的内容。 而在基于XML的切面声明中, 我们需要使用<aop:pointcut>元素。 如下的XML展现了如何将通用的切点表达式抽取到一个切点声明中, 这样这个声明就能在所有的通知元素中使用了
使用<aop:pointcut>定义命名切点
唯一的差别在于这里使用and关键字而不是“&&”(因为在XML中, “&”符号会被解析为实体的开始)
第5章 构建Spring Web应用程序
5.1.1 跟踪Spring MVC的请求
1)在请求离开浏览器时 , 会带有用户所请求内容的信息, 至少会包含请求的URL。 但是还可能带有其他的信息, 例如用户提交的表单信息。
2)请求旅程的第一站是Spring的DispatcherServletSpring。 MVC所有的请求都会通过一个前端控制器(front controller) Servlet。 前端控制器是常用的Web应用程序模式, 在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。 在Spring MVC中, DispatcherServlet就是前端控制器。
DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller) 。 控制器是一个用于处理请求的Spring组件。 在典型的应用程序中可能会有多个控制器, DispatcherServlet需要知道应该将请求发送给哪个控制器。 所以DispatcherServlet以会查询一个或多个处理器映射(handler mapping) 来确定请求的下一站在哪里。 处理器映射会根据请求所携带的URL信息来进行决策
3)一旦选择了合适的控制器, DispatcherServlet会将请求发送给选中的控制器 。 到了控制器, 请求会卸下其负载(用户提交的信息) 并耐心等待控制器处理这些信息。 (实际上, 设计良好的控制器本身只处理很少甚至不处理工作, 而是将业务逻辑委托给一个或多个服务对象进行处理。 )
控制器在完成逻辑处理后, 通常会产生一些信息, 这些信息需要返回给用户并在浏览器上显示。 这些信息被称为模型(model) 。 不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化, 一般会是html。 所以, 信息需要发送给一个视图(view) , 通常会是JSP
4)控制器所做的最后一件事就是将模型数据打包, 并且标示出用于渲染输出的视图名。 它接下来会将请求连同模型和视图名发送回DispatcherServlet 。
这样, 控制器就不会与特定的视图相耦合, 传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。 实际上, 它甚至并不能确定视图就是JSP。 相反, 它仅仅传递了一个逻辑名称, 这个名字将会用来查找产生结果的真正视图。 DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名匹配为一个特定的视图实现, 它可能是也可能不是JSP。
5)既然DispatcherServlet已经知道由哪个视图渲染结果, 那请求的任务基本上也就完成了。 它的最后一站是视图的实现(可能是JSP) , 在这里它交付模型数据。 请求的任务就完成了。 视图将使用模型数据渲染输出, 这个输出会通过响应对象传递给客户端(不会像听上去那样硬编码) 。