转载:http://www.cnblogs.com/me-sa/archive/2009/06/09/AopInAspNetMVC.html
不同的观点,开拓迥然不同的世界.
Another point of view can sometimes open up a whole new world.
—- HSBC
本文内容包括:
SoC
Separation of concerns (SoC) is the process of breaking a computer program into distinct features that overlap in functionality as little as possible.
源文档 <http://en.wikipedia.org/wiki/Separation_of_concerns>
上面是维基百科中对SoC的定义,在不同的场景中Soc有着不同的含义
- Soc是一个过程:SoC是一个将功能点分解以尽量减小功能交叉的过程.
- SoC是软件开发的一个基本原则
- SoC是一个指标:关注点的分离度
SoC的实践体现在不同的编程模型(Programming paradigm)中,编程模型帮助开发者提高关注点分离度.过程化编程模型把关注点分解成多个过程.在面向对象编程中,关注点抽象为对象/类.面向方面编程模型中,将关注点分解为多个"方面".
这里有一个非常有意思的思考,我们回顾一下上面三种编程模型的发展历程:面向过程编程模型是人们面对复杂问题的一种自然思路:功能分解.需求的不断变化,功能分解已经不足以应对变化,于是面向对象编程模型的最核心的方法论是抽象.通过在不同视角进行抽象,抽象之后概念视角和规约视角能够保证相对的稳定,具体实现的变化被隔离开.说到这里我们不厌其烦的再次回首面向对象设计原则:SRP原则和ISP原则.单一职责和接口隔离本质上就是在强调对变化的隔离.再具体一点,MVC设计模式也是在强调一种分离,只不过它的是把内容和展示做了分离.
AOP
Aspect-oriented programming (AOP) is a programming paradigm that increases modularity by enabling improved separation of concerns. This entails breaking down a program into distinct parts (so-called concerns, cohesive areas of functionality). All programming paradigms support some level of grouping and encapsulation of concerns into separate, independent entities by providing abstractions (e.g. procedures, modules, classes, methods) that can be used to implement, abstract and compose these concerns. But some concerns defy these forms of implementation and are called crosscutting concerns because they "cut across" multiple abstractions in a program.
源文档 <http://en.wikipedia.org/wiki/Aspect-oriented_programming>
上面的文字同样来自维基百科,我们来做细致解读:
OOP引入封装、继承和多态性等机制来建立一种对象层次结构.当我们需要为分散的对象引入公共行为的时候,00P则显得无能为力。一个典型的例子:日志功能。日志代码往往水平地散布在所有对象层次中.而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码如权限检查、异常处理也是如此。这种散布在各处的无关的代码被称为横切(cross—cutting)代码,在OOP设计中.它导致了大量代码的重复,而不利于各个模块的重用。本质上核心功能和权限检查之类的功能处于不同的维度,但是在实现的时候所有的代码都被平铺在同一个维度上了.简单看一下下面的示意图:
OOP解决横切代码的乏力,AOP将散落各处的却又重复的代码组织在一起,在OOP工作的基础上进一步完成模块化.AOP解决这一问题的机制被称为"横切技术";横切技术实现有两种方式:一是采用动态代理技术,利用截取消息的方式.对该消息进行装饰,以取代原有对象行为的执行.二是采用静态织入的方式,引入特
定的语法创建“方面”.从而使得编译器可以在编译期间织入有关“方面”的代码通常将静态横切称为introduce.
AOP in Asp.net
AOP真的离我们很远么?举两个现实中Asp.net的例子:
- 我们要在用户访问若干页面的时候检查是否具有对该页面的权限
- 我们要为页面做内容的繁简体转换
上面的两个问题你打算怎么解决?在每一个页面的Page_Load里面都做一下权限检查么?然后在页面事件的最后做一下繁简体转换?
是的,我们的解决方法是做了HTTPModule.通过对用户请求的拦截,做权限检查.在页面输出到客户端之前获取控制权完成页面繁简体转换.我们发现这样做的好处是:1.对页面核心代码没有任何修改 2.控制灵活:一旦不需要做权限检查了,只需要注释掉Web.config对应的配置节就可以了.之所以能够获得这样的便利,本质上是因为我们做到了权限检查代码的模块化.我们将权限检查的功能和核心业务成功的分离开,而权限检查功能的切入方式就是通过消息拦截.
我们把HTTPMoudle理解为一种动态横切,Web应用程序生命周期中的特定事件就是Join point.HTTPModule中对特定事件的绑定就是PointCut,执行逻辑是Advice,一个HTTPModule代表一个Aspect.也许有学院派的朋友会不同意我的看法.我个人的看法是AOP是一种思想,这种思想还没有一个严格的定义,AOP需要更多的实践可能性来丰富,它不拘泥于AspectJ或者Aspect#.
在享受HTTPMoudle便利的同时,我们也发现了这种方案的特点:应用程序生命周期中所暴露的事件就是可利用的Join Point,由于在应用程序级实施横切,横切功能的粒度是应用程序级的.我们思考的方向自然走到这里:Asp.net是不是可以有一个更细粒度的横切实践?嗯哼,ASP.NET MVC出现了.
AOP in ASP.NET MVC
上回书我们已经讨论过了Asp.netMVC生命周期,我们截取请求处理的其中一个片段,我们从ActionInvoker.InvokeAction方法开始讨论.基于上面对于AOP的思考,当我们再次审视下图的时候,呈现在我们面前的已经是一个个Join Point.下图中核心的代码执行逻辑是Action Execution–>ActionResult Execution –>View.Render.在这些核心功能的执行前后会成为我们的切入点,类似于应用程序事件的Pre和Post一样,我们看到了OnActionExecuting ,OnActionExecuted,OnResultExecuting,OnResultExecuted.
OutputCache是一个典型的方面应用,我们通过OutputCacheAttribute这样一个系统的默认实现来做具体分析:
1/* ****************************************************************************
2 *
3 * Copyright (c) Microsoft Corporation. All rights reserved.
4 *
5 * This software is subject to the Microsoft Public License (Ms-PL).
6 * A copy of the license can be found in the license.htm file included
7 * in this distribution.
8 *
9 * You must not remove this notice, or any other, from this software.
10 *
11 * ***************************************************************************/
12
13namespace System.Web.Mvc {
14 using System;
15 using System.Diagnostics.CodeAnalysis;
16 using System.Web;
17 using System.Web.UI;
18
19 [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes",
20 Justification = "Unsealed so that subclassed types can set properties in the default constructor.")]
21 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
22 public class OutputCacheAttribute : ActionFilterAttribute {
23
24 private OutputCacheParameters _cacheSettings = new OutputCacheParameters();
25
26 public string CacheProfile {
27 get {
28 return _cacheSettings.CacheProfile ?? String.Empty;
29 }
30 set {
31 _cacheSettings.CacheProfile = value;
32 }
33 }
34
35 internal OutputCacheParameters CacheSettings {
36 get {
37 return _cacheSettings;
38 }
39 }
40
41 public int Duration {
42 get {
43 return _cacheSettings.Duration;
44 }
45 set {
46 _cacheSettings.Duration = value;
47 }
48 }
49
50 public OutputCacheLocation Location {
51 get {
52 return _cacheSettings.Location;
53 }
54 set {
55 _cacheSettings.Location = value;
56 }
57 }
58
59 public bool NoStore {
60 get {
61 return _cacheSettings.NoStore;
62 }
63 set {
64 _cacheSettings.NoStore = value;
65 }
66 }
67
68 public string SQLDependency {
69 get {
70 return _cacheSettings.SQLDependency ?? String.Empty;
71 }
72 set {
73 _cacheSettings.SQLDependency = value;
74 }
75 }
76
77 public string VaryByContentEncoding {
78 get {
79 return _cacheSettings.VaryByContentEncoding ?? String.Empty;
80 }
81 set {
82 _cacheSettings.VaryByContentEncoding = value;
83 }
84 }
85
86 public string VaryByCustom {
87 get {
88 return _cacheSettings.VaryByCustom ?? String.Empty;
89 }
90 set {
91 _cacheSettings.VaryByCustom = value;
92 }
93 }
94
95 public string VaryByHeader {
96 get {
97 return _cacheSettings.VaryByHeader ?? String.Empty;
98 }
99 set {
100 _cacheSettings.VaryByHeader = value;
101 }
102 }
103
104 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param",
105 Justification = "Matches the @ OutputCache page directive.")]
106 public string VaryByParam {
107 get {
108 return _cacheSettings.VaryByParam ?? String.Empty;
109 }
110 set {
111 _cacheSettings.VaryByParam = value;
112 }
113 }
114
115 public override void OnResultExecuting(ResultExecutingContext filterContext) {
116 if (filterContext == null) {
117 throw new ArgumentNullException("filterContext");
118 }
119
120 // we need to call ProcessRequest() since there's no other way to set the Page.Response intrinsic
121 OutputCachedPage page = new OutputCachedPage(_cacheSettings);
122 page.ProcessRequest(HttpContext.Current);
123 }
124
125 private sealed class OutputCachedPage : Page {
126 private OutputCacheParameters _cacheSettings;
127
128 public OutputCachedPage(OutputCacheParameters cacheSettings) {
129 // Tracing requires Page IDs to be unique.
130 ID = Guid.NewGuid().ToString();
131 _cacheSettings = cacheSettings;
132 }
133
134 protected override void FrameworkInitialize() {
135 // when you put the <%@ OutputCache %> directive on a page, the generated code calls InitOutputCache() from here
136 base.FrameworkInitialize();
137 InitOutputCache(_cacheSettings);
138 }
139 }
140
141 }
142}
143
下面对OutputCacheAttribute的特点做一个归纳:
- OuputCache标签是应用在Controller级别上的,由于一个Controller可以对应多个View,所以它的粒度介于应用程序级和页面级之间
- OuputCache标签是静态引入到Controller的,在编译时完成OutputCache的引入而非运行时,是一种静态横切的实现
- OuputCache标签的执行的PointCut是OnResultExecuting
- OutputCacheAttribute的继承关系是:OutputCacheAttribute —–> ActionFilterAttribute—–> FilterAttribute, IActionFilter, IResultFilter
我们今天的主角出现了,看一下ActionFilterAttribute的代码:
2 *
3 * Copyright (c) Microsoft Corporation. All rights reserved.
4 *
5 * This software is subject to the Microsoft Public License (Ms-PL).
6 * A copy of the license can be found in the license.htm file included
7 * in this distribution.
8 *
9 * You must not remove this notice, or any other, from this software.
10 *
11 * ***************************************************************************/
12
13namespace System.Web.Mvc {
14 using System;
15
16 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
17 public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter {
18
19 // The OnXxx() methods are virtual rather than abstract so that a developer need override
20 // only the ones that interest him.
21
22 public virtual void OnActionExecuting(ActionExecutingContext filterContext) {
23 }
24
25 public virtual void OnActionExecuted(ActionExecutedContext filterContext) {
26 }
27
28 public virtual void OnResultExecuting(ResultExecutingContext filterContext) {
29 }
30
31 public virtual void OnResultExecuted(ResultExecutedContext filterContext) {
32 }
33 }
34}
35
ActionFilterAttribute定义的实际上定义了四个PoinCut,可以看到这四个pointCut是虚方法,注释里面解释了这样做的缘由:可以按照需要来重写感兴趣的方法,就是编写Advice代码了;Asp.net HTTPModule实现本质上是读HTTP请求的拦截,而在Asp.net框架中通过ActionFilterAttribute定义了PointCut,从而创建了更多扩展的机会.除此之外,Asp.net MVC框架还提供了AuthorizeAttribute,HandleErrorAttribute等等常规"方面"默认实现,我们同样可以进行扩展.
比较一下Asp.net和Asp.netMVC在AOP实现上的不同:
|
Asp.net |
Asp.net MVC |
AOP粒度 |
应用程序级 |
灵活的粒度控制 |
AOP实现方法 |
动态横切 |
静态横切 |
Asp.net MVC继承了Asp.net的优良传统,同时Asp.net MVC框架承诺的更灵活,扩展性更好在这里有一个更好的体现.当我们耐心的做一下比较,我们发现这不是一句空话,而今天我们看到的灵活性和扩展性,也仅仅是一个"方面"而已.