SpringMVC介绍之约定优于配置

所谓的约定优于配置就是指在程序开发过程中我们约定好一些规则可以使我们更少的进行配置和代码编写。就这么简单的一句话可能你还不是很懂什么是约定优于配置,没关系,看完后面对SpringMVC的约定优于配置的介绍之后你就会明白了。

SpringMVC对约定优于配置的支持主要表现在三个方面,Model、View和Controller。

Model:SpringMVC对Model的约定优于配置的支持是基于ModelMap的,由于ModelAndView中的Model就是一个ModelMap,所以ModelAndView也是支持的。约定优于配置在Model上的表现是通过ModelMap的addObject方法实现的,当我们需要往ModelMap中添加一个对象的时候,我们可以只添加一个对象,而不指定其对应的属性名称,即调用addObject(Object obj)方法,而不是调用addObject(String attrName, Object obj)方法。这个时候Spring就会根据它自身约定的一套机制给我们一个默认的属性名称。Spring的约定是这样的:

(1)采用这种方式添加的对象不能为null,所以如果添加的对象有可能为null时就不应该使用这种方式,还是使用addObject(String attrName,Object obj)方法指定一个属性名称比较好。

(2)采用这种方式添加的集合Collection不能为empty,同样如果该Collection有可能为empty的时候就不应该使用这种方式。

(3)简单对象,当是一个简单对象的时候取该对象的类名称,不包括包名,然后采用JavaBean的属性命名规则来确定属性名称,如添加com.host.app.model.User对象时取的属性名称就是user,添加com.host.app.model.HelloWorld对象时取的属性名称就是helloWorld,添加com.host.app.model.ABCdef对象时取的属性名称就是ABCdef。

(4)数组,当添加的是一个数组的时候,Spring会取该数组的类型按照规则3取到一个名称作为基名称,之后再加上List后缀作为约定的属性名称。如添加一个Object数组时,Spring约定的属性名称就是objectList,添加一个com.host.app.model.ABCdef数组时,Spring取的属性名称就是ABCdefList。

(5)集合Collection,当添加的是一个集合的时候,Spring会采用Iterator的方式取该集合的第一个元素类型按照规则3取到一个名称作为基名称,之后再加上List后缀作为约定的名称。如添加一个List时,如果取出的该List的第一个元素是java.lang.Object对象,那么Spring取的属性名称就是objectList;添加一个包含的都是com.host.app.model.User的Set时,Spring取的属性名称就是userList;如果添加的HashSet中同时包含多种类型的对象时就应该使用addObject(String attrName, Object obj)方法指定自己的属性名称,因为HashSet内部的元素是无序的,每次取的第一个元素可能不一样,这也就会导致Spring取的属性名称会不一样。

看下面一个示例,当我们访问控制器处理方法testModel时,往返回的模型中添加了一个包含User的List对象,这样在ModelAndView内部内置的ModelMap中就会采用约定好的方式取userList为该对象在模型中的属性名称。

  1. public ModelAndView testModel() {
  2. ModelAndView mav = new ModelAndView("modelTest");
  3. User user = new User(1, "zhangsan");
  4. List<User> list = new ArrayList<User>();
  5. list.add(user);
  6. mav.addObject(list);
  7. return mav;
  8. }

View:当Controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过Spring定义的org.springframework.web.servlet.RequestToViewNameTranslator接口的getViewName方法来实现的,我们可以实现自己的RequestToViewNameTranslator接口来约定好没有返回视图名称的时候如何确定视图名称。Spring已经给我们提供了一个它自己的实现,那就是org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。

在介绍DefaultRequestToViewNameTranslator是如何约定视图名称之前我们先来看一下它支持用户定义的属性:

(1)prefix:前缀,表示约定好的视图名称需要加上的前缀,默认是空串。

(2)suffix:后缀,表示约定好的视图名称需要加上的后缀,默认是空串。

(3)separator:分隔符,默认是斜杠“/”。

(4)stripLeadingSlash:如果首字符是分隔符,是否要去除,默认是true。

(5)stripTrailingSlash:如果最后一个字符是分隔符,是否要去除,默认是true。

(6)stripExtension:如果请求路径包含扩展名是否要去除,默认是true。

(7)urlDecode:是否需要对URL解码,默认是true。它会采用request指定的编码或者ISO-8859-1编码对URL进行解码。

当我们没有在SpringMVC的配置文件中手动的定义一个名为viewNameTranlator的bean的时候,Spring就会为我们提供一个默认的viewNameTranslator,即DefaultRequestToViewNameTranslator。

