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的源码如下:
-
@Target({ElementType.METHOD, ElementType.TYPE})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
@Mapping
-
public
@interface RequestMapping {
-
-
/**
-
* Assign a name to this mapping.
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used on both levels, a combined name is derived by concatenation
-
* with "#" as separator.
-
* @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
-
* @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
-
*/
-
// 请求路径
-
String name() default "";
-
-
/**
-
* The primary mapping expressed by this annotation.
-
* <p>In a Servlet environment this is an alias for {@link #path}.
-
* For example {@code @RequestMapping("/foo")} is equivalent to
-
* {@code @RequestMapping(path="/foo")}.
-
* <p>In a Portlet environment this is the mapped portlet modes
-
* (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings inherit
-
* this primary mapping, narrowing it for a specific handler method.
-
*/
-
// 请求路径,可以是数组
-
@AliasFor(
"path")
-
String[] value()
default {};
-
-
/**
-
* In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
-
* Ant-style path patterns are also supported (e.g. "/myPath/*.do").
-
* At the method level, relative paths (e.g. "edit.do") are supported within
-
* the primary mapping expressed at the type level. Path mapping URIs may
-
* contain placeholders (e.g. "/${connect}")
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings inherit
-
* this primary mapping, narrowing it for a specific handler method.
-
* @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
-
* @since 4.2
-
*/
-
// 请求路径,数组
-
@AliasFor(
"value")
-
String[] path()
default {};
-
-
/**
-
* The HTTP request methods to map to, narrowing the primary mapping:
-
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings inherit
-
* this HTTP method restriction (i.e. the type-level restriction
-
* gets checked before the handler method is even resolved).
-
* <p>Supported for Servlet environments as well as Portlet 2.0 environments.
-
*/
-
// 请求类型,比如是HTTP的GET请求还是POST请求等,HTTP请求枚举取值范围为:GET、HEAD、PUT、PATCH、DELETE、OPTIONS、TRACE,常用的是GET和POST请求
-
RequestMethod[] method()
default {};
-
-
/**
-
* The parameters of the mapped request, narrowing the primary mapping.
-
* <p>Same format for any environment: a sequence of "myParam=myValue" style
-
* expressions, with a request only mapped if each such parameter is found
-
* to have the given value. Expressions can be negated by using the "!=" operator,
-
* as in "myParam!=myValue". "myParam" style expressions are also supported,
-
* with such parameters having to be present in the request (allowed to have
-
* any value). Finally, "!myParam" style expressions indicate that the
-
* specified parameter is <i>not</i> supposed to be present in the request.
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings inherit
-
* this parameter restriction (i.e. the type-level restriction
-
* gets checked before the handler method is even resolved).
-
* <p>In a Servlet environment, parameter mappings are considered as restrictions
-
* that are enforced at the type level. The primary path mapping (i.e. the
-
* specified URI value) still has to uniquely identify the target handler, with
-
* parameter mappings simply expressing preconditions for invoking the handler.
-
* <p>In a Portlet environment, parameters are taken into account as mapping
-
* differentiators, i.e. the primary portlet mode mapping plus the parameter
-
* conditions uniquely identify the target handler. Different handlers may be
-
* mapped onto the same portlet mode, as long as their parameter mappings differ.
-
*/
-
// 请求参数,当请求带有配置的参数时,才匹配处理器
-
String[] params()
default {};
-
-
/**
-
* The headers of the mapped request, narrowing the primary mapping.
-
* <p>Same format for any environment: a sequence of "My-Header=myValue" style
-
* expressions, with a request only mapped if each such header is found
-
* to have the given value. Expressions can be negated by using the "!=" operator,
-
* as in "My-Header!=myValue". "My-Header" style expressions are also supported,
-
* with such headers having to be present in the request (allowed to have
-
* any value). Finally, "!My-Header" style expressions indicate that the
-
* specified header is <i>not</i> supposed to be present in the request.
-
* <p>Also supports media type wildcards (*), for headers such as Accept
-
* and Content-Type. For instance,
-
* <pre class="code">
-
* @RequestMapping(value = "/something", headers = "content-type=text/*")
-
* </pre>
-
* will match requests with a Content-Type of "text/html", "text/plain", etc.
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings inherit
-
* this header restriction (i.e. the type-level restriction
-
* gets checked before the handler method is even resolved).
-
* <p>Maps against HttpServletRequest headers in a Servlet environment,
-
* and against PortletRequest properties in a Portlet 2.0 environment.
-
* @see org.springframework.http.MediaType
-
*/
-
// 请求头,当HTTP请求头为配置项时,才匹配处理器
-
String[] headers()
default {};
-
-
/**
-
* The consumable media types of the mapped request, narrowing the primary mapping.
-
* <p>The format is a single media type or a sequence of media types,
-
* with a request only mapped if the {@code Content-Type} matches one of these media types.
-
* Examples:
-
* <pre class="code">
-
* consumes = "text/plain"
-
* consumes = {"text/plain", "application/*"}
-
* </pre>
-
* Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
-
* all requests with a {@code Content-Type} other than "text/plain".
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings override
-
* this consumes restriction.
-
* @see org.springframework.http.MediaType
-
* @see javax.servlet.http.HttpServletRequest#getContentType()
-
*/
-
// 请求类型为配置类型才匹配处理器
-
String[] consumes()
default {};
-
-
/**
-
* The producible media types of the mapped request, narrowing the primary mapping.
-
* <p>The format is a single media type or a sequence of media types,
-
* with a request only mapped if the {@code Accept} matches one of these media types.
-
* Examples:
-
* <pre class="code">
-
* produces = "text/plain"
-
* produces = {"text/plain", "application/*"}
-
* produces = "application/json; charset=UTF-8"
-
* </pre>
-
* <p>It affects the actual content type written, for example to produce a JSON response
-
* with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used.
-
* <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
-
* all requests with a {@code Accept} other than "text/plain".
-
* <p><b>Supported at the type level as well as at the method level!</b>
-
* When used at the type level, all method-level mappings override
-
* this produces restriction.
-
* @see org.springframework.http.MediaType
-
*/
-
// 处理器之后的响应用户的结果类型,比如{“application/json;charset=UTF-8”,"text/plain","application/*"}
-
String[] produces()
default {};
-
-
}
这里最常用到的时请求路径和请求类型,其他的大部分作为限定项,根据需要进行配置。例如在MyController中加入一个index2方法,代码如下:
-
@RequestMapping(value =
"/index2",method = RequestMethod.GET)
-
public ModelAndView index2() {
-
ModelAndView mv =
new ModelAndView();
-
mv.setViewName(
"index");
-
return mv;
-
}
这样对于/my/index2.do的HTTP GET请求提供响应了。
2. 控制器的开发
控制器开发是Spring MVC的核心内容,其步骤一般会分为3步。
- 获取请求参数
- 处理业务逻辑
- 绑定模型和视图
2.1 获取请求参数
在Spring MVC中接收参数的方法很多,建议不要使用Servlet容器所给予的API,因为这样控制器将会依赖于Servlet容器,比如:
-
@RequestMapping(value =
"/index2",method = RequestMethod.GET)
-
public ModelAndView index2(HttpSession session, HttpServletRequest request) {
-
ModelAndView mv =
new ModelAndView();
-
mv.setViewName(
"index");
-
return mv;
-
}
Spring MVC会自动解析代码中的方法参数session、request,然后传递关于Servlet容器的API,所以是可以获取到的。通过request或者session都可以很容易地得到HTTP请求过来的参数,这固然是一个方法,但并非一个好的方法。因为如果这样做了,那么对于index2方法而言,它就和Servlet容器紧密关联了,不利于扩展和测试。为了给予更好的灵活性,Spring MVC给予了更多的方法和注解以获取参数。
如果要获取一个HTTP请求的参数——id,它是一个长整型,那么可以使用注解@RequestParam来获取它,代码修改为:
-
@RequestMapping(value =
"/index2",method = RequestMethod.GET)
-
public ModelAndView index2(@RequestParam("id") Long id) {
-
System.out.println(
"params[id] = " + id);
-
ModelAndView mv =
new ModelAndView();
-
mv.setViewName(
"index");
-
return mv;
-
}
在默认的情况下对于注解了@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中获取对应的数据。代码如下:
-
@RequestMapping(value = "/index3",method = RequestMethod.GET)
-
public ModelAndView index3(
@SessionAttribute("userName") String userName) {
-
System.
out.println(
"session[userName] = " + userName);
-
ModelAndView mv = new ModelAndView();
-
mv.setViewName(
"index");
-
return mv;
-
}
2.2 实现逻辑和绑定视图
一般而言,实现的逻辑和数据库有关联,如果采用XML的方式,那么只需要在applicationContext.xml中配置关于数据库的部分就可以了;如果使用Java配置的方式,那么需要在配置类WebConfig中的getRootConfigClasses加入对应的配置类即可。
有时候在使用第三方包开发的时候,使用XML方式会比注解方式方便一些,因为不需要设计太多关于第三方包内容的Java代码,甚至可以是混合使用,示例配置如下:
-
<?xml version=
'1.0' encoding=
'UTF-8' ?>
-
<beans xmlns=
"http://www.springframework.org/schema/beans"
-
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance" xmlns:p=
"http://www.springframework.org/schema/p"
-
xmlns:tx=
"http://www.springframework.org/schema/tx" xmlns:context=
"http://www.springframework.org/schema/context"
-
xmlns:mvc=
"http://www.springframework.org/schema/mvc"
-
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
-
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
-
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
-
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
-
<!-- 使用注解驱动 -->
-
<context:annotation-config />
-
<!-- 数据库连接池 -->
-
<bean id=
"dataSource"
class=
"org.apache.commons.dbcp.BasicDataSource">
-
<property name=
"driverClassName" value=
"com.mysql.jdbc.Driver" />
-
<property name=
"url" value=
"jdbc:mysql://localhost:3306/chapter14" />
-
<property name=
"username" value=
"root" />
-
<property name=
"password" value=
"123456" />
-
<property name=
"maxActive" value=
"255" />
-
<property name=
"maxIdle" value=
"5" />
-
<property name=
"maxWait" value=
"10000" />
-
</bean>
-
-
<!-- 集成mybatis -->
-
<bean id=
"SqlSessionFactory"
class=
"org.mybatis.spring.SqlSessionFactoryBean">
-
<property name=
"dataSource" ref=
"dataSource" />
-
<property name=
"configLocation" value=
"classpath:/mybatis/mybatis-config.xml" />
-
</bean>
-
-
<!-- 配置数据源事务管理器 -->
-
<bean id=
"transactionManager"
-
class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
-
<property name=
"dataSource" ref=
"dataSource" />
-
</bean>
-
-
<!-- 采用自动扫描方式创建mapper bean -->
-
<bean
class=
"org.mybatis.spring.mapper.MapperScannerConfigurer">
-
<property name=
"basePackage" value=
"com.ssm.chapter14" />
-
<property name=
"SqlSessionFactory" ref=
"SqlSessionFactory" />
-
<property name=
"annotationClass" value=
"org.springframework.stereotype.Repository" />
-
</bean>
-
</beans>
假设上述的XML配置文件,已经通过扫描的方式初始化了一个Spring IoC容器中的Bean——RoleService,而且它提供了一个参数为long型的方法getRole来获取角色,那么可以通过自动装配的方式在控制器中注入它。角色控制器代码如下:
-
/**************import ***************/
-
@Controller
-
@RequestMapping(
"/role")
-
public
class RoleController {
-
// 注入角色服务类
-
@Autowired
-
private RoleService roleService =
null;
-
-
@RequestMapping(value =
"/getRole", method = RequestMethod.GET)
-
public ModelAndView getRole(@RequestParam("id") Long id) {
-
Role role = roleService.getRole(id);
-
ModelAndView mv =
new ModelAndView();
-
mv.setViewName(
"roleDetails");
-
// 给数据模型添加一个角色对象
-
mv.addObject(
"role", role);
-
return mv;
-
}
-
}
从代码中注入了RoleService,这样就可以通过这个服务类使用传递的参数id来获取角色,最后把查询出来的角色添加给模型和视图以便将来使用。
3. 视图渲染
一般地,Spring MVC会默认使用JstlView进行渲染,也就是它将查询出来的模型绑定到JSTL(JSP标准标签库)模型中,这样通过JSTL就可以把数据模型在JSP中读出展示数据了,在Spring MVC中,还存在着大量的视图可供使用,这样就可以方便地将数据渲染到视图中,用以响应用户的请求。
在上文的代码中使用了roleDetails的视图名,根据配置,它会使用文件/WEB-INF/jsp/roleDetail.jsp去响应,也就是要在这个文件中编写JSTL标签将模型数据读出即可,例如:
-
<%@ page pageEncoding="utf-8"%>
-
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
-
<html>
-
<head>
-
<title>out标签的使用
</title>
-
</head>
-
<body>
-
</body>
-
<center>
-
<table border="1">
-
<tr>
-
<td>标签
</td>
-
<td>值
</td>
-
</tr>
-
<tr>
-
<td>角色编号
</td>
-
<td>
<c:out value="${role.id}">
</c:out>
</td>
-
</tr>
-
<tr>
-
<td>角色名称
</td>
-
<td>
<c:out value="${role.roleName}">
</c:out>
</td>
-
</tr>
-
<tr>
-
<td>角色备注
</td>
-
<td>
<c:out value="${role.note}">
</c:out>
</td>
-
</tr>
-
</table>
-
</center>
-
</html>
在目前的前端技术中,普遍使用Ajax技术,在这样的情况下,往往后台需要返回JSON数据给前端使用,对此,Spring MVC在模型和视图也给予了良好的支持。getRole的代码修改为:
-
// 获取角色
-
@RequestMapping(value =
"/getRole2", method = RequestMethod.GET)
-
public ModelAndView getRole2(@RequestParam("id") Long id) {
-
Role role = roleService.getRole(id);
-
ModelAndView mv =
new ModelAndView();
-
mv.addObject(
"role", role);
-
// 指定视图类型
-
mv.setView(
new MappingJackson2JsonView());
-
return mv;
-
}
代码中视图类型为MappingJackson2JsonView,这就要下载关于Jackson2的包。由于这是一个JSON视图,这样Spring MVC就会通过这个视图去渲染所需的结果。于是会在我们请求后得到需要的JSON数据,提供给Ajax异步请求使用了。它的执行流程如下:
只是这不是将结果变为JSON的唯一方法,使用注解@ResponeBody是更为简单和广泛使用的方法。
转载:https://blog.csdn.net/ARPOSPF/article/details/105490945