项目目录
- SpringMVC
- day01
- Spring MVC - HelloWorld - 新款 - 开发步骤
- 练习
- 在Spring MVC中接收用户的请求参数
- 在Spring MVC中响应用户的请求
- 注解
- 今天重点
- day02
- 【回顾】 Spring MVC
- 案例 - 用户管理
- 1 设定目标
- 2 开发-1:访问注册页面
- 2 开发-2:创建数据库与数据表
- 2 开发-3:开发持久层
- 2 开发-4:测试持久层
- 2 开发-5:释放数据库相关资源
- 2 开发-6:添加业务层
- 2 开发-7:持久层-查询用户
- 2 开发-8:业务层-完善注册业务
- 小结
- Day03
- 案例 - 用户管理
- 项目开发流程
- 乱码问题
- Spring中的拦截器(Interceptor)
- 4 进阶测试
- 小结
SpringMVC
day01
作用
解决了View
-> Controller
和Controller
-> View
的细节问题,简化了开发。
开发Spring MVC的项目
1 创建项目,加载Spring
创建Maven Project,通过Eclipse生成web.xml,添加Tomcat运行环境,添加spring-webmvc依赖,复制配置文件spring-mvc.xml。
在web.xml中配置DispatcherServlet
接收*.do
请求,并为这个Servlet添加初始化参数,参数名称是contextConfigLocation
,值为classpath:spring-mvc.xml
,即取值是Spring的配置文件,然后将这个Servlet配置为默认加载。
经过以上配置后,当Tomcat启动该项目时,就会加载DispatcherServlet
,并在过程中加载spring-mvc.xml
的配置文件。
2 接收请求
首先,设计好请求的路径,例如http://SERVER:PORT/PROJECT/hello.do
。
经过第1步骤的配置,当客户端提交以上路径的请求时,DispatcherServlet
将开始运行,首先,会去找HandlerMapping
,查询该请求路径对应哪个Controller
,所以,此时应该配置HandlerMapping
,在SpringMVC中,实际工作的会是SimpleUrlHandlerMapping
,所以,需要在spring-mvc.xml
对这个类进行配置,配置时不需要指定bean id,因为后续在工作时,SpringMVC会根据类型完全装配。
在配置SimpleUrlHandlerMapping
时,这个类中的mappings
属性是用于确定请求路径与Controller
的映射关系的,所以,需要配置该属性。这个属性是Properties
类型的,所以将通过<props>
和若干个<prop>
节点进行配置:
<bean class="xx.xx.xx.xx.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<!-- 以下请求路径指的是自定义的部分 -->
<!-- 中间的文本节点是接收并响应请求的Controller的bean id -->
<prop key="请求路径">Controller bean id</prop>
</props>
</property>
</bean>
至此,就必须开始开发Controller
,在SpringMVC中,Controller
是接口,需要自定义类实现该接口,并重写其中的抽象方法:
public class HelloController implements Controller {
// 重写抽象方法
}
由于此前的SimpleUrlHandlerMapping
的配置中需要使用HelloController
的bean id,所以,需要在HelloController
类上添加注解,可以使用@Component
,也可以使用@Controller
:
@Controller
public class HelloController implements Controller {
// 重写抽象方法
}
注意:必须开启组件扫描,且能够扫描到相关的类所在的包!
3 准备响应
实现Controller
接口后,需要重写的抽象方法是:
public ModeAndView handleRequest(
HttpServletRequest request, HttpServletResponse response)
throws Exception {
}
这个方法的使用和Servlet
中的service()
或doGet()
或doPost()
类似,区别在于这个方法必须返回ModelAndView
类型的对象!
ModelAndView
应该是一个封装了需要转发的数据和转发的目标(View,通常是jsp页面)的对象,且强烈推荐需要确定转发的目标,此时,应该先设计好由哪个jsp页面进行视图的显示,假设是项目中的/WEB-INF/hello.jsp
,则在ModelAndView
对象中设置的viewName
就是这个jsp的文件名部分,即hello
。
当Controller
返回了ModelAndView
对象后,DispatcherServlet
将可以明确接下来由哪个名为viewName(即hello
)负责最后的显示,但是,并不明确这个viewName对应的是哪个视图组件(哪个jsp页面),此时,DispatcherServlet
将通过ViewResolver
来确定,所以,需要在spring-mvc.xml
中进行配置。
与HandlerMapping
相似,ViewResolver
也是一个接口,实际使用的类可能有多种,目前推荐使用InternalResourceViewResolver
,后续在运行过程中,SpringMVC依然是通过类型来装配使用这个ViewResolver
,所以,还是需要在spring-mvc.xml
中配置,且还是不需要配置bean id,需要配置的属性有prefix
和suffix
,分别表示前缀与后缀,最终,前缀与后缀与此前ModelAndView
中的viewName
组合起来,就是jsp文件的路径,所以,配置如下:
<bean class="xx.xx.xx.xx.InternalResourceViewResolver">
<property name="prefix" value="/WEG-INF/" />
<property name="suffix" value=".jsp" />
</bean>
最后,创建对应的jsp,也就是/WEB-INF/hello.jsp
,设计界面的内容,如果此前ModelAndView
中还封装了数据,则在这个jsp页面中还可以通过EL表达式去显示这些数据!
Spring MVC - HelloWorld - 新款 - 开发步骤
1 创建项目,加载Spring
参考旧款
2 接收并处理请求
设计请求路径:http://SERVER:PORT/PROJECT/hello.do
先跳过HandlerMapping
的步骤,直接创建Controller
类:
public class HelloController { ...
创建类之后,先在类之前添加注解@Controller
:
@Controller
public class HelloController { ...
注意:新款做法中不必再实现Controller接口!
在类中添加处理请求的方法:
public ModelAndView handleRequest(
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return null;
}
在方法内部,自行编写如何处理请求,无论如何,最终返回的必须是ModelAndView
对象,并且指定了View组件名。
至此,已经完成了使用Controller
接收并处理请求,却还没有配置请求路径与Controller
的映射关系,新的做法是在处理请求的方法上添加注解:
@RequestMapping("/hello.do")
public ModelAndView handleRequest(
当完成以上注解后,在SpringMVC工作时,就已经有了“请求路径与处理方法”之间的映射关系!
事实上,处理请求的方法名称可以自由定义,方法的参数可以自由定义,例如:
@RequestMapping("/hello.do")
public ModelAndView showHello() {
// ...
}
并且,返回值类型不一定是ModelAndView
类型的,如果响应的方式只是单纯的转发请求,并且没有数据需要转发到jsp页面,可以将返回值设计为String类型。
3 准备响应
参考旧款,配置ViewResolver
,创建jsp页面即可。
小结
在编写Controller
类时,不必实现接口,方法均可以自由定义,注意:
1 方法的权限应该是public的,方法使用@RequestMapping
进行注解
2 方法的返回值可以是String,也可以是ModelAndView,或其它类型,暂时只在String和ModelAndView中二选一,无论是哪种,必须体现后续将由哪个View负责响应
3 不必再配置HandlerMapping
,但是,还是需要配置ViewSolver
练习
设计请求:http://SERVER:PORT/PROJECT/login.do
响应请求的类:cn.tedu.spring.UserController
响应请求的方法:public String showLogin()
响应页面:/WEB-INF/login.jsp
页面内容:添加2个输入框,分别表示用户名和密码,输入框的name属性分别是username
和password
在Spring MVC中接收用户的请求参数
1 设计
请求路径:http://SERVER:PORT/PROJECT/handleLogin.do
请求方式:POST
请求参数:username=tomcat&password=1234
2 开发步骤
在UserController
添加方法:
public String handleLogin() {
return null;
}
使用@RequestMapping
注释该方法:
@RequestMapping("/handleLogin.do")
public String handleLogin() {
return null;
}
在处理请求的方法中,添加与前端页面提交的数据的名称相匹配的参数:
@RequestMapping("/handleLogin.do")
public String handleLogin(String username, String password) {
return null;
}
在SpringMVC调用handleLogin()
方法时,就会自动将提交的参数用于调用方法,所以,在编写方法时,直接使用这些参数即可!
在Spring MVC中响应用户的请求
1 设计
基于以上的案例,假设正确的用户名是tomcat
,匹配的密码是123456
,则可以判断用户登录成功与否。
在Controller
中判断登录后,将登录结果保存在String result
变量中,转发到jsp。
使用login_result.jsp
页面显示登录结果,在该页面中提示“登录成功”或“用户名不存在,登录失败”或“密码错误,登录失败”的字样。
2 开发步骤
首先,在WEB-INF
下创建login_result.jsp
,通过EL表达式${result }
显示登录结果。
在Controller
中的handleLogin()
方法中,判断用户名和密码,为String result
变量进行赋值。
将result
转发。
返回"login_result"
。
注解
@RequestParam
该注解用于对Controller
中的方法的参数进行注解。
如果前端页面提交的数据的参数名与Controller
中方法的参数名不一致,则可以使用该注解:
@RequestMapping("/handleLogin.do")
public String handleLogin(
@RequestParam("uname") String username,
@RequestParam("upwd") String password) {
// ...
}
@RequestMapping
在一般情况下,使用@RequestMapping("xxx")
用于标识接下来方法将处理哪个请求路径。
也可以在注解中添加更多的参数:
@RequestMapping(
method=RequestMethod.POST,
value="/handleLogin.do")
@RequestMapping
不仅仅是用于对方法进行注解,还可以对类进行注解:
@RequestMapping("/user")
@Controller
public class UserController { ...
在类上面的@RequestMapping
并不能取代方法上面的注解,只是用于添加请求的路径而已,当添加以上注解后,这个Controller
中将处理的请求都必须是http://SERVER:PORT/PROJECT/user
这个路径之下的某个资源!
注意:无论使用@RequestMapping
注解类还是方法,其参数都推荐使用/
开头!!!
今天重点
获取请求参数
1 【不推荐】 在处理请求的方法中添加HttpServletRequest
参数,然后调用getParameter()
方法
2 【推荐】 在处理请求的方法中添加与请求参数名称匹配的参数,这些参数的值就是请求参数的值,如果请求参数中的名称与处理请求的方法中的参数名称不一致,还可以使用@RequestParam
注解来解决,这种做法的不足在于不适用于过多请求参数
3 【推荐】 如果前端页面提交的请求参数较多,则应该在服务器端创建对应的实体类,然后,在处理请求的方法中添加实体类作为参数即可,SpringMVC会自动将请求参数用于对实体类对象的属性进行赋值!使用这种做法时,需要保证请求参数的名称与实体类中的属性名称和数据类型保持一致!
向jsp转发数据
1 【不推荐】 为方法添加HttpServletRequest
,然后向该对象中封装需要转发的数据
2 【一般】 需要将处理请求的方法的返回值设置为ModelAndView
类型,将转发的数据先封装到Map<String, ?>
,然后通过ModelAndView
的构造方法使用该Map<String, ?>
,最后返回ModelAndView
对象
public ModelAndView handleLogin(String username, String password) {
String result;
if ("tomcat".equals(username)) {
if ("123456".equals(password)) {
result = "登录成功!";
} else {
result = "密码错误,登录失败!";
}
} else {
result = "用户名不存在,登录失败!";
}
// 将result封装,以准备转发到jsp
String viewName = "login_result";
Map<String, Object> model
= new HashMap<String, Object>();
model.put("result", result);
ModelAndView mav
= new ModelAndView(viewName, model);
// 返回,执行转发
return mav;
}
3 【推荐】 在处理请求的方法中添加新的参数ModelMap
,方法的返回值类型不需要是ModelAndView
,可以根据需求选择返回值类型,例如设置为String
类型的返回值,然后,在方法中,调用ModelMap
对象的addAttribute(String, Object)
方法即可封装数据,后续该ModelMap
中的数据将被转发,而不需要编写其它代码。
public String handleLogin(
String username,
String password,
ModelMap modelMap) {
// 声明登录结果
String result;
// 判断...
// 将需要转发的数据封装到ModelMap中
modelMap.addAttribute(
"result", result);
// 执行转发
return "login_result";
}
处理HttpSession
不推荐的做法是在处理请求的方法中添加HttpServletRequest
,然后调用它的getSession()
获取HttpSession
对象。
推荐的做法可参考HttpServletRequest
或ModelMap
,也就是在处理请求的方法中添加HttpSession
参数:
public String handleLogin(String username, String password,
HttpSession session) {
// 通过session参数封装数据到HttpSession,或获取其中的数据
}
转发与重定向
在Controller
中,处理请求的方法最终需要转发或重定向时,都推荐使用String
类型的返回值:
public String xxx { ...
默认的方式是转发,所以,当需要转发时,直接返回View组件的名称就可以,也可以添加forward:
前缀:
public String xxx {
return "index";
}
public String xxx {
return "forward:index";
}
重定向的作法是使用redirect:
作为前缀:
public String xxx {
return "redirect:index";
}
另外:还可以在方法中创建RedirectView
对象,然后装其封装到ModelAndView
中,以实现重定向,但是,不推荐。
day02
【回顾】 Spring MVC
注解
使用@Controller
注解项目中的Controller
类
使用@RequestMapping
可以注解类和方法,并且可以配置接收的请求类型到底是GET或是POST
使用@RequestParam
可以注解Controller
类中的处理请求的方法的参数,用于请求参数与方法的参数名称不匹配的问题
如果在Controller
中还需要访问到其它的被Spring管理的组件,也就是需要声明其它组件,需要被注入值,则还会使用到@Resource
注解
接收请求参数
在处理请求的方法中,添加与请求参数名称相同的方法参数即可。
如果请求参数过多,还可以在处理请求的方法中使用实体类的类型作为参数。
转发数据
在处理请求的方法中,添加ModelMap
类型的参数,然后将需要转发的数据封装到该参数对象中即可,Spring MVC会实现后续的转发。
处理Session
在处理请求的方法中,添加HttpSession
类型的参数,然后通过参数对象获取Session中的数据,或者向Session中封装数据。
重定向
将处理请求的方法的返回值设置为String
类型,返回值使用redirect:路径
即可。
案例 - 用户管理
1 设定目标
用户通过界面可以注册和登录,注册信息中包括:用户名(username)、密码(password)、手机号码(phone)、电子邮件(email),最终数据将存入到MySQL数据库中,整个项目应该体现MVC的设计理念。
2 开发-1:访问注册页面
a) 创建项目:09-SpringMVC-DAY02-UserManagement
b) 项目的常规操作:添加web.xml,依赖spring-webmvc
,添加Tomcat Runtime,复制spring-mvc.xml
,检查spring-mvc.xml
中的注解扫描和ViewSolver
的配置,在web.xml
中配置DispatcherServlet
c) 创建register.jsp
表示注册页面,在界面中添加1个<form>
表单,并在其中添加4个<inpu type="text" />
表示4个注册数据的输入框,还需要添加1个<input type="submit" />
表示提交按钮,整个表单将通过POST
方式提交,且提交到http://SERVER:PORT/PROJECT/handleRegister.do
d) 设计访问该界面的URL:http://SERVER:PORT/PROJECT/register.do
e) 创建cn.tedu.spring.controller.UserController
类,使用@Controller
对类进行注解,并添加方法:
@RequestMapping("/register.do")
public String showRegister() {
return "register";
}
f) 测试:通过(d)设计的网址进行访问,应该可以看到设计的页面,但是提交后出错
2 开发-2:创建数据库与数据表
a) 创建数据库:tedu_ums
(User Management System)
b) 使用数据库:use tedu_ums;
c) 创建数据表:t_user
CREATE TABLE t_user (
id INT AUTO_INCREMENT,
username VARCHAR(16) NOT NULL UNIQUE,
password VARCHAR(16) NOT NULL,
phone VARCHAR(16),
email VARCHAR(50),
PRIMARY KEY(id)
);
d) 检查:desc t_user;
2 开发-3:开发持久层
a) 添加依赖:
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<!--数据库连接池 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
b) 在src\main\resources
创建数据库的配置文件db.properties
:
url=jdbc:mysql://localhost:3306/tedu_ums
driver=com.mysql.jdbc.Driver
user=root
password=root
initSize=2
maxActive=10
c) 在spring-mvc.xml
中配置<util:properties>
读取以上配置文件:
<util:properties id="dbConfig"
location="classpath:db.properties" />
d) 在spring-mvc.xml
中配置数据库连接池的<bean>
节点:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="url"
value="#{dbConfig.url}" />
<property name="driverClassName"
value="#{dbConfig.driver}" />
<property name="username"
value="#{dbConfig.user}" />
<property name="password"
value="#{dbConfig.password}" />
<property name="initialSize"
value="#{dbConfig.initSize}" />
<property name="maxActive"
value="#{dbConfig.maxActive}" />
</bean>
e) 创建实体类:cn.tedu.spring.bean.User
,声明与数据表中对应的5个属性,注意:请把id
的类型设置为Integer
类型
f) 创建持久层接口:cn.tedu.spring.dao.IUserDao
,并设计抽象方法:
/**
* 增加用户数据
* @param user
* @return 新增加的用户的id
*/
Integer insert(User user);
g) 创建持久层实现类:cn.tedu.spring.dao.UserDaoImpl
,实现以上抽象方法,在类中还需要声明全局变量private BasicDataSource dataSource
并使用@Resource
注解,然后在重写的方法中:
// 1 声明必要的变量(例如Connection、PreparedStatement、SQL语句的字符串等等)和返回值
// 2 获取连接
// 3 预编译SQL
// 4 执行,如果可以获取返回值,则获取
// 5 处理结果,通常是因为执行的是select或insert,将得到ResultSet
// 6 释放资源
// 7 返回
2 开发-4:测试持久层
a) 添加junit
依赖
b) 在src\test\main
创建测试类TestUserDao
,并添加测试“增加用户”的方法,并使用@Test
进行注解:
@Test
public void testInsert() {
}
c) 完成测试方法,测试方法的编写方式与此前编写main方法相同,注意:请为UserDaoImpl
补上注解@Repository("userDao")
2 开发-5:释放数据库相关资源
a) 为了便于关闭相关对象,并且减少代码量,推荐创建一个新的类,然后在这个类中添加方法用于释放资源,而不是在持久层的操作中编写相关代码。这个类可以创建为:cn.tedu.spring.DBUtils
,然后创建相关的释放资源的方法,为了便于调用这些方法,推荐使用static进行修饰:
public class DBUtils {
public static void close(AutoCloseable... obj) {
for (int i = 0; i < obj.length; i++) {
if (obj[i] != null) {
try {
obj[i].close());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
b) 释放连接池资源,在BasicDataSource
中有close()
方法用于释放资源,则在spring-mvc.xml
中配置它时,应该在<bean>
节点添加destroy-method
属性,值为close
方法的名称,以使得当项目停止运行时,能及时释放连接池中的资源
2 开发-6:添加业务层
a) 创建业务层接口:cn.tedu.spring.service.IUserService
,并添加抽象方法:
/**
* 注册新用户
* @param user 新用户的信息
* @return 新用户的ID,如果注册失败,则返回-1
*/
Integer register(User user);
b) 创建业务层实现类:cn.tedu.spring.service.UserServiceImpl
,实现以上接口,使用@Service("userService")
对类进行注解,并添加IUserDao
类型的属性,在这个属性上添加@Resource(name="userDao")
以注入属性值,然后再重写抽象方法,在抽象方法中,直接调用IUserDao
的对象的insert()
方法即可!
2 开发-7:持久层-查询用户
a) 在持久层接口IUserDao
接口中声明抽象方法:
/**
* 根据用户名查询用户信息
* @param 用户名
* @return 查询到的用户数据,如果没有查询到匹配的记录,则返回null
*/
User findUserByUsername(String username);
b) 在UserDaoImpl
中实现以上抽象方法
c) 单元测试
2 开发-8:业务层-完善注册业务
小结
1 当开发新的内容(没有相关经验)时,如果目标不明确,就做界面。
2 一次只解决一个问题!!!
3 在编写任何代码之前,应该先尽量多的设计好需要做的事情,然后再开始写代码,切记不要没想好就开始写!
4 数据的访问流程应该是:Controller
-> Service
-> Dao
-> DB
,如果此次访问的相关功能还没有开发,则开发的流程应该是反向的,即最先完成DB
,即数据库中创建库、创建表等操作,然后再开发Dao
,再开发Service
,最后开发Controller
!
5 当在MySQL中创建数据表时,最好先在文本文档中写好代码,然后粘贴到控制台
Day03
案例 - 用户管理
2 开发-9:控制器层及前端页面
在cn.tedu.spring.controller.UserController
类,添加处理登录请求的方法:
@RequestMapping(
method=RequestMethod.POST,
value="/handleRegister.do")
public String handleRegister(User user) {
}
由于实现注册时,需要使用Service,所以,在类中声明private IUserService userService
,并使用@Resource
进行注解,这样的话,在handleRegister
中,就可以调用Service对象来完成注册!
当调用Service执行注册时,应该获取返回值,并对其进行判断,如果结果为-1,表示用户名已经存在,注册失败,否则,注册成功,为了保证能够很好的显示最终结果,应该设计前端页面!
创建error.jsp
表示操作错误的提示页面,在该页面中,通过${errorMessage}
提示错误信息。
创建login.jsp
表示登录页面,其内容可以直接从现有的register.jsp
中复制,删除原注册表单中的手机号码、电子邮件,修改表示提交到的路径,及相关文字。
完成前端页面后,如果在handleReigster
方法中得到的结果是注册成功,则直接return "redirect:login.do";
,如果得到的结果是注册失败,则需要先在当前方法中添加ModelMap
参数,并向该参数中封装名为errorMessage
的数据,内容是对错误信息的描述,最后,返回return "error"
。
最后,在UserController
中,添加方法,对/login.do
路径进行处理,转发到login.jsp
页面。
项目开发流程
1 创建项目,完成基本配置
2 总体设计:规划数据库与数据表
3 创建数据库与数据表
4 创建与数据表结构匹配的实体类
5 设计目标:页面请求路径、处理功能路径、响应页面路径
6 设计前端页面
7 处理Model-持久层,并单元测试
8 处理Model-业务层,并单元测试
9 处理控制器层
10 整合测试
乱码问题
1 为什么可能出现乱码
出现乱码的唯一原因就是编码不统一!解决乱码问题的唯一做法就是使用统一的某1种支持中文的编码格式!
2 在哪些位置需要确定编码
取决于数据的传递过程会接触到哪些位置!
包括:程序的源代码、前端页面、数据库、程序与数据库之间的连接
其中,程序的源代码的编码格式可以设置开发工具的默认编码,或每次创建项目后检查并设置。
前端页面如果显示乱码(确定问题出在前端页面),可以通过配置<meta />
标签来解决:<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
,如果前端页面是通过.jsp
文件来生成的,则还应该:<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
对于普通的GET
请求,还应该配置Tomcat
的默认处理编码格式,即在conf\server.xml
文件中,查询8080
关键字,在其所在的<Connector>
节点中添加URIEncoding="utf-8"
即可。
3 Spring MVC中的CharacterEncodingFilter
不要尝试通过Controller
中使用HttpServletRequest
解决乱码问题,因为,如果编码不统一,在Spring MVC
的DispatcherServlet
中就已经出现乱码了,而Controllner
是在DispatcherServlet
之后运行的,所以无法解决已经存在的乱码。
在Spring MVC
中,已经定义了CharacterEncodingFilter
这个过滤器,很显然,在Java WEB项目,Filter
是在Servlet
之前运行的,所以,当解决View
-> Controller
这个过程中的乱码问题时,只需要在web.xml
中配置该过滤器即可:
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
4 MySQL乱码问题
最权威的解决方案:删除以后重新安装!
注意:在MySQL的安装完成后,第1次运行配置向导时,会要求指定数据库默认编码,此时一定仔细确认!
在MySQL控制台中,输入\s
即可查看当前MySQL的编码设置情况。
如果一定要在现有的MySQL中解决问题,首先,每次创建数据库与数据表时都明显的指定编码格式,例如:
CREATE TABLE xxx (字段设计……) DEFAULT CHARSET=UTF8;
为了避免数据从Tomcat服务器到MySQL服务器的过程中可能出现乱码,应该在数据库连接的url
后添加参数:?useUnicode=true&characterEncoding=utf8
Spring中的拦截器(Interceptor)
1 基本概念
拦截器(Interceptor)是运行在DispatcherServlet
之后,在每个Controller
之前的一个组件。
通过配置拦截器,可以使得满足配置条件的请求都必须先经过拦截器,才可以被Controller
中处理请求的方法来处理,所以,拦截器具有“拦截”和“放行”的功能。
对于许多处理请求时都需要执行的任务,就可以通过拦截器来处理,例如验证用户是否登录。
2 开发步骤
2.1 准备工作
设计请求路径:
http://SERVER:PORT/PROJECT/index.do(主页)
http://SERVER:PORT/PROJECT/login.do(登录页)
http://SERVER:PORT/PROJECT/user_center.do(用户中心页)
创建cn.tedu.spring.controller.MainController
,添加3个方法:
showIndex()
showLogin()
showUserCenter()
分别用于处理以上3种请求,并分别转发到WEB-INF/index.jsp
、WEB-INF/login.jsp
他WEB-INF/user_center.jsp
,每个页面中都添加一段文字表示当前页面。
2.2 创建拦截器类
创建cn.tedu.spring.interceptor.SampleInterceptor
,实现HandlerInterceptor
接口,重写其中的抽象方法。
2.3 配置拦截器
在spring-mvc.xml
文件中配置:
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- mapping用于配置哪些请求路径将被拦截 -->
<!-- /* 表示根路径下所有资源 -->
<!-- 例如 /index.do和/login.do和/user_center.do都将被拦截 -->
<mvc:mapping path="/*"/>
<bean class="cn.tedu.spring.interceptor.SampleInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
注意:配置时需要区分先后顺序,必须最先配置<mvc:mapping />
,最后配置<bean />
。
3 详细介绍
3.1 3个抽象方法
只有preHandle()
会在Controller
处理请求之前运行,另2个方法都会在处理请求之后运行!
3.2 【重要】preHandle()方法
该方法的返回值是boolean
类型,返回false
时表示拦截,返回true
时表示放行。
3.3 postHandle()方法
当Controller
处理完请求之后,postHandle()
方法就会被运行,其中,ModelAndView
对象中会封装后续处理的View组件名称和转发的数据。
3.3 afterCompletion()方法
最后运行的方法。
4 进阶测试
4.1 需求
创建新的拦截器SessionInterceptor
,要求只对user_center.do
进行拦截,另2个不拦截(不处理)。
4.2 解决方案
如果只是少量的请求需要被拦截,可以:
<mvc:mapping path="/user_center.do" />
但是,这样做的缺点在于难以应付许多请求都需要被拦截。则可以在spring-mvc.xml
中配置拦截器时,使用<mvc:exclude-mapping />
节点配置例外,也就是在<mvc:mapping />
的基础之上添加例外!
<mvc:mapping path="/*" />
<mvc:exclude-mapping path="/login.do" />
<mvc:exclude-mapping path="/index.do" />
在以上配置中,需要注意:必须先配置<mvc:mapping />然后再配置<mvc:exclude-mapping />!
以上配置中,如果添加了例如,则当服务器接收到例外请求时,拦截器根本就不运行!更不存在放行或拦截的问题。
注意:使用/*
只能表示匹配根路径下的所有内容!如果有子级,则匹配不上,例如可以匹配/login.do
和/index.do
,却不能匹配到/user/???.do
,如果需要匹配根路径及各层级子级路径,需要使用/**
才可以!
5 拦截器链
与Filter
类似,在Spring MVC
中的拦截器也可以存在多个,并且形成拦截器链,仅当所有匹配的拦截器的preHandle()
都执行之后,才会调用Controller
中处理请求的方法,然后再执行所有匹配的拦截器的postHandler()
,再执行所有匹配的拦截器的afterCompletion()
。
在拦截器链中,各拦截器的执行先后顺序取决于配置文件中配置的节点的先后顺序!
6 练习:登录过滤器
6.1 设计目标
如果已经登录,可以正常访问user/user_center.do
如果没有登录,当访问user/user_center.do
时,将自动重定向到login.do
6.2 准备工作
新建UserController
,使用@RequestMapping("/user")
和@Controller
注解,并将原来在MainController
中的showUserCenter()
方法剪切到UserController
中。
将spring-mvc.xml
中通配符都设置为/**
。
在login.jsp
中添加<form>
和<input type="submit" />
,<form>
提交到http://SERVER:PORT/PROJECT/user/handle_login.do
。
则在UserController
中添加方法以处理以上请求:
@RequestMapping(value="/handle_login.do",
method=RequestMethod.POST)
public String handleLogin(HttpSession session) {
session.setAttribute("uid", 9527);
return "redirect:user_center.do";
}
在user_center.jsp
中显示Session信息。
为了便于测试,还应该在user_center.jsp
中添加“退出登录”功能。则在该页面中添加<a href="${根路径}/user/logout.do">退出登录</a>
,并在UserController
中添加方法以处理这个新的请求:
@RequestMapping("logout.do")
public String handleLogout(HttpSession session) {
session.invalidate();
return "redirect:../login.do";
}
6.3 实现拦截
检查拦截器是否能匹配相关的路径,及不处理不必要的路径。特别是user/handle_login.do
必须添加为不处理!
拦截的标准就是:判断Session中是否存在名为uid
的值,如果存在,表示已经登录,如果不存在,则表示没有登录!
编写拦截功能的方法显然是preHandle()
方法,代码示例:
// 获取Session信息
HttpSession session = request.getSession();
// 检查Session中是否有登录信息
if (session.getAttribute("uid") == null) {
// 确定重定向的目标位置
String url = request.getContextPath() + "/login.do";
// 没有登录,则重定向
response.sendRedirect(url);
// 返回
return false;
} else {
// 已经登录,则放行
}
7 小结
【何时使用】当处理多种请求都需要执行相同或极为相似的代码时,应该使用拦截器,并且将这些代码编写在拦截器中,在编写时,可能使用preHandle()
方法!
【如何配置】如果只有极少量的请求需要使用拦截器,则使用多个<mvc:mapping />
配置这些请求路径就可以!如果有大量请求都需要使用拦截器,却只有少量请求不使用,则应该使用<mvc:mapping />
配置通配符,再添加若干个<mvc:exclude-mapping />
以配置例外!如果业务分明,甚至可以配置拦截器只匹配指定的路径,例如:<mvc:mapping path="/user/**" />
,合理的使用路径,可以简化拦截器的配置。
【拦截器与过滤器】这两者最大的区别在于:过滤器会在DispatcherServlet
之前运行,意味着可以对所有的请求进行处理!拦截器会在DispatcherServlet
之后运行,也就是说只能对的web.xml
中配置DispatcherServlet
时的映射的路径进行处理!
小结
1 在开发项目之前,请认真的检查所有关于编码的问题,包括:项目源码的编码、数据库与数据表的编码(保证每次创建数据表时都添加DEFAULT CHARSET=UTF8
)、TOMCAT的编码(配置8080端口的节点上的URIEncoding="utf-8"
)、连接数据库的url(添加?useUnicode=true&characterEncoding=utf-8
)、配置Spring中的CharacterEncodingFilter
(固定配置),并在后续每一个前端页面都配置编码为UTF-8,就不会出现乱码问题。