接下来看一下,当Controller处理器方法没有返回逻辑视图名称的时候,DefaultRequestToViewNameTranslator是如何约定视图名称的。DefaultRequestToViewNameTranslator会获取到请求的URI,然后根据提供的属性做一些改造,把改造之后的结果作为视图名称返回。我们以请求路径http://localhost/app/product/index.html为例来说明DefaultRequestToViewNameTranslator是如何工作的。该请求路径对应的请求URI为/product/index.html,我们来看以下几种情况,它分别对应的逻辑视图名称是什么。

(1)prefix和suffix都存在,其他为默认值是对应返回的逻辑视图名称应该是prefixproduct/indexsuffix。

(2)stripLeadingSlash和stripExtension都为false,其他默认,这时候对应的逻辑视图名称是/product/index.html。

(3)都采用默认配置时,返回的逻辑视图名称应该是product/index。

如果我们的逻辑视图名称都跟请求路径相同或者相关关系是一样的,我们就可以采用Spring为我们事先约定好的逻辑视图名称返回,这可以大大简化我们的开发工作。

下面是一个在SpringMVC配置文件中配置一个RequestToViewNameTranslator bean的示例。记住,该bean的名称只能为viewNameTranslator,如果不为viewNameTranslator时,Spring还是会使用默认配置的DefaultRequestToViewNameTranslator来约定逻辑视图名称。

  1. <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator">
  2. <property name="prefix" value="prefix"/>
  3. <property name="suffix" value="suffix"/>
  4. <property name="stripLeadingSlash" value="false"/>
  5. <property name="stripExtension" value="false"/>
  6. </bean>

Controller:此处讲SpringMVC对约定优于配置的支持是基于注解配置Controller的情况。考虑这样一种情况:定义了一个Controller,名为MyController,然后在MyController类上使用了@Controller注解进行标记,没有类级别的@RequestMapping。MyController中定义了一个处理器方法add,该方法使用了@RequestMapping注解标记。大概就是下面这个样子。

  1. @Controller
  2. public class MyController {
  3. @RequestMapping("add")
  4. public String add() {
  5. return "add";
  6. }
  7. }

这个时候如果我们需要访问到处理器方法add的话,我们需要请求/add.do,然后有另外一个AnotherController,里面也有一个add方法对应/add.do请求,这个时候如果你再请求/add.do的时候Spring就不知道到底该访问那个方法,然后就会抛出异常,这个时候我们最简单的解决办法就是在MyController上加上类级别的@RequestMapping用以区别,表示请求的路径到底是哪个Controller下面的,类似的情况还有我们会把相关的处理器方法放在一个Controller中,比如UserController下面会有user的add方法,user的del方法等。Spring为我们提供了一种机制可以自动根据我们的Controller类名称来进行请求映射,这是通过在SpringMVC的配置文件中定义一个org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping bean来实现的。通过它的名称我们可以知道ControllerClassNameHandlerMapping就是通过Controller的类名称来进行类级别的请求映射的。使用ControllerClassNameHandlerMapping的默认属性配置的时候Spring是这样来进行请求映射的:

(1)首先取得去掉包名称的Controller类名称。

(2)如果该类名称是以Controller结尾,则只取Controller前面的部分,如MyController取My。

(3)将取到的名称取全部小写,如MyHome取myhome。

