一、SpringMVC概述
1. 基本组件
- SpringMVC组件
2. 工作流程
- SpringMVC工作流程
- SpringMVC摆脱了每个servlet程序都要在web.xml中配置对应的servlet和servletMapping的麻烦。通过配置==DispatcherServlet==来拦截所有的请求,至于具体的每个请求,则单独在==controller包里配置不同的Controller类==,每个类都自定义一个==String返回值(当然也可以无返回值)的方法==,@RequestMapping注解在这些方法上,就匹配了对应的请求路径,return的值通过==视图解析器==就找到了对应的文件路径。从而完成了页面跳转。在web.xml中配置去读取SpringMVC的配置文件的init-param
3. 工作环境搭建
- 创建Maven-webapp工程,如果下载速度过慢,将修改Maven仓库镜像源
- 添加一个键值对archeTypeCatalog,internal,解决工程创建过慢问题
- 导入坐标
- 配置Tomcat服务器
- 添加SpringMVC.xml配置文件
- 配置web.xml文件
依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
web.xml
<!--配置servlet-->
<servlet>
<!--配置Servlet的名称和对应的类DispatcherServlet(Spring固定的类(servlet))-->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置DispatcherServlet的初始化参数,让它去读取bean.xml配置文件(应该说是SpringMVC的配置文件,而不是Spring的配置文件),从而可以扫描包下的注解-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:bean.xml</param-value>
</init-param>
<!--配置load-on-startup,以后启动服务器的时候就创建DispatcherServlet,而不是在第一次请求的时候创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置servlet匹配-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--配置拦截,/表示任何请求都被拦截-->
<url-pattern>/</url-pattern>
</servlet-mapping>
二、实例配置
1. springmvc配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<-- 开启SpringMVC注解配置支持,开启它,就可以使得requestMapping生效,一定要配置-->
//区别于Spring的annotaiton-driven,后者是开启Spring的事务注解配置支持<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<!--开启注解扫描-->
<mvc:annotation-driven/>
<!--扫描指定包下的注解-->
<context:component-scan base-package="com.zt"/>
<!--视图解析器对象InternalResourceViewResolver类,将来通过return的字符内容找目录内容里对应名称的jsp文件,跳转到对应的jsp页面-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置解析的目录(前缀)-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!--配置解析的文件后缀名-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
2. Controller配置
- 每一个controller对应一个应用
//交给Spring管理该控制类
@Controller
public class HelloController{
// 给该方法添加一个请求路径,以后访问该web工程下的这个路径,就执行这个方法
@RequestMapping(path = "/hello") //请求的路径
public String sayHello(){
System.out.println("Hello SpringMVC");
// return的内容一般默认为是要跳转的页面的名称(success.jsp,在WEB-INF下创建)
// 当返回success时,通过bean.xml中配置的视图解析器,找到对应目录下的对应名称的jsp文件(视图解析器配置了前缀和后缀路径)
return "success";
}
}
3. web.xml配置中文拦截器
- 配置字符集过滤器,拦截中文字符,转为utf-8格式
<!--配置解决中文乱码的过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--CharacterEncodingFilter中的参数encoding-->
<param-name>encoding</param-name>
<!--设置encoding字符集为UTF-8-->
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!--拦截所有-->
<url-pattern>/*</url-pattern>
</filter-mapping>
三、部分注解
1. @RequestMapping
- 作用:用于建立请求url和处理请求的方法之间的关系。放在某个类上,就相当于:
<a href="user/hello">SpringMVC入门</a>
@Controller
@RequestMapping(path = "/user")
public class HelloController{
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello SpringMVC");
return "success";
}
}
- value=属性,path=属性
- method=post\get\delete...等等(参数为数组类型)
@RequestMapping(method = {RequestMethod.GET})
- params,要求必须传入一个该参数
@RequestMapping(path = "/hello",params = {"username"}) @RequestMapping(path = "/hello",params = {"username=abc"}) //要求页面上的username值必须是abc才能访问
2. @RequestParams
- 作用:1、把请求中指定名称的参数赋值给方法中的形参 2、要求必须提供一个该名称的参数,与@RequstMapping的param区别在于,这里的参数名为一个键,"name=zt"是作为一个整体,而不是要求必须值为zt。
@RequestMapping(value = "/testRequestParam")
// @RequestParam修饰Parameters,name="name",表示访问时客户端url必须传入一个name属性的值,
// 即使是和形参username相同名字的也不行
// 然后将拿到的值放进username
public String testAnno(@RequestParam(name = "name",required = true) String username){
System.out.println(username);
return "success";
}
3. @RequestBody
- 区别于@ResponseBody,@ResponseBody可作用于类或方法上,把return的值直接返回给客户端页面如果有了@ResponseBody,那么同样的字符串"success"就会覆盖视图解析器的功能。
- 作用:用于获取请求体(整个form表单)内容,直接使用得到的是key=value&key=value...结构的数据GET请求方式不适用
- 属性:required:表示是否有请求体,默认值是true,当取值为true时,GET请求方式会报错。如果取值为false,GET请求得到的是null。
<form action="anno/testRequestBody" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="提交">
</form>
public String testBody(@RequestBody String body){
System.out.println(body);
return "success";
}
4. @PathVariable
- 用占位符获取客户端传递的参数
// {uid}作占位符,和@PathVariable中的value名相同
@RequestMapping(value = "/testPathVariable/{uid}")
public String testPath(@PathVariable(value = "uid") String id){
System.out.println(id);
return "success";
}
5. @CookieValue
- 获取Cookie的值,value为固定的"JSESSIONID"
@RequestMapping(value = "/testCookie")
public String testCookie(@CookieValue(value = "JSESSIONID") String cookieValue){
System.out.println(cookieValue);
return "success";
}
6. @ModelAttribute
- 范围:方法或者参数
- 作用:出现在方法上,方法会优先于servlet程序的方法执行。即用户请求所访问的那个url对应的方法会在注解标记的方法之后执行。应用场景:用户没有提交完整的javabean中的所有属性的值,即空了一些属性没有写,这时候可以用@ModelAttribute注解修饰的方法来填充这些没有填的属性的值(可以用数据库中的历史值)
设置表单中只提交两项,第三项由@ModelAttribute注解的方法添加
<form action="anno/testModelAttribute" method="post">
姓名<input type="text" name="name"><br/>
年龄<input type="text" name="age"><br/>
<input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/testModelAttribute")
public String testAttribute(User u){
System.out.println(u);
return "success";
}
@ModelAttribute
public User setUser(User user){ //参数user,把表单中的项添加到user中
user.setDate(new Date());
return user;
}
7. @SessionAttribute
- 作用:用于多次执行控制器方法间的参数共享,只能作用在类上。
- 属性: value\name:用于指定存入的属性名称 types:用于指定存入的数据类型
@Controller
@RequestMapping(value = "/anno")
//把name=value键值对存到session域中
@SessionAttributes(value = {"name"})
public class Annotation {
@RequestMapping(value = "/testSessionAttributes")
public String testSession(Model model){
// SpringMVC底层会存储到request域对象中
model.addAttribute("name","周涛");
return "success";
}
@RequestMapping(value = "/getSessionAttributes")
public String getSession(ModelMap modelMap){
String value =(String)modelMap.get("name"); //modelMap取到request域中的值
System.out.println(value);
return "success";
}
@RequestMapping(value = "/delSessionAttributes")
public String delSession(SessionStatus status){
status.setComplete();
return "success";
}
四、请求参数绑定
4.1 封装基本类型
- 用户参数username=abcd,password=1234MVC框架,sayhello(String username,String password)当MVC框架形参名和用户参数的name属性名相同时,会自动传递过来
<a href="param/testParam?username=我">请求参数绑定</a>
@RequestMapping(value = "testParam")
public String testParam(String username){
System.out.println(username);
return "success";
}
4.2 封装Javabean
1.Javabean中只有基本类型
- 表单项内容要和Javabean的属性名称一致
<form action="param/saveAccount" method="post">
<input type="text" name="username"><br/> //名称要对应
<input type="password" name="password"><br/>
<input type="text" name="money"><br/>
<input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
System.out.println(account);
return "success";
}
2.Javabean中有别的引用类型
- 表单项要和引用类型的属性名相同
<form action="param/saveAccount" method="post">
<input type="text" name="username"><br/>
<input type="password" name="password"><br/>
<input type="text" name="money"><br/>
<input type="text" name="user.name"><br/> //这两行改为Account对象中的user对象加属性,user.name
<input type="text" name="user.age"><br/>
<input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
System.out.println(account);
return "success";
}
3.封装集合对象
<form action="param/saveAccount" method="post">
姓名<input type="text" name="username"><br/>
密码<input type="password" name="password"><br/>
工资<input type="text" name="money"><br/>
Listname<input type="text" name="users[0].name"> //要封装的是User,实际需要对应的是User的name和age
Listage<input type="text" name="users[0].age">
mapusername<input type="text" name="map['1'].name"> //手动给String=1,map[1]就表示user(根据键得值)
mapuserage<input type="text" name="map['1'].age">
<input type="submit" value="提交"/>
</form>
private List<User> users;
private Map<String,User> map;
4.自定义类型转换器
- 对于浏览器提交的数据类型,有些需要进行转换才能封装到Javabean中
转换日期
- 前端页面
<form action="param/saveUser" method="post">
姓名<input type="text" name="name"><br/>
年龄<input type="text" name="age"><br/>
生日<input type="text" name="date"><br/>
<input type="submit" value="提交"/>
</form>
- 自定义日期类型转换器
public class StringToDateConvertor implements Converter<String,Date> { //实现Converter<S,T>接口
@Override
public Date convert(String source) {
Date date = null ;
if (source==null) {
throw new RuntimeException("请输入日期");
}else {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
date = df.parse(source);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
- 将类型转换器配置到SpringMVC.xml中
<!--开启SpringMVC注解配置支持,支持conversionService-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--配置converters属性,converters是set集合类型-->
<property name="converters">
<!--配set集合用set标签-->
<set>
<!--注入自定义转换器到IoC容器中,可以配置多个转换器-->
<bean class="com.zt.utils.StringToDateConvertor"/>
</set>
</property>
</bean>
- controller中接收
@RequestMapping(value = "/saveUser")
public String saveUser(User user){
System.out.println(user);
return "success";
}
五、响应数据和结果视图
1. Controller控制器返回
1.方法返回值为String字符串
@RequestMapping(value = "/testString")
public String testString(Model model){
System.out.println("testString方法执行了");
// 模拟从数据库中查询出一个用户的对象,把对象存起来转发到页面上
User user = new User();
user.setUsername("美美");
user.setPassword("123");
user.setAge(20);
// 把user存入到request域中
model.addAttribute("users",user);
return "success";
}
${users.username}<br/>
${users.password}<br/>
${users.age}
2.方法返回值为void类型
- 默认会转发到请求路径上,也就是访问的资源路径。
/*
* * @param
*@return 如果不写请求转发或重定向,默认转发到value名的jsp页面:/testVoid.jsp页面
* 如果项目中没有testVoid.jsp页面,可以请求转发或重定向到已有页面
*/
@RequestMapping(value = "/testVoid")
public void voidTest(HttpServletRequest req, HttpServletResponse resp){
System.out.println("voidTest被执行了");
}
/*
请求转发:
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
请求重定向:
resp.sendRedirect(req.getContextPath()+"/index.jsp"); //req.getContextPath()获得项目路径
直接响应:
resp.getWriter().write("你好");
*/
3.返回ModelAndView类型
- ModelAndView底层返回视图时调用的是视图解析器,return字符串的底层其实就是调用的ModelAndView
@RequestMapping(value = "/testModelAndView")
public ModelAndView testModel(){
ModelAndView mv = new ModelAndView();
User user = new User();
user.setUsername("美美");
user.setPassword("123");
user.setAge(20);
// 往ModelAndView中添加对象
mv.addObject("users",user);
// 调用视图解析器返回success.jsp
mv.setViewName("success");
return mv;
}
2. 转发和重定向
- 转发和重定向用不了视图解析器,所以路径得自己去补全。==重定向不能访问WEB-INF这个安全目录==,因为重定向相当于两次客户端请求,而客户端是不能直接访问的。
@RequestMapping(value = "/testForward")
public String Forward(){
// 使用forward: 关键字表明这是一个请求转发
// 以前直接return字符串需要先配置视图解析器,现在可以不用配置而直接转发
'/'在服务器被解析为tomcat服务器配置时的路径,https://ip:port/+工程名/,也就是webapp下
return "forward:/WEB-INF/pages/success.jsp";请求转发和重定向也可以重定向到某个方法
return "redirect:/index.jsp"; //使用关键字的重定向不用加项目名,即req.getContextPath()获得项目路径,可以直接写该项目下的路径
}
}
3. @ResponseBody响应数据
- @ResponseBody可以直接响应给客户端字符串数据,以JSON数据格式返回
六、SpringMVC文件上传实例
1. 依赖环境
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
表单项
<%--必须是多表单和post请求--%>
<form action="file/upload" method="post" enctype="multipart/form-data"> //必须是多表单和post请求,设置多表单的目的是把这个表单中的文件内容和普通表单项分开
比如分开username,password和file。
<input type="text" name="username"><br/>
<input type="password" name="password"><br/>
<input type="file" name="upload"><br/> //必须是file类型
<input type="submit" value="上传"><br/>
</form>
2. 传统文件上传
@Controller
@RequestMapping(value = "/file")
public class FileController {
@RequestMapping(value = "/upload")
public String upload(HttpServletRequest request) throws Exception {
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items){
if (!item.isFormField()){//如果不是普通表单项,则是文件项
String path = request.getSession().getServletContext().getRealPath("/upload/"); //取得upload目录在磁盘中的绝对路径
UUID uuid = UUID.randomUUID();
String name = uuid + item.getName(); //UUID是随机生成的,在分布式系统中是唯一值,以确保下载的文件不重名,即使是相同文件
item.write(new File(path+item.getName())); //加上文件名,写入
}
}
return "success";
}
3. SpringMVC上传
1.配置文件解析器
<!--配置文件解析器对象,名称必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--maxUploadSize属性:文件最大size,单位比特-->
<property name="maxUploadSize" value="10485760"/>
</bean>
2.配置前端页面
<form action="user/upload" enctype="multipart/form-data" method="post">
姓名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="file" name="upload"><br/> //file的name属性要和controller的形参名一样,这样才能参数绑定,完成解析
<input type="submit" value="提交"><br/>
</form>
3.配置controller
@RequestMapping(value = "/upload")
public String upload(HttpServletRequest req, MultipartFile upload) throws Exception { //需要upload,来完成上传,需要req来取得路径
// 解析的目的是区分普通表单项和文件项
// 文件upload已经通过绑定参数拿到,不需要再解析
// 拿到web工程下upload目录的绝对路径,可以先判断该路径是否存在,如果不存在就创建这个目录。路径取决于该web工程输出到哪,而不是Java工程的路径
String path = req.getSession().getServletContext().getRealPath("/upload/");//如果是部署在maven工程下,则输出到的是target下
File file = new File(path);
if (!file.exists()){
file.mkdirs(); //如果不存在就创建这个目录
}
// 拿到文件名,传统文件名,上传时的文件名
String filename = upload.getOriginalFilename();
// 通过设置UUID把文件的名称设置为唯一值
// 把UUID中的"-"用""替代
String uuid = UUID.randomUUID().toString().replace("-","");
// 修改当前上传到服务器的文件名
filename = uuid + "_" + filename;
// 完成文件上传,File(String parent,String Child)
upload.transferTo(new File(path,filename));
return "success";
}
4.原理分析
4. 跨服务器文件上传
- 在很多时候,应用服务器和文件服务器分别负责不同的功能,文件多数由应用服务器转发保存到文件服务器。
- 每个服务器都是一个进程,要配置不同的端口号
导入依赖
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19.4</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.19.4</version>
</dependency>
编写服务
@RequestMapping(value = "/upload2")
public String upload2(HttpServletRequest req, MultipartFile upload) throws Exception {
// 拿到跨服务器的图片服务器的path,上传至此
String path = "http://localhost:9090/SpringMVC_02_image/images/";
// 生成唯一的文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid + "_" + filename;
// 应用服务器作为客户端
Client client = Client.create();
// 和图片服务器进行连接
WebResource webResource = client.resource(path + filename);
// 上传文件,通过字节流的方式
webResource.put(upload.getBytes());
return "success";
}
七、拦截器Interceptors
- 拦截器是SpringMVC框架特有的,类似Servlet中的过滤器。也有拦截器链(类似过滤器链),先执行前置代码,然后放行(类似doFilter())最后执行后置代码。
- 区别:拦截器的/只会拦截Controller类中的方法,而不会拦截.jsp.html等等。拦截器能做的过滤器都能做,过滤器能做的拦截器不一定能做。过滤器在DispatcherServlet之前执行,拦截器在DispatcherServlet之后执行*。
- 预处理:preHandle:类似AOP的前置通知,在controller的方法被执行之前执行
- 后处理:postHandle:类似AOP的后置通知,在controller的方法执行之后(但是在方法中的return语句之前,如果方法没有return值,那就在方法全部执行之后再执行)执行,要求预处理必须return true
- 最终处理:afterCompletion :类似AOP的最终通知,在controller的return执行完后执行
- 定义拦截器类
//实现拦截器必须实现HandlerInterceptor方法
//如果在拦截器预处理或后处理中指定了跳转页面,就不会再执行controller中的跳转页面了
public class MyInterceptor implements HandlerInterceptor {
/*拦截器预处理,在执行controller中的方法之前执行
* 返回true表示放行,false表示不放行,即无法执行controller中的方法,也不会执行后处理
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截成功");
request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
/*
* 要求预处理必须返回true的时候才会执行后处理
* 后处理方法,在controller方法执行后,方法中的return之前
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("后置方法执行了");
}
/*
* 最终处理,一定会执行
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("最终处理执行了");
}
}
- xml中配置拦截器
<mvc:interceptors>
<!--配置某个拦截器-->
<mvc:interceptor>
<!--mapping配置需要拦截的路径,/*表示拦截user目录下的任意controller方法-->
<mvc:mapping path="/user/*"/>
<!--exclude-mapping配置不需要拦截的路径-->
<!-- <mvc:exclude-mapping path=""/>-->
<!--配置用哪个拦截器来拦截-->
<bean id="interceptor" class="com.zt.controller.interceptors.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>