(五)Spring MVC起步
Sping MVC是基于模型-视图-控制器(Model-View-Controller,MVC)模式实现的Web层框架。
一个案例教你使用Spring MVC 构建Web应用程序。
<!--more-->
我们还是以一张图来了解Spring MVC如何做到和前端页面交互的:
# 搭建Spring MVC
首先我们就需要导入jar,这在前面的文章中已经介绍了,不在赘述。(需要jar可以去Maven仓库进行下载)
1. DispatcherServlet
根据图示,页面请求首先走的就是Spring MVC的前端控制器`DispatcherServlet`,我们需要在`web.xml`中配置前端控制器Servlet:
```xml
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.DispatcherServlet</servlet-class>
<load-on>1</load-on>
</servlet>
```
默认情况下,`DispatcherServlet`加载时会从一个基于这个Servlet名字的XML文件中加载Spring上下文。
接下来我们必须声明`DispatcherServlet`处理哪些URL:
```xml
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</pattern>
</servlet-mapping>
```
**注意:** 我们常见的配置此资源请求路径为`*.do`,这样,在浏览器的请求路径通常是`xxx.do`这样,但是存在暴露URL实现细节的问题。
所以我们通过将`DispatcherServlet`映射到`/`,声明了它作为默认的Servlet会处理所有请求,包括静态资源的请求。
2. 命名空间
同时,Spring提供了`<mvc:resources>`元素,让你更灵活的配置如何处理静态资源的请求。下面我们创建`springmvc.xml`
```xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:resources mapping="/resources/**" location="/resources/">
</beans>
```
如上所示这个`spring.xml`并没有太大区别,仅仅是多了一个命名空间。当我们想对静态资源请求做处理,`<mvc:resources>`就正好满足了我们的需求:
属性`mapping`设置为`/resources/**`表明路径必须以`/resources`开始,而且包含其任意子路径;属性`location`定义了这里静态资源的存储位置。
3. 处理器映射器和控制器
配置前端控制器Servlet和Spring MVC的命名空间,下面我们应该配置Spring MVC的控制器(`DispatcherServlet`需要咨询一个或多个处理器映射来明确的将请求分发给哪个控制器)。Spring自带多个处理器映射:
* `BeanNameUrlHandlerMapping`:根据Bean的名字将控制器映射到URL
* `ControllerBeanNameHandlerMapping`:根据控制器Bean的名字将控制器映射到URL。(Bean的名字不需要遵循URL的约定)
* `ControllerClassNameHandlerMapping`:通过使用控制器的类名作为URL基础将控制器映射到URL
* `DefaultAnnotationHandlerMapping`:将请求映射给使用@RequestMapping注解的控制器和控制方法
* `SimpleUrlHandlerMapping`:使用定义在Spring应用上下文的属性集合将控制器映射到URL
如上几种控制器,因为我们使用`@RequestMapping`,所以以上控制器我们并不需要直接去使用,取而代之的是`<mvc:annotation-driven>`标签将自动为我们分配所需要的控制器。
4. 视图解析器
当前台`request`请求发送过来,先经过Spring MVC的`DispatcherServlet`前端控制器,然后经过Spring的处理器映射器和处理器控制器(Controller)对接收到URL信息进行处理;控制器会将处理后的信息(Model)返回给用户;之后再讲这些数据进行格式化,发送给一个视图(View);
控制器最后再将这些模型视图数据打包,将渲染输出的模型和视图名称发送给`DispatcherServlet`;最后`DispatcherServlet`将使用视图解析器把逻辑视图名匹配为一定特定的视图实现。
所以,接下来我们要配置视图解析器:
```xml
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
```
Spring MVC中常用`InternalResourceViewResolver`视图解析器根据我们定义的路径将视图解析到浏览器上。`prefix`表示模板视图(JSP)所在路径的前缀名,`suffix`表示模板视图的后缀名。
**注意:**
如果你的JSP放到了`WEB-INF`下,那么我们在浏览器上是无法直接访问的,必须通过控制层进行跳转,但同时这也保证了一定的安全性。所以我们习惯将JSP文件放到`WEB-INF/`下,将静态资源放到`webapp`下。
5. 编写控制层
```java
@Controller
@RequestMapping(value="/user")
public class UserController{
@RequestMapping(value="/save", method = RquestMethod.POST)
public String save(@RequestParam String username, @RequestParam String password, Model model){
//逻辑处理
return "save";
}
}
```
如上,这就是一个简单的控制器,我们接下来分析一下这个Controller:
1. `@Controller`(`@Component`注解的一种)表明了该类是一个控制器类(我们需要现在配置文件中配置扫描这个控制器类`<context:component-scan>`),`<xontext:component-scan>`会查找被`@Component`标注的类,将其注册为Bean。
2. `@RequestMapping`注解有两个作用:1.表名此方法(`save()`)是一个请求处理方法;2.指明了该方法要处理请求`/save`路径下的请求。所以我们看到`@RequestMapping`注解可以标注在类上和方法上,其实分别表示两种不同的作用范围,因为类包裹了方法。如我们现在的配置,就表示`save()`方法的在浏览器上的请求路径应该是:`localhost:8080/(项目名)/user/save`(如果我们指定了前端控制器`DispatcherServlet`的映射路径,如:`*,do`,那么`save()`方法的请求路径就应该是`localhost:8080/(项目名)/user/save.do`)。
3. `method=RequestMethod.POST`指定了当前请求是POST请求
4. `return`表明了该映射方法需要返回到哪里。首先我们观察该`save()`方法的返回值数据类型,那么设置为`String`在Spring MVC的映射方法中就表示要返回一个视图,我们只需要定义返回视图的名字,Spring MVC会根据视图解析器查找此名字对应的视图。如我们现在的配置返回的视图路径其实就是:`/WEB-INF/save.jsp`(在视图解析器中我们已经配置了前缀和后缀)。
5. `@RequestParam`:该注解并不是必须的,它提供了一些属性如可以设置默认值等,在参数不匹配的时候有用,且对于当前我们这种请求包含多个参数时尽量使用`@RequestParam`,避免一些未知的问题。
6. `Model`对象内部类似`Map<String, Obeject>`,且提供了一些方式用来设置值。通过`Model`对象的`addAttribute()`方法设置的值可以在JSP中用EL表达式取出来,类似放到了域对象中
# 入门案例
首先我们看一下项目目录结构(**注意** :我这里使用的是IDEA+Maven),可能和大家的有所区别,但大家目前不要过分关注这些。
## web.xml
```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>Archetype Created Web Application</display-name>
<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置Spring的字符编码过滤器 -->
<filter>
<filter-name>CharacterEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ContextLoaderListener监听器监听servlet的创建 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 告诉监听器需要加载那些配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</context-param>
<!-- Spring MVC的前端控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
```
注意之前没有介绍`CharacterEncodingFilter`,此过滤器是Spring提供的为避免`request`请求乱码问题而设定的。
配置Spring MVC的前端控制器时,包含了`<init-param>`标签旨在加载前端控制器时同时去加载Spring MVC的上下文。
## spring.xml
```xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="service"/>
<!-- 加载数据库资源配置文件 -->
<context:property-placeholder location="classpath:resource/jdbc.properties"/>
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注入Bean -->
<bean id="userService" class="service.UserServiceImp"/>
<bean id="userDao" class="dao.UserDaoImp">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
```
注意这里配置连接池我们使用的是加载外置的属性文件:`jdbc.properties`,使用`<context:property-placeholder>`加载。
## springmvc.xml
```xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描Controller层 -->
<context:component-scan base-package="controller"/>
<!-- 处理静态资源 -->
<mvc:resources mapping="/resources/**" location="/resources/"/>
<!-- 一个注解实现配置:BeanNameUrlHandlerMapping(处理器映射器)和DefaultAnnotationHandlerMapping(处理器控制器) -->
<mvc:annotation-driven/>
<!-- 配置Spring MVC视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
```
## Controller层
```java
@Controller
@RequestMapping(value = "/user")
public class UserController {
//注入
@Autowired
private UserService userService;
//跳转到添加用户信息页
@RequestMapping(value="/savePage")
public String savePage(){
return "view/save";
}
//保存用户
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@RequestParam String username,@RequestParam String password, Model model){
userService.save(username,password);
model.addAttribute("message","保存数据成功");
return "view/success";
}
}
```
如果我们想要访问保存信息的JSP页面,应在浏览器中输入:`localhost:8080/user/savePage`;而保存表单信息方法的路径是:`localhost:8080/user/save`
## Service层
1. `UserService`
```java
public interface UserService {
void save(String username, String password);
}
```
2. `UserServiceImp`
```java
@Transactional
public class UserServiceImp implements UserService {
@Autowired
private UserDao userDao;
//保存
public void save(String username, String password) {
userDao.save(username,password);
}
}
```
用`@Transaction`标识该类,Spring会为该类设置事务性性通知(配合`<tx:annotation-driven>`)
## Dao层
1. `UserDao`
```java
public interface UserDao {
void save(String username, String password);
}
```
2. `UserDaoImp`
```java
public class UserDaoImp extends JdbcDaoSupport implements UserDao {
//保存数据
public void save(String username, String password) {
//使用Spring提供的JDBC模板可以直接执行SQL语句
this.getJdbcTemplate().update("insert into user(id,username,password) values(?,?,?)",null,username,password);
}
}
```
这里我们仍使用的是Spring提供的JDBC模板类,只需要dao继承这个模板类即可。使用`JdbcDaoSupport`的`getJdbcTemple()`方法执行SQL语句。(`this`表示当前对象)
## 前端页面
1. `save.jsp`
```html
<h2>表单</h2>
<hr/>
<form action="<%=basePath%>/user/save" method="post">
username: <input type="text" name="username"/><br/>
password: <input type="password" name="password"/><br/>
<input type="submit" value="提交"/>
</form>
```
2. `success.jsp`
```html
<head>
<title>Title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>${message}</h1>
</body>
</html>
```
## 运行
首先我们需要把项目部署到Tomcat服务器上,然后启动Tomcat:
没有报错,接下来我们来浏览器中输入我们项目中添加页面的访问路径:`localhost:8080/user/savePage`
我们输入中文信息,并点击提交,通过断点查看:
后台映射方法`save()`接收到了JSP表单传递进来的参数,并且中文没有乱码(如果没有配置`CharacterEncodingFilter`就可能乱码)。继续执行:
当我们的request请求经控制层处理完毕后,`DispatcherServlet`将视图数据返回到页面上,我们`return`已经设置了返回页面`/WEB-INF/view/success.jsp`,那么请求成功就会返回到该页面上。并且我们在`success.jsp`中用EL表达式取出来了`message`这个属性的值,当我们在`Controller`控制层设置了`message`值为`数据保存成功`,就相当于保存进了request域中一个名为`message`的值。
数据库:
<br/>
# 交流
如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!
<br/>
# 联系
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.
- [[email protected]'s blog](http://www.tycoding.cn)
- [[email protected]](https://github.com/TyCoding)
- [[email protected]](https://www.zhihu.com/people/tomo-83-82/activities)