(4)然后将上面取到的名称映射为/name/*,如上面取到的名称是myhome,则映射为/myhome/*。

  1. @Controller
  2. public class MyController {
  3. @RequestMapping("add")
  4. public String add() {
  5. return "add";
  6. }
  7. }

还是这一段代码,当我们使用ControllerClassNameHandlerMapping来进行映射的时候,我们需要访问MyController的处理器方法add的时候就需要请求/my/add.do。

ControllerClassNameHandlerMapping支持用户定义如下属性:

(1)caseSensitive:大小写是否敏感,默认是false,该属性主要是用来生成映射路径的时候匹配大小写是否敏感,如果为true,则只把Controller类名称的首字母小写,其他的保持原样进行请求路径映射,如果为false,则全部小写。basePackage也使用同样的规则,只是basePackage在caseSensitive为true的时候不需要首字母小写。

(2)basePackage:表示要用于生成路径映射的基包,默认是null,这个时候就采用Controller不包含包名称的类名称来映射,映射规则跟之前介绍的映射规则相同。如果定义了basePackage,假设为com.host.app,这个时候如果Controller类的全名称是com.host.app.abc.edf.MyController,那么映射的路径就是/abc/edf/my/*。

(3)pathPrefix:表示映射路径的前缀,默认是空串。假设pathPrefix为prefix,basePackage为com.host.app,那么com.host.app.abc.MyController的映射路径就是/prefix/abc/my/*。

(4)excludedPackages:是数组形式,表示需要把哪些包排除在ControllerClassNameHandlerMapping的映射范围之内。

(5)excludedClasses:是数组形式,表示需要把哪些类排除在ControllerClassNameHandlerMapping的映射范围之内。

在下面一个例子就排除了com.host.app.web.mymodule包、com.host.app.web.modulea.MyController和com.host.app.web.moduleb.UserController。

  1. <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
  2. <property name="order" value="1"/>
  3. <property name="excludedPackages">
  4. <array>
  5. <value>com.host.app.web.mymodule </value>
  6. </array>
  7. </property>
  8. <property name="excludedClasses">
  9. <array>
  10. <value>com.host.app.web.modulea.MyController</value>
  11. <value>com.host.app.web.moduleb.UserController</value>
  12. </array>
  13. </property>
  14. </bean>

使用ControllerClassNameHandlerMapping需要注意的地方:

(1)需要使用ControllerClassNameHandlerMapping来映射的Controller类上如果加了@RequestMapping注解ControllerClassNameHandlerMapping也是可以进行URL路径映射的。

(2)使用ControllerClassNameHandlerMapping映射的是类似于/controllerName/*这样的形式,这也就是说只有在处理器方法映射不存在斜杠的时候才可以使用这种形式访问到。看下面一个例子,在下面代码中MyController类能够映射的请求路径是/my/*,这也意味着只有满足/my/*的请求路径才能映射到MyController,才能访问到它里面的处理器方法,所以当请求/my/test1.do的时候毫无疑问可以访问到处理器方法test1,但是当想访问MyController的test2方法,请求/my/test/test2.do的时候由于它不能映射到MyController,所以不能如愿的访问到MyController的test2方法。这也是ControllerClassNameHandlerMapping 一个缺陷。

  1. @Controller
  2. public class MyController {
  3. @RequestMapping("test1")
  4. public String test1() {
  5. return "test";
  6. }
  7. @RequestMapping("test/test2")
  8. public String test2() {
  9. return "test";
  10. }
  11. }

(3)由于在SpringMVC应用中可以同时定义多个HandlerMapping,这就涉及到一个映射的优先级问题。HandlerMapping都实现了Ordered接口,所以我们可以通过HandlerMapping的order属性来指定匹配映射的先后顺序。我们知道在ViewResolver链中,如果一个逻辑视图被一个ViewResolver解析了之后,该次视图解析就结束了,其他的视图解析器就不能再解析这个视图了,它的order属性是用来定义ViewResolver进行视图解析的先后顺序的。但是HandlerMapping不一样,它是在SpringMVC的配置文件中定义的所有的HandlerMapping都可以进行URL映射,它的order属性是用于指定映射匹配的先后顺序的。看一个例子:

  1. <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping">
  2. <property name="order" value="1"/>
  3. </bean>
  4. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
  5. <property name="order" value="10"/>
  6. </bean>

我们在SpringMVC的配置文件里面定义了两个HandlerMapping,代码如上所示,由它们的order属性我们知道ControllerClassNameHandlerMapping会先于DefaultAnnotationHandlerMapping进行映射匹配。定义了一个MyTestController,代码如下所示:

  1. @Controller
  2. public class MyTestController {
  3. @RequestMapping("mytest/test")
  4. public void test() {
  5. System.out.println("--------hello test---------");
  6. }
  7. @RequestMapping("test")
  8. public void test2() {
  9. System.out.println("--------hello test2---------");
  10. }
  11. }

我们知道ControllerClassNameHandlerMapping会把MyTestController映射为“/mytest/*”,按照这种方式我们只能利用/mytest/test.do请求到MyTestController的test2方法,而没法利用/mytest/mytest/test.do请求到test方法。而DefaultAnnotationHandlerMapping会把MyTestController的test2方法映射为“/test.do”,把test方法映射为“/mytest/test.do”。而根据Spring定义了多个HandlerMapping就会有多个映射机制存在的这么一个机制我们知道上述几种映射关系都是会存在的。那么这个时候如果我请求/mytest/test.do会请求哪个方法呢?我们知道,如果是按照ControllerClassNameHandlerMapping的映射机制会访问MyTestController的test2方法,而按照DefaultAnnotationHandlerMapping的映射机制就会访问MyTestController的test方法。这个时候HandlerMapping的order属性就起作用了,order属性越小的就会先匹配,由上面的配置我们知道ControllerClassNameHandlerMapping的order属性相对较小,所以将使用它的映射URL来匹配这次请求,所以处理的是MyTestController的test2方法。

SpringMVC介绍之约定优于配置的更多相关文章

  1. convention over configuration 约定优于配置 按约定编程 约定大于配置 PEP 20 -- The Zen of Python

    为什么说 Java 程序员必须掌握 Spring Boot ?_知识库_博客园 https://kb.cnblogs.com/page/606682/ 为什么说 Java 程序员必须掌握 Spring ...

  2. spring boot约定优于配置的这种做法在如今越来越流行了

    约定优于配置的这种做法在如今越来越流行了,它的特点是简单.快速.便捷.但是这是建立在程序员熟悉这些约定的前提上.而 Spring 拥有一个庞大的生态体系,刚开始转到 Spring Boot 完全舍弃 ...

  3. Maven之(八)约定优于配置

    maven的配置文件看似很复杂,其实只需要根据项目的实际背景,设置个别的几个配置项而已.maven有自己的一套默认配置,使用者除非必要,并不需要去修改那些约定内容.这就是所谓的"约定优于配置 ...

  4. spring boot中的约定优于配置

    Spring Boot并不是一个全新的框架,而是将已有的Spring组件整合起来. Spring Boot可以说是遵循约定优于配置这个理念产生的.它的特点是简单.快速和便捷. 既然遵循约定优于配置,则 ...

  5. Struts2注解 及 约定优于配置

    Struts2注解 1 Struts2注解的作用 使用注解可以用来替换struts.xml配置文件!!! 2 导包 必须导入struts2-convention-plugin-2.3.15.jar包, ...

  6. “约定优于配置”与Magento改造尝试四之block、helper和model载入

    暂定本章为这个系列最后一章,还是继续沿用模块的别名(alias)概念 <modules> <Mage_Wishlist> <version>1.6.0.0</ ...

  7. 8.Maven之(八)约定优于配置

    转自:“https://blog.csdn.net/qq_25460531/article/details/79423961” maven的配置文件看似很复杂,其实只需要根据项目的实际背景,设置个别的 ...

  8. 【面试普通人VS高手系列】Spring Boot的约定优于配置,你的理解是什么?

    对于Spring Boot约定优于配置这个问题,看看普通人和高手是如何回答的? 普通人的回答: 嗯, 在Spring Boot里面,通过约定优于配置这个思想,可以让我们少写很多的配置, 然后就只需要关 ...

  9. Spring 4 官方文档学习(十一)Web MVC 框架之约定优于配置

    当返回一个ModelAndView时,可以使用其addObject(Object obj)方法,此时的约定是: An x.y.User instance added will have the nam ...

随机推荐

  1. 单片机IO处理 电容触摸按键

    原理说明: 通过检测感应按键PAD的电容量变化来判断是否有触摸动作.当手指触摸PAD时,电容量增加,充放电时间变长. 本方案中利用M48的20个双向IO口实现了20个触摸按键,而且所用原器件最少.其中 ...

  2. java学习之查找

    在一组数据当中我们取出一个我们想要的数据的过程,谓之查找. 1.简单查找: 需求:在一组数据当中找到你想要的一个数据,并且返回该数据在数组当中的索引. 思路:循环遍历整个数组,然后拿各个元素与所要找出 ...

  3. [LeetCode#82]Remove Duplicates from Sorted Array II

    Problem: Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? F ...

  4. 「Poetize4」玉蟾宫

    描述 Description 这片土地被分成N*M个格子,每个格子里写着'R'或者'F',R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda.现在freda要在这里卖萌...它要找 ...

  5. SQL SERVER大数据分页

    select * from (select rownum r, a.* from (select * from  table_name order by ndatetime desc ) a wher ...

  6. Qt入门(8)——事件和事件过滤器

    在Qt里,一个事件是继承自QEvent的对象.事件通过调用QObject::event(),被发送到继承自 QObject 的对象.事件发送就是一个事件已经产生,由 QEvent正好去表达,且QObj ...

  7. Delphi 客户端调用Webservice 的TClientdataset 报出“http://www.borland.com/namespaces/Types-IAppServerSOAP”

    http://www.borland.com/namespaces/Types-IAppServerSOAP 服务器未能识别 HTTP 头 SOAPAction 的值 (2011-04-25 16:4 ...

  8. Vagrant 集群的部署

    使用Vagrant部署集群 一.运行多个虚拟机 我们通过配置Vagrantfile配置两个虚拟机--web服务器和数据库服务器. Vagrant::configure("2") d ...

  9. POJ 3050 穷举

    题意:给定一个5*5的地图,每个格子上有一个数字.从一个格子出发(上下左右4个方向),走5步将数字连起来可以构造出一个6位数.问该地图可以构造出多少个不同的6位数. 分析:可以对每个格子做深度优先遍历 ...

  10. jSP的3种方式实现radio ,checkBox,select的默认选择值。

    jSP的3种方式实现radio ,checkBox,select的默认选择值.以radiao 为例:第一种方式:在jsp中使用java 脚本,这个方法最直接,不过脚本太多,不容易维护<%Stri ...