概述

首先拿张图,这里面就是 Spring 可以胜任的功能,其中 Web 模块其实就是我们接下来要学习的 SpringMVC,使用 Spring 来控制 Web 项目里面的 Model View Controller 三层模型。

298.jpg

开始使用

首先需要导入 Spring 的核心包,也就是支持 Spring 运作的 Beans Core Context SpEL 和用于测试 Spring 的 Test 包。

而对于 SpringMVC 来说,实现基础的功能只需要导入 Web 和 Servlet 两个包即可。

SpringMVC 的模式和传统的 MVC 有一点点不同,那就是多了一个前端控制器,这个控制器也是 SpringMVC 控制整个 Web MVC 流程的实现基础。而这个前端控制器本身是一个 Servlet,交给 Tomcat 控制。然后 Tomcat 会拦截所有的 Servlet 请求,全都交给前端控制器这个 Servlet,前端控制器则根据 SpringMVC 的框架标准,控制处于 Spring 容器中的各种对象来完成业务的实现。

299.jpg

所以先在 web.xml 中配置 DispatcherServlet ,初始化参数给 spring 配置文件的地址,也就是 applicationContext.xml 用来控制 IOC 容器(如果不配置的话会默认去 WEB-INF/springDispathcherServlet-servlet.xml 读配置文件,其中文件前缀就是配置的 DispathcerServlet 的 servlet-name),并配置拦截 / 下的所有 Serlvet 请求(/* 会拦截所有资源的请求,包括 jsp, /只会拦截路径型 URL,不会拦截有后缀的访问的),当然除了拦截 Servlet 请求,静态资源的请求也从 Tomcat 手里抢了过来,这个问题以后回补充解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
<servlet>
<servlet-name>springDispathcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispathcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

搞好了前端控制器之后,就可以写 Controller 层的控制器了,只需要随便写一个类,然后标注 @Controller ,就可以被 IOC 容器标注为控制层了,等到 DispathcerServlet 调用自己即可。

而对于这个伪装的 Servlet 如何有选择的处理不同的请求呢,这就依靠 @RequestMapping 注解,也就是请求映射,将请求和处理方法建立映射。

因为我们将请求映射配置为 /,所以 Spring MVC 将捕获 WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理,因找不到对应处理器将导致错误。解决方案就是在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/> 的方式解决静态资源的问题:
<mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

请求处理

1
2
3
4
5
6
7
@Controller
public class FirstController {
@RequestMapping({"/index.jsp","/index"})
public String toIndex() {
return "WEB-INF/pages/index.jsp";
}
}

这就是将 /index.jsp/index 两处的请求,转发到 return 里面填写的地址上面,这里使用的是请求转发。

RequestMapping 相关

RequestMapping 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequestMapping {
String name() default "";

@AliasFor("path")
String[] value() default {};

@AliasFor("value")
String[] path() default {};

RequestMethod[] method() default {};

String[] params() default {};

String[] headers() default {};

String[] consumes() default {};

String[] produces() default {};
}

标注位置

RequestMapping 可以被标注到 TYPE 也就是 Class 上和方法上,标注到 Class 上面是为了更大范围的前置约束,比如可以写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
@RequestMapping(value="/fisrt",method="post")
public class FirstController {
@RequestMapping("/one")
public String one() {
return "one";
}

@RequestMapping("/two")
public String two() {
return "two";
}

}

这样首先访问为 /first/XXX 的 POST 请求都会被定位到 FirstController 这个类中,然后根据 /XXX 的值精细化定位处理方法。

参数列表

  1. value/path 设置处理此方法映射的访问地址。详情请看:请求路径

  2. method 设置映射的访问方法,可以设置 public enum RequestMethod 里面设定的几种访问方法。

  3. params 设置请求中的参数要求,必须包含某参数或者某参数为 XXX 值等。详情请看:参数限制

  4. headers 设置请求头包含 “请求头:设置” 的请求被本方法处理。

  5. consumes 设置消费类型,是根据 Content-Type 请求头里面进行比对的,比如设置 application/json 就限制了请求里面的数据必须是 application/json 类型的请求才会被此方法处理

  6. produces 设置要求生产类型,是根据 Accept 请求头进行比对的。根据请求想要获得的数据类型来决定本方法是否处理此请求。

请求路径

其中可以使用 {VariableName} 的方式从 URI 中截取数据,保存到 VariableName 的变量中,然后下面通过注解 @PathVariable 将保存的截取数据送入方法参数中,如果参数名和截取数据所保存的变量名相等,就可以不在注解里面填写数据。如果注解标注的位置是 Map 类型,则会将所有截取的数据放入 Map 中。

1
2
3
4
5
6
7
8
@Controller
public class FirstController {
@RequestMapping("/index/{id}")
public String toIndex(@PathVariable("id") String id) {
System.out.println(id);
return "index";
}
}

如果对截取的数据加以正则限制,则可以使用:{varName:regex} 来对某部分进行正则表达式限制。或者比较粗暴的使用 * 来省略被限制的部分路径。


当一个请求被多个方法映射成功之后,遵循的原则是映射匹配精确度越高,方法处理的优先级越高。

参数限制

在 params 上设置只有用户名为 Xorex 的用户才会被处理:

1
2
3
4
5
6
7
8
@Controller
public class FirstController {
@RequestMapping(params="username=Xorex")
public String toAdmin() {
System.out.println("[Admin] Xorex login.");
return "admin";
}
}

可以使用 username!=XXX 表示参数 username 不等于某个值即可(包括没有 username 参数),!username 表示必须没有 username 参数。password 表示必须要有密码参数。

return 地址

请求映射方法的 return 值(为 String)就是此方法处理完请求之后会被转发到的地址(一般是 View 层)。

而转发地址里面有很多都是重复的,如 WEB-INF/pages/.jsp 所以就可以再搞一个内部资源视图控制器,也就是 jsp 的一个小的控制器,只需要将其放入 IOC 容器中,这个控制器就可以设置对视图资源的访问,设置一个前缀和后缀即可。

1
2
3
4
5
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
<!-- 放入 IOC 容器之后,这个实例会在转发 return 后面的地址之前自动被调用-->
<property name="suffix" value=".jsp" />
<property name="prefix" value="/WEB-INF/pages/" />
</bean>