飞道的博客

【Java EE】Spring MVC开发流程详解

326人阅读  评论(0)

Spring MVC开发流程详解


有了上文的初始化配置,开发Spring MVC流程并不困难。开发Spring MVC程序,需要掌握Spring MVC的组件和流程,所以开发过程中也会贯穿着Spring MVC的运行流程。

在目前的开发过程中,大部分都会采用注解的开发方式。使用注解在Spring MVC中十分简单,主要是以一个注解@Controller标注,一般只需要通过扫描配置,就能够将其扫描处理,只是往往还要结合注解@RequestMapping去配置它。@RequestMapping可以配置在类或者方法之上,它的作用是指定URI和哪个类(或者方法)作为一个处理请求的处理器,为了更加灵活,Spring MVC还定义了处理器的拦截器,当启动Spring MVC的时候,Spring MVC就会去解析@Controller中@RequestMapping的配置,再结合所配置的拦截器,这样它就会组成多个拦截器和一个控制器的形式,存放到一个HandlerMapping中去。当请求来到服务器,首先是通过请求信息找到对应的HandlerMapping,进而可以找到对应的拦截器和处理器,这样就能够运行对应的控制器和拦截器。

1. 配置@RequestMapping

@RequestMapping的源码如下:


  
  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Mapping
  5. public @interface RequestMapping {
  6. /**
  7. * Assign a name to this mapping.
  8. * <p><b>Supported at the type level as well as at the method level!</b>
  9. * When used on both levels, a combined name is derived by concatenation
  10. * with "#" as separator.
  11. * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
  12. * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
  13. */
  14. // 请求路径
  15. String name() default "";
  16. /**
  17. * The primary mapping expressed by this annotation.
  18. * <p>In a Servlet environment this is an alias for {@link #path}.
  19. * For example {@code @RequestMapping("/foo")} is equivalent to
  20. * {@code @RequestMapping(path="/foo")}.
  21. * <p>In a Portlet environment this is the mapped portlet modes
  22. * (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
  23. * <p><b>Supported at the type level as well as at the method level!</b>
  24. * When used at the type level, all method-level mappings inherit
  25. * this primary mapping, narrowing it for a specific handler method.
  26. */
  27. // 请求路径,可以是数组
  28. @AliasFor( "path")
  29. String[] value() default {};
  30. /**
  31. * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
  32. * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
  33. * At the method level, relative paths (e.g. "edit.do") are supported within
  34. * the primary mapping expressed at the type level. Path mapping URIs may
  35. * contain placeholders (e.g. "/${connect}")
  36. * <p><b>Supported at the type level as well as at the method level!</b>
  37. * When used at the type level, all method-level mappings inherit
  38. * this primary mapping, narrowing it for a specific handler method.
  39. * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
  40. * @since 4.2
  41. */
  42. // 请求路径,数组
  43. @AliasFor( "value")
  44. String[] path() default {};
  45. /**
  46. * The HTTP request methods to map to, narrowing the primary mapping:
  47. * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
  48. * <p><b>Supported at the type level as well as at the method level!</b>
  49. * When used at the type level, all method-level mappings inherit
  50. * this HTTP method restriction (i.e. the type-level restriction
  51. * gets checked before the handler method is even resolved).
  52. * <p>Supported for Servlet environments as well as Portlet 2.0 environments.
  53. */
  54. // 请求类型,比如是HTTP的GET请求还是POST请求等,HTTP请求枚举取值范围为:GET、HEAD、PUT、PATCH、DELETE、OPTIONS、TRACE,常用的是GET和POST请求
  55. RequestMethod[] method() default {};
  56. /**
  57. * The parameters of the mapped request, narrowing the primary mapping.
  58. * <p>Same format for any environment: a sequence of "myParam=myValue" style
  59. * expressions, with a request only mapped if each such parameter is found
  60. * to have the given value. Expressions can be negated by using the "!=" operator,
  61. * as in "myParam!=myValue". "myParam" style expressions are also supported,
  62. * with such parameters having to be present in the request (allowed to have
  63. * any value). Finally, "!myParam" style expressions indicate that the
  64. * specified parameter is <i>not</i> supposed to be present in the request.
  65. * <p><b>Supported at the type level as well as at the method level!</b>
  66. * When used at the type level, all method-level mappings inherit
  67. * this parameter restriction (i.e. the type-level restriction
  68. * gets checked before the handler method is even resolved).
  69. * <p>In a Servlet environment, parameter mappings are considered as restrictions
  70. * that are enforced at the type level. The primary path mapping (i.e. the
  71. * specified URI value) still has to uniquely identify the target handler, with
  72. * parameter mappings simply expressing preconditions for invoking the handler.
  73. * <p>In a Portlet environment, parameters are taken into account as mapping
  74. * differentiators, i.e. the primary portlet mode mapping plus the parameter
  75. * conditions uniquely identify the target handler. Different handlers may be
  76. * mapped onto the same portlet mode, as long as their parameter mappings differ.
  77. */
  78. // 请求参数,当请求带有配置的参数时,才匹配处理器
  79. String[] params() default {};
  80. /**
  81. * The headers of the mapped request, narrowing the primary mapping.
  82. * <p>Same format for any environment: a sequence of "My-Header=myValue" style
  83. * expressions, with a request only mapped if each such header is found
  84. * to have the given value. Expressions can be negated by using the "!=" operator,
  85. * as in "My-Header!=myValue". "My-Header" style expressions are also supported,
  86. * with such headers having to be present in the request (allowed to have
  87. * any value). Finally, "!My-Header" style expressions indicate that the
  88. * specified header is <i>not</i> supposed to be present in the request.
  89. * <p>Also supports media type wildcards (*), for headers such as Accept
  90. * and Content-Type. For instance,
  91. * <pre class="code">
  92. * &#064;RequestMapping(value = "/something", headers = "content-type=text/*")
  93. * </pre>
  94. * will match requests with a Content-Type of "text/html", "text/plain", etc.
  95. * <p><b>Supported at the type level as well as at the method level!</b>
  96. * When used at the type level, all method-level mappings inherit
  97. * this header restriction (i.e. the type-level restriction
  98. * gets checked before the handler method is even resolved).
  99. * <p>Maps against HttpServletRequest headers in a Servlet environment,
  100. * and against PortletRequest properties in a Portlet 2.0 environment.
  101. * @see org.springframework.http.MediaType
  102. */
  103. // 请求头,当HTTP请求头为配置项时,才匹配处理器
  104. String[] headers() default {};
  105. /**
  106. * The consumable media types of the mapped request, narrowing the primary mapping.
  107. * <p>The format is a single media type or a sequence of media types,
  108. * with a request only mapped if the {@code Content-Type} matches one of these media types.
  109. * Examples:
  110. * <pre class="code">
  111. * consumes = "text/plain"
  112. * consumes = {"text/plain", "application/*"}
  113. * </pre>
  114. * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
  115. * all requests with a {@code Content-Type} other than "text/plain".
  116. * <p><b>Supported at the type level as well as at the method level!</b>
  117. * When used at the type level, all method-level mappings override
  118. * this consumes restriction.
  119. * @see org.springframework.http.MediaType
  120. * @see javax.servlet.http.HttpServletRequest#getContentType()
  121. */
  122. // 请求类型为配置类型才匹配处理器
  123. String[] consumes() default {};
  124. /**
  125. * The producible media types of the mapped request, narrowing the primary mapping.
  126. * <p>The format is a single media type or a sequence of media types,
  127. * with a request only mapped if the {@code Accept} matches one of these media types.
  128. * Examples:
  129. * <pre class="code">
  130. * produces = "text/plain"
  131. * produces = {"text/plain", "application/*"}
  132. * produces = "application/json; charset=UTF-8"
  133. * </pre>
  134. * <p>It affects the actual content type written, for example to produce a JSON response
  135. * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used.
  136. * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
  137. * all requests with a {@code Accept} other than "text/plain".
  138. * <p><b>Supported at the type level as well as at the method level!</b>
  139. * When used at the type level, all method-level mappings override
  140. * this produces restriction.
  141. * @see org.springframework.http.MediaType
  142. */
  143. // 处理器之后的响应用户的结果类型,比如{“application/json;charset=UTF-8”,"text/plain","application/*"}
  144. String[] produces() default {};
  145. }

