1. 关于@RequestParam注解
1.1. 基础使用
使用@RequestParam
注解,可以解决提交的请求参数与处理请求的方法参数名称不一致的问题,例如:
@RequestMapping("/handle_login.do")
public String handleLogin(
@RequestParam("uname") String username,
String password) {
System.out.println("username=" + username);
System.out.println("password=" + password);
return null;
}
使用@RequestParam
注解后,默认情况下,对应的请求参数是必须的,如果提交的请求不包含这项参数,则会提示:
HTTP Status 400 - Required String parameter 'uname' is not present
1.2. 注解的配置
使用@RequestParam
注解请求参数名称时的完整语法是:
@RequestParam(value="uname")
在@RequestParam
注解中,还有另一个属性required
,取值为boolean
类型,例如:
@RequestParam(value="uname", required=false)
可以发现,使用@RequestParam
时,其required
属性的默认值是true
。
除此以外,该注解还有一个String
类型的属性defaultValue
:
@RequestParam(value="uname", required=false, defaultValue="hello")
这项defaultValue
表示的是默认值,即没有提交这项请求参数时,将按照默认值来处理,如果提交了这项请求参数,则这项配置没有意义。
从4.2版本后,该注解还可以使用name属性,意义与value属性相同。
/**
* The name of the request parameter to bind to.
* @since 4.2
*/
@AliasFor("value")
String name() default "";
2. 转发数据
2.1. 【不推荐】通过HttpServletRequest参数转发数据
当需要转发数据时,可以在处理请求的方法中添加HttpServletRequest
对象作为参数,然后调用该参数对象的setAttribute(String, Object)
方法封装即可:
@RequestMapping("/handle_reg.do")
public String handleReg(User user,
HttpServletRequest request) {
System.out.println(user);
// 暂视为所有用户名注册时都是失败的,原因:用户名已经被占用
// 封装错误信息,以准备转发
String message = "您输入的用户名(" + user.getUsername()
+ ")已经被占用!";
request.setAttribute("message", message);
// 执行转发
return "error";
}
2.2. 【不推荐】使用ModelAndView
在处理请求时,使用ModelAndView
作为方法的返回值。
在ModelAndView
中,Model
表示需要转发的数据,其类型是Map<String, ?>
类型,View
表示负责响应的视图组件,可以使用String viewName
表示。
@RequestMapping("/handle_reg.do")
public ModelAndView handleReg(String username) {
// 暂视为所有用户名注册时都是失败的,原因:用户名已经被占用
String message = "MODEL:您输入的用户名("
+ username + ")已经被占用!";
// 封装转发的数据
Map<String, Object> model
= new HashMap<String, Object>();
model.put("message", message);
// 准备返回值
ModelAndView mav
= new ModelAndView("error", model);
// 执行转发
return mav;
}
2.3. 【推荐】使用ModelMap
ModelMap
是SpringMVC中提供的用于封装转发数据的类型,其本质就是一个Map
,你可以像使用一般的Map
类似的方式去使用它,同时,当你需要封装转发数据时,使用与HttpServletRequest
相同的方式去使用即可!
@RequestMapping("/handle_reg.do")
public String handleReg(String username,
ModelMap modelMap) {
// 暂视为所有用户名注册时都是失败的,原因:用户名已经被占用
String message = "MODELMAP:您输入的用户名("
+ username + ")已经被占用!";
// 封装需要转发的数据
modelMap.addAttribute("message", message);
// 执行转发
return "error";
}
3 重定向
在处理请求的方法中,返回值类型依然使用String
,并且,返回的字符串使用redirect:
作为前缀,然后写出目标位置的相对路径或绝对路径。
// 重定向
@RequestMapping("/handle_reg.do")
public String handleReg() {
// 假设注册一定成功,并到登录页
// 当前位置: /user/handle_reg.do
// 重定向到登录,即:/user/login.do
return "redirect:login.do";
}
4. 处理Session
与ModelMap
的使用方式是一样的!即:把HttpSession
作为参数添加到处理请求的方法中,然后,这整个方法中就可以使用Session。
5. 关于@RequestMapping
使用@RequestMapping
主要用于配置所处理的请求的路径,完整配置应该是:
@RequestMapping(value="/handle_reg.do")
除此以外,还可以添加配置method
属性,以限制请求的类型,例如:
@RequestMapping(value="/handle_reg.do", method=RequestMethod.POST)
如果以上路径配置了限制为POST请求,然后,实际发出的是GET请求,则会提示405错误:
HTTP Status 405 - Request method 'GET' not supported
通常,对于GET类型的请求并不配置method
,而POST类型的请求应该配置method
。但是,出于规范的考虑,无论什么请求,都应该显式的限制请求类型,除非你确定这些请求类型都是允许的。
6. 阶段小结
SpringMVC主要解决了如何接收请求,并给予的响应的问题。
在处理请求时,请求的参数(含多个请求参数构成的对象类型)、HttpServletRequest
、HttpServletResponse
、ModelMap
、HttpSession
都可以根据需要,自行添加为处理请求的方法的参数,然后,在方法内部直接使用即可!
处理请求的方法返回值可以是String
,默认的响应方式是转发,如果需要重定向,则返回的字符串应该是redirect:相对路径或绝对路径
。
使用@RequestMapping
注解可以配置请求路径,甚至限制请求类型。
使用@RequestParam
注解可以确定请求参数的名称、是否必须、默认值。
7. 阶段案例
7.1. 案例目标
用户可以在注册页面中填写:用户名、密码、年龄,提交后,暂且将admin
用户名视为已注册,即当用户提交的用户名是admin
时,提示错误,否则,视为注册成功!
如果注册成功,则跳转到登录页,在处理登录时,如果用户名是root
且密码是1234
,视为登录成功,否则,视为登录失败!
如果登录成功,将用户名存储在Session中,并跳转到主页,要求在主页中显示当前登录的用户名。
以上请求的路径分别是:
- 显示注册页面:/user/reg.do
- 显示登录页面:/user/login.do
- 处理注册请求:/user/handle_reg.do
- 处理登录请求:/user/handle_login.do
- 显示主页:/main/index.do
7.2. 开发步骤
7.2.1. 创建项目
创建Maven Project,Group Id为cn.tedu.spring
,Artifact Id为SPRINGMVC-03-SAMPLE
,Packaging为war
,然后,执行固定的5项任务:生成web.xml
,复制此前项目中pom.xml
中依赖,添加Tomcat Runtime,复制springmvc.xml
,复制此前项目中web.xml
中关于DispatcherServlet
的配置。
7.2.2. 分析案例
一次只解决一个问题。
先下手为强。
大致的开发步骤应该分为:
显示注册页面
处理注册数据
显示登录页面
处理登录数据
显示主页
7.2.3. 显示注册页面
设计请求:
请求路径:/user/reg.do
请求方式:GET
请求参数:无
响应方式:转发:reg.jsp
创建cn.tedu.spring.controller.UserController
类,使用@Controller
和@RequestMapping("/user")
注解。
然后,在类中添加处理请求的方法:
@RequestMapping("/reg.do")
public String showReg() {
return "reg";
}
检查springmvc.xml
的配置是否符合当前项目,例如组件扫描的包,视图解析器的前缀与后缀的配置。
然后,在webapp/WEB-INF
下创建reg.jsp
页面,在页面中添加用户名、密码、年龄的输入框和提交按钮,且表单以POST
方式提交到handle_reg.do
。
7.2.4. 处理注册数据
设计请求:
请求路径:/user/handle_reg.do
请求方式:POST
请求参数:User(username, password, age),ModelMap
响应方式:成功时重定向到/user/login.do,失败时转发到error.jsp
先创建cn.tedu.spring.entity.User
类,并声明以上3个属性,参考Java Bean设计规范。
然后,在UserController
中添加处理请求的方法:
@RequestMapping("/handle_reg.do")
public String handleReg(User user, ModelMap modelMap) {
// 判断用户名是否是"admin"
// -- 是:视为失败,用户名被占用
// -- -- 向ModelMap对象中封装需要转发的数据:错误信息
// -- -- 执行转发
// -- 否:视为成功
// -- -- 重定向到/user/login.do
}
最后,创建error.jsp
,并在页面中通过EL表达式显示错误信息。
练习
在SPRINGMVC-03-SAMPLE
案例中继续完成:
显示登录页面
分析请求:
请求路径:/user/login.do
请求类型:GET
请求参数:无
响应方式:转发
所以,在UserController
中添加处理请求的方法:
@RequestMapping("/login.do")
public String showLogin() {
return "login"; // /WEB-INF/login.jsp
}
复制并调整现有的reg.jsp
,得到login.jsp
。
处理登录数据
分析请求:
请求路径:/user/handle_login.do
请求类型:POST
请求参数:username, password, ModelMap, HttpSession
响应方式:成功时重定向到/main/index.do,失败时转发到error
所以,在UserController
中添加处理请求的方法:
@RequestMapping(value="/handle_login.do",
method=RequestMethod.POST)
public String handleLogin(
@RequestParam("username") String username,
@RequestParam("password") String password,
ModelMap modelMap, HttpSession session) {
// 如果用户名是`root`且密码是`1234`,视为登录成功,否则,视为登录失败!
// 判断用户名是否为root
// 用户名正确 > 判断密码是否为1234
// 密码正确 > 登录成功 > 记Session
// 重定向到/main/index.do
// 密码错误 > 封装错误信息
// 转发到error
// 用户名错误 > 封装错误信息:用户名或密码错误
// 转发到error
}
显示主页
分析请求:
请求路径:/main/index.do
请求类型:GET
请求参数:无
响应方式:转发
由于此次的路径是/main
而不是之前的/user
,所以,必须新建另一个控制器类来完成,则创建cn.tedu.spring.controller.MainController
,和之前的控制器类一样,添加2项注解。
然后,在这个新创建的控制器类中添加处理请求的方法:
@RequestMapping("/index.do")
public String showIndex() {
return "index"; // /WEB-INF/index.jsp
}
然后,复制error.jsp
得到login.jsp
。
其它
1. 关于构造方法
每个类都有构造方法,用于确定对象的创建过程,当需要创建对象时,使用例如new Object();
这样的语法,就是在调用构造方法。
在编写类的时候,可以不需要显式的编写构造方法,当没有显式的编写时,编译器将自动添加公有的、无参数的构造方法!如果已经显式的编写了任何构造方法,编译器将不会自动添加构造方法!
构造方法也是方法,可以重载。
每次创建对象时,都会默认先调用其父类的构造方法!即:
public class User {
public User() {
super(); // 调用其父类的构造方法
}
}
且:使用super
关键字调用父类的构造方法的语句,必须编写在子类的构造方法中,必须是子类构造方法中的第1条有效代码!
抽象类的构造方法不允许直接调用,即不允许使用new
语法调用抽象类的构造方法来直接创建抽象类的对象!
通常,会在以下情景中自定义构造方法:
更便捷的创建对象,会自定义若干个带参数的构造方法,使得创建对象时就可以直接确定某些属性的值;
需要确定创建对象时,就执行的任务,或确定某些属性的值;
需要限制创建对象,可能将构造方法的访问权限设置得更加严格!
由于使用的系统不同,可能某些系统上快捷键不同,或会发生冲突,如果无法调整,则应该使用Source
菜单中对应的功能。
2. 转发与重定向
转发的本质是客户端只发出了1次请求,而重定向的话,客户端发出了2次请求。
转发是发生在服务端内部的,对于客户端而言,并不知道服务端内部有转发的过程,即使是控制器(无论是SpringMVC中的Controller还是传统的Servlet)转发到了JSP,对于客户端而言,也并不知晓。
在执行转发时,URL是不会发生变化的,而重定向会发生变化!
如果需要将较多的数据进行传递,可以使用转发!而重定向则不适合大量数据的传递,因为重定向对应的是2次请求,而2次请求之间数据并不共享!
如果需要地址栏发生变化,与当前页面显示的内容匹配,则必须使用重定向!
3. 关于响应码
服务端对客户端进行响应时,除了有响应的正文以外,还会有响应代码。常见的响应码:
2xx:正确响应,例如:200、206
3xx:重定向,例如:301、302
4xx:请求错误,例如:400、404、405、406
5xx:服务器内部错误,例如:500
4. 枚举类型
使用enum
关键字可以声明枚举类型,例如:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
定义枚举的前提是:可以穷举所有可能的值,每个值都不相同,并不关心值本身而只关心它表达的意义。