WebMVC
WebMVC 模块在 YMP 框架中是除了 JDBC 持久化模块以外的另一个非常重要的模块,集成了 YMP 框架的诸多特性,在功能结构的设计和使用方法上依然保持一贯的简单风格,同时也继承了主流 MVC 框架的基因,对于了解和熟悉 SSH 或 SSM 等框架技术的开发人员来说,上手极其容易,毫无学习成本。
其主要功能特性如下:
- 标准 MVC 实现,结构清晰,完全基于注解方式配置简单;
- 支持约定模式,无需编写控制器代码,直接匹配并执行视图渲染;
- 支持多种视图技术(Binary、Forward、Freemarker、HTML、HttpStatus、JSON、JSP、Redirect、Text、Velocity 等);
- 支持 RESTful 模式及 URL 风格;
- 支持请求参数与控制器方法参数的自动绑定;
- 支持参数有效性验证;
- 支持控制器方法的拦截;
- 支持注解配置控制器请求路由映射;
- 支持自动扫描控制器类并注册;
- 支持事件和异常的自定义处理;
- 支持I18N资源国际化;
- 支持控制器方法和视图缓存;
- 支持插件扩展;
Maven包依赖
<dependency>
<groupId>net.ymate.platform</groupId>
<artifactId>ymate-platform-webmvc</artifactId>
<version>2.1.4-dev</version>
</dependency>
模块初始化
在 Web 程序中监听器(Listener)是最先被容器初始化的,所以 WebMVC 模块是由监听器负责对 YMP 框架进行初始化:
监听器(Listener):net.ymate.platform.webmvc.support.WebAppEventListener
处理浏览器请求并与模块中控制器匹配、路由的过程可分别由过滤器(Filter)和服务端程序(Servlet)完成:
过滤器(Filter):net.ymate.platform.webmvc.support.DispatchFilter
服务端程序(Servlet):net.ymate.platform.webmvc.support.DispatchServlet
以下为完整的 web.xml 配置文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="ymate-cms-webapp" version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<listener>
<listener-class>net.ymate.platform.webmvc.support.WebAppEventListener</listener-class>
</listener>
<filter>
<filter-name>GeneralWebFilter</filter-name>
<filter-class>net.ymate.platform.webmvc.support.GeneralWebFilter</filter-class>
<init-param>
<param-name>responseHeaders</param-name>
<!--
HTTP 响应头信息中的 X-Frame-Options,可以指示浏览器是否应该加载一个 iframe 中的页面。
如果服务器响应头信息中没有 X-Frame-Options,则该网站存在 ClickJacking 攻击风险。
网站可以通过设置 X-Frame-Options 阻止站点内的页面被其他页面嵌入从而防止点击劫持。
添加 X-Frame-Options 响应头,赋值有如下三种:
1、DENY: 不能被嵌入到任何iframe或者frame中。
2、SAMEORIGIN: 页面只能被本站页面嵌入到iframe或者frame中。
3、ALLOW-FROM uri: 只能被嵌入到指定域名的框架中。
-->
<param-value>X-Frame-Options=SAMEORIGIN</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>GeneralWebFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>DispatchFilter</filter-name>
<filter-class>net.ymate.platform.webmvc.support.DispatchFilter</filter-class>
<!--
参数项 "requestIgnoreUrls" 用于配置 需要排除的请求地址前缀,多个前缀之间用 "|" 分隔。
-->
<!--
<init-param>
<param-name>requestIgnoreUrls</param-name>
<param-value>/service1|/serviceN</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>DispatchFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<!--
<servlet>
<servlet-name>DispatchServlet</servlet-name>
<servlet-class>net.ymate.platform.webmvc.support.DispatchServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatchServlet</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
-->
<!--
OPTIONS 方法是用于请求获得由 Request-URI 标识的资源在请求/响应的通信过程中可以使用的功能选项。
通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。
OPTIONS 方法可能会暴露一些敏感信息,这些信息将帮助攻击者准备更进一步的攻击。
-->
<!--
<security-constraint>
<web-resource-collection>
<web-resource-name>RequestMethodAllowed</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method-omission>GET</http-method-omission>
<http-method-omission>POST</http-method-omission>
<http-method-omission>OPTIONS</http-method-omission>
<http-method-omission>PUT</http-method-omission>
<http-method-omission>HEAD</http-method-omission>
<http-method-omission>TRACE</http-method-omission>
<http-method-omission>DELETE</http-method-omission>
<http-method-omission>SEARCH</http-method-omission>
<http-method-omission>COPY</http-method-omission>
<http-method-omission>MOVE</http-method-omission>
<http-method-omission>PROPFIND</http-method-omission>
<http-method-omission>PROPPATCH</http-method-omission>
<http-method-omission>MKCOL</http-method-omission>
<http-method-omission>LOCK</http-method-omission>
<http-method-omission>UNLOCK</http-method-omission>
</web-resource-collection>
<auth-constraint/>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
-->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>400</error-code>
<location>/WEB-INF/templates/error.jsp?status=400</location>
</error-page>
<error-page>
<error-code>401</error-code>
<location>/WEB-INF/templates/error.jsp?status=401</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/templates/error.jsp?status=404</location>
</error-page>
<error-page>
<error-code>405</error-code>
<location>/WEB-INF/templates/error.jsp?status=405</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/templates/error.jsp?status=500</location>
</error-page>
</web-app>
模块配置
配置文件参数说明
基本参数配置
#-------------------------------------
# WebMVC模块初始化参数
#-------------------------------------
# 控制器请求映射路径分析器, 可选值为已知分析器名称或自定义分析器类名称, 默认值: default, 目前支持已知分析器[default|...]
ymp.configs.webmvc.request_mapping_parser_class=
# 控制器请求处理器, 可选值为已知处理器名称或自定义处理器类名称, 自定义类需实现net.ymate.platform.webmvc.IRequestProcessor接口, 默认值: default, 目前支持已知处理器[default|json|xml|...]
ymp.configs.webmvc.request_processor_class=
# 异常错误处理器, 可选参数, 默认值: net.ymate.platform.webmvc.impl.DefaultWebErrorProcessor
ymp.configs.webmvc.error_processor_class=
# 缓存处理器, 可选参数, 此类需实现net.ymate.platform.webmvc.IWebCacheProcessor接口, 默认值: net.ymate.platform.webmvc.support.WebCacheProcessor
ymp.configs.webmvc.cache_processor_class=
# 默认字符编码集设置, 可选参数, 默认值: UTF-8
ymp.configs.webmvc.default_charset_encoding=
# 默认Content-Type设置, 可选参数, 默认值: text/html
ymp.configs.webmvc.default_content_type=
# 国际化资源文件存放路径, 可选参数, 默认值: ${root}/i18n/
ymp.configs.webmvc.resources_home=
# 国际化资源文件名称, 可选参数, 默认值: messages
ymp.configs.webmvc.resource_name=
# 国际化语言设置参数名称, 可选参数, 默认值: _lang
ymp.configs.webmvc.language_param_name=
# 请求忽略后缀集合, 可选参数, 默认值: jsp|jspx|png|gif|jpg|jpeg|js|css|swf|ico|htm|html|eot|woff|woff2|ttf|svg|map
ymp.configs.webmvc.request_ignore_regex=
# 请求方法参数名称, 可选参数, 默认值: _method
ymp.configs.webmvc.request_method_param=
# 请求路径前缀, 可选参数, 默认值: 空
ymp.configs.webmvc.request_prefix=
# 请求路径匹配是否启用严格模式,默认值: false
ymp.configs.webmvc.request_strict_mode_enabled=
# 控制器视图文件基础路径(必须是以 '/' 开始和结尾), 默认值: /WEB-INF/templates/
ymp.configs.webmvc.base_view_path=
#-------------------------------------
# Cookie相关参数配置
#-------------------------------------
# Cookie键前缀, 可选参数, 默认值: 空
ymp.configs.webmvc.cookie_prefix=
# Cookie作用域, 可选参数, 默认值: 空
ymp.configs.webmvc.cookie_domain=
# Cookie作用路径, 可选参数, 默认值: /
ymp.configs.webmvc.cookie_path=
# Cookie密钥, 可选参数, 默认值: 空
ymp.configs.webmvc.cookie_auth_key=
# Cookie密钥验证是否默认开启, 默认值: false
ymp.configs.webmvc.cookie_auth_enabled=
# Cookie是否默认使用HttpOnly, 默认值: false
ymp.configs.webmvc.cookie_use_http_only=
#-------------------------------------
# 文件上传相关参数配置
#-------------------------------------
# 文件上传临时目录, 为空则默认使用:System.getProperty("java.io.tmpdir")
ymp.configs.webmvc.upload_temp_dir=
# 上传文件数量最大值,默认值:-1
ymp.configs.webmvc.upload_file_count_max=
# 上传文件大小最大值(字节), 默认值: -1(注: 10485760 = 10M)
ymp.configs.webmvc.upload_file_size_max=
# 上传文件总量大小最大值(字节), 默认值: -1(注:10485760 = 10M)
ymp.configs.webmvc.upload_total_size_max=
# 内存缓冲区的大小, 默认值: 10240字节(=10K), 即如果文件大 于10K, 将使用临时文件缓存上传文件
ymp.configs.webmvc.upload_size_threshold=
# 文件上传状态监听器, 可选参数, 此类需实现org.apache.commons.fileupload.ProgressListener接口, 默认值: 空
ymp.configs.webmvc.upload_listener_class=
-
在服务端程序(Servlet)方式的请求处理中,请求忽略正则表达式
request_ignore_regex参数无效。 -
在配置
request_ignore_regex参数时,可以通过首元素为~符号实现继承,即在默认值的基础上追加。例如:为默认请求忽略后缀集合增加
xml和json后缀。ymp.configs.webmvc.request_ignore_regex=~|xml|json
扩展参数配置
#-------------------------------------
# 扩展参数配置
#-------------------------------------
# 控制器请求URL后缀, 默认值: 空
ymp.params.webmvc.request_suffix=
# 服务名称, 默认值: request.getServerName();
ymp.params.webmvc.server_name=
# 异常信息视图文件, 默认值: error.jsp
ymp.params.webmvc.error_view=
# 系统异常分析是否关闭, 默认值: false
ymp.params.webmvc.exception_analysis_disabled=
# 默认异常响应视图格式, 默认值: 空, 可选范围: json|xml
ymp.params.webmvc.error_default_view_format=
# 异常响应时是否使用状态码, 默认值: false, 若开启则当发生异常时响应的状态码值为400(BAD_REQUEST)
ymp.params.webmvc.error_with_status_code=
# 验证结果消息模板, 默认值: ${items}
ymp.params.webmvc.validation_template_element=
# 验证结果消息项模板, 默认值: ${message}<br>
ymp.params.webmvc.validation_template_item=
# 允许访问和重定向的主机名称, 多个主机名称用'|'分隔, 默认值: 空(表示不限制)
ymp.params.webmvc.allowed_access_hosts=
# 允许上传的文件类型, 多个用'|'分隔, 默认值: 空(表示不限制)
ymp.params.webmvc.allowed_upload_content_types=
# 参数签名检测时间戳是否在有效周期内,默认为30秒,当取值小于等于0时表示不检测,单位:秒
ymp.params.webmvc.signature_time_lifecycle=
# 是否禁用参数签名验证,默认值: false
ymp.params.webmvc.signature_verification_disabled=
配置注解参数说明
@WebConf
| 配置项 | 描述 |
|---|---|
| mappingParserClass | 控制器请求映射路径分析器 |
| requestProcessClass | 控制器请求处理器 |
| errorProcessorClass | 异常错误处理器 |
| cacheProcessorClass | 缓存处理器 |
| resourceHome | 国际化资源文件存放路径 |
| resourceName | 国际化资源文件名称 |
| languageParamName | 国际化语言设置参数名称,可选参数,默认值为 _lang |
| defaultCharsetEncoding | 默认字符编码集设置 |
| defaultContentType | 默认Content-Type设置 |
| requestIgnoreSuffixes | 请求忽略后缀集合 |
| requestMethodParam | 请求方法参数名称 |
| requestPrefix | 请求路径前缀 |
| requestStrictModeEnabled | 请求路径匹配是否启用严格模式 |
| baseViewPath | 控制器视图文件基础路径 |
| cookiePrefix | Cookie键前缀 |
| cookieDomain | Cookie作用域 |
| cookiePath | Cookie作用路径 |
| cookieAuthKey | Cookie密钥 |
| cookieAuthEnabled | Cookie密钥验证是否默认开启 |
| cookieUseHttpOnly | Cookie是否默认使用 HttpOnly |
| uploadTempDir | 文件上传临时目录 |
| uploadFileCountMax | 上传文件数量最大值 |
| uploadFileSizeMax | 上传文件大小最大值(字节) |
| uploadTotalSizeMax | 上传文件总量大小最大值(字节) |
| uploadSizeThreshold | 内存缓冲区的大小(字节) |
| uploadListenerClass | 文件上传状态监听器 |
模块事件
事件枚举对象 WebEvent 包括以下事件类型:
| 事务类型 | 说明 |
|---|---|
| SERVLET_CONTEXT_INITIALIZED | 容器初始化事件 |
| SERVLET_CONTEXT_DESTROYED | 容器销毁事件 |
| SERVLET_CONTEXT_ATTR_ADDED | 容器属性添加事件 |
| SERVLET_CONTEXT_ATTR_REMOVED | 容器属性移除事件 |
| SERVLET_CONTEXT_ATTR_REPLACED | 容器属性替换事件 |
| SESSION_CREATED | 会话创建事件 |
| SESSION_DESTROYED | 会话销毁事件 |
| SESSION_ATTR_ADDED | 会话属性添加事件 |
| SESSION_ATTR_REMOVEED | 会话属性移除事件 |
| SESSION_ATTR_REPLACED | 会话属性替换事件 |
| REQUEST_INITIALIZED | 请求初始化事件 |
| REQUEST_DESTROYED | 请求销毁事件 |
| REQUEST_ATTR_ADDED | 请求属性添加事件 |
| REQUEST_ATTR_REMOVEED | 请求属性移除事件 |
| REQUEST_ATTR_REPLACED | 请求属性替换事件 |
| REQUEST_RECEIVED | 接收控制器方法请求事件 |
| REQUEST_COMPLETED | 完成控制器方法请求事件 |
| REQUEST_UNEXPECTED_ERROR | 控制器方法执行过程中发生异常错误事件 |
控制器(Controller)
控制器是 MVC 体系中的核心,它负责处理浏览器发起的所有请求和决定响应内容的逻辑处理,控制器就是一个标准的 Java 类,不需要继承任何基类,通过类中的非静态公共方法向外部暴露接口,该方法的返回结果将决定向浏览器响应的具体内容。
下面通过示例展示如何编写控制器:
@Controller
@RequestMapping("/hello")
public class HelloController {
@RequestMapping(value = "/", method = {Type.HttpMethod.GET, Type.HttpMethod.POST})
public IView hello() throws Exception {
return View.textView("Everything depends on ability! -- YMP :)");
}
}
启动 Tomcat 服务并在浏览器中访问 http://localhost:8080/hello,页面输出结果:
Everything depends on ability! -- YMP :)
以上示例代码中使用了 @Controller 和 @RequestMapping 两个注解,它们的作用及参数含义是:
@Controller
声明一个类为控制器,框架在启动时将会自动扫描所有声明该注解的类并注册。
| 配置项 | 描述 |
|---|---|
| name | 控制器名称,默认为空(该参数暂时未被使用) |
| singleton | 是否为单例控制器,默认为 true |
@RequestMapping
声明控制器请求路径映射。
| 配置项 | 描述 |
|---|---|
| value | 控制器请求路径映射,默认为空(即使用方法名称) |
| method | 允许的请求方式,默认为 Type.HttpMethod.GET取值范围: GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS、TRACE |
| header | 请求头中必须存在的头名称 |
| param | 请求中必须存在的参数名称 |
| suffix | 允许的请求路径扩展名,默认为空数组(即不允许带扩展名),支持 .* 通配符匹配任意扩展名@since 2.1.4 |
示例一
创建非单例控制器,并完成如下规则设置:
- 设置控制器方法仅支持
POST和PUT方式访问。- 设置请求头参数中必须包含
X-Requested-With=XMLHttpRequest(即判断是否Ajax请求)。- 设置请求参数中必须存在
name参数。
@Controller(singleton = false)
@RequestMapping("/demo")
public class DemoController {
@RequestMapping(value = "/sayhi",
method = {Type.HttpMethod.POST, Type.HttpMethod.PUT},
header = {"X-Requested-With=XMLHttpRequest"},
param = {"name=*"})
public IView sayHi() {
return View.textView("Hi, YMPer!");
}
}
本例主要展示了如何使用 @Controller 和 @RequestMapping 注解对控制器和控制器方法对进配置,需要注意的是控制器方法必须使用 public 修饰,否则无效。
由于控制器类和方法上都声明了 @RequestMapping 注解,所以控制器方法的请求路径映射将继承上层配置,即:/demo/sayhi。
另外,@RequestMapping 注解的 header 和 param 参数值支持*通配符,如:key=*,表示请求中须存在名称为 key 的请求头或参数。
示例二
创建单例控制器,并完成如下规则设置:
- 通过注解设置响应头参数
X-From=China和X-Age=18。- 通过注解设置控制器返回文本视图,其返回内容为:
Hi, YMPer!
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/sayhi")
@ResponseView(value = "Hi, YMPer!", type = Type.View.TEXT)
@ResponseHeaders({
@ResponseHeader(name = "X-From", value = "China"),
@ResponseHeader(name = "X-Age", value = "18", type = Type.HeaderType.INT)})
public void sayHi() {
}
}
本例中使用了 @ResponseView、@ResponseHeaders 和 @ResponseHeader 三个新注解,它们的作用及参数含义是:
示例三:扩展名匹配
@since 2.1.4
使用 @RequestMapping 的 suffix 属性配置扩展名匹配:
@Controller
@RequestMapping("/demo")
public class SuffixController {
// 匹配带 .html 扩展名的请求
@RequestMapping(value = "/html", suffix = ".html")
public IView html() {
return View.textView("HTML View");
}
// 匹配带任意扩展名的请求
@RequestMapping(value = "/any", suffix = ".*")
public IView any(@RequestSuffix String suffix) {
return View.textView("Suffix: " + suffix);
}
// 匹配带 .json 或 .xml 扩展名的请求
@RequestMapping(value = "/format", suffix = {".json", ".xml"})
public IView format(@RequestSuffix String suffix) {
if ("json".equals(suffix)) {
return View.jsonView(Collections.singletonMap("message", "Hello JSON"));
} else if ("xml".equals(suffix)) {
return View.textView("<message>Hello XML</message>", "application/xml");
}
return View.textView("Unknown Format");
}
}
通过浏览器访问以下URL地址,输出结果:
-
http://localhost:8080/demo/html.html:HTML View -
http://localhost:8080/demo/any.txt:Suffix: txt -
http://localhost:8080/demo/format.json:{"message":"Hello JSON"} -
http://localhost:8080/demo/format.xml:<message>Hello XML</message>
在使用 @RequestMapping 的 suffix 属性配置扩展名匹配时,需要注意 webmvc.request_ignore_regex 配置项(默认值:jsp|jspx|png|gif|jpg|jpeg|js|css|swf|ico|htm|html|eot|woff|woff2|ttf|svg|map)可能会过滤掉指定的扩展名。如果需要匹配的扩展名在默认过滤列表中,需要修改该配置项以包含所需的扩展名。
例如,如果需要匹配 .html 扩展名,则需要从 request_ignore_regex 中移除 html,或者使用 ~ 符号在默认值基础上进行修改。
@RequestMapping 的 suffix 属性支持继承特性,配置优先级从高到低为:方法级别 > 类级别 > 包级别。
- 若方法上配置了
suffix属性,则使用方法上的配置 - 若方法上未配置,则使用类上的
@RequestMapping注解的suffix配置 - 若类上也未配置,则使用包上的
@RequestMapping注解的suffix配置 - 若包上类上方法上都有声明,则方法上的配置会替换掉类或包上的配置,并生效
示例:
// 包级别配置
@RequestMapping(suffix = ".*")
package com.example.controller;
// 类级别配置
@Controller
@RequestMapping(value = "/demo", suffix = ".html")
public class DemoController {
// 使用类级别的 .html 扩展名配置
@RequestMapping("/class")
public IView classLevel() {
return View.textView("Class level suffix");
}
// 使用方法级别的 .json 扩展名配置,覆盖类级别的配置
@RequestMapping(value = "/method", suffix = ".json")
public IView methodLevel() {
return View.jsonView(Collections.singletonMap("message", "Method level suffix"));
}
}
@ResponseView
声明控制器方法默认返回的视图,仅在方法无返回值或返回值无效时使用。
| 配置项 | 描述 |
|---|---|
| value | 视图模板文件路径,默认为空 |
| type | 视图文件类型,默认为 Type.View.NULL取值范围: BINARY、FORWARD、FREEMARKER、VELOCITY、HTML、HTTP_STATES、JSON、JSP、NULL、REDIRECT、TEXT |
@ResponseHeaders
设置控制器方法返回结果时增加响应头参数的注解。
| 配置项 | 描述 |
|---|---|
| value | 响应头参数集合 |
@ResponseHeader
声明一个响应头键值对。
| 配置项 | 描述 |
|---|---|
| name | 响应头键名称,必选参数 |
| value | 响应头值,默认为空 |
| type | 响应头值类型,默认为 Type.HeaderType.STRING取值范围: STRING、INT、DATE |
控制器参数(Parameter)
WebMVC 模块不但让编写控制器变得非常简单,处理请求参数也变得更加容易!WebMVC 会根据控制器方法参数或类成员的注解配置,自动转换与方法参数或类成员对应的数据类型,参数的绑定涉及以下注解:
基本参数注解
@RequestParam
绑定请求中的参数。
@RequestHeader
绑定请求头中的参数变量。
@CookieVariable
绑定Cookie中的参数变量。
以上注解拥有以下相同的配置参数:
| 配置项 | 描述 |
|---|---|
| prefix | 绑定的参数名称前缀,默认为空 |
| value | 绑定的参数名称,若未指定则默认采用方法参数变量名 |
| defaultValue | 默认值,默认为空 |
| fullScope | 是否尝试其它作用域下获取参数值,默认为 false优先级顺序:request > session > application,默认为仅从 request 中获取 |
示例代码
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/param")
public IView testParam(@RequestParam String name,
@RequestParam(defaultValue = "18") Integer age,
@RequestParam(value = "name", prefix = "user") String username,
@RequestHeader(defaultValue = "BASIC") String authType,
@CookieVariable(defaultValue = "false") Boolean isLogin) {
return View.textView(String.format("Hi, %s, UserName: %s, Age: %d, AuthType: %s, IsLogin: %s", name, username, age, authType, isLogin));
}
}
通过浏览器访问 URL 地址:http://localhost:8080/demo/param?name=webmvc&userName=ymper,输出结果:
Hi, webmvc, UserName: ymper, Age: 18, AuthType: BASIC, isLogin: false
特别的参数注解
@PathVariable
绑定请求映射中的路径参数变量。
| 配置项 | 描述 |
|---|---|
| value | 绑定的参数名称,若未指定则默认采用方法参数变量名 |
示例一
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/path/{userName}/{age}")
public IView testPath(@PathVariable(value = "userName") String name,
@PathVariable Integer age,
@RequestParam(prefix = "user") String sex) {
return View.textView(String.format("Hi, %s, Age: %d, Sex: %s", name, age, sex));
}
}
通过浏览器访问URL地址:http://localhost:8080/demo/path/webmvc/20?userSex=F,输出结果:
Hi, webmvc, Age: 20, Sex: F
WebMVC 模块可以通过实现 IRequestMappingParser 接口自定义请求路径规则分析器逻辑并通过配置 request_mapping_parser_class 参数使其生效,当使用默认的请求路径规则分析器时,路径中变量可以不连续,即以下格式均能正确解析:
- 正确:
/path/{name}/{age}- 正确:
/path/{name}/age/{sex}
@RequestSuffix
@since 2.1.4
绑定请求路径的扩展名值到控制器方法参数。
注意:此注解仅在控制器方法的 @RequestMapping 注解中配置了 suffix 属性时生效。
示例:
@Controller
@RequestMapping("/demo")
public class SuffixController {
// 将扩展名值注入到参数中
@RequestMapping(value = "/any", suffix = ".*")
public IView any(@RequestSuffix String suffix) {
return View.textView("Suffix: " + suffix);
}
}
通过浏览器访问URL地址:http://localhost:8080/demo/any.txt,输出结果:
Suffix: txt
@ModelBind
对象参数绑定注解。
注意:模型对嵌套绑定处理的支持并不完善,不推荐使用。当接收模型数组时将仅处理被声名
@RequestParam注解的成员属性。
| 配置项 | 描述 |
|---|---|
| prefix | 绑定的参数名称前缀,可选参数,默认为空 |
示例二
public class MemberDTO {
@PathVariable
private String name;
@RequestParam
private String sex;
@RequestParam(prefix = "ext")
private Integer age;
// 省略Get和Set方法
}
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/bind/{demo.name}")
public IView testBind(@ModelBind(prefix = "demo") MemberDTO member) {
return View.textView(String.format("Hi, %s, Age: %d, Sex: %s", member.getName(), member.getAge(), member.getSex()));
}
}
通过浏览器访问 URL 地址:http://localhost:8080/demo/bind/webmvc?demoSex=F&demoExtAge=20, 输出结果:
Hi, webmvc, Age: 20, Sex: F
-
从框架
v2.1.3版本开始将优先使用@VField(prefix = "xxx")替代@RequestParam、@RequestHeader、@CookieVariable注解的前缀属性。 -
从框架
v2.1.3版本开始将优先使用@VModel(prefix = "xxx")替代@ModelBind注解的前缀属性。 -
当前缀以
.或_结尾时,前缀与参数名拼接时将不做任何处理,否则与前缀拼接时参数名称首字母将大写,如:@RequestParam @VField(prefix = "user.") String name; // 参数名:user.name
@RequestParam @VField(prefix = "user") String name; // 参数名:userName -
@RequestHeader和@CookieVariable注解的前缀与参数名拼接时同样不会做任何处理。 -
当使用
@EnableSnakeCaseParam注解时,参数名称将自动转换后与前缀拼接,如:@RequestParam @VField(prefix = "real_") @EnableSnakeCaseParam String userName; // 参数名:real_user_name
@RequestParam @VField(prefix = "real") @EnableSnakeCaseParam String userName; // 参数名:realUser_name
@SplitArrayWith
字符串数组拆分注解。
| 配置项 | 描述 |
|---|---|
| separator | 指定的用于拆分字符串数组的分隔符,默认值为:, |
示例三
@Controller
@RequestMapping("/demo")
public class ArrayController {
@RequestMapping("/splitArray")
public IView split(@RequestParam @SplitArrayWith String[] names) throws Exception {
return View.textView(StringUtils.join(names, ", "));
}
}
通过浏览器访问 URL 地址:http://localhost:8080/demo/splitArray?names=A&names=B&names=C,输出结果:
A, B, C
在请求的 URL 地址中有多个 names 参数,这是最常用的数组类型参数传递方式,但在拼装URL参数时相对比较麻烦,这时我们可以通过 @SplitArrayWith 注解,让其支持以下方式进行数组参数的传递:
http://localhost:8088/demo/splitArray?names=A,B,C
数组元素之间的分隔符默认为 ,,可以通过注解中的 separator 参数自定义。
参数签名验证
为了保障接口调用的安全性,验证接口访问者身份的合法、有效,避免请求参数被非法篡改等,目前比较简单、有效的方法是对请求的参数进行签名,参数签名需要将请求中的参数按照规则并行拼装并使用客户端与服务端约定的密钥进行加密,WebMVC 模块将常用的签名逻辑进行封装并允许通过 @SignatureValidate 注解进行自定义配置。
@SignatureValidate
用于开启和配置参数签名验证规则。
| 配置项 | 描述 |
|---|---|
| paramName | 签名参数名称,默认值: sign |
| nonceName | 随机参数名称,如: nonce |
| timestampName | 时间戳参数名称,默认值: timestamp |
| timeLifecycle | 时间戳有效时间(秒),默认值: 30,值为 0 表示不检查 |
| encode | 是否进行URLEncoder编码,默认值: fasle |
| upperCase | 是否转换签名字符串为大写,默认值: true |
| disabled | 是否已禁用,默认值: fasle |
| excludedParams | 排除的参数名称集合 |
| validatorClass | 签名验证器类 |
| parserClass | 签名参数分析器类型 |
| processorClass | 附加签名参数处理器类型 |
默认实现的签名规则
-
将所有参与签名的参数放入集合中并将参数名称按
ASCII码从小到大(字典序)排序; -
参数值为空不参与签名;
-
参数名称区分大小写且参数内容不要进行
URLEncoder编码处理; -
签名参数(如:
sign)本身不参与签名; -
将参与签名的参数以URL键值对的格式进行拼接,示例如下:
String signStr = "client_id=CLIENT_ID&create_time=1487178385184&event=subscribe&union_id=o6_bmasdasdsad6_2sgVt7hMZOPfL"; -
将拼接好的
signStr字符串与client_secret密钥进行拼接并生成签名,示例如下:// 拼接密钥
signStr = signStr + "&client_secret=6bf18fa2f9a136273fb90e58dff4a964";
// 执行SHA1签名并将字符转换为大写
signStr = DigestUtils.sha1Hex(signStr).toUpperCase(); -
如果有必要,可以在请求参数集合中增加随机参数(如:
nonce),通过随机数函数生成并转换为字符串,从而保证签名的不可预测性; -
客户端与服务端均采用相同规则进行参数签名后进行结果比对,两端签名结果一致则验签通过;
示例代码
@Controller
@RequestMapping("/demo")
public class SignController {
@RequestMapping("/sign")
@EnableSnakeCaseParam
@SignatureValidate(nonceName = "nonce", timestampName = "create_time")
public IView sign(@RequestParam String clientId,
@RequestParam Long createTime,
@RequestParam String event,
@RequestParam String unionId) throws Exception {
return View.textView("Hi!");
}
}
通过浏览器访问 URL 地址:http://localhost:8080/demo/sign?client_id=...&create_time=...&nonce=...&sign=...,若请求中 缺少签名所需参数或验签失败时,都将响应如下错误(以JSON格式为例)报文:
{"ret":-8,"msg":"参数签名无效","data":{}}
开启SnakeCase参数命明方式
在上例代码中,我们用到了 @EnableSnakeCaseParam 注解,该注解可以应用在包、类和方法之上,它的作用是将驼峰式参数名称自动转换为采用下划线分隔的方式,上例中的参数名称转换后的结果如下:
| 原参数名称 | 转换后 |
|---|---|
| clientId | client_id |
| createTime | create_time |
| event | event |
| unionId | union_id |
非单例控制器的特殊用法
控制器按其运行模式可以分为单例和非单例两种,单例控制器与非单例控制器的区别:
- 单例控制器类在向模块注册时就已经被实例化。
- 非单例控制器类则是在每次接收到请求时都将创建实例对象,请求结束后该实例对象将被释放。
综上所述,非单例控制器是可以通过类成员来接收请求参数的,示例代码如下:
@Controller(singleton = false)
@RequestMapping("/demo")
public class DemoController {
@RequestParam
private String content;
@RequestMapping("/sayHi")
public IView sayHi(@RequestParam String name) {
return View.textView("Hi, " + name + ". " + content);
}
}
通过浏览器访问 URL 地址:http://localhost:8080/demo/sayHi?name=YMPer&content=Welcome!,输出结果:
Hi, YMPer. Welcome!
在单例模式下,WebMVC 模块将忽略对控制器类成员赋值,同时也建议在单例模式下不要使用成员变量做为参数,在并发多线程环境下会产生意想不到的问题!!
环境上下文对象(WebContext)
为了让开发人员能够随时随地获取和使用像 Application、Request、Response、Session 等 Web 容器对象,在 WebMVC 模块中提供了一个名叫 WebContext 的 Web 环境上下文封装类,简单又实用,先了解一下它提供的一些方法:
获取Web容器对象
- 获取 ServletContext 对象:
WebContext.getServletContext();
- 获取 HttpServletRequest 对象:
WebContext.getRequest();
- 获取 HttpServletResponse 对象:
WebContext.getResponse();
- 获取 PageContext 对象:
WebContext.getPageContext();
获取WebMVC容器对象
- 获取 IRequestContext 对象:该对象为 WebMVC 请求上下文接口,主要用于分析请求路径及存储相关参数。
WebContext.getRequestContext();
- 获取 WebContext 对象当前线程实例:
WebContext.getContext();
WebContext 将 Application、Session、Request 等 Web 容器对象的属性转换成 Map 映射存储,同时向 Map 的赋值也将自动同步至 Web 容器对象中,起初的目的是为了能够方便代码移植并脱离 Web 环境依赖进行开发测试(功能参考 Struts2):
-
WebContext.getContext().getApplication();
-
WebContext.getContext().getSession();
-
WebContext.getContext().getAttribute(Type.Context.REQUEST);
-
WebContext.getContext().getAttributes();
-
WebContext.getContext().getLocale();
-
WebContext.getContext().getOwner();
-
WebContext.getContext().getParameters();
WebContext对象方法
Application相关
-
boolean getApplicationAttributeToBoolean(String name);
-
int getApplicationAttributeToInt(String name);
-
long getApplicationAttributeToLong(String name);
-
String getApplicationAttributeToString(String name);
-
<T> T getApplicationAttributeToObject(String name);
-
WebContext addApplicationAttribute(String name, Object value)
Session相关
-
boolean getSessionAttributeToBoolean(String name);
-
int getSessionAttributeToInt(String name);
-
long getSessionAttributeToLong(String name);
-
String getSessionAttributeToString(String name);
-
<T> T getSessionAttributeToObject(String name);
-
WebContext addSessionAttribute(String name, Object value)
Request相关
- boolean getRequestAttributeToBoolean(String name);
- int getRequestAttributeToInt(String name);
- long getRequestAttributeToLong(String name);
- String getRequestAttributeToString(String name);
- <T> T getRequestAttributeToObject(String name);
- WebContext addRequestAttribute(String name, Object value)
Parameter相关
-
boolean getParameterToBoolean(String name);
-
int getParameterToInt(String name)
-
long getParameterToLong(String name);
-
String getParameterToString(String name);
Attribute相关
- <T> T getAttribute(String name);
- WebContext addAttribute(String name, Object value);
上传文件相关
- IUploadFileWrapper getUploadFile(String name);
- IUploadFileWrapper[] getUploadFiles(String name);
- Set<IUploadFileWrapper> getUploadFiles();
文件上传(Upload)
WebMVC 模块针对文件的上传处理以及对上传的文件操作都非常的简单,通过注解就轻松搞定:
@FileUpload
声明控制器方法需要处理上传的文件流,无参数。
需要注意的是文件上传处理的表单 enctype 属性值必须是 multipart/form-data,示例:
<form action="/demo/upload" method="POST" enctype="multipart/form-data">
......
</form>
IUploadFileWrapper
上传文件包装器接口,自定义的控制器方法参数类型,用于包装已上传的文件对象并提供对文件操作的一系列方法:
| 方法名称 | 描述 |
|---|---|
| String getPath() | 获取完整的文件名及路径 |
| String getName() | 获取文件名称 |
| long getSize() | 获取文件大小 |
| File getFile() | 获取临时文件对象 |
| String getContentType() | 获取文件 Content-Type 值 |
| void transferTo(File dest) | 转移文件 |
| void writeTo(File dest) | 保存文件 |
| void delete() | 删除文件 |
| InputStream getInputStream() | 获取文件输入流对象 |
| OutputStream getOutputStream() | 获取文件输出流对象 |
示例一
@Controller
@RequestMapping("/demo")
public class UploadController {
// 处理单文件上传
@RequestMapping(value = "/upload", method = Type.HttpMethod.POST)
@FileUpload
public IView doUpload(@RequestParam IUploadFileWrapper file) throws Exception {
// 获取文件名称
file.getName();
// 获取文件大小
file.getSize();
// 获取完整的文件名及路径
file.getPath();
// 获取文件Content-Type
file.getContentType();
// 转移文件
file.transferTo(new File("/temp", file.getName()));
// 保存文件
file.writeTo(new File("/temp", file.getName()));
// 删除文件
file.delete();
// 获取文件输入流对象
file.getInputStream();
// 获取文件输出流对象
file.getOutputStream();
// ......
return View.nullView();
}
// 处理多文件上传
@RequestMapping(value = "/uploads", method = Type.HttpMethod.POST)
@FileUpload
public IView doUploadBatch(@RequestParam IUploadFileWrapper[] files) throws Exception {
// ......
return View.nullView();
}
}