代码地址: https://github.com/Zhuyaqiang/spring-study
目录
14 SpringMVC
14.1 简介
-
Model, View, Controller. MVC的主要作用是降低了视图和业务逻辑之间的双向耦合
-
MVC的工作:
- 将url映射到java类或java类的方法
- 封装用户提交的数据
- 处理请求–调用相关的业务处理–封装响应数据
- 将响应的数据进行渲染 .jsp/html等表示层数据
-
测试
-
web.xml中配置springmvc
<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:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
编写配置文件springmvc-servlet.xml
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean> <bean id="/hello" class="com.zyq.controller.HelloController"></bean>
-
编写Controller类
public class HelloController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { // ModelAndView 模型和视图 ModelAndView mv = new ModelAndView(); // 封装对象 mv.addObject("msg", "HelloSpringMVC"); // 封装要跳转到的视图 mv.setViewName("hello"); // /WEB-INF/jsp/hello.jsp return mv; } }
可能遇到的问题: 访问404
解决: File–>Project Structure–>Artifacts, 和classes文件夹同级创建lib文件夹添加jar包
-
14.2 实现分析(重点)
对应项目springmvc-02-hellomvc, 课程p5p6
- DispatcherServlet表示前置控制器, 是整个SpringMVC的控制中心.
- HandlerMapping为处理映射, DispatcherServlet调用, 根据请求找到对应的Controller处理器
- HandlerExecution表示具体的Handler, 主要作用是根据url查找控制器, 将解析的信息返回给DispatcherServlet
- HandlerAdapter表示处理器适配器, 按照对应的规则执行Handler
- Handler让具体的Controller执行
- Controller将具体的执行信息返回给HandlerAdapter, 如MV
- HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
- DispatcherServlet会调用视图解析器来解析HandlerAdapter传递的逻辑视图名, 之后将解析的逻辑视图名传给DispatcherServlet
- DispatcherServlet根据视图解析器解析的试图结果, 调用具体的视图
14.3 正式开发
-
project structure 导入lib
-
配置web.xml
<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:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
配置springmvc-servlet.xml
<!-- 自动扫描包, 让指定包下的注解生效, 由IOC容器统一管理--> <context:component-scan base-package="com.zyq.controller"></context:component-scan> <!-- 让Springmvc不处理静态资源 .css .js...--> <mvc:default-servlet-handler></mvc:default-servlet-handler> <!-- 支持mvc注解驱动 在Spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效 必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理 而annotation-driven配置帮助自动完成上述两个实例的注入 --> <mvc:annotation-driven></mvc:annotation-driven> <!-- 视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean>
-
创建Controller
@Controller public class HelloController { @RequestMapping("/hello") public String Hello(Model model) { // 封装数据 model.addAttribute("msg", "Hello, SpringMVCAnnotation!"); return "hello"; // 会被视图解析器处理 } }
-
创建视图层
14.4 Controller总结
注: 使用@RestController
注解, 该控制器下的所有函数返回值都不走视图解析器(或使用@RestController
和@RequestBody
结合)
14.4.1 方式一
-
编写web.xml配置文件配置DispatcherServlet
<!-- 配置DispatcherServlet--> <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:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
编写springmvc配置文件
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <property name="suffix" value=".jsp"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> </bean>
-
编写Controller类, 实现Controller接口, 重写handleRequest函数
public class ControllerTest1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", "ControllerTest1"); modelAndView.setViewName("test"); return modelAndView; } }
-
去Spring配置文件中注册bean, name对应请求路径, class对应处理请求的类
<bean name="/t1" class="com.zyq.controller.ControllerTest1"></bean>
-
编写前端页面
是较老的方法, 缺点是一个Controller中只有一个方法, 如果需要多个方法需要定义多个Controller
14.4.2 方式二(使用注解)
-
spring配置文件开启注解支持以及设置扫描包
<context:component-scan base-package="com.zyq.controller"></context:component-scan> <mvc:annotation-driven></mvc:annotation-driven> <mvc:default-servlet-handler></mvc:default-servlet-handler>
-
配置Controller类, 使用
@Controller
和RequestMapping
注解@Controller // 代表这个类会被Spring接管, 被注解类中所有的方法如果返回值是String且有具体页面可以跳转, 那么就会被视图解析器解析 public class ControllerTest2 { @RequestMapping("/t2") public String test1(Model model) { model.addAttribute("msg", "ControllerTest2"); return "test"; } }
14.5 ResquestMapping
@RequestMapping
注解用于映射url到控制器类或一个特定的处理程序方法, 可用于类或方法上
14.6 Restful风格
Restful是一种资源定位及资源操作的风格, 可以通过不同的请求方式来实现不同的效果
- 在Controller中传递的每个url参数前加
@PathVariable
注解, 之后访问的url变为…/url/param1/param2/… - 注解中通过使用method属性可以指定请求类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE等
- 方法级别注解的变体有
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
,@PatchMapping
- 方法级别注解的变体有
Restful风格的优点:
- 使路径变得更加简介
- 获得参数更加方便, 框架会自动进行类型转换
- 通过路径变量的类型可以约束访问参数
14.7 SpringMVC的重定向和转发
无视图解析器的情况下, 使用完整路径进行转发或重定向
public class ModelTest1 {
// 转发1
@RequestMapping("/m1/t1")
public String test(Model model) {
model.addAttribute("msg", "ModelTest");
return "/WEB-INF/jsp/test.jsp";
}
// 转发2
@RequestMapping("/m1/t1")
public String test(Model model) {
model.addAttribute("msg", "ModelTest");
return "forward:/WEB-INF/jsp/test.jsp";
}
// 重定向
@RequestMapping("/m1/t1")
public String test(Model model) {
model.addAttribute("msg", "ModelTest");
return "redirect:/WEB-INF/jsp/test.jsp";
}
}
14.8 数据处理与回显
14.8.1 数据处理
-
参数名即为传递url传递的参数(要求二者相同), 或在参数前添加
@RequestParam(参数名)
注解@RequestMapping("/t1") public String test1(@RequestParam("username") String name, Model model) { // 1. 接收前端参数 System.out.println("接收到前端的参数为: " + name); // 2. 将返回的结果传递给前端, Model model.addAttribute("msg", name); return "test"; }
-
前端传递多个参数, 使用对象接收, 要求前端传递的参数名必须和对象的属性名一致, 否则为null
// 前端接收的是一个对象: id, name, age // 1. 接收前端用户传递的参数, 判断参数的名字, 假设名字直接在方法上, 可以直接使用 // 2. 假设传递的是一个对象, 匹配对象中的字段名, 如果名字一致则ok, 不一致则忽略 @GetMapping("/t2") public String test2(User user) { System.out.println(user); return "test"; }
14.8.2 数据回显
-
ModelAndView
modelAndView.addObject("msg", "Controller"); // 设置属性值 modelAndView.setViewName("test"); // 跳转的路径
-
Model
model.addAttributes("msg", "controller");
-
ModelMap
14.9 乱码问题
-
过滤器解决
- 编写过滤器类, 实现servlet的Filter接口
- 在web.xml中注册filter
对post方式无效
-
在web.xml中配置spring自带的过滤器
<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern> </filter-mapping>
极端情况下, 该方式对get方式支持不好
-
第三方通用自定义过滤器
-
tomcat中添加编码支持
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
-
自定义过滤器
package com.kuang.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; /** * 解决get和post请求 全部乱码的过滤器 */ public class GenericEncodingFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //处理response的字符编码 HttpServletResponse myResponse=(HttpServletResponse) response; myResponse.setContentType("text/html;charset=UTF-8"); // 转型为与协议相关对象 HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 对request包装增强 HttpServletRequest myrequest = new MyRequest(httpServletRequest); chain.doFilter(myrequest, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } } //自定义request对象,HttpServletRequest的包装类 class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; //是否编码的标记 private boolean hasEncode; //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰 public MyRequest(HttpServletRequest request) { super(request);// super必须写 this.request = request; } // 对需要增强方法 进行覆盖 @Override public Map getParameterMap() { // 先获得请求方式 String method = request.getMethod(); if (method.equalsIgnoreCase("post")) { // post请求 try { // 处理post乱码 request.setCharacterEncoding("utf-8"); return request.getParameterMap(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } else if (method.equalsIgnoreCase("get")) { // get请求 Map<String, String[]> parameterMap = request.getParameterMap(); if (!hasEncode) { // 确保get手动编码逻辑只运行一次 for (String parameterName : parameterMap.keySet()) { String[] values = parameterMap.get(parameterName); if (values != null) { for (int i = 0; i < values.length; i++) { try { // 处理get乱码 values[i] = new String(values[i] .getBytes("ISO-8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } } hasEncode = true; } return parameterMap; } return super.getParameterMap(); } //取一个值 @Override public String getParameter(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); if (values == null) { return null; } return values[0]; // 取回参数的第一个值 } //取所有值 @Override public String[] getParameterValues(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); return values; } }
-
14.10 JSON
前后端分离: 后端提供接口和数据------json------前端渲染后端的数据
14.10.1 前端方面
JSON.stringify()
JSON.parse()
14.10.2 后端方面
Jackson
-
导入依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency>
-
生成json
@RequestMapping("/j1") @ResponseBody // 加了该注解, 就不会走试图解析器, 会直接返回一个字符串 public String json1() throws JsonProcessingException { // jackson, ObjectMapper ObjectMapper mapper = new ObjectMapper(); User user = new User("小明", 5, "男"); System.out.println("-----------------------------"); String str = mapper.writeValueAsString(user); return str; }
-
配置springmvc-servlet`解决json乱码问题
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"></constructor-arg> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"></property> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
FastJson
-
导入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.60</version> </dependency>
-
静态方法互相转换
14.11 Ajax
踩坑记录: lombok提示初始化失败, 没有加载全参构造器.经过排查是同时导入了两个lombok依赖的原因
14.11.1 简介
Ajax = Asynchronous JavaScript and XML, 是一种在无需重新加载整个网页的情况下, 能够更新部分网页的技术
Ajax的核心是XMLHttpRequest对象(XHR)
jQuery提供多个与Ajax有关的方法
14.11.2 jQuery
- jQuery.post/jQuery.get等方法都是调用jQuery.ajax方法
14.12 拦截器
拦截器是AOP思想的具体应用, SpringMVC框架自带的
拦截器只会拦截访问的控制器方法
实现
-
编写拦截器类, 实现HandlerIntercepter接口
public class MyInterceptor implements HandlerInterceptor { // return true 执行下一个拦截器, 放行 // return false 不执行下一个拦截器 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("========处理前=============="); return true; } // 日志用 @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("========清理=============="); } }
-
配置applicationContext.xml
<mvc:interceptors> <mvc:interceptor> <!-- 包括这个请求下所有的请求--> <mvc:mapping path="/**"/> <bean class="com.zyq.config.MyInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
14.13 文件上传和下载
14.13.1 上传
前端要求
要求前端将表单的method设置为post, 且将enctype设置为multipart/form-data. 这种编码方式会以二进制流的方式来处理表单数据, 会把文件域指定文件的内容封装到请求参数中, 不会对字符编码
后端要求
-
导入依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
-
在
applicationContext.xml
中配置上传类<!-- 文件上传配置--> <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"> <!-- 请求的编码格式, 必须和jsp的pageEncoding属性一直, 以便正确读取表单的内容, 默认为ISO-8859-1--> <property name="defaultEncoding" value="utf-8"></property> <!-- 上传文件的大小上线, 单位为字节(这里设置10M)--> <property name="maxUploadSize" value="10485760"></property> <property name="maxInMemorySize" value="40960"></property> </bean>
-
编写控制器
@RestController public class FileController { // @RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile对象 // 批量上传CommonsMultipartFile为数组即可 @RequestMapping("/upload") public String upload(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException { // 获取文件名: file.getOriginalFilename(); String uploadFileName = file.getOriginalFilename(); // 如果文件名为空, 直接回首页 if (uploadFileName.equals("")) 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("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"; } }
14.13.2 下载
- 设置response响应头
- 读取文件
- 写出文件
- 执行操作
- 关闭流