1. MVC模式
使用MVC模式的目的:将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。
- M(Model):模型是指模型表示业务规则,被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,减少了代码的重复性
- V(View):视图是指用户看到并与之交互的界面,在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式
- C(Controller):控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求;控制器本身不输出任何东西和做任何处理,它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据
应用分层:
- 请求处理层(Web层):主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等
- 业务逻辑层(Service层):相对具体的业务逻辑服务层
- 数据持久层(DAO层):数据访问层,与底层MySQL、Oracle、Hbase、OB等进行数据交互
分层后的异常处理:
- 在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储
- 在Service层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场
- Web层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息
分层的意义:
- 解耦:降低层与层之间的耦合性
- 可维护性:提高软件的可维护性,对现有的功能进行修改和更新时不会影响原有的功能
- 可扩展性:提升软件的可扩展性,添加新的功能的时候不会影响到现有的功能
- 可重用性:不同层之间进行功能调用时,相同的功能可以重复使用
SSM指:SpringMVC(Web层)+Spring(贯穿三层)+MyBatis(DAO层)
SSH指:Struts(Web层)+Spring(贯穿三层)+Hibernate(DAO层)
自定义DispatcherServlet的思路:
- 编写@Controller注解,作用在类上,标注这个类是一个控制器
- 编写@RequestMapping注解,作用在方法上,其value为要进行匹配的路径
- 在DispatcherServlet类中设置一个map类型属性,存储@RequestMapping的值和方法对象
- 在DispatcherServlet的init方法中,获取指定包中所有类的字节码对象的集合,遍历出每一个字节码对象,如果这个类上有@Controller注解,就遍历出这个类的每一个方法,判断方法上是否有@RequestMapping注解,若有则将@RequestMapping的值和方法对象放入到map中
- 在DispatcherServlet的doPost或doGet方法中,从请求路径URI中截取出要进行匹配的路径,如果截取出的路径在map中,则调用对应的方法
SpringMVC和Struts2的区别:
- SpringMVC和Struts2的底层都离不开Servlet,并且处理请求的机制都是采用一个前端控制器
- SpringMVC的入口是Servlet,而Struts2是Filter
- SpringMVC是基于方法的,每次请求执行控制器的方法;而Struts2是基于类的,每次执行都会创建一个动作类(多例的),因此SpringMVC比Struts2稍微快一些
2. SpringMVC基本使用*
-
创建Maven工程(war),导入坐标
org.springframework spring-webmvc 5.0.2.RELEASE javax.servlet servlet-api 2.5 provided com.fasterxml.jackson.core jackson-databind 2.9.0 com.fasterxml.jackson.core jackson-core 2.9.0 com.fasterxml.jackson.core jackson-annotations 2.9.0 commons-fileupload commons-fileupload 1.3.1 com.sun.jersey jersey-core 1.18.1 com.sun.jersey jersey-client 1.18.1 javax.servlet.jsp jsp-api 2.0 provided org.projectlombok lombok 1.18.12 -
创建Controller类,给方法添加注解
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @RequestMapping("/show") public String show() { System.out.println("hello, world"); // 请求转发跳转到成功页面 return "success"; } }
-
创建SpringMVC的配置文件applicationContext.xml
-
在web.xml里配置DispatcherServlet
dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext.xml 1 dispatcherServlet / 部署后访问测试:http://localhost:8080/user/show
以上几步的执行流程:
- 服务器启动时加载DispatcherServlet,根据初始化参数加载Spring的配置文件,然后创建Spring容器并初始化容器中的对象
- DispatherServlet根据捕获到的请求解析出URI,根据该URI调用HandlerMapping获取对应的Handler配置的所有相关对象,然后以HandlerExecutionChain对象的形式返回
- DispatherServlet根据获取到的Handler选择一个合适的HandlerAdapter,将Request中的模型数据传给Handler的方法,然后执行Handler的方法,给DispatherServlet返回一个ModelAndView
- DispatherServlet根据返回的ModelAndView选择一个合适的ViewResolver来结合Model和View,渲染视图
- 将渲染结果响应给客户端
SpringMVC的请求响应流程*:
- DispatcherServlet(前端控制器):相当于MVC模式中的C,DispatcherServlet接收客户端的请求、控制其它组件的执行、做出响应、跳转页面,DispatcherServlet的存在降低了组件之间的耦合性,符合迪米特法则
- HandlerMapping(处理器映射器):根据请求找到Handler,映射成一个执行链接,不同的映射器实现不同的映射方式
- HandlerAdapter(处理器适配器):根据执行链接,适配到对应的处理器来执行
- Handler(处理器):就是自己写的Controller类,真正去执行处理请求的就是Handler
- ViewResolver(视图解析器):先根据Controller返回的字符串,将逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象,最后对View进行渲染并将处理结果通过展示给客户端
- Model(数据模型):可以往域对象中存放数据
3. SpringMVC常用注解*
@RequestMapping:建立请求URL和处理方法之间的对应关系,可以作用在方法和类上
- 作用在类上:请求URL的第一级访问目录,不写就相当于应用的根目录,写的话需要以/开头;它的目的是为了按照模块化管理URL
- 作用在方法上:请求URL的第二级访问目录
请求URL绝对路径写法:${pageContext.request.contextPath}+作用作用在类上的+作用在方法上的
-
请求URL相对路径写法:作用作用在类上的+作用在方法上的
@RequestMapping( path = "/请求的URL", value = "和path功能一样", // 指定该方法的请求方式,可以配置多个,默认任意请求方式都可以 method = {RequestMethod.POST}, // 指定限制请求参数的条件,如:请求参数必须是age=18,不是则会报错 params = {"age=18"}, // 发送的请求中必须包含的请求头 headers = "content-type=text/*")
@RequestParam("请求参数名"):作用在参数上,可以将指定名称的请求参数绑定给形参;在获取List、Map类型的请求参数时必须添加@RequestParam注解
@RequestBody:作用在参数上,可以获取请求体信息,将请求体中的请求参数封装到POJO或Map中;使用时要导入JSON转换的依赖
@ResponseBody:可以作用在类或方法上
- 作用在方法上:可以将方法返回的POJO或Map通过HttpMessageConverter接口转换为指定格式的数据,如:json、xml等,然后通过Response响应给客户端;使用时要导入JSON转换的依赖
- 作用在类上:相当于给类的所有方法都加了@ResponseBody
- @RestController注解相当于@Controller+@ResponseBody
@PathVariable(value = "URI中的占位符名称", required = true):作用在参数上,获取RESTful(Representational State Transfer,REST)风格的URI中的路径参数
required属性表示是否必须提供占位符,默认为true
-
RESTful风格中每一个URI代表1种资源;客户端使用GET请求来查询资源,使用POST请求来新建/保存资源,使用PUT请求来更新资源,使用DELETE请求来删除资源
// @GetMapping("/{userId}/{userName}") // @PostMapping("/{userId}/{userName}") // @PutMapping("/{userId}/{userName}") @DeleteMapping("/{userId}/{userName}") public void deleteById(@PathVariable("userId") int id, @PathVariable("userName") String name) { System.out.println("id = " + id + ", name = " + name); }
@RequestHeader(value = "请求头名称", required = true):作用在参数上,可以获取请求头信息;required属性表示是否必须有此请求头
@CookieValue(value = "cookie的名称", required = true):作用在参数上,可以获取指定名称的cookie;required属性表示是否必须有此cookie
@SessionAttributes(value = {"request域对象中的key"}, types = {String.class}):作用在类上,用于多次请求间的数据共享
@ModelAttribute("key"):SpringMVC4.3以后新加入的,作用在方法或参数上;若作用在方法上,则该方法会在控制器的方法执行之前执行
- key可以是POJO的属性名,也可以是Map的key
- 当表单提交数据不是完整的实体类数据时,要保证没有提交数据的字段使用数据库对象原来的数据,就可以在一个方法上加@ModelAttribute注解,将实体类补完整,然后将实体类返回给控制器的方法作为参数
@DateTimeFormat(pattern = "yyyy-MM-dd"):作用在属性上可以对日期类型属性进行格式化
4. 请求参数处理
4.1 请求参数乱码处理+
在web.xml里面配置编码过滤器
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
4.2 自定义类型转换器-
SpringMVC默认实现了一些数据类型的自动转换,内置转换器在org.springframework.core.convert.support包下,但若有特殊类型转换的要求,就需要自己定义类型转换器。
自定义类型转换器:
-
定义一个类,实现Converter
接口import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; // Converter接口的泛型:<源类型, 目标类型> public class DateConverter implements Converter
{ @Override public Date convert(String source) { // 形参source为要进行转换的String SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { // 返回值为目标Date return simpleDateFormat.parse(source); } catch (ParseException e) { e.printStackTrace(); throw new RuntimeException("转换异常"); } } } -
在SpringMVC配置文件中配置自定义的转换器
-
在annotation-driven标签中引用配置的类型转换服务
4.4 用ServletAPI中的对象作为方法的参数-
SpringMVC还支持使用原始ServletAPI对象作为控制器中方法的参数,如:HttpServletRequest、HttpServletResponse、HttpSession、java.security.Principal、Locale、InputStream、OutputStream、Reader、Writer
4.4 绑定form-data格式请求参数*
form-data格式请求参数:参数1名=参数1值&参数2名=参数2值
- get请求和表单提交为form-data格式请求参数
json格式参数:{"参数1名":参数1值, "参数2名":参数2值}
- Vue的post请求为json格式参数
SpringMVC把请求参数作为控制器中方法的形参进行绑定:
- 绑定简单类型的请求参数:形参名和请求参数名一致
- 绑定POJO类型的请求参数:形参类型为POJO类型,并且请求参数名与POJO的属性名一致;若POJO中包含集合,使用下标给List中的元素赋值,使用键值对给Map中的元素赋值
- 若没有对应的POJO,则可以用Map来绑定请求参数:Map的key为参数名,value为参数值且value只能是String类型;形参类型前加@RequestParam注解,形参类型为Map
- 绑定多个同名的请求参数:形参类型前加@RequestParam注解,形参类型为List,并且请求参数名与形参名一致
5. 转发和重定向
Controller中的方法:
- 返回String可以指定逻辑视图名,如果有视图解析器则通过视图解析器解析为物理视图地址
- 返回ModelAndView对象,既能够绑定视图名,也能够绑定数据(将数据存储到request域对象)
forward转发:Controller中的方法返回String后,默认就是请求转发
-
转发到页面
return "forward:/页面url"; 相当于:request.getRequestDispatcher("/本项目资源路径").forward(request, response);
-
转发到其它的Controller方法
return "forward:/类上的RequestMapping/方法上的RequestMapping";
redirect重定向:
-
重定向到页面
return "redirect:/页面url"; 相当于:response.sendRedirect(页面url)
-
重定向到其它的Controller方法
return "redirect:/类上的RequestMapping/方法上的RequestMapping";
6. 设置DispatcherServlet不拦截静态资源*
DispatcherServlet如果配置映射路径为/,则会拦截到所有的资源(除了jsp),导致静态资源(img、css、js、...)也会被拦截到,从而不能被使用。
解决方式1-:在SpringMVC配置文件中,配置不拦截静态资源
解决方式2*:在SpringMVC配置文件中,配置让DefaultServlet去处理静态资源
解决方式3+:修改DispatcherServlet的映射路径为扩展名匹配,如:*.do
7. 文件上传*
文件上传要求:
- 客户端:
- 表单提交方式为post
- 提供文件上传框(组件),如:
- 表单的enctype属性必须为multipart/form-data,此时使用原生request.getParameter()获取的参数都为null
- 服务器端:
- 获取客户端上传的文件
- 准备一个目录存储客户端上传的文件
- 将客户端上传的文件写入到准备好的目录中
commons-fileupload是apache的一款专门处理文件上传的工具包,SpringMVC封装了commons-fileupload
7.1 SpringMVC传统方式文件上传
页面upload.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
文件上传
编写一个上传工具类:
import java.util.Random;
import java.util.UUID;
public class UploadUtils {
// 获取一个随机名称
public static String getUUIDName(String fileName) {
// 获取后缀名
int index = fileName.lastIndexOf(".");
if (index == -1) {
return UUID.randomUUID().toString().replace("-", "").toUpperCase();
} else {
return UUID.randomUUID().toString().replace("-", "").toUpperCase() + fileName.substring(index);
}
}
// 获取一个随机目录
public static String getRandomDirectory() {
String s = "0123456789ABCDEF";
Random r = new Random();
// 16*16=256种随机目录
return "/" + s.charAt(r.nextInt(16)) + "/" + s.charAt(r.nextInt(16));
}
}
编写Controller:
import com.liu2m.utils.UploadUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public String upload(String describe, MultipartFile upload, HttpServletRequest request) {
System.out.println("文件描述:" + describe);
// 1. 获取存放文件的目录
String realPath = request.getSession().getServletContext().getRealPath("file/" + UploadUtils.getRandomDirectory());
File filePath = new File(realPath);
// 如果目录不存在就创建目录
if (!filePath.exists()) {
filePath.mkdirs();
}
// 2. 获取客户端上传的文件的文件名
String originalFilename = upload.getOriginalFilename();
// 将文件重命名成唯一的文件名-->UUID
String uuidName = UploadUtils.getUUIDName(originalFilename);
try {
// 3. 将客户端上传的文件写入到文件夹中
upload.transferTo(new File(filePath, uuidName));
} catch (IOException e) {
e.printStackTrace();
}
// 转发到success页面
return "success";
}
}
在SpringMVC的配置文件中配置文件解析器组件(文件上传解析器的id是固定的,不能起别的名称,否则无法绑定请求参数)
7.2 SpringMVC跨服务器方式文件上传
客户端将文件上传给web服务器,然后web服务器将文件上传给文件服务器,客户端需要文件时从文件服务器下载
准备两个服务器,由于tomcat默认不允许其他服务器往它里面写入数据,因此修改文件服务器的tomcat中conf目录下的web.xml,添加初始化参数readonly值为false:
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
readonly
false
1
修改Controller:
import com.liu2m.utils.UploadUtils;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public String upload(String describe, MultipartFile upload, HttpServletRequest request) {
System.out.println("文件描述:" + describe);
// 1. 获取客户端上传的文件的文件名
String originalFilename = upload.getOriginalFilename();
// 将文件重命名成唯一的文件名-->UUID
String uuidName = UploadUtils.getUUIDName(originalFilename);
// 2. 连接文件服务器
WebResource resource = Client.create().resource("http://localhost:8080/file/" + uuidName);
try {
// 3. 通过resource将客户端上传的文件写入到文件服务器
resource.put(upload.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// 转发到success页面
return "success";
}
}
8. 自定义异常处理器-
系统的Dao、Service、Controller出现异常都往上抛,最后由DispatcherServlet将异常交给异常处理器去处理
自定义异常处理器:
-
定义一个类,实现HandlerExceptionResolver接口
import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class GlobalExceptionHandler implements HandlerExceptionResolver { // 重写resolveException方法,对异常进行统一处理 @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { ModelAndView modelAndView = new ModelAndView(); // 添加一个数据模型 modelAndView.addObject("msg", e.getMessage()); // 打印异常信息 e.printStackTrace(); // 设置视图名 modelAndView.setViewName("error"); return modelAndView; } }
-
在SpringMVC配置文件中对自定义异常处理器进行IoC
9. SpringMVC中的拦截器*
SpringMVC中的拦截器类似于Servlet中的Filter,用于对Controller进行预处理和后处理。拦截器链就是将拦截器按一定的顺序联结成一条链,在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
拦截器和Filter的区别:
- 拦截器使用在SpringMVC项目中,只会拦截访问Controller中方法的请求
- Filter可以使用在任何Web项目中,可以拦截任何资源
自定义拦截器:
-
定义一个类,实现HandlerInterceptor接口
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class PermissionInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("在执行Controller方法之前执行;优先级高的先执行"); // 返回true代表放行,如果后面还有拦截器就执行下一个拦截器,如果后面没有拦截器就执行Controller方法 // 返回false代表拦截,拦截后也需要返回到一个具体的结果(页面、Controller) // 若拦截了,则不再执行本拦截器的postHandle、afterCompletion return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("在执行Controller方法完毕后执行;优先级高的后执行"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("在本次请求完成后执行;优先级高的后执行"); } }
-
在SpringMVC配置文件中配置拦截器