这里最常用到的时请求路径和请求类型,其他的大部分作为限定项,根据需要进行配置。例如在MyController中加入一个index2方法,代码如下:


  
  1. @RequestMapping(value = "/index2",method = RequestMethod.GET)
  2. public ModelAndView index2() {
  3. ModelAndView mv = new ModelAndView();
  4. mv.setViewName( "index");
  5. return mv;
  6. }

这样对于/my/index2.do的HTTP GET请求提供响应了。

2. 控制器的开发

控制器开发是Spring MVC的核心内容,其步骤一般会分为3步。

  • 获取请求参数
  • 处理业务逻辑
  • 绑定模型和视图

2.1 获取请求参数

在Spring MVC中接收参数的方法很多,建议不要使用Servlet容器所给予的API,因为这样控制器将会依赖于Servlet容器,比如:


  
  1. @RequestMapping(value = "/index2",method = RequestMethod.GET)
  2. public ModelAndView index2(HttpSession session, HttpServletRequest request) {
  3. ModelAndView mv = new ModelAndView();
  4. mv.setViewName( "index");
  5. return mv;
  6. }

Spring MVC会自动解析代码中的方法参数session、request,然后传递关于Servlet容器的API,所以是可以获取到的。通过request或者session都可以很容易地得到HTTP请求过来的参数,这固然是一个方法,但并非一个好的方法。因为如果这样做了,那么对于index2方法而言,它就和Servlet容器紧密关联了,不利于扩展和测试。为了给予更好的灵活性,Spring MVC给予了更多的方法和注解以获取参数。

