创作不易,各位看官点赞收藏.
Spring MVC:Spring MVC是Spring Framework的一部分,是基于java实现的MVC的轻量级Web框架。
官网文档地址:https://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html
MVC:是一种软件架构思想,按照软件的模型、试图、控制器进行划分。
M:model,模型层工程中的 javaBean 用于处理数据。
V:view,视图层工程中显示数据的页面,用于与用户交互使用。
C:controller,控制器层,用于接收前端请求和响应浏览器。
导入依赖:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.8version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2version>
dependency>
<dependency>
<groupId>javax.servlet.jsp.jstlgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
dependencies>
创建 web.xml 配置文件:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springmvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
创建 spring 容器配置文件:springmvc-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
beans>
编写controller:
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
// 创建ModelAndView对象,用于返回业务的数据
ModelAndView mv = new ModelAndView();
// 把业务获得的数据封装到mv中
mv.addObject("msg","Hello,SpringMVC!");
// 把要跳转的视图封装到mv中
mv.setViewName("hello"); // 在springmvc-servlet.xml中拼接为 /WEB-INF/jsp/hello.jsp
// 把mv返回给DispatcherServlet
return mv;
}
}
controller 注入 Spring 容器:
<bean id="/hello" class="com.xiayuan.controller.HelloController"/>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
test
${msg}
配置 Tomcat并测试:
注意:在配置DisapatcherServlet时,拦截的请求是 / 表示拦截所有的请求, /* 也是拦截所有的请求,但是 / 不包括jsp文件,而 /* 会包括jsp文件,所以只能写 / 来拦截所有的请求。
@RequestMapping:注解用于映射 url 到控制器类或一个特定的处理程序方法。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@Controller
@RequestMapping("/test")
public class HelloController {
// 映射的 url 地址就是 /test/helllo
@RequestMapping("/hello")
public String sayHello(Model model){
model.addAttribute("msg","Hello,SpringMVCAnnotation!");
return "hello";
}
}
@RequestMapping 注解属性:
params={"test","admin"}
:请求中必须同时包含test、admin两个参数。@RequestMapping 派生注解:
@RequestParam 注解:用于控制器的请求方法的参数上,将请求参数与方法参数两个相互映射。
@GetMapping("/test1")
// 将前端的user参数与name参数映射
public String test(@RequestParam("user") String name){
return name;
}
@RequestHeader 注解:用于控制器方法上的参数,请求头信息与参数之间的关系映射。
@PostMapping("/test1")
// 将请求头中的Accept的值赋值给header
public String test(@RequestHeader("Accept") String header){
return header;
}
@CookieValue 注解:用于控制器方法上的参数,将请求中的 cookie 与参数之间的关系映射起来。
@PostMapping("/test1")
public String test(@CookieValue String cookie){
return cookie;
}
@Controller:用于类上,标识这个类是一个处理前端请求的类,会根据 url 地址映射到处理请求的方法上。
@Controller
public class ControllerTest {
}
@RestController:作用与 @Controller 相同,但是这个类处理请求返回的结果是 json 格式。
请求参数:每一次请求都会携带一些参数数据,后端可以获取这些参数数据进行一系列操作,获取请求参数的方式很多。
@RequestMapping("/t1")
public String test1(@RequestParam("name") String name, @RequestParam("password") String password, Model model){
// @RequestParam注解中的name和password就是从前端传过来的参数名称
// 必须和前端参数名称一样,后面的string的名称可以和前端参数名称不一样
System.out.println("name = "+name);
System.out.println("password = "+password);
return "test";
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private String password;
}
@RequestMapping("/t2")
public String test2(User user,Model model){
System.out.println(user);
return "test";
}
注意:把user作为参数来接受前端数据,而且前端的参数名称必须要和接受对象的字段名称保持一致,不能不一样。如果字段不一样的字段,就会自动为默认值。
@PostMapping("/test2")
public String test(@RequestBody Map<String,String> params){
System.out.println(params.get("name"));
return "ok";
}
请求参数乱码问题:在前端传入的参数中有中文字符,在后端就会存在乱码问题,我们通常使用过滤器去解决乱码问题。
<filter>
<filter-name>encodingfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
<init-param>
<param-name>forceResponseEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
request 域数据设置:
ModelAndView:这是之前的数据封装方式,使用的就是ModelAndView来给前端显示数据的。
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv = new ModelAndView();
// 数据
mv.addObject("msg","封装的数据!");
// 视图的名称
mv.setViewName("hello");
return mv;
}
ModelMap:
@RequestMapping("/t3")
public String test3(ModelMap modelMap){
modelMap.addAttribute("msg","封装的数据!");
return "test";
}
Model:
@RequestMapping("/t4")
public String test4(Model model){
model.addAttribute("msg","封装的数据!");
return "test";
}
注意:ModelMap 它继承了 LinkedHashMap
,所有他有该类的所有操作,而 Model 是 ModelMap 的简洁版本,没有继承 LinkedHashMap
。
Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解; ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性; ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转,它们都是把数据封装到在 request 域中的,实际上在底层返回中请求都是返回的一个 ModelAndView 对象。
session 域数据:这个数据是当前会话共享的数据。
@GetMapping("/test")
public String test(HttpServletSession session){
session.setAttribute("data","这是一个数据");
return "ok";
}
application 域数据:这个数据是应用程序共享的数据。
@GetMapping("/test")
public String test(HttpSession session){
// 应用全局上下文
ServletContext context = session.getServletContext();
context.setAttribute("data","这是一个数据");
return "ok";
}
请求转发:
@RequestMapping("/t2")
public String test2(HttpServletRequest request, HttpServletResponse response,Model model){
// 请求转发:可以转发到页面,也可以转发到一个请求
return "forward:/test"
return "forward:/login.jsp";
}
重定向:
@RequestMapping("/t2")
public String test2(HttpServletRequest request, HttpServletResponse response,Model model){
// 相当于重新发出一个请求
return "redirect:/login.jsp"
}
:注意重定向不能重定向到WEB-INF下的资源。
Restful:是一个资源定位及资源操作的风格,是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
操作 | 传统方式 | RESTFul风格 |
---|---|---|
查询 | getById?id=1 | getById/1 ==== get方式 |
新增 | insert | insert === post方式 |
删除 | deletedByid?id=1 | deletedByid/1 ==== delete方式 |
更新 | updateByid?id=1 | updateByid/1 ==== put方式 |
url 地址参数绑定:
@Controller
public class RestFulController {
@RequestMapping("/hello/{a}/{b}")
public String test(@PathVariable String a, @PathVariable String b, Model model){
// 需要使用`@PathVariable`注解来注解参数,上面a和b就是参数的名称。
model.addAttribute("msg","Hello,RestFul!");
return "hello";
}
}
注意:在 Spring MVC 中所有的请求都会先呗被 DispatcherServlte 拦截并且映射到对应的控制器上,但是对于静态资源就会出现找不到的问题。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
beans>
处理流程:
消息转换器,将请求信息转换成 Java 对象,或者将 Java 对象装换成响应报文给前端。HttpMessageConverter 提供了两个注解和两个实体类:@RequestBody、@ResponseBody、RequestEntity、ResponseEntity。
@RequestBody:获取请求体中的参数,与方法参数绑定在一起,一般请求方式是 POST 方法也可以是 GET 方法,但是必须有请求体。
@PostMapping("/")
public String fun1(@RequestBody User user){ // 将请求体的参数与user对象进行映射,参数名需要对应
return "ok";
}
RequestEntity:封装请求报文的一种类型,其中包含请求头、请求体等信息,在控制器方法上指定这个采参数就可以直接获取请求信息。
@PostMapping("/entity")
public String fun2(RequestEntity<String> entity){
System.out.println(entity.getHeaders());
System.out.println(entity.getBody());
return "ok";
}
@ResponseBody:标识在控制器方法上,如果项目引入了 Json 解析包,对于返回的对象会解析成 Json 字符串。
@GetMapping("/")
// 将方法返回值作为响应体,不再是视图名臣
@ResponseBody
public User fun3(){
User user = new User();
user.setUsername("张三");
user.setPassword("123456");
return user;
}
ResponseEntity:用于控制器方法的返回值,该方法的返回值就是响应到浏览器的响应报文。
@GetMapping("/entity")
public ResponseEntity<User> fun4(){
User user = new User();
user.setUsername("张三");
user.setPassword("123456");
// 构建响应报文
return new ResponseEntity<>(user, HttpStatus.OK);
}
过滤器:是基于函数回调的,过滤器中都会实现一个
doFilter()
方法,这个方法有一个FilterChain
参数,而实际上它是一个回调接口。ApplicationFilterChain
是它的实现类, 这个实现类内部也有一个doFilter()
方法就是回调方法。
// 自定义过滤器
@Component
public class MyFilter implements Filter {
// 初始化方法,这个方法必须执行成功,否则过滤器不生效
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 进行业务处理,如果不放行就通过request重定向或转发到对应页面
// 放行请求
filterChain.doFilter(servletRequest,servletResponse);
}
// 拦截器销毁方法
@Override
public void destroy() {
Filter.super.destroy();
}
}
拦截器:应用中可以同时存在多个拦截器
Interceptor
, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行,拦截器
则是基于 Java 的反射机制(动态代理)实现的,只有 Spring MVC 框架才能使用该拦截器。
// 自定义拦截器
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
过滤器的使用:编写配置类,将过滤器注册到 Spring 容器中并设置对应的拦截路径。
@Configuration
public class WebMvcConfig {
@Bean("myFilterRegister")
public FilterRegistrationBean<MyFilter> myFilter(MyFilter myFilter) {
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(myFilter);
registrationBean.addUrlPatterns("/*"); //url拦截,不配置拦截所有请求
registrationBean.setOrder(1); // 过滤顺序,越小越先过滤
return registrationBean;
}
}
拦截器的使用(方式一):将拦截器通过 XML 方式注入到 Spring 容器中。
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<bean class="com.xiayuan.interceptor.MyInterceptor"/>
mvc:interceptor>
mvc:interceptors>
拦截器的使用(方式二):通过配置类的方式注入到 Spring 容器中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// order越小越先拦截
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(1);
}
}
执行流程:
多个拦截器执行顺序:多个拦截器主要是通过配置顺序进行执行,也可以通过配置 order 设置执行顺序。
拦截器都放行:preHandle 方法按照拦截器依次执行,postHandle、afterCompletion会按照拦截器顺序倒序执行。
存在拦截器拦截:preHandle 方法按照拦截循序执行,被拦截以及后面的拦截器对应的 preHandle 都不执行。postHandle 所有拦截器都不会执行,afterCompletion 只会执行未被拦截的拦截器并且倒序执行(有一个拦截器执行的 index 记录执行拦截器的下标)。
针对异常不想显示为 Spring Mvc 默认的异常处理,可以之定义异常控制器来处理指定的异常。
// 实质就是一个 Controller 控制器
// @ControllerAdvice 方法返回结果是视图名称
@RestControllerAdvice // 方法返回结果是 json 字符串
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 拦截未知的运行时异常,参数是一个指定处理异常的 Class 对象
*/
@ExceptionHandler(value = {RuntimeException.class,NullPointerException.class})
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
}
Spring MVC 的原理图:
深入理解 Spring MVC 原理:
Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的controller声明方式。
Spring MVC框架像其他其它MVC框架一样,以请求为驱动,围绕一个中心控制器的servlet派送请求和提供功能,中心控制器实际上就是一个servlet,它是继承至HttpServlet基类。所有的请求给中心控制器来拦截,然后在派送请求。
导入相应的依赖:
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.3version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
配置文件解析器:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
bean>
注意:必须配置id属性,因为这个是根据id获取的,bean的id属性的名称必须是multipartResolver。
文件上传控制器(方式一):
public String upload(@RequestParam("my_file") CommonsMultipartFile file , HttpServletRequest request) throws IOException{
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
// 如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
// 上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
// 如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName));
// 文件输出流
// 读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
文件上传控制器(方式二):
/* * 采用file.Transto 来保存上传的文件 */
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("my_file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
// 上传文件地址
System.out.println("上传文件保存地址:"+realPath);
// 通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";}
}
文件下载(方式一):通过超链接来下载文件,但是存在一个弊端,超链接下载文件对于浏览器能够解析的资源就直接在页面给你展示,浏览器不能够解析的资源会让你下载。
<a href="${pageContext.request.contextPath}/upload/lab.png">通过超链接下载a>
文件下载(方式二):
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception {
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "lab.png";
// 1、设置response 响应头
response.reset();
// 设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8");
// 字符编码
response.setContentType("multipart/form-data");
// 二进制传输数据
// 设置响应头
response.setHeader("Content-Disposition","attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path,fileName);
// 2、 读取文件--输入流
InputStream input=new FileInputStream(file);
// 3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff =new byte[1024];
int index=0;
// 4、执行 写出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return null;
}