SpringMVC
1. 作用
- 原生的JavaEE中,使用Servlet接收并处理请求的,在实际应用中,某个项目中可能涉及许多种请求,例如:用户注册、用户登录、修改密码、修改资料、退出登录等许多功能,在许多情况下,自行创建的每个Servlet只处理1种请求,即对应1个功能,如果项目中有几百个功能,则需要几百个Servlet来处理!进而,就会导致在项目运行过程中,需要几百个Servlet的实例,同时,编写代码时,每个Servlet都需要在web.xml中进行配置,就会产生大量的配置,在代码管理方面,难度也比较大!
- 使用SpringMVC主要解决了V-C交互问题,即Controller如何接收用户的请求,如何向客户端进行响应,同时,它解决了传统的JavaEE中的问题,每个Controller可以处理若干种不同的请求,且并不存在大量的配置。
2. 练习
***1.创建项目
-
在
pom.xml
中添加必要的依赖,SpringMVC项目至少需要添加spring-webmvc
依赖。 -
在
web.xml
中对DispatcherServlet
进行配置: -
在此前的项目中,复制Spring的配置文件到当前项目中来,同时,为了保证项目启动时,就加载该配置文件,应该修改
DispatcherServlet
的配置,使之成为项目启动时即加载的Servlet,且配置它的启动参数,指定Spring配置文件的路径:<.servlet.>
<.servlet-name.>SpringMVC</servlet-name.>
<servlet-class.>org.springframework.web.servlet.DispatcherServlet</servlet-class.>
<init-param.>
<param-name.>contextConfigLocation</param-name.>
<param-value.>classpath:spring-mvc.xml</param-value.>
</init-param.>
<load-on-startup.>1</load-on-startup.>
</servlet.>
<servlet-mapping.>
<servlet-name.>SpringMVC</servlet-name.>
<url-pattern.>*.do</url-pattern.>
</servlet-mapping.>
***2.显示注册页面
-
创建
cn.tedu.spring.UserController
类,在类之前添加@Controller
,表示这个类是一个控制器组件,同时,为了保证这个类将被Spring容器所管理,还需要在Spring的配置文件中添加组件扫描,扫描该类所在的包: -
如果希望某个类能够被Spring管理,第1种做法是在Spring的配置文件中通过节点进行配置,第2种做法是在Spring的配置文件中添加组件扫描,并在类之前添加@Component/@Controller/@Service/@Repository注解。如果配置自行编写的类,应该使用第2种做法,如果配置是他人编写的类,则应该使用第1种做法。
-
由于后续涉及的请求有
/user/reg.do
、/user/handle_reg.do
等,都是使用/user
作为请求路径的前一部分的,所以,应该在类之前添加@RequestMapping("/user")
注解。
目前,处理请求的方法应该是:- 使用
public
权限; - 使用
String
类型的返回值(暂定); - 方法名称可以自定义;
- 方法没有参数;
A. 通常使用的视图解析是InternalResourceViewResolver
,它将根据配置的前缀+处理请求的方法的返回值+配置的后缀,得到位于webapp
下的某个视图文件的路径。
B. 默认情况下,处理请求的方法返回的字符串,表示最终将转发到某个JSP页面,且返回值是JSP的路径中去除视图解析器中前缀与后缀的部分。
C. 假设后续创建的JSP文件是/webapp/WEB-INF/register.jsp
,且视图解析器配置的前缀是/WEB-INF/
,而后缀是.jsp
,则处理请求的方法应该返回"register"
。
所以,处理请求的方法:
@RequestMapping("/reg.do")
public String showReg() {
System.out.println(“UserController.showReg()”);
return “register”;
}
并且,在Spring的配置文件中添加视图解析器的配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/" /> <property name="suffix" value=".jsp" /> </bean>
- 使用
***3.业务处理
【方式1】 通过HttpServletRequest接收请求参数【不推荐】
在处理请求的方法中添加HttpServletRequest
参数:
@RequestMapping("/handle_reg.do")
public String handleReg(HttpServletRequest request) {
String username = request.getParameter(“username”);
String password = request.getParameter(“password”);
Integer age = Integer.valueOf(request.getParameter(“age”));
String email = request.getParameter(“email”);
System.out.println(“username=” + username);
System.out.println(“password=” + password);
System.out.println(“age=” + age);
System.out.println(“email=” + email);
return “login”;
}
通常并不推荐这种做法,主要原因有:[1] 需要调用方法才可以获取参数,比较麻烦;[2] getParameter()
方法返回的是String
类型,如果需要的数据类型是其它类型,则需要自行转换;[3] 不便于执行单元测试。
【方式2】 在处理请求的方法中直接添加所需的数据 【推荐】
@RequestMapping("/handle_reg.do")
public String handleReg(String username, String password, Integer age,String email) {
System.out.println("[2] username=" + username);
System.out.println("[2] password=" + password);
System.out.println("[2] age=" + (age+1));
System.out.println("[2] phone=" + phone);
System.out.println("[2] email=" + email);
return “login”;
}
使用时,需要注意的是:**参数的名称必须与请求参数的名称保持一致!**如果名称不一致,对应的参数将是null值。
这种做法的缺点是:不太适用于请求参数过多的应用场合。
【方式3】 将多个参数封装到某个类中,并使用这个类型作为处理请求的方法的参数 【推荐】
@RequestMapping("/handle_reg.do")
public String handleReg(User user) {
System.out.println(user);
return “login”;
}
使用时,需要注意的是:自定义类中的属性的名称必须与请求参数的名称保持一致!如果名称不一致,对应的属性将是null值。
【小结】 如何接收客户端提交的请求参数
如果客户端提交的请求参数较多,则优先使用第3种方式;
如果客户端提交的请求参数的数量可能发生变化(例如随着需求更新而改版),则优先使用第3种方式;
如果客户端提交的请求参数的数量较少,并且固定,则优先使用第2种方式。
注意:以上2种方式可以组合使用。
【重定向】 当处理请求的方法的返回值以redirect:
作为前缀时,表示重定向,在使用重定向时,冒号右侧的部分必须是相对路径或绝对路径,不可以是视图名! return “redirect:login.do”;
// 密码正确,登录成功,跳转到主页:/main/index.do
// 当前位置:/user/handle_login.do
// 目标位置:/main/index.do
return “redirect:…/main/index.do”;
3. 关于@RequestMapping注解
- @RequestMapping`注解的主要作用是配置请求路径。该注解可以添加在类之前,也可以添加在某个处理请求的方法之前,当添加在类之前,用于配置路径中的层次(类似于描述本地路径中的文件夹),当添加在方法之前,用于配置类的注解的路径右侧的剩余部分
- 例如用于配置请求的资源(reg.do或login.do),如果请求的路径之前还有其它路径,也一并配置在这里,例如某请求是
http://localhost:8080/PROJECT/user/list/add.do
,如果在类之前配置的只有/user
,则在方法之前配置为/list/add.do
,总的原则就是类之前的注解配置拼接上方法之前的注解配置可以得到完整请求路径即可。 - 在配置时,最左侧的
/
是可以忽略的,例如某请求是http://localhost:8080/PROJECT/user/reg.do
,在类和方法之前分别配置
/user /reg.do
user reg.do
/user reg.do
user /reg.do
以上4种配置都是正确的!
a) 关于@RequestMapping
注解,还可以用于限制请求类型,当限制了请求类型之后,如果客户端使用错误的方式来提交请求,则会出现405错误,提示内容例如: Request method ‘GET’ not supported
b) 实现方式为: @RequestMapping(value="/handle_login.do", method=RequestMethod.POST)
c) 关于value
属性,取值是String[]
,当只配置1个路径时,直接使用字符串值即可,如果有多个路径,则写成数组格式,例如: @RequestMapping({"/reg.do", “/register.do”})
d) 另有path
属性,与value
是完全等效的,该属性是从4.2版本开始启用的。
e) 关于method
属性,用于设置请求方式,取值为RequestMethod
枚举的数组,与value
一样,如果只有1个值时可以直接编写,如果有多个值,必须写成数组格式。
f) 与@RequestMapping
注解相似的还有@GetMapping
、@PostMapping
……这些就是限制了请求类型的@RequestMapping
,例如@PostMapping("/handle_login.do")
等效于@RequestMapping(value="/handle_login.do", method=RequestMethod.POST)
,这些注解是从4.3版本开始启用的。
4. 关于@RequestParam注解
a) @RequestParam
是添加在请求参数之前的注解。
b) 通过@RequestParam
注解可以解决请求参数名与处理请求的方法的参数名不一致的问题,例如:
@RequestParam(“uname”) String username
c) 需要注意的是:一旦使用以上注解,默认情况下,该参数是必须提交的!如果客户端没有提交对应的参数,则会导致400错误: HTTP Status 400 - Required String parameter ‘uname’ is not present
d) 所以,该注解还可以用于强制要求客户端必须提交某些参数。
e) 该注解有属性required
,是boolean
类型的,表示是否必须的意思,默认值为true
。
f) 该注解还有属性defaultValue
,是String
类型的,表示默认值,即客户端的请求中并不包含该参数时,视为提交是某个值!设置默认值时,需要将required显式的设置为false!
1. 关于HttpSession
哪些数据需要保存到Session:
- 当前登录的用户的唯一标识,例如用户的id等;
- 使用频率极高的数据,例如用户的用户名,用户的头像等;
- 不便于使用其它方式传递或保存的数据。
在SpringMVC的控制器中,处理请求时,如果需要使用Session(无论向Session中存入数据,还是从Session中读取数据),则在处理请求的方法中添加HttpSession
作为参数,然后在方法体内部使用即可!
2. 拦截器:Interceptor
a) SpringMVC中的拦截器可以应用于许多请求,当用户尝试对指定的请求路径进行访问时,拦截器将被执行,且最终会拦截或放行。
b) 例如创建验证登录的拦截器,首先,需要自定义类,并实现HandlerInterceptor
接口:
c) 关于拦截器的配置,可以配置多项拦截路径,也可以添加“例外”:
<!-- 拦截器链 -->
<mvc:interceptors>
<!-- 第1个拦截器 -->
<mvc:interceptor>
<!-- 1. 拦截路径(黑名单) -->
<mvc:mapping path="/**"/>
<!-- 2. 例外(白名单) -->
<mvc:exclude-mapping path="/user/reg.do"/>
<mvc:exclude-mapping path="/user/handle_reg.do"/>
<mvc:exclude-mapping path="/user/login.do"/>
<mvc:exclude-mapping path="/user/handle_login.do"/>
<!-- 3. 拦截器类 -->
<bean class="cn.tedu.spring.LoginInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
a) 凡是不在黑名单中的访问路径,或在白名单的访问路径,都不会触发拦截器的执行。
b) 在配置拦截器的拦截路径或例外路径时,可以使用通配符:*
表示某层路径或某个资源,例如/user/*
可以通配/user/reg.do
及/user/login.do
等,?
可以通配1个字符,**
可以通配多层级内容,例如/user/**
可以通配/user/1/edit
及/user/1/delete
等。
【附】 拦截器(Interceptor)与过滤器(Filter)的区别
拦截器与过滤器有高度相似之处,例如都可以应用于许多请求,并且都可以起到“拦截”的效果,也都可以存在多个组件形成“链”。
关于这2者的区别:
- 归属不同,过滤器是Java EE中的组件,而拦截器是SpringMVC中的组件,所以,只要开发Java WEB项目,都可以使用过滤器,但是,仅当开发Java WEB项目且使用了SpringMVC框架才可以使用拦截器,并且,由于拦截器是SpringMVC框架下的,只有被DispatcherServlet处理的请求才有可能被拦截器处理;
- 配置不同,过滤器的配置比较单一,拦截器的配置非常灵活;
- 执行时间节点不同,过滤器是执行在所有Servlet之前的,而拦截器的第1次执行是执行在DispatcherServlet之后且在Controller之前的。
【附】 抽象类与接口的区别
- 语法区别
- 类表示的是“类别”,接口表示的是“规范、标准、行为模式”
- 类与抽象类之间是“类 is a 抽象类”的关系,类与接口之间的关系是“类 has a 接口”的关系