如果要获取一个HTTP请求的参数——id,它是一个长整型,那么可以使用注解@RequestParam来获取它,代码修改为:


  
  1. @RequestMapping(value = "/index2",method = RequestMethod.GET)
  2. public ModelAndView index2(@RequestParam("id") Long id) {
  3. System.out.println( "params[id] = " + id);
  4. ModelAndView mv = new ModelAndView();
  5. mv.setViewName( "index");
  6. return mv;
  7. }

在默认的情况下对于注解了@RequestParam的参数而言,它要求参数不能为空,也就是当获取不到HTTP请求参数的时候,Spring MVC将会抛出异常。有时候还希望给参数一个默认值,为了解决这样的困难,@RequestParam还给了两个有用的配置项:

  • required是一个布尔值(boolean),默认是true,也就是不允许参数为空,如果要允许为空,则配置它为false。
  • defaultValue的默认值为"\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n",可以通过配置修改它为想要的内容。 

获取session中的内容,假设当前的Session中设置了userName,那么应该如何获取它呢?Spring MVC还提供了注解@SessionAttribute去从Session中获取对应的数据。代码如下:


  
  1. @RequestMapping(value = "/index3",method = RequestMethod.GET)
  2. public ModelAndView index3( @SessionAttribute("userName") String userName) {
  3. System. out.println( "session[userName] = " + userName);
  4. ModelAndView mv = new ModelAndView();
  5. mv.setViewName( "index");
  6. return mv;
  7. }

 

2.2 实现逻辑和绑定视图

一般而言,实现的逻辑和数据库有关联,如果采用XML的方式,那么只需要在applicationContext.xml中配置关于数据库的部分就可以了;如果使用Java配置的方式,那么需要在配置类WebConfig中的getRootConfigClasses加入对应的配置类即可。

有时候在使用第三方包开发的时候,使用XML方式会比注解方式方便一些,因为不需要设计太多关于第三方包内容的Java代码,甚至可以是混合使用,示例配置如下:


  
  1. <?xml version= '1.0' encoding= 'UTF-8' ?>
  2. <beans xmlns= "http://www.springframework.org/schema/beans"
  3. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:p= "http://www.springframework.org/schema/p"
  4. xmlns:tx= "http://www.springframework.org/schema/tx" xmlns:context= "http://www.springframework.org/schema/context"
  5. xmlns:mvc= "http://www.springframework.org/schema/mvc"
  6. xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
  7. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
  8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
  9. http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
  10. <!-- 使用注解驱动 -->
  11. <context:annotation-config />
  12. <!-- 数据库连接池 -->
  13. <bean id= "dataSource" class= "org.apache.commons.dbcp.BasicDataSource">
  14. <property name= "driverClassName" value= "com.mysql.jdbc.Driver" />
  15. <property name= "url" value= "jdbc:mysql://localhost:3306/chapter14" />
  16. <property name= "username" value= "root" />
  17. <property name= "password" value= "123456" />
  18. <property name= "maxActive" value= "255" />
  19. <property name= "maxIdle" value= "5" />
  20. <property name= "maxWait" value= "10000" />
  21. </bean>
  22. <!-- 集成mybatis -->
  23. <bean id= "SqlSessionFactory" class= "org.mybatis.spring.SqlSessionFactoryBean">
  24. <property name= "dataSource" ref= "dataSource" />
  25. <property name= "configLocation" value= "classpath:/mybatis/mybatis-config.xml" />
  26. </bean>
  27. <!-- 配置数据源事务管理器 -->
  28. <bean id= "transactionManager"
  29. class= "org.springframework.jdbc.datasource.DataSourceTransactionManager">
  30. <property name= "dataSource" ref= "dataSource" />
  31. </bean>
  32. <!-- 采用自动扫描方式创建mapper bean -->
  33. <bean class= "org.mybatis.spring.mapper.MapperScannerConfigurer">
  34. <property name= "basePackage" value= "com.ssm.chapter14" />
  35. <property name= "SqlSessionFactory" ref= "SqlSessionFactory" />
  36. <property name= "annotationClass" value= "org.springframework.stereotype.Repository" />
  37. </bean>
  38. </beans>

