<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; MVC</title>
	<atom:link href="https://blog.gmem.cc/tag/mvc/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>ExtJS 4的MVC框架</title>
		<link>https://blog.gmem.cc/mvc-framework-of-extjs-4</link>
		<comments>https://blog.gmem.cc/mvc-framework-of-extjs-4#comments</comments>
		<pubDate>Mon, 25 Mar 2013 07:39:26 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4896</guid>
		<description><![CDATA[<p>ExtJS 4引入MVC架构，可以用来创建ExtJS应用程序，与经典的Ext.onReady回调方式很不相同。 MVC架构 在开发ExtJS应用时，往往习惯于把所有JavaScript代码编写在单个文件中，生产环境下的应用通常都比较复杂，可能包括了面板、表单、表格、布局、模型、存储等多种组件的组合。大量的组件代码导致JS文件急剧膨胀，难以维护，因此很有必要根据职责的不同把代码划分到不同的文件中，提高代码的可重用性。 在ExtJS 4以前的版本下，如何组织代码由开发人员自行决定，而MVC则是ExtJS 4提供的模式化选择。在ExtJS看来，MVC代表： Model：包含一系列字段、及其数据的集合，由Model类表示，亦可使用Store类来加载、保存数据 View：可以是某种视觉组件，例如Grid、Tree、Form Controllers：包含了一些动作逻辑，例如用户点击按钮时需要执行的操作 老式的ExtJS应用 在这里我们先创建一个老式的ExtJS应用，并在后面尝试将其迁移到MVC架构下，以阐述ExtJS MVC的用法。 该应用包含一个GRID和一个详情面板，JavaScript代码全部放在app.js中： [crayon-69ebc42dc8d6d721540478/]  HTML代码： [crayon-69ebc42dc8d75554830724/] 迁移/创建基于MVC的ExtJS应用 在这里我们使用MVC重新实现上一段中的应用。下图描述了JavaScript代码的目录结构：  我们可以看到，所有的JavaScript代码被分配在app文件夹中，它们的职责划分如下： app.js：包装整个应用程序，通常我们使用单页面方式来开发MVC应用 controller：控制器类存放目录 <a class="read-more" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">ExtJS 4的MVC框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>ExtJS 4引入MVC架构，可以用来创建ExtJS应用程序，与经典的Ext.onReady回调方式很不相同。</p>
<div class="blog_h2"><span class="graybg">MVC架构</span></div>
<p>在开发ExtJS应用时，往往习惯于把所有JavaScript代码编写在单个文件中，生产环境下的应用通常都比较复杂，可能包括了面板、表单、表格、布局、模型、存储等多种组件的组合。大量的组件代码导致JS文件急剧膨胀，难以维护，因此很有必要根据职责的不同把代码划分到不同的文件中，提高代码的可重用性。</p>
<p>在ExtJS 4以前的版本下，如何组织代码由开发人员自行决定，而MVC则是ExtJS 4提供的模式化选择。在ExtJS看来，MVC代表：</p>
<ol>
<li>Model：包含一系列字段、及其数据的集合，由Model类表示，亦可使用Store类来加载、保存数据</li>
<li>View：可以是某种视觉组件，例如Grid、Tree、Form</li>
<li>Controllers：包含了一些动作逻辑，例如用户点击按钮时需要执行的操作</li>
</ol>
<div class="blog_h2"><span class="graybg">老式的ExtJS应用</span></div>
<p>在这里我们先创建一个老式的ExtJS应用，并在后面尝试将其迁移到MVC架构下，以阐述ExtJS MVC的用法。</p>
<p>该应用包含一个GRID和一个详情面板，JavaScript代码全部放在app.js中：</p>
<pre class="crayon-plain-tag">Ext.onReady( function()
{
    Ext.define( 'Book', {
        extend : 'Ext.data.Model',
        fields : [
            'id', 'title', 'pages', 'numChapters', 'topic', 'publisher', 'isbn', 'isbn13'
        ]
    } );
    var store = Ext.create( 'Ext.data.Store', {
        model : 'Book',
        proxy : {
            type : 'ajax',
            url : 'data/books.json'
        }
    } );
    var grid = Ext.create( 'Ext.grid.Panel', {
        store : store,
        title : 'Books',
        columns : [
            {
                text : "Title",
                width : 120,
                dataIndex : 'title',
                sortable : true
            }, {
                text : "Pages",
                flex : 1,
                dataIndex : 'pages',
                sortable : true
            }, {
                text : "Topic",
                width : 115,
                dataIndex : 'topic',
                sortable : true
            }, {
                text : "Publisher",
                width : 100,
                dataIndex : 'publisher',
                sortable : true
            }
        ],
        viewConfig : {
            forceFit : true
        },
        region : 'center'
    } );
    var details = Ext.create( 'Ext.panel.Panel', {
        id : 'bookDetail',
        bodyPadding : 7,
        bodyStyle : "background: #ffffff;",
        html : 'Please select a book to see additional details.',
        height : 150,
        split : true,
        region : 'south'
    } );
    var bookTplMarkup = [
        '&lt;b&gt;Title:&lt;/b&gt; {title}&lt;br/&gt;',
        '&lt;b&gt;Pages:&lt;/b&gt; {pages}&lt;br/&gt;',
        '&lt;b&gt;No Chapters:&lt;/b&gt; {numChapters}&lt;br/&gt;',
        '&lt;b&gt;Topic:&lt;/b&gt; {topic}&lt;br/&gt;',
        '&lt;b&gt;Publisher:&lt;/b&gt; {publisher}&lt;br/&gt;',
        '&lt;b&gt;ISBN:&lt;/b&gt; {isbn}&lt;br/&gt;',
        '&lt;b&gt;ISBN 13:&lt;/b&gt; {isbn13}&lt;br/&gt;'
    ];
    var bookTpl = Ext.create( 'Ext.Template', bookTplMarkup );
    grid.getSelectionModel().on( 'selectionchange', function( sm, selectedRecord )
    {
        if ( selectedRecord.length )
        {
            var detailPanel = Ext.getCmp( 'bookDetail' );
            bookTpl.overwrite( detailPanel.body, selectedRecord[0].data );
        }
    } );
    Ext.create( 'Ext.container.Viewport', {
        frame : true,
        layout : 'border',
        items : [
            grid, details
        ]
    } );
    store.load();
} );</pre>
<p> HTML代码：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
&lt;title&gt;MVC Architecture&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" /&gt;
&lt;script type="text/javascript" src="extjs/ext-all.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="app.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h2"><span class="graybg">迁移/创建基于MVC的ExtJS应用</span></div>
<p>在这里我们使用MVC重新实现上一段中的应用。下图描述了JavaScript代码的目录结构：</p>
<p><a href="/wp-content/uploads/2013/03/ExtJS-MVC.png"><img class="size-full wp-image-4906 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/03/ExtJS-MVC.png" alt="ExtJS MVC" width="444" height="272" /></a></p>
<p> 我们可以看到，所有的JavaScript代码被分配在app文件夹中，它们的职责划分如下：</p>
<ol>
<li>app.js：包装整个应用程序，通常我们使用单页面方式来开发MVC应用</li>
<li>controller：控制器类存放目录</li>
<li>model：模型类存放目录</li>
<li>store：存储类存放目录</li>
<li>view：视图类存放目录</li>
<li>data：由于本示例不合服务器交互，该目录存放JSON数据</li>
</ol>
<p>为了使用动态加载，以下ExtJS SDK文件需要被引入到extjs目录下：</p>
<ol>
<li>ext-debug.js或者ext.js：用于ExtJS自举</li>
<li>resources目录：包含样式定义文件</li>
<li>src目录：包含ExtJS所有组件的源代码</li>
</ol>
<div class="blog_h3"><span class="graybg">创建MVC骨架程序</span></div>
<p>修改后的HTML代码如下：</p>
<pre class="crayon-plain-tag">&lt;head&gt;
&lt;title&gt;MVC Architecture&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="exts/resources/css/ext-all.css" /&gt;
&lt;script type="text/javascript" src="extjs/ext-debug.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="app.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p> app.js为MVC骨架，内容如下：</p>
<pre class="crayon-plain-tag">Ext.require( 'Ext.container.Viewport' );
Ext.application( {
    name : 'App',   //可选的应用的名称，也在全局创建了变量App，会作为所有模型、视图、存储、控制器的名字空间
    appFolder : 'app', //应用程序组件存放位置
    controllers: ['Books'], //声明该应用使用的控制器
    launch : function() //应用创建后自动执行的动作，必须覆写
    {
        Ext.create( 'Ext.container.Viewport', { 
            //所有组件均放置于该Viewport中
        } );
    }
} );</pre>
<div class="blog_h3"><span class="graybg">创建控制器类</span></div>
<pre class="crayon-plain-tag">//注意类名于目录结构限定的名字空间一致
Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    init : function()
    {
        //在App的launch之前执行
    }
} );</pre>
<p>控制器类继承自Ext.app.Controller，控制器负责与Store、View通信，并在特定事件发生时执行对应的Action。 </p>
<p>控制器的init方法在App.launch方法之前执行，可以在这里编写所有需要在创建Viewport前执行的代码，例如：</p>
<ol>
<li>实现与视图交互的逻辑，主要是相关的事件监听</li>
<li>加载数据</li>
<li>其它业务逻辑</li>
</ol>
<div class="blog_h3"><span class="graybg">在控制器类中操控视图</span></div>
<p>修改上面编写的控制器为：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    init : function()
    {
        this.control( {
            //对于viewport的直接子panel，监听其render事件
            'viewport &gt; panel' : {
                render : this.onPanelRendered
            }
        } );
    },
    onPanelRendered : function()
    {
    }
} );</pre>
<p>使用control方法可以注册对视图组件生命周期的监听器，在这里，我们监听Viewport子面板的render事件。 </p>
<div class="blog_h3"><span class="graybg">创建模型、存储类</span></div>
<p>模型类：</p>
<pre class="crayon-plain-tag">//注意类名于目录结构限定的名字空间一致
Ext.define( 'App.model.Book', {
    extend : 'Ext.data.Model',
    fields : [
        'id', 'title', 'pages', 'numChapters', 'topic', 'publisher', 'isbn', 'isbn13'
    ]
} );</pre>
<p>存储类：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.store.Book', {
    extend : 'Ext.data.Store',
    model : 'App.model.Book',
    proxy : {
        type : 'ajax',
        url : 'data/books.json'
    }
} );</pre>
<div class="blog_h3"><span class="graybg">添加表格面板</span></div>
<pre class="crayon-plain-tag">Ext.define( 'App.view.book.Grid', {
    extend : 'Ext.grid.Panel',
    alias : 'widget.bookList',
    title : 'Books',
    initComponent : function()
    {
        this.store = 'Book';
        this.columns = [
            {
                text : "Title",
                width : 120,
                dataIndex : 'title',
                sortable : true
            }, {
                text : "Pages",
                flex : 1,
                dataIndex : 'pages',
                sortable : true
            }, {
                text : "Topic",
                width : 115,
                dataIndex : 'topic',
                sortable : true
            }, {
                text : "Publisher",
                width : 100,
                dataIndex : 'publisher',
                sortable : true
            }
        ];
        this.viewConfig = {
            forceFit : true
        };
        this.callParent( arguments );
    }
} );</pre>
<div class="blog_h3"><span class="graybg">添加详情面板</span></div>
<pre class="crayon-plain-tag">Ext.define( 'App.view.book.DetailPanel', {
    extend : 'Ext.Panel',
    alias : 'widget.detailPanel',  //xtype别名
    bookTplMarkup : [  //作为实例变量来定义，而不是全局变量
        '&lt;b&gt;Title:&lt;/b&gt; {title}&lt;br/&gt;',
        '&lt;b&gt;Pages:&lt;/b&gt; {pages}&lt;br/&gt;',
        '&lt;b&gt;No Chapters:&lt;/b&gt; {numChapters}&lt;br/&gt;',
        '&lt;b&gt;Topic:&lt;/b&gt; {topic}&lt;br/&gt;',
        '&lt;b&gt;Publisher:&lt;/b&gt; {publisher}&lt;br/&gt;',
        '&lt;b&gt;ISBN:&lt;/b&gt; {isbn}&lt;br/&gt;',
        '&lt;b&gt;ISBN 13:&lt;/b&gt; {isbn13}&lt;br/&gt;'
    ],
    startingMarkup : 'Please select a book to see additional details.',
    bodyPadding : 7,
    initComponent : function()
    {
        this.tpl = Ext.create( 'Ext.Template', this.bookTplMarkup );
        this.html = this.startingMarkup;
        this.bodyStyle = {
            background : '#ffffff'
        };
        this.callParent( arguments );
    },
    updateDetail : function( data )  //供控制器调用，更新此视图组件
    {
        this.tpl.overwrite( this.body, data );
    }
} );</pre>
<p> 同时，修改应用骨架、控制器类为最终版本：</p>
<pre class="crayon-plain-tag">Ext.require( 'Ext.container.Viewport' );
Ext.application( {
    name : 'App',
    appFolder : 'app',
    controllers : [ 'Books'],
    launch : function()
    {
        Ext.create( 'Ext.container.Viewport', {
            layout : 'fit',
            items : [
                {
                    xtype : 'panel',
                    title : 'Books',
                    items : [
                        {
                            xtype : 'booklist'
                        }, {
                            xtype : 'detailPanel'
                        }
                    ]
                }
            ]
        } );
    }
} );</pre><br />
<pre class="crayon-plain-tag">Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    stores : [ 'Book' ],  //不需要声明完整的名称，ExtJS会自动在当前App的名字空间、约定目录下寻找
    models : [ 'Book' ],
    views : [ 'book.Grid', 'book.DetailPanel' ],  //控制器可以控制的视图的列表
    refs : [ //该配置用于外部引用控制器内的视图对象，使用Ext.ComponentQuery查询来获取视图
        {
            //变量名，可以直接使用Getter获取
            ref : 'panel',
            //组件选择符
            selector : 'detailPanel'
        }
    ],
    init : function()
    {
        this.getBookStore().load();  //getBookStore是自动生成的方法，等价于this.getStore('Book').load();
        this.control( {
            //控制ViewPort下的bookList（表格）的dataview组件
            'viewport &gt; bookList dataview' : {
                itemclick : this.bindGridToPanel
            }
        } );
    },
    bindGridToPanel : function( grid, record )
    {
        //直接使用Getter获取详情面板，并调用其方法更新视图
        this.getPanel().updateDetail( record.data );
    }
} ); </pre>
<div class="blog_h3"><span class="graybg">控制器的getter方法</span></div>
<p>声明在stores、views、models配置项中的对象，可以使用控制器类自动生成getter方法访问，这些方法包括两类：</p>
<ol>
<li>其中一类是形如getName[Store|View|Model]()的方法，其中Name是配置项中声明的组件的名称。</li>
<li>另外一些带单个形参的getModel、getStore、getView方法，使用时通过参数传递组件的名称：</li>
</ol>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> getter方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>getModel(name)</td>
<td>返回指定模型的引用，目标模型必须在models配置项中声明</td>
</tr>
<tr>
<td>getStore(name)</td>
<td>返回指定存储的引用，目标存储必须在stores配置项中声明</td>
</tr>
<tr>
<td>getView(name)</td>
<td>返回指定视图的引用，目标视图必须在views配置项中声明</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">一些MVC开发的TIPS</span></div>
<ol>
<li>创建Model、Store、View类（扩展已有UI组件）</li>
<li>在View类中不会执行任何操作（事件句柄），View只是提供方法，让控制器调用后改变视图</li>
<li>在Controller中列出其需要使用的Model、Store、View</li>
<li>注意Controller自动生成很多getter方法</li>
<li>为了方便的在Controller中获取View的引用，可以使用ref配置项</li>
<li>使用control函数声明针对View、Components的事件监听</li>
<li>注意Controller的init在App的launch之前执行</li>
</ol>
<div class="blog_h3"><span class="graybg">嵌套Model与MVC</span></div>
<p>如果为Model声明关联（associations），则必须使用关联目标的全名：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.model.Author', {
    extend : 'Ext.data.Model',
    fields : [
        {
            name : 'id',
            type : 'int'
        }, {
            name : 'name',
            type : 'string'
        }
    ],
    hasMany : {
        model : 'App.model.Book',   //如果仅仅写'Book'会出错
        foreignKey : 'authorId',
        name : 'books'
    },
    proxy : {
        type : 'ajax',
        url : 'data/authors/1.json',
        reader : {
            type : 'json',
            root : 'authors'
        }
    }
} );</pre>
<div class="blog_h2"><span class="graybg">为生产环境构建应用</span></div>
<p>Sencha SDK tools是ExtJS官方提供的开发套件，它可以生成JSB3（(JSBuilder file format)）格式的JavaScript依赖关系，并且依此生成最小化的运行时JavaScript文件，便于部署。</p>
<p>使用如下的命令可以生成JSB依赖文件：</p>
<pre class="crayon-plain-tag">sencha create jsb -a index.html -p app.js
rem 亦可指向服务器上的HTML文件
sencha create jsb -a http://localhost:8080/ext-theme/index.html -p app.jsb3</pre>
<p>上述命令会生成一个app.jsb3文件，其内容如下：</p>
<pre class="crayon-plain-tag">{
    "projectName" : "Project Name",
    "licenseText" : "Copyright(c) 2011 Company Name",
    "builds" : [
        {
            "name" : "All Classes",
            "target" : "all-classes.js",
            "options" : {
                "debug" : true
            },
            "files" : [
                {
                    "path" : "extjs/src/util/",
                    "name" : "Observable.js"
                }, {
                    "path" : "extjs/src/data/",
                    "name" : "Association.js"
                }, {
                    "path" : "extjs/src/data/",
                    "name" : "Operation.js"
                }
            ]
        }, {
            "name" : "Application - Production",
            "target" : "app-all.js",
            "compress" : true,
            "files" : [
                {
                    "path" : "",
                    "name" : "app.js"
                }
            ]
        }
    ],
    "resources" : []
}</pre>
<p>然后，执行命令：</p>
<pre class="crayon-plain-tag">sencha build -p app.jsb3 -d</pre>
<p>会生成两个文件：</p>
<ol>
<li>all-classes.js：包含应用程序用到的所有ExtJS类，未压缩，易于DEBUG</li>
<li>app-all.js：包含应用程序用到的所有ExtJS类、app.js，压缩版本</li>
</ol>
<p>我们可以修改HTML，引用生成的JavaScript文件：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;MVC Architecture&lt;/title&gt;
    &lt;link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" /&gt;
    &lt;script type="text/javascript" src="extjs/ext.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="app-all.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">ExtJS 4的MVC框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mvc-framework-of-extjs-4/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SpringMVC知识集锦</title>
		<link>https://blog.gmem.cc/springmvc-faq</link>
		<comments>https://blog.gmem.cc/springmvc-faq#comments</comments>
		<pubDate>Sun, 09 Sep 2012 03:53:28 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1224</guid>
		<description><![CDATA[<p>基于仿冒的SpringMVC测试用例示意 [crayon-69ebc42dc9467626178460/] 常用问题 强制产生JSON格式的结果 配置[crayon-69ebc42dc946c567866477-i/]  SpringMVC 设置 /* 过滤全部，导致JSP无法显示的问题 [crayon-69ebc42dc946f391893776/] 找不到控制器类的问题：No mapping found for HTTP request with URI 注意URL拦截的前置部分，如/admin，不要再控制器的映射路径中体现，例如地址/admin/user在控制器中直接配： /user [crayon-69ebc42dc9471609199225/] 使用Ajax配合@ResponseBody时出现乱码 <a class="read-more" href="https://blog.gmem.cc/springmvc-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/springmvc-faq">SpringMVC知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基于仿冒的SpringMVC测试用例示意</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.ctrl;

import static org.junit.Assert.*;

import javax.inject.Inject;

import net.greenmemory.commons.lang.StringUtils;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith ( SpringJUnit4ClassRunner.class )
@ContextConfiguration ( locations = { "/spring-classgen-disabled.xml", "/spring-mvc.xml" } )
@TransactionConfiguration ( transactionManager = "txManager", defaultRollback = true )
public class SpringMVCControllerTest
{
    private static final org.slf4j.Logger LOGGER       = LoggerFactory.getLogger( SpringMVCControllerTest.class );

    @Inject
    private RequestMappingHandlerAdapter  handlerAdapter;

    @Inject
    private RequestMappingHandlerMapping  handlerMapping;

    @Inject
    private SessionFactory                sf;

    @Inject
    private ObjectMapper                  om;

    private MockHttpServletRequest        request;

    private MockHttpServletResponse       response;

    @Before
    @Transactional
    public void setUp()
    {
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();

        Session sess = sf.getCurrentSession();
    }

    @Test
    @Transactional
    public void testController() throws Exception
    {
        request.setRequestURI( "/uri/" ); //设置请求的URI
        Object handler = handlerMapping.getHandler( request ).getHandler(); //获取Handler对象（就是控制器类）
        handlerAdapter.handle( request, response, handler ); //转发给Handler处理，并获取响应
        String contentType = response.getContentType();
        String json = response.getContentAsString();
        assertTrue( StringUtils.containsIgnoreCase( contentType, "json" ) );
        assertTrue( StringUtils.containsIgnoreCase( contentType, "utf-8" ) );
    }
}</pre>
<div class="blog_h2"><span class="graybg">常用问题</span></div>
<div class="blog_h3"><span class="graybg">强制产生JSON格式的结果</span></div>
<p>配置<pre class="crayon-plain-tag">@RequestMapping( produces = "application/json" )</pre> </p>
<div>
<div class="blog_h3"><span class="graybg">SpringMVC 设置 /* 过滤全部，导致JSP无法显示的问题</span></div>
</div>
<pre class="crayon-plain-tag">&lt;!--下面的URL模式会匹配所有url：路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)--&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;!--解决：下面会匹配到/login这样的路径型url，不会匹配到模式为*.jsp这样的后缀型url--&gt;
&lt;url-pattern&gt;/&lt;/url-pattern&gt;</pre>
<div>
<div class="blog_h3"><span class="graybg">找不到控制器类的问题：No mapping found for HTTP request with URI</span></div>
</div>
<div>
<ol>
<li>注意URL拦截的前置部分，如/admin，不要再控制器的映射路径中体现，例如地址/admin/user在控制器中直接配： /user<br />
<pre class="crayon-plain-tag">&lt;url-pattern&gt;/admin/*&lt;/url-pattern&gt;</pre>
</li>
</ol>
</div>
<div>
<div class="blog_h3"><span class="graybg">使用Ajax配合@ResponseBody时出现乱码</span></div>
</div>
<p>要注意JavaScript组件的编码问题，例如使用ExtJS的时候可以这样设置：</p>
<pre class="crayon-plain-tag">Ext.Ajax.defaultHeaders = {
   'Accept' : 'application/json',
   'Accept-Charset' : 'UTF-8'
};</pre>
<div>
<div class="blog_h3"><span class="graybg">SpringMVC重复扫描Spring配置中的包导致错误</span></div>
</div>
<p>可能报错：<br /> org.hibernate.HibernateException: No Session found for current thread<br /> SpringMVC作为子ApplicationContext时，不要扫描应用层的任何类，只扫描MVC控制器。可以把控制器全部放在ctrl包里面，并设置Spring扫描规则：</p>
<pre class="crayon-plain-tag">&lt;context:component-scan base-package="cc.gmem.demo.ctrl"&gt;
    &lt;context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /&gt;
&lt;/context:component-scan&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/springmvc-faq">SpringMVC知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/springmvc-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Spring MVC 3.0学习笔记</title>
		<link>https://blog.gmem.cc/spring-mvc-3-study-note</link>
		<comments>https://blog.gmem.cc/spring-mvc-3-study-note#comments</comments>
		<pubDate>Tue, 21 Aug 2012 05:39:26 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7904</guid>
		<description><![CDATA[<p>Spring MVC框架简介 Spring MVC 3.0新特性 支持REST风格的URL 添加更多注解，可完全注解驱动 引入HTTP输入输出转换器（HttpMessageConverter） 和数据转换、格式化、验证框架无缝集成 对静态资源处理提供特殊支持 更加灵活的控制器方法签名，可完全独立于Servlet API Spring MVC的请求处理框架 请求处理过程的细节分析：MVC模式 下图说明了Spring MVC的请求处理流程： 结合例子： [crayon-69ebc42dc9937314273193/] 则处理流程图为： 请求地址映射 Spring <a class="read-more" href="https://blog.gmem.cc/spring-mvc-3-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-mvc-3-study-note">Spring MVC 3.0学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Spring MVC框架简介</span></div>
<div class="blog_h2"><span class="graybg">Spring MVC 3.0新特性</span></div>
<ol>
<li>支持REST风格的URL</li>
<li>添加更多注解，可完全注解驱动</li>
<li>引入HTTP输入输出转换器（HttpMessageConverter）</li>
<li>和数据转换、格式化、验证框架无缝集成</li>
<li>对静态资源处理提供特殊支持 更加灵活的控制器方法签名，可完全独立于Servlet API</li>
</ol>
<div class="blog_h2"><span class="graybg">Spring MVC的请求处理框架</span></div>
<p>请求处理过程的细节分析：<a href="/mvc-pattern#springmvc">MVC模式</a></p>
<p>下图说明了Spring MVC的请求处理流程：</p>
<p><img class="aligncenter  wp-image-7906" src="https://blog.gmem.cc/wp-content/uploads/2012/08/springmvc-process-flow.png" alt="springmvc-process-flow" width="542" height="323" /></p>
<p>结合例子：</p>
<pre class="crayon-plain-tag">package com.kingsmartsi.bmcs.sysmgr.ctrl;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
//Indicate that UserController is a Handler
@RequestMapping ( "/user" )
public class UserController
{
    @RequestMapping ( value = "/home" )
    //Request path to serve, relative to /user
    public String register()
    {
        return "sysmgr/user"; //Logic view name
    }
}</pre>
<p>则处理流程图为： <img class="aligncenter  wp-image-7910" src="https://blog.gmem.cc/wp-content/uploads/2012/08/springmvc-process-flow-ex.png" alt="springmvc-process-flow-ex" width="542" height="312" /></p>
<div class="blog_h1"><span class="graybg">请求地址映射</span></div>
<div class="blog_h2"><span class="graybg">Spring MVC进行映射的依据</span></div>
<p>Spring MVC是根据来自HTTP请求报文中的各项属性来判断由哪一个Handler来处理当前请求的：<br /><img class="aligncenter  wp-image-7912" src="https://blog.gmem.cc/wp-content/uploads/2012/08/http.png" alt="http" width="534" height="284" /></p>
<div class="blog_h2"><span class="graybg">URL通配符和路径变量</span></div>
<p>@RequestMapping不但支持标准的URL，还支持在URL中包含Ant风格的通配符：</p>
<pre class="crayon-plain-tag">*匹配0-N个字符
/user/*/create-user      匹配 /user/any/create-user
**匹配0-N个目录
/user/**/create-user     匹配 /user/create-user、/user/any/create-user
?匹配单个字符
/user/create-user ??     匹配 /user/create-user00</pre>
<p>@RequestMapping的URL中可以包含路径变量：</p>
<pre class="crayon-plain-tag">/user/{id}   匹配 /user/10000</pre>
<p>一个URL中可以包含多个路径变量。 </p>
<div class="blog_h3"><span class="graybg">绑定路径变量（@PathVariable注解）</span></div>
<p>使用此注解可以把路径变量绑定为处理请求的方法的参数。 启用-debug编译时，如果路径变量名和方法参数名一致，可以自动匹配：</p>
<pre class="crayon-plain-tag">@RequestMapping("/sysmgr/user/{id}")
public ModelAndView get(@PathVariable("id") String id){
	ModelAndView mv = new ModelAndView();
	mv.setViewName("user/user-detail");
	mv.addObject("user", userService.getUserById(id));
	return mv;
}

@RequestMapping("/sysmgr/user-role/{userId}")
public ModelAndView getRole(@PathVariable String userId){
	ModelAndView mv = new ModelAndView();
	mv.setViewName("user/user-role");
	mv.addObject("user", userService.getRoles(id));
	return mv;
}</pre>
<div class="blog_h2"><span class="graybg">限定HTTP方法的映射</span></div>
<p>HTTP方法，在HTTP协议中称为动词（verb），除了常用的（GET和POST）之外，标准方法集合中还包含PUT、DELETE、HEAD和OPTIONS。一般浏览器只支持GET和POST方法。各方法说明如下：</p>
<table cellspacing="0" cellpadding="1">
<tbody>
<tr>
<td style="width: 80px;">
<p><b>序号</b></p>
</td>
<td style="width: 100px;">
<p><b>请求方法</b></p>
</td>
<td>
<p><b>说明</b></p>
</td>
</tr>
<tr>
<td>
<p>1</p>
</td>
<td>
<p>GET</p>
</td>
<td>
<p>使用GET方法检索一个表述（representation）——也就是对资源的描述。多次执行同一GET请求，不会对系统造成影响，GET方法具有幂等性[指多个相同请求返回相同的结果]。GET请求可以充分使用客户端的缓存。</p>
</td>
</tr>
<tr>
<td>
<p>2</p>
</td>
<td>
<p>POST</p>
</td>
<td>
<p>POST方法，通常表示“创建一个新资源”，但它既不安全也不具有幂等性（多次操作会产生多个新资源）。</p>
</td>
</tr>
<tr>
<td>
<p>3</p>
</td>
<td>
<p>DELETE</p>
</td>
<td>
<p>DELETE，表示删除一个资源，你也可以一遍又一遍地操作它，直到得出结果：删除不存在的东西没有任何问题</p>
</td>
</tr>
<tr>
<td>
<p>4</p>
</td>
<td>
<p>PUT</p>
</td>
<td>
<p>幂等性同样适用于PUT（基本的含义是“更新资源数据，如果资源不存在的话，则根据此URI创建一个新的资源”）</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">//处理任何HTTP方法：
@RequestMapping(value="/delete") 
public String del(@RequestParam("userId") String userId){
    return "user/del";			
}

//仅处理POST方法提交的请求：
@RequestMapping(value="/delete",method=RequestMethod.POST) 
public String del(@RequestParam("userId") String userId){
    return "user/del";			
}</pre>
<p>Spring还提供了模拟HTTP方法的机制：通过在web.xml中配置一个org.springframework.web.filter.HiddenHttpMethodFilter，利用POST请求的<span style="background-color: #c0c0c0;">_method</span>参数指定HTTP方法，HiddenHttpMethodFilter动态更改HTTP头信息。</p>
<div class="blog_h2"><span class="graybg">限定请求参数/请求头的映射</span></div>
<p>请求参数限定支持如下格式。这些格式同样适用于请求头限定：</p>
<ol>
<li><pre class="crayon-plain-tag">param1</pre> ：表示请求必须包含名为param1的请求参数</li>
<li><pre class="crayon-plain-tag">!param1</pre> ：表示请求不能包含名为param1的请求参数</li>
<li><pre class="crayon-plain-tag">param1!=value1</pre> ：表示请求包含名为param1的请求参数，但其值不能为value1</li>
<li><pre class="crayon-plain-tag">{"param1=value1","param2"}</pre> ：请求必须包含名为param1和param2的两个请求参数，且param1参数的值必须为value1</li>
</ol>
<div class="blog_h3"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">//通过请求参数限定
@RequestMapping(value="/delete", params="userId") 
public String test1(@RequestParam("userId") String userId){
    ...			
}

//通过请求头限定
@RequestMapping(value="/show",headers="content-type=text/*")
public String test2(@RequestParam("userId") String userId){
     ...			
}</pre>
<div class="blog_h1"><span class="graybg">请求数据的绑定</span></div>
<p>Spring MVC总是把各种请求数据绑定到Handler的入参中，其行为主要取决于注解配置。</p>
<div class="blog_h2"><span class="graybg">绑定不同来源的数据</span></div>
<p>常用数据绑定注解包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 注解</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>@RequestParam</td>
<td>绑定请求参数（包括URL参数）</td>
</tr>
<tr>
<td>@RequestHeader</td>
<td>绑定请求头</td>
</tr>
<tr>
<td>@CookieValue</td>
<td>绑定Cookie值</td>
</tr>
<tr>
<td>@PathVariable</td>
<td>绑定路径变量，已经在上一章介绍</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">举例</span></div>
<pre class="crayon-plain-tag">@RequestMapping(value="/handle")
public String handle(@CookieValue("JSESSIONID") String sessionId,
       @RequestHeader("Accept-Language") String accpetLanguage){
}
@RequestMapping(value="/handle")
public String handle(@RequestBody User u){
}</pre>
<div class="blog_h2"><span class="graybg">@RequestParam</span></div>
<p>@RequestParam注解用于绑定<span style="background-color: #c0c0c0;">单个</span>GET/POST请求参数到Handler的入参，它以下三个参数：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">参数 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>value</td>
<td>请求参数的名称</td>
</tr>
<tr>
<td>required</td>
<td>是否必需，默认为true，表示请求中必须包含对应的参数名，如果不存在将抛出异常</td>
</tr>
<tr>
<td>defaultValue</td>
<td>默认参数名，设置该参数时，自动将required设为false</td>
</tr>
</tbody>
</table>
<p>如果Handler入参类型是：</p>
<ol>
<li>Map，且注解指定了请求参数的名称，那么该参数的值依据配置的转换服务，转换为Map对象</li>
<li>Map&lt;String, String&gt;或者MultiValueMap&lt;String, String&gt;，且注解没有指定请求参数的名称，那么所有请求参数被注入到入参中</li>
</ol>
<div class="blog_h3"><span class="graybg">举例</span></div>
<pre class="crayon-plain-tag">public String request(@RequestParam("username") String username) {} </pre>
<div class="blog_h2"><span class="graybg">绑定请求参数为对象</span></div>
<p>Spring MVC支持绑定请求参数到POJO。按：<span style="background-color: #c0c0c0;">HTTP请求参数名 = POJO的属性名</span>的规则，自动绑定请求数据，支持<span style="background-color: #c0c0c0;">级联属性名（属性导航）</span>，自动进行基本类型数据转换。使用级联属性名时，会<span style="background-color: #c0c0c0;">自动创建</span>点号导航牵涉到的对象，甚至是集合对象：</p>
<pre class="crayon-plain-tag">@RequestMapping(value = "/save")
public String save( User user ) {
    //Request URL : /user/save?address.zip=1
    assertEquals(1,user.getAddress().getZip());
}
@RequestMapping ( value = "/save" )
public void save( User user )
{
    //Request URL : /user/save?addresses[0].zip=1
    assertEquals(1,user.getAddresses().get( 0 ).getZip() );
}</pre>
<p>注意：被绑定的Handler入参上不需要注解，Spring MVC会自动从请求参数中查找匹配入参属性的条目。</p>
<div class="blog_h3"><span class="graybg">绑定为命令对象</span></div>
<p>你还可以把绑定的对象添加到模型中，供视图访问：</p>
<pre class="crayon-plain-tag">public String request(@ModelAttribute("u") UserModel user){}</pre>
<p>在JSP视图页面中，我们可以通过表达式<pre class="crayon-plain-tag">${u.username}</pre> 访问到上面绑定的对象。</p>
<div class="blog_h2"><span class="graybg">注入各种对象</span></div>
<table class="full-width fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">对象类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Servlet API</td>
<td>可以注入各种Servlet API对象作为Handler的参数，包括HttpServletRequest、HttpServletResponse、HttpSession等<br />如果Handler自行使用HttpServletResponse返回响应，则Handler的返回值设置成void即可</td>
</tr>
<tr>
<td>Spring的Servlet API代理类</td>
<td>Spring MVC在org.springframework.web.context.request包中定义了若干个可代理Servlet原生API类的接口，如WebRequest、NativeWebRequest，它们也允许作为Handler的参数，通过这些代理类可访问请求对象的任何信息</td>
</tr>
<tr>
<td>I/O类</td>
<td>Spring MVC允许控制器的Handler使用java.io.InputStream/java.io.Reader及java.io.OutputStream/java.io.Writer作为方法的参数<br />Spring MVC将获取ServletRequest的InputStream/Reader或ServletResponse的OutputStream/Writer，然后按类型匹配的方式，传递给控制器的Handler入参</td>
</tr>
<tr>
<td>其它</td>
<td>除可以注入前述类型的参数以外，还支持java.util.Locale、java.security.Principal，可以通过Servlet的HttpServletRequest 的getLocale()及getUserPrincipal()得到相应的值。如果Handler的入参类型为Locale或Principal，Spring MVC自动从请求对象中获取相应的对象并传递给Handler</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">HTTP消息转换器机制</span></div>
<p>消息转换机制是Spring MVC数据绑定机制的补充，是@RequestBody、@ResponseBody注解正常工作的基础，其工作示意图如下：</p>
<p><img class="aligncenter  wp-image-7928" src="https://blog.gmem.cc/wp-content/uploads/2012/08/httpconv.png" alt="httpconv" width="536" height="275" /></p>
<div class="blog_h2"><span class="graybg">HTTP消息转换器类</span></div>
<pre class="crayon-plain-tag">public interface HttpMessageConverter&lt;T&gt; {
  //从HTTP输入消息中读取一个指定类型的对象
  T read(Class&lt;? extends T&gt; clazz, HttpInputMessage inputMessage); 
  //将指定类型的对象写入到HTTP输出消息中
  void write(T t, MediaType contentType, HttpOutputMessage    
    outputMessage);
}</pre>
<p>现有的实现类包括：StringHttpMessageConverter、FormHttpMessageConverter XmlAwareFormHttpMessageConverter、ResourceHttpMessageConverter、BufferedImageHttpMessageConverter、ByteArrayHttpMessageConverter SourceHttpMessageConverter、MarshallingHttpMessageConverter Jaxb2RootElementHttpMessageConverter、MappingJacksonHttpMessageConverter RssChannelHttpMessageConverter、AtomFeedHttpMessageConverter。可以支持XML、JSON等常用格式。</p>
<div class="blog_h2"><span class="graybg">@RequestBody和@ResponseBody注解</span></div>
<p>这两个注解可以将HttpServletRequest的getInputStream()内容绑定到参数，或者将Handler返回值写入到HttpServletResponse的getOutputStream()中。它们支持把<span style="background-color: #c0c0c0;">请求体绑定为POJO，或者把POJO写入到响应体</span>中。读写的格式由Http消息转换器决定。Http消息转换器不必须手工配置，SpringMVC会根据请求头、请求体等进行灵活的判断。这两个注解的缺点是<span style="background-color: #c0c0c0;">不能访问请求头、响应头</span>。下面是一个例子：</p>
<pre class="crayon-plain-tag">@RequestMapping(value = "/handle")
public String handle (@RequestBody  User user) {
}
@ResponseBody
public byte[] handle () throws IOException {
    User user = new User()
    return user;
}</pre>
<div class="blog_h2"><span class="graybg">HttpEntity和ResponseEntity类</span></div>
<p>这两个类和@RequestBody、@ResponseBody类似，可以访问请求头、响应头，但是失去方法签名灵活性优势。下面是一个例子：</p>
<pre class="crayon-plain-tag">@RequestMapping(value = "/handle")
public String handle (HttpEntity&lt;User&gt; httpEntity){  
    User user = httpEntity.getBody();                           
}
@RequestMapping(value = "/handle")  
public ResponseEntity&lt;User&gt; handle(){   
    HttpHeaders headers = new HttpHeaders();   
    MediaType mt=new MediaType("text","json", "UTF-8");   
    headers.setContentType(mt);   
    ResponseEntity&lt;User&gt; re=null;   
    User user = new User("WangZhen");   
    re=new ResponseEntity&lt;User&gt;(user ,headers, HttpStatus.OK);   
    return re;   
}</pre>
<div class="blog_h2"><span class="graybg">手工配置消息转换器</span></div>
<p><img class="aligncenter  wp-image-7929" src="https://blog.gmem.cc/wp-content/uploads/2012/08/httpconv-config.png" alt="httpconv-config" width="560" height="490" /></p>
<div class="blog_h1"><span class="graybg">数据转换、格式化、校验</span></div>
<div class="blog_h2"><span class="graybg">工作原理示意图</span></div>
<p><img class="aligncenter  wp-image-7931" src="https://blog.gmem.cc/wp-content/uploads/2012/08/databinding-flow.png" alt="databinding-flow" width="547" height="261" /></p>
<div class="blog_h2"><span class="graybg">数据类型转换 </span></div>
<p>旧版本的Spring只支持标准的PropertyEditor类型体系，存在以下缺陷：</p>
<ol>
<li>只能用于字符串和Java对象的转换，不适用于任意两个Java类型之间的转换</li>
<li>对源对象及目标对象所在的上下文信息（如注解、所在宿主类的结构等）不敏感，在类型转换时不能利用这些上下文信息实施高级转换逻辑</li>
</ol>
<p>Spring 3.0在核心模型中添加了一个通用的类型转换模块， ConversionService是Spring类型转换体系的核心接口。Spring 3.0同时支持PropertyEditor和ConversionService 进行类型转换，在<span style="background-color: #c0c0c0;">Bean配置</span>、Spring <span style="background-color: #c0c0c0;">MVC的Handler参数绑定</span>中使用类型转换体系进行转换。</p>
<p>由于ConversionService在进行类型转换时，可以使用到Bean类的上下文信息（包括类结构，注解信息），所以可以实施更加高级的类型转换，如注解驱动的格式化等功能：</p>
<pre class="crayon-plain-tag">public class User {
      @DateTimeFormat(pattern="yyyy-MM-dd")
      private Date birthday;
}</pre>
<p>以上User类，通过一个@DateTimeFormat注解，为类型转换提供了一些“额外”的信息，即代表日期的“源字符器”格式是“yyyy-MM-dd”。</p>
<div class="blog_h2"><span class="graybg">自定义类型转换器</span></div>
<div class="blog_h3"><span class="graybg">实现类型转换器</span></div>
<p>你可以实现org.springframework.core.convert.converter.Converter&lt;From,To&gt;以自定义转换器：</p>
<pre class="crayon-plain-tag">public class DateTimeConvertor implements Converter&lt;String, Date&gt; {

    private String[] formats;

    public DateTimeConvertor( String[] formats ) {
        this.formats = formats;
    }

    public Date convert( String source ) {
        return DateUtils.parseDate( source, formats );
    }

}</pre>
<div class="blog_h3"><span class="graybg">注册到Spring</span></div>
<p>编写类型转换器完毕后，必须在Spring上下文中注册，才能生效：</p>
<pre class="crayon-plain-tag">&lt;mvc:annotation-driven conversion-service="conversionService"/&gt;
&lt;!-- 此工厂生成的对象是org.springframework.format.support.FormattingConversionService，它同时是转换服务、格式化服务--&gt;
&lt;bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"&gt;
    &lt;!-- 它持有一系列的转换器 --&gt;
    &lt;property name="converters"&gt;
        &lt;set&gt;
            &lt;bean class="com.kingsmartsi.ssheutils.spring.DateTimeConvertor"&gt;
                &lt;constructor-arg&gt;
                    &lt;array&gt;
                        &lt;value&gt;#{appCfg.dateFormat}&lt;/value&gt;
                        &lt;value&gt;#{appCfg.dateTimeFormat}&lt;/value&gt;
                    &lt;/array&gt;
                &lt;/constructor-arg&gt;
            &lt;/bean&gt;
        &lt;/set&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<div class="blog_h2"><span class="graybg">数据格式化机制</span></div>
<p>上面的FormattingConversionService除了可以持有一系列的转换器（converters），还可以持有一系列的格式化器（formatters）。格式化器用来将来自<span style="background-color: #c0c0c0;">HTTP请求报文的文本转换为Java数据类型，或者反向转换</span>： <img class="aligncenter  wp-image-7933" src="https://blog.gmem.cc/wp-content/uploads/2012/08/data-formatter.png" alt="data-formatter" width="589" height="302" /></p>
<div class="blog_h3"><span class="graybg">自定义格式化器</span></div>
<p>你可以实现org.springframework.format.Formatter&lt;T&gt;接口，以便把HTTP文本解析为T类型对象，以及把T类型对象格式化为HTTP文本：</p>
<pre class="crayon-plain-tag">public class DateTimeFormatter implements Formatter&lt;Date&gt; {

    private String[] formats;

    public DateTimeFormatter( String[] formats ) {
        this.formats = formats;
    }

    // 此方法用于将HTTP文本解析为Java对象
    @Override
    public Date parse( String text, Locale locale ) throws ParseException {
        return null;
    }
    // 此方法用于将Java对象转换为HTTP文本
    @Override
    public String print( Date object, Locale locale ) {
        return null;
    }
}</pre>
<div class="blog_h3"><span class="graybg">注册到Spring</span></div>
<p>自定义的格式化器需要到Spring上下文注册：</p>
<pre class="crayon-plain-tag">&lt;mvc:annotation-driven conversion-service="conversionService"/&gt;
&lt;bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"&gt;
    &lt;property name="formatters"&gt;
        &lt;set&gt;
            &lt;bean class="com.kingsmartsi.ssheutils.spring.DateTimeFormatter"&gt;
                &lt;constructor-arg&gt;
                    &lt;array&gt;
                        &lt;value&gt;#{appCfg.dateFormat}&lt;/value&gt;
                        &lt;value&gt;#{appCfg.dateTimeFormat}&lt;/value&gt;
                    &lt;/array&gt;
                &lt;/constructor-arg&gt;
            &lt;/bean&gt;
        &lt;/set&gt;
    &lt;/property&gt;
&lt;/bean&gt;</pre>
<p>Spring默认的 <pre class="crayon-plain-tag">&lt;mvc:annotation-driven/&gt;</pre> 标签默认创建的ConversionService实例就是一个FormattingConversionServiceFactoryBean，自动支持如下的格式化注解：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">格式化注解 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>@NumberFormatter</td>
<td>用于数字类型对象的格式化</td>
</tr>
<tr>
<td>@CurrencyFormatter</td>
<td>用于货币类型对象的格式化</td>
</tr>
<tr>
<td>@PercentFormatter</td>
<td>用于百分数数字类型对象的格式化</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Spring对JSR-303的支持</span></div>
<p>JSR-303是Java为Bean数据合法性校验所提供的标准框架，它已经包含在Java EE 6.0中。JSR-303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则，并通过标准的验证接口对Bean进行验证。JSR-303包含的注解有：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">注解 </td>
<td style="text-align: center;">功能说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@Null</td>
<td>被注释的元素必须为 null</td>
</tr>
<tr>
<td>@NotNull</td>
<td>被注释的元素必须不为 null</td>
</tr>
<tr>
<td>@AssertTrue</td>
<td>被注释的元素必须为 true</td>
</tr>
<tr>
<td>@AssertFalse</td>
<td>被注释的元素必须为 false</td>
</tr>
<tr>
<td>@Min(value)</td>
<td>被注释的元素必须是一个数字，其值必须大于等于指定的最小值</td>
</tr>
<tr>
<td>@Max(value)</td>
<td>被注释的元素必须是一个数字，其值必须小于等于指定的最大值</td>
</tr>
<tr>
<td>@DecimalMin(value)</td>
<td>被注释的元素必须是一个数字，其值必须大于等于指定的最小值</td>
</tr>
<tr>
<td>@DecimalMax(value)</td>
<td>被注释的元素必须是一个数字，其值必须小于等于指定的最大值</td>
</tr>
<tr>
<td>@Size(max, min)</td>
<td>被注释的元素的大小必须在指定的范围内</td>
</tr>
<tr>
<td>@Digits (integer, fraction)</td>
<td>被注释的元素必须是一个数字，其值必须在可接受的范围内</td>
</tr>
<tr>
<td>@Past</td>
<td>被注释的元素必须是一个过去的日期</td>
</tr>
<tr>
<td>@Future</td>
<td>被注释的元素必须是一个将来的日期</td>
</tr>
</tbody>
</table>
<p>Spring 3.0拥有自己独立的数据校验框架，位于Spring的org.springframework.validation包。该框架同时支持JSR- 303标准的校验框架。Spring 的<span style="background-color: #c0c0c0;">DataBinder在进行数据绑定时，可同时调用校验框架完成数据校验工作</span>。在Spring MVC中，则可直接通过注解驱动的方式进行数据校验。<br />需要注意的是，Spring本身没有提供JSR-303的实现，所以必须将JSR- 303的实现者（如Hibernate Validator）的jar文件放到类路径下，Spring将自动加载并装配好JSR-303的实现者。</p>
<div class="blog_h2"><span class="graybg">数据验证注解示例</span></div>
<p> <pre class="crayon-plain-tag">&lt;mvc:annotation-driven/&gt;</pre> 会默认装配好一个LocalValidatorFactoryBean，通过在Handler参数上标注@Valid注解即可让Spring MVC在完成数据绑定后执行数据校验的工作。 <br />在已经标注了JSR-303注解的、作为Handler参数的POJO前标注一个@Valid，Spring MVC框架在将请求数据绑定到该对象后，就会调用校验框架根据注解声明的校验规则实施校验：</p>
<pre class="crayon-plain-tag">public class User {   
    @Pattern(regexp="w{4,30}")
    private String userName;
    @Length(min=2,max=100)
    private String realName;
    @Past 
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birthday;
    @DecimalMin(value="1000.00")
    @DecimalMax(value="100000.00") 
    @NumberFormat(pattern="#,###.##")
    private long salary;
}
@Controller
public class UserController{
    @RequestMapping
    public String handle(
        //可以在被验证对象后面紧跟着参数用于存放验证结果，这些参数必须是BindingResult或Errors类型
        @Valid User user, BindingResult br, //User和其绑定结果的对象
        @Valid Dept dept, Errors errors   //Dept和其校验的结果对象
){}</pre>
<div class="blog_h1"><span class="graybg">数据模型控制</span></div>
<div class="blog_h2"><span class="graybg">数据模型访问示意图</span></div>
<p><img class="aligncenter  wp-image-7939" src="https://blog.gmem.cc/wp-content/uploads/2012/08/access-data-model.png" alt="access-data-model" width="560" height="405" /></p>
<div class="blog_h2"><span class="graybg">访问数据模型</span></div>
<p>可以通过ModelAndView访问：</p>
<pre class="crayon-plain-tag">@RequestMapping(method = RequestMethod.POST)
public ModelAndView createUser(User user) {
	userService.createUser(user);
	ModelAndView mv = new ModelAndView();
	mv.setViewName("user/createSuccess");
	mv.addObject("user", user);
	return mv;
}</pre>
<p>也可以通过@ModelAttribute访问：</p>
<pre class="crayon-plain-tag">//Handler会先执行该方法，获取一个初始的User对象，并使用请求覆盖对应属性
@ModelAttribute("user")
public User getUser(){
	User user = new User();
	user.setUserId("1001"); 
	return user;
}
@RequestMapping(value = "/handle")
public String handle(@ModelAttribute("user") User user){
	user.setUserName("tom");
	return "/user/showUser";
}</pre>
<p>如果Handler方法的参数中有：org.springframework.ui.Model、java.util.Map类型，SpringMVC会自动把请求中隐含的模型对象传递给这些参数：</p>
<pre class="crayon-plain-tag">@RequestMapping(value = "/handle")
public String  handle (ModelMap modelMap){
     modelMap.addAttribute("test",“val");
     User user = (User)modelMap.get("user");
     user.setUserName("wz");		
     return "/user/showUser";
}</pre>
<p>如果希望在多个请求之间共用某个模型属性数据，则可以在控制器类标注一个@SessionAttributes，Spring MVC会将模型中对应的属性暂存到HttpSession中：</p>
<pre class="crayon-plain-tag">@Controller
@RequestMapping("/user")
@SessionAttributes("user")//声明会话属性
public class UserController {
    @RequestMapping(value = "/handle0")
    public String  handle0(@ModelAttribute("user") User user){//设置会话属性
        user.setUserName("John");
        return "redirect:/user/handle1.html";
    }
    @RequestMapping(value = "/handle1")
    public String  handle1(ModelMap modelMap,SessionStatus sessionStatus){
        User user = (User)modelMap.get("user");//获取会话属性
        if(user != null){
            user.setUserName("Jetty");
            sessionStatus.setComplete();//清除会话属性	
        }
        return "/user/showUser";
    }		
}</pre>
<div class="blog_h1"><span class="graybg">视图及解析器</span></div>
<div class="blog_h2"><span class="graybg">视图解析原理</span></div>
<p><img class="aligncenter  wp-image-7941" src="https://blog.gmem.cc/wp-content/uploads/2012/08/view-resolver.png" alt="view-resolver" width="557" height="396" /></p>
<div class="blog_h2"><span class="graybg">视图解析器的分类</span></div>
<ol>
<li>完成单一解析逻辑的视图解析器：InternalResourceViewResolver、FreeMarkerViewResolver、BeanNameViewResolver、XmlViewResolver等</li>
<li>协商视图解析器：ContentNegotiatingViewResolver。该解析器是Spring 3.0新增的，它不负责具体的视图解析，而是作为一个中间人的角色根据<span style="background-color: #c0c0c0;">请求所要求的MIME类型（Accept请求头）</span>，从上下文中选择一个适合的视图解析器，再将视图解析工作委托其负责</li>
</ol>
<div class="blog_h2"><span class="graybg">视图解析器的配置</span></div>
<p>视图解析器Bean的parameterName可以用来判断请求所期望的MIME类型，order则声明视图解析器的优先级：</p>
<pre class="crayon-plain-tag">&lt;bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
    p:order="0" p:defaultContentType="text/html" p:ignoreAcceptHeader="true" 
    p:favorPathExtension="false" p:favorParameter="true" p:parameterName="content"&gt;
    &lt;property name="mediaTypes"&gt;
        &lt;map&gt;
            &lt;entry key="html" value="text/html" /&gt;
	  &lt;entry key="xml" value="application/xml" /&gt;   
            &lt;entry key="json" value="application/json" /&gt;
        &lt;/map&gt;
    &lt;/property&gt;
    &lt;property name="defaultViews"&gt;
        &lt;list&gt;
            &lt;bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"
                p:renderedAttributes="userList" /&gt;
            &lt;bean class="org.springframework.web.servlet.view.xml.MarshallingView"
                p:modelKey="userList" p:marshaller-ref="xmlMarshaller" /&gt;
        &lt;/list&gt;
    &lt;/property&gt;
&lt;/bean&gt;
&lt;bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:order="100" p:viewClass="org.springframework.web.servlet.view.JstlView"
    p:prefix="/WEB-INF/views/" p:suffix=".jsp" /&gt;</pre>
<div class="blog_h1"><span class="graybg">静态资源处理</span></div>
<div class="blog_h2"><span class="graybg">静态资源的配置</span></div>
<p>在web.xml让所有请求都由Spring MVC处理，注意<span style="background-color: #c0c0c0;">url-pattern是/而不是/*</span>：</p>
<pre class="crayon-plain-tag">&lt;servlet&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;servlet-class&gt;
        org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
    &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;springmvc&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
<p>在MVC的Spring配置文件中设置：</p>
<pre class="crayon-plain-tag">&lt;!-- 设置默认，大多数应用服务器的Servlet的名称都是“default” --&gt;
&lt;mvc:default-servlet-handler default-servlet-name="default"/&gt;
&lt;!-- 物理静态资源路径映射逻辑资源路径 --&gt;
&lt;mvc:resources mapping="/resources/**" location="/,classpath:/META-INF/publicResources/"/&gt;
&lt;mvc:resources mapping="/json/**" location="/json/" /&gt;
&lt;mvc:resources mapping="/css/**" location="/css/" /&gt;
&lt;mvc:resources mapping="/icons/**" location="/icons/" /&gt;
&lt;mvc:resources mapping="/images/**" location="/images/" /&gt;
&lt;mvc:resources mapping="/themes/**" location="/themes/" /&gt;
&lt;mvc:resources mapping="/sound/**" location="/sound/" /&gt;
&lt;mvc:resources mapping="/js/**" location="/js/" /&gt;
&lt;mvc:resources mapping="/html/**" location="/html/" /&gt; </pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/spring-mvc-3-study-note">Spring MVC 3.0学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/spring-mvc-3-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MVC模式</title>
		<link>https://blog.gmem.cc/mvc-pattern</link>
		<comments>https://blog.gmem.cc/mvc-pattern#comments</comments>
		<pubDate>Thu, 20 Mar 2008 01:30:42 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[架构模式]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=7861</guid>
		<description><![CDATA[<p>模式说明 经典的MVC模式 MVC，即模型-视图-控制器，是一种用于用户界面设计/展示层的架构模式，起初出现在Smalltalk。MVC模式的核心是利用控制器作为中介，将视图和模型解耦。 MVC模式中三者的职责分工明确： 模型（Model）：持有所有的数据、状态和程序逻辑，模型不会感知到视图和控制器（下层组件不依赖于上层）。模型提供了操作和检索状态的接口，并且将状态改变信息推送给观察者（视图），在某些设计中，视图需要的数据被一同推送 视图（View）：用来呈现模型，视图通常直接从模型中获取它需要显示的状态和数据 控制器（Controller）：负责获取用户输入，并解读其对于模型的意义。控制器也可能直接要求视图做出改变。控制器不应当包含业务逻辑 注意和传统的模型含义不同，MVC中的模型不仅仅是指数据对象。以播放器为例，播放状态、当前歌曲、播放行为等都属于模型的组成部分。 三者的调用关系图如下： 上图虽然有箭头从Model指向View，但是这是一种松耦合的、基于观察者模式的通知方式。此外，某些设计中把Controller也作为Model的观察者，图中没有标出。 一般MVC中会用到三种设计模式： 观察者模式：Model作为事件源，View、Controller作为监听器 策略模式：对于View来说，Controller是具体策略，如果需要改变行为，可以更换一个Controller 组合模式：View中的GUI控件一般都呈现出树状组合关系 MVC2 经典的MVC模式适用于单机桌面程序的开发，由于Model、View运行在统一进程中，因而互相之间基于函数调用的通信很方便。在常见的Web应用场景中，Model是无法通知到View的，因为： 在使用服务器端脚本（PHP、JSP、ASP等）作为视图组件的情况下，这些视图都是一次性生成的过程，结果是HTML代码，并传递到浏览器端运行。这些视图无法后续接收通知（调用） 在使用RIA时，View完全运行在浏览器中，View与服务器端只有数据的交互，同样不能接收Model的通知（调用） 为了解决这一问题，MVC2架构出现了，MVC2将View和Model完全解耦，两者之间的交互完全通过Controller间接完成。MVC2中三者的调用关系图如下： 随着基于JavaScript的UI框架以及WebSocket等协议的兴起，现在很多应用抛弃了服务器端脚本技术。View和Controller之间的通信完全基于数据而不是函数调用，WebSocket支持双向通信，这意味着Model在数据发生变化时即时通知Controller，由后者推送给View成为可能。这样的应用场景导致MVC架构又有了新的变化： 经典应用 Servlet与JSP <a class="read-more" href="https://blog.gmem.cc/mvc-pattern">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-pattern">MVC模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">模式说明</span></div>
<div class="blog_h3"><span class="graybg">经典的MVC模式</span></div>
<p>MVC，即模型-视图-控制器，是一种用于用户界面设计/展示层的架构模式，起初出现在Smalltalk。MVC模式的核心是利用控制器作为中介，将视图和模型解耦。</p>
<p>MVC模式中三者的职责分工明确：</p>
<ol>
<li>模型（Model）：持有<span style="background-color: #c0c0c0;">所有的数据、状态和程序逻辑</span>，模型<span style="background-color: #c0c0c0;">不会感知到视图和控制器</span>（下层组件不依赖于上层）。模型<span style="background-color: #c0c0c0;">提供了操作和检索状态的接口</span>，并且将<span style="background-color: #c0c0c0;">状态改变信息推送给观察者</span>（视图），在某些设计中，视图需要的数据被一同推送</li>
<li>视图（View）：用来呈现模型，视图通常<span style="background-color: #c0c0c0;">直接从模型中获取</span>它需要显示的状态和数据</li>
<li>控制器（Controller）：负责<span style="background-color: #c0c0c0;">获取用户输入</span>，并解读其对于模型的意义。控制器也可能<span style="background-color: #c0c0c0;">直接要求视图</span>做出改变。控制器不应当包含业务逻辑</li>
</ol>
<p>注意和传统的模型含义不同，MVC中的模型不仅仅是指数据对象。以播放器为例，播放状态、当前歌曲、播放行为等都属于模型的组成部分。</p>
<p>三者的调用关系图如下：</p>
<p><img class="aligncenter size-full wp-image-7867" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC.png" alt="patterns_MVC" width="645" height="411" /></p>
<p>上图虽然有箭头从Model指向View，但是这是一种松耦合的、基于观察者模式的通知方式。此外，某些设计中把<span style="background-color: #c0c0c0;">Controller也作为Model的观察者</span>，图中没有标出。</p>
<p>一般MVC中会用到三种设计模式：</p>
<ol>
<li>观察者模式：Model作为事件源，View、Controller作为监听器</li>
<li>策略模式：对于View来说，Controller是具体策略，如果需要改变行为，可以更换一个Controller</li>
<li>组合模式：View中的GUI控件一般都呈现出树状组合关系</li>
</ol>
<div class="blog_h3"><span class="graybg">MVC2</span></div>
<p>经典的MVC模式适用于单机桌面程序的开发，由于Model、View运行在统一进程中，因而互相之间基于函数调用的通信很方便。在常见的Web应用场景中，Model是无法通知到View的，因为：</p>
<ol>
<li>在使用服务器端脚本（PHP、JSP、ASP等）作为视图组件的情况下，这些视图都是一次性生成的过程，结果是HTML代码，并传递到浏览器端运行。这些视图无法后续接收通知（调用）</li>
<li>在使用RIA时，View完全运行在浏览器中，View与服务器端只有数据的交互，同样不能接收Model的通知（调用）</li>
</ol>
<p>为了解决这一问题，MVC2架构出现了，MVC2将View和Model完全解耦，两者之间的交互完全通过Controller间接完成。MVC2中三者的调用关系图如下：</p>
<p><img class="aligncenter size-full wp-image-7880" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC2.png" alt="patterns_MVC2" width="536" height="362" /></p>
<p>随着基于JavaScript的UI框架以及WebSocket等协议的兴起，现在很多应用抛弃了服务器端脚本技术。View和Controller之间的通信完全基于数据而不是函数调用，WebSocket支持双向通信，这意味着Model在数据发生变化时即时通知Controller，由后者推送给View成为可能。这样的应用场景导致MVC架构又有了新的变化：</p>
<p><a href="/wp-content/uploads/2008/03/patterns_MVC2_2.png"><img class="aligncenter size-full wp-image-7882" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC2_2.png" alt="patterns_MVC2_2" width="536" height="362" /></a></p>
<div class="blog_h2"><span class="graybg">经典应用</span></div>
<div class="blog_h3"><span class="graybg">Servlet与JSP</span></div>
<p>JavaEE中的Servlet与JSP技术可以用来构建MVC模式，其中Servlet充当Controller、JSP充当View、后端的Bean则充当Model：</p>
<p><img class="aligncenter size-full wp-image-7884" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC_Servlet.png" alt="patterns_MVC_Servlet" width="621" height="289" /></p>
<div class="blog_h3"><span class="graybg">Struts 1.x</span></div>
<p>Struts 是早期的Java Web框架，其内置了MVC模式的支持。Struts的核心接口是Action，它是一个HTTP请求与相应业务逻辑之间的适配器，作为Controller的RequestProcessor负责把请求分发给合适的Action；Struts没有对View和Model建模，但是在视图方面提供了多种标签库，简化开发。在Struts，ActionForm被用来作为数据传输对象，可以在Model和View之间传递信息。Struts的核心类图如下：</p>
<p><img class="aligncenter size-full wp-image-7888" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC_struts.png" alt="patterns_MVC_struts" width="100%" /></p>
<div class="blog_h3"><span class="graybg"><a id="struts2"></a>Struts 2.x</span></div>
<p>Struts2源于Webwork框架，它朝向零配置的方向发展，原先需要通过XML进行的配置，现在都可以使用Annotation完成。在Struts2中Controller的角色由FilterDispatcher承担，与Struts1中的ActionServlet不同，该类是一个Servlet过滤器；Model角色部分由Action类承担；View角色则部分由Result接口承担。</p>
<p>Struts2最大的不同是，Action没有接口约束，它的角色（该角色实质是由它的一个方法承担）也变成Model，框架通过反射调用之。Action类上可以通过Annotation附加大量元数据信息，例如Action负责哪些请求的处理，请求处理完毕后转向什么页面，下面是一个例子：</p>
<pre class="crayon-plain-tag">import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.ExceptionMapping;
import org.apache.struts2.convention.annotation.ExceptionMappings;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.opensymphony.xwork2.ActionSupport;

@ParentPackage ( "struts-default" )
@Namespace ( "/web-manager" )
@Results ( { @Result ( name = "success", location = "/main.jsp" ),
        @Result ( name = "error", location = "/error.jsp" ) } )
@ExceptionMappings ( { @ExceptionMapping ( exception = "java.lang.RuntimeException", result = "error" ) } )
public class LoginAction extends ActionSupport
{
    private static final long serialVersionUID = 1L;

    @Action ( "login" )
    public String login() throws Exception
    {
        return SUCCESS;
    }

    @Action ( value = "add", results = { @Result ( name = "success", location = "/index.jsp" ) } )
    public String add() throws Exception
    {
        return SUCCESS;
    }
}</pre>
<p>Struts2的类图如下（使用带标签的序号说明了某个场景下的调用顺序）：</p>
<p><img class="aligncenter size-full wp-image-7894" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC_Struts2.png" alt="patterns_MVC_Struts2" width="100%" /></p>
<p>可以看到，Struts2处理HTTP请求的大致流程如下：</p>
<ol>
<li>FilterDispatcher拦截到来自Servlet容器的HTTP请求，创建ActionContext，该对象是Action执行的上下文，并依据HttpRequest和ConfigurationManager获取ActionMapping，然后调用Dispatcher.serviceAction()，进行分发处理</li>
<li>ActionMapping知道哪个类的方法负责处理当前请求，Dispatcher根据ActionMapping创建extraContext，这是一个简单的Map，然后根据extraContext创建ActionProxy，后者是代理模式的实现。Struts2使用的代理类是StrutsActionProxy。Dispatcher调用ActionProxy.execute()，将请求的处理权转给StrutsActionProxy</li>
<li>StrutsActionProxy没有任何状态，因而不需要为每次请求重新创建它，该代理会在请求处理前后设置/恢复线程本地的ActionContext信息，这样在整个处理期间，ActionContext都不需要通过方法参数传递了。代理通过调用ActionInvocation.invoke()，转移请求处理器</li>
<li>ActionInvocation是Structs2请求处理的核心，主要干三件事情：
<ol>
<li>调用Interceptor链条，对请求进行处理，这里是职责链模式的应用</li>
<li>通过反射调用Action的方法，Action是用户程序的主要扩展点</li>
<li>调用Result，处理视图</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg"><a id="springmvc"></a>Spring MVC</span></div>
<p>Spring的MVC框架和Struct2的思想很相似，都是倾向于走向零配置、大量使用注解。Spring MVC中的Controller类与Struct2的Action在功能上是相当的，但是Spring MVC倾向于将其划分到C而不是M；DispatcherServlet是Spring MVC的（核心）控制器角色；ModelAndView类对V、M进行了通用建模，不过很明显了，Spring MVC把Model的概念弱化了，在它这里，M仅仅是DTO。</p>
<p>SpringMVC的处理流程比Struts2清晰简明，逻辑一直把握在DispatcherServlet手上：</p>
<ol>
<li>DispatcherServlet使用模板方式模式的变体来组织整个请求处理过程，它定义了整体处理算法：<span style="background-color: #c0c0c0;">请求预处理⇨设置属性⇨调用拦截器预处理⇨调用Handler⇨调用拦截器后处理⇨处理渲染⇨调用拦截器完成处理</span>。这些具体步骤中，很大一部分交由用户提供的拦截器、Handler完成。Handler可以是任意Java类，只需要将其标记@Controller注解并扫描。</li>
<li>DispatcherServlet能够接受到Spring事件通知，监听器是它父类的一个内部类ContextRefreshListener，该内部类转调了DispatcherServlet的onApplicationEvent</li>
<li>DispatcherServlet设置当前HttpServletRequest对象的若干属性，例如它关联的ApplicationContext</li>
<li>DispatcherServlet执行拦截器链条，这是一个职责链模式的变体，具有3条职责路径（预处理、后处理、完成后处理），首先执行preHandle()进行预处理</li>
<li>DispatcherServlet调用HandlerAdapter.handle()处理请求，最常用的HandlerAdapter实现是AnnotationMethodHandlerAdapter，基于注解使用Spring MVC时，会自动使用该类。从名字上可以看出，这是一个适配器模式，它基于反射机制，对Handler继续完全的适配：
<ol>
<li>Handler的入参数量、类型都是任意的，HandlerAdapter需要把HttpServletRequest、HttpServletResponse等对象适配为这些类型。AnnotationMethodHandlerAdapter依靠反射机制和各种注解完成这一适配</li>
<li>Handler的返回值类型是任意的，HandlerAdapter需要将其适配为ModelAndView类型</li>
</ol>
</li>
<li>DispatcherServlet执行拦截器链条的postHandle()进行后处理</li>
<li>DispatcherServlet调用View.render()执行渲染</li>
<li>DispatcherServlet执行拦截器链条的afterCompletion()进行最终处理</li>
</ol>
<p>Spring MVC的主要类图如下：</p>
<p><img class="aligncenter size-full wp-image-7900" src="https://blog.gmem.cc/wp-content/uploads/2008/03/patterns_MVC_Spring.png" alt="patterns_MVC_Spring" width="100%" /></p>
<p>下面是一个简单的Handler的例子，可以看到和Struts的Action是很类似的：</p>
<pre class="crayon-plain-tag">import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping ( "/web-manager" )
public class LoginController
{

    @RequestMapping ( value = "login/{mode}", method = { RequestMethod.POST } )
    @ResponseBody
    public Object login( @PathVariable String mode, @RequestParam String uname )
    {
        return null;
    }
}</pre>
<div class="blog_h3"><span class="graybg">ExtJS的MVC框架</span></div>
<p>ExtJS是一个基于JavaScript的框架，它提供了大量基于Web的UI控件，ExtJS还内置了MVC模式，具体可以参考：<a href="/mvc-framework-of-extjs-4">ExtJS 4的MVC框架</a></p>
<p>在这里我们可以注意到，理解MVC的概念不能死板。假设我们同时使用Spring MVC和ExtJS MVC，那么这两个框架什么关系呢？岂不是重复了？实际上，对于ExtJS来说，整个服务器端应用程序都属于M的一部分；而对于Spring来说，整个ExtJS和Spring的JsonView都属于V的部分。角度不同，思考也就不同，所谓横看成岭侧成峰嘛。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-pattern">MVC模式</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mvc-pattern/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