假设上述的XML配置文件,已经通过扫描的方式初始化了一个Spring IoC容器中的Bean——RoleService,而且它提供了一个参数为long型的方法getRole来获取角色,那么可以通过自动装配的方式在控制器中注入它。角色控制器代码如下:


  
  1. /**************import ***************/
  2. @Controller
  3. @RequestMapping( "/role")
  4. public class RoleController {
  5. // 注入角色服务类
  6. @Autowired
  7. private RoleService roleService = null;
  8. @RequestMapping(value = "/getRole", method = RequestMethod.GET)
  9. public ModelAndView getRole(@RequestParam("id") Long id) {
  10. Role role = roleService.getRole(id);
  11. ModelAndView mv = new ModelAndView();
  12. mv.setViewName( "roleDetails");
  13. // 给数据模型添加一个角色对象
  14. mv.addObject( "role", role);
  15. return mv;
  16. }
  17. }

 从代码中注入了RoleService,这样就可以通过这个服务类使用传递的参数id来获取角色,最后把查询出来的角色添加给模型和视图以便将来使用。

3. 视图渲染 

一般地,Spring MVC会默认使用JstlView进行渲染,也就是它将查询出来的模型绑定到JSTL(JSP标准标签库)模型中,这样通过JSTL就可以把数据模型在JSP中读出展示数据了,在Spring MVC中,还存在着大量的视图可供使用,这样就可以方便地将数据渲染到视图中,用以响应用户的请求。

在上文的代码中使用了roleDetails的视图名,根据配置,它会使用文件/WEB-INF/jsp/roleDetail.jsp去响应,也就是要在这个文件中编写JSTL标签将模型数据读出即可,例如:


  
  1. <%@ page pageEncoding="utf-8"%>
  2. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
  3. <html>
  4. <head>
  5. <title>out标签的使用 </title>
  6. </head>
  7. <body>
  8. </body>
  9. <center>
  10. <table border="1">
  11. <tr>
  12. <td>标签 </td>
  13. <td></td>
  14. </tr>
  15. <tr>
  16. <td>角色编号 </td>
  17. <td> <c:out value="${role.id}"> </c:out> </td>
  18. </tr>
  19. <tr>
  20. <td>角色名称 </td>
  21. <td> <c:out value="${role.roleName}"> </c:out> </td>
  22. </tr>
  23. <tr>
  24. <td>角色备注 </td>
  25. <td> <c:out value="${role.note}"> </c:out> </td>
  26. </tr>
  27. </table>
  28. </center>
  29. </html>

在目前的前端技术中,普遍使用Ajax技术,在这样的情况下,往往后台需要返回JSON数据给前端使用,对此,Spring MVC在模型和视图也给予了良好的支持。getRole的代码修改为:


  
  1. // 获取角色
  2. @RequestMapping(value = "/getRole2", method = RequestMethod.GET)
  3. public ModelAndView getRole2(@RequestParam("id") Long id) {
  4. Role role = roleService.getRole(id);
  5. ModelAndView mv = new ModelAndView();
  6. mv.addObject( "role", role);
  7. // 指定视图类型
  8. mv.setView( new MappingJackson2JsonView());
  9. return mv;
  10. }

代码中视图类型为MappingJackson2JsonView,这就要下载关于Jackson2的包。由于这是一个JSON视图,这样Spring MVC就会通过这个视图去渲染所需的结果。于是会在我们请求后得到需要的JSON数据,提供给Ajax异步请求使用了。它的执行流程如下:

只是这不是将结果变为JSON的唯一方法,使用注解@ResponeBody是更为简单和广泛使用的方法

 


转载:https://blog.csdn.net/ARPOSPF/article/details/105490945
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场