SpringMVC框架——初学知识点篇2
注:作者本人也是初学者,所以本文有些总结性见解可能存在问题,但是多数问题都是在上网查询过资料后总结的,如果有逻辑或者原理上的错误,或者见解不同,欢迎在评论区讨论!!!
目录
作用在方法上,用于取消正常的视图解析器解析,直接进行数据响应
使用带有@ResponseBody的SpringMVC处理AJAX请求
1.视图解析器
1)ThymeleafView
当使用html时,我们一般会结合Thymeleaf 前端视图技术,而再SpringMVC中的配置文件已经配置好有关Thymeleaf的视图解析器,通过Thymeleaf视图解析器解析之后得到的视图就是ThymeleafView。
当我们使用Thymeleaf技术时,就需要绑定ThymeleafView去解析。我们一般在Controller的方法中设置的View名称,只含有页面名称,不含有任何前缀时,那么该视图名称就会被DispatcherServlet中配置的配置文件中所配置的视图解析器解析,通过拼接前后缀获取最终路径,最后通过转发的方式去实现页面间的跳转。我们使用html时一般会配置ThymeleafView结合Thymeleaf使用。
@RequestMapping(value = "/hello") public String hellomethod() { System.out.println("hello..."); return "index"; }
2)InternalResourceView
当我们在Controller的方法中设置的View的名称带有“forward:”前缀时,此时视图不会被自己配置的视图解析器去解析,而是会创建InternalResourceView视图,并且去掉前缀“forward:”,然后不做任何拼接,直接将去除后的结果作为最终路径,通过转发的形式进行跳转。
问题写法:
@RequestMapping(value = "/forward") public String hellomethod() { System.out.println("前往hello.html"); return "forward:/WEB-INF/pages/hello.html"; }
以上写法有着十分严重的错误,我们一般使用html页面时,会结合thymeleaf处理后端发来的数据,因此多数情况必不可少的会使用"th:"有关的标签,所以如果直接转发到该页面,那么生成的不是ThymeleafView视图,而是InternetResourceView视图,如果直接转发到html页面那么就不能被配置的视图解析器thymeleaf解析到,也就不能访问到页面。你页面都使用了thymeleaf了,怎么访问?所以当使用转发时,必须转发到一个方法上通过@RequestMapping配置的地址,再通过方法配置的路径,经过ThymeleafView视图对应的视图解析器解析来到html页面。(这里如果使用jsp就不需要这么麻烦)。
3)RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转。
注意点与转发类似,但是重定向时两次请求,如果要重定向到WEB-INF目录下的页面,那么就相等于冲浏览器直接发送了一条访问WEB-INF目录的请求。而浏览器时无法直接访问WEB-INF目录下的页面的。所以哪怕使用的是jsp页面,也无法通过重定向去访问页面。所以只能通过重定向到其他方法,在由其他方法使用默认转发(无前缀),经过ThymeleafView的视图解析器来到WEB-INF目录下。
@RequestMapping(value = "/redirect") public String hellomethod() { System.out.println("重定向到hello.html"); return "redirect:/middle"; }
@RequestMapping("/middle") public String middle(){ return "hello"; }
4)视图控制器view-controller
当Controller中配置的方法仅仅是用来做页面跳转,不需要处理业务逻辑时,就可以使用view-controller配置来替代
path属性:类似与@RequestMapping中的path
view-name属性:也就是设置View的名称
<mvc:view-controller path="/toHello" view-name="hello"></mvc:view-controller>
如果使用的是jsp页面,那么就不需要考虑有关Thymeleaf的视图解析器了,正常使用即可。
2.报文信息转换器HttpMessageConverter
报文可以简单的理解为我们前端页面传入后端代码的一些键值对,例如在登录案例中,前端页面就需要传入浏览器中用户输入的username和password,而这些信息内容在不同的请求中处于不同的位置,但是无论是get还是post请求亦或是其他类型请求,他们把信息转化成请求键值对的格式是都是一样的:key=value&key=value...
而报文转换器的主要作用就是将请求的报文转化为java对象,或者将java对象转化为响应报文,而报文转换器HttpMessageConverter针对这两个操作分别提供了两个注解:@RequestBody,@ResponseBody和两个类RequestEntity,ResponseEntity
1)@RequestBody
此注解作用在方法的参数上,用于将前端传入的报文信息封装到一个指定类型的java对象中
前端表单设计
<form th:action="@{/testRequestBody}" method="post"> username:<input type="text" name="username"> password:<input type="password" name="password"> <input type="submit" value="提交"> </form>
后端Controller映射方法
//使用@RequestBody注解实现将请求报文转化为java对象(这里转化为String类型对象) @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody String requestBody){ System.out.println(requestBody); return "target"; }
2)RequestEntity类
此类型可以用于作为映射方法的形参,封装请求报文的对象
前端页面设置
<form th:action="@{/testRequestEntity}" method="post"> username:<input type="text" name="username"> password:<input type="password" name="password"> <input type="submit" value="提交"> </form>
控制器映射方法设置
@RequestMapping("/testRequestEntity") public String testRequestEntity(RequestEntity<String> requestEntity){ //获取请求头对象 System.out.println(requestEntity.getHeaders()); //获取请求体对象 System.out.println(requestEntity.getBody()); //获取请求URI对象 System.out.println(requestEntity.getUrl()); return "target"; }
此时的RequestEntity类是作为了方法的参数,接收前端的数据,getHeaders获取了请求头,也就是我们在前端页面打开f12,通过network中查看所有请求,点击请求就可以查看到当前的请求对象数据。
下面有关response,我们在学习完javaweb之后我们可以清楚的感受到request的主要作用其实就是获取前端的信息,有些信息是我们程序员需要看的,但是有的信息是我们的计算机需要看的。我们人需要看的就类似于登录案例的username和password请求信息,而像浏览器版本信息之类的请求信息以及被高度封装的计算机处理,所以我们要处理的request信息并不多。
但是我们回应给浏览器的方式有很多,内容也十分复杂。比如我们可以通过response的getwriter方法获取字符输出流将信息反馈输出到前台页面。这是一种回应,这种方法我们在学习Javaweb阶段一般不是回应页面显示时使用的,而是和异步请求AJAX(以及json)联合使用。另外一种回应就是response的重定向setRedirect以及request的转发。
所以终点在于我们根据前台信息的逻辑判断反馈给浏览器的各种服务,response这块也就自然的变成了这部分学习的重点
3)@ResponseBody
作用在方法上,用于取消正常的视图解析器解析,直接进行数据响应
@RequestMapping("/testResponseBody") @ResponseBody public String testResponseBody(){ return "testResponseBody"; }
作用与response通过write输出字符基本一致
@RequestMapping("/testResponse") public void testResponseBody(HttpServletResponse response) throws IOException { response.getWriter().write("testResponse"); }
使用@ResponseBody直接返回一个Java对象
由于java对象时无法与前端页面进行直接交互的,我们可以通过json字符串的形式将java对象传输到前端页面
步骤:
1.导入json核心jar包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
2.在配置文件中开启注解驱动
<!--开启注解驱动--> <mvc:annotation-driven></mvc:annotation-driven>
3.在Controller的映射方法上添加@ResponseBody注解取消视图跳转
@RequestMapping("/testResponseBody2") @ResponseBody public User testResponseBody2(){ User user = new User(1,"zhangsan","123456"); System.out.println(user); return user; }
4.直接返回java对象,此时对象就会被解析为json字符串输出到前端页面。SpringMVC 会帮助我们将对象或者集合进行json字符串的转换并回写。处理器适配器消息转换参数,指定使用jackson进行对象或者集合转换,因此需要在springMvc.xml中配置适配器处理器。
使用带有@ResponseBody的SpringMVC处理AJAX请求
AJAX请求,也就是异步请求,发送请求时页面的其他区域还可以照常运作,当前端通过异步请求来到后端后只需要正常处理即可,一般会结合json使用。对于AJAX请求的实现,可以通过vue方式来实现,当然也可以通过jQuery的方式来实现,如果麻烦点甚至可以使用原生的js方式实现。这里需要对前端有一定的了解。但是对于后端无非就是处理数据,根据需要返回json字符串或者进行页面的跳转。
4)ResponseEntity<>类
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文,下面还是用一个案例来感受一下ResponseEntity类的使用。
案例:使用ResponseEntity完成文件下载
文件下载案例我们在学习javaweb的时候多少会接触过一点。当时是当前端页面点击超链接时,后端服务以字节流的形式将文件输出到前端页面。在输出到前端页面之前,我们需要设置响应头,告知浏览器以何种形式接收后端传入的字节流。
我们的ResponseEntity对象创建就需要一个字节数组,该数组存储了文件的所有字节码;还需要一个响应头,该响应头就存储了告知浏览器的信息;最后就是响应状态码。
前端页面设计:
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a th:href="@{/downloadImg/1.jpg}">下载图片</a> </body> </html>
后端代码设计:
@RequestMapping("/downloadImg/{name}") @ResponseBody public ResponseEntity<byte[]> downloadImg(@PathVariable("name")String filename,HttpSession session) throws IOException { //创建ResponseEntity对象需要三个对象,1.文件字节数组,2.文件接收响应头信息,3.响应状态码 //创建ServletContext获取文件真实路径 ServletContext servletContext = session.getServletContext(); String realPath = servletContext.getRealPath("/static/img/" + filename); //使用真实路径读取文件 InputStream is = new FileInputStream(realPath); //设置读取字节数组 byte[] bytes = new byte[is.available()]; //字节输入流读取文件,此时is已经将文件数据读取到字节数组中 is.read(bytes); //设置响应头 MultiValueMap<String,String> headers = new HttpHeaders(); //设置文件下载方式 headers.add("Content-Disposition", "attachment;filename=" + filename); //设置响应状态码 HttpStatus statusCode = HttpStatus.OK; ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes,headers,statusCode); //关闭输入流 is.close(); return responseEntity; }
5)@RestController注解
由于后期学习内容中大量使用@ResponseBody注解,以及类上的@Controller注解,所以SpringMVC提供了这两个注解的复合注解@RestController,当该注解写在类上时,表示该类被@Controller注解,同时所有方法被@ResponseBody注解。
3.拦截器Interceptor
拦截器,顾名思义,主要用于拦截一些指定的方法。在SpringMVC中,拦截器主要用于对映射方法进行拦截处理。
1)实现拦截器HandlerInterceptor
@Component("myInterceptor") public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("在映射方法执行之前进行拦截"); //返回true那么就不会被拦截停止,返回false那么就会被拦截住 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("处理完视图和模型数据,渲染视图完毕之后执行"); } }
我们想要达到拦截映射方法的目的就需要了解拦截器本身的核心方法以及工作原理。HandlerInterceptor接口继承了Interceptor接口,其中使用default修饰的三大方法就是拦截器的三个核心方法:在执行映射方法之前进行拦截的preHandler方法,在执行映射方法之后执行的postHandler方法,以及在处理完视图之后执行的afterCompletion方法。
2)拦截器在xml文件中的配置
1.第一种,根据类 配置文件
<mvc:mapping>标签用于标记拦截的path路径,用于配置拦截器拦截的范围路径
<mvc:exclude-mapping>标签用于标记拦截器拦截范围内不需要拦截的路径
<bean>用于注入拦截器对象
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/*"/> <mvc:exclude-mapping path="/img"/> <bean class="com.ling.mvc.MyInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
2.第二种,根据id配置文件
<ref>用于通过名称id注入拦截器对象,bean属性为拦截器配置的名称
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/*"/> <mvc:exclude-mapping path="/img"/> <ref bean="myInterceptor"></ref> </mvc:interceptor> </mvc:interceptors>
3)拦截器的执行顺序
当多个拦截器同时对一个目标生效,那么拦截器的preHandler方法会根据配置文件中拦截器配置的顺序逐个执行,并且使用一个属性记录成功执行完的拦截器数量InterceptorIndex。当所有拦截器的preHandler方法都以true为返回值执行完后,执行控制器的映射方法,然后按配置文件中配置的拦截器逆序进行postHandler方法。最终执行完后通过成功执行的数量InterceptorIndex 递减遍历。最终按照配置文件逆序执行afterCompletion方法。
如果存在三个拦截器A,B,C拦截了同一个控制器映射方法,三个拦截器在xml文件中配置的顺序为ABC,且三个方法的preHandler方法都成功执行,那么preHandler执行顺序为ABC,postHandler执行顺序为CBA,afterCompletion执行顺序为CBA。
如果有一个拦截器的preHandler返回false,那么preHandler方法就会直接返回false,InterceptorIndex就会只记录到上一个返回true的拦截器在执行链中的索引。所有的postHandler方法都会被取消执行。但是afterCompletion按照成功执行的数量逆序遍历执行。此时ABC三个拦截器中,假设C的preHandler方法返回了false,那么preHandler方法执行顺序为AB,postHandler方法不执行,afterCompletion方法返回BA。
4.异常
SpringMVC默认处理异常的接口为DefaultHandlerExceptionResolver,他实现了HandlerExceptionResolver接口,主要用于当页面出现一个异常时解析该异常,并且根据异常分类跳转到指定页面展示异常。而SimpleMappingExceptionResolver则可以自定义处理异常,我们可以通过xml配置文件的方式进行SimpleMappingExceptionResolver相关使用
基于xml
1.配置bean容器,路径为org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
2.配置SimpleMappingExceptionResolver对象的exceptionMappings属性(该属性是一个属性集类型property,所以按照属性集配置属性的方法对exceptionMapping初始化)
3.配置属性集的键值对,每个key代表出现的各种异常,value代表出现异常后需要跳转的页面名称
4.配置exceptionAttribute属性,他的值是一个键值对,通过value给键命名,值则是异常类型的名称
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--配置SimpleMappingExceptionResolver的Property类型的exceptionMapping属性--> <property name="exceptionMappings"> <props> <!--key为异常,value为出现异常需要跳转的页面(经过视图解析器解析)--> <prop key="java.lang.ArithmeticException">mathexception</prop> </props> </property> <!--exceptionAttribute属性,需要与value联用,value值代表异常信息在请求域中的key名称,而值为当前出现的异常信息--> <property name="exceptionAttribute" value="a"></property> </bean>
基于注解
1.@ControllerAdvice:用于在类上注解,表示该类是一个特殊的控制器对象,用来处理异常跳转
2.@ExceptionHandler:作用在方法上,value值用于配置异常类对象的信息。当映射方法出现该异常时启动执行该方法。
3.参数Model用于共享域对象,将后端的异常信息反馈到前端页面,与request用法类似,与xml配置文件中的exceptionAttribute属性类似
4.View视图名称,与xml配置文件中exceptionMappings属性集配置的目标页面类似
//该注解中配置了Controller,拥有Controller的功能,并且可以配置异常处理方法 @ControllerAdvice public class ExceptionController { //该注解用于配置异常类型,当出现该类型异常时执行该方法,类似于xml配置中配置的exceptionMappings属性集 @ExceptionHandler(value = {ArithmeticException.class}) //方法参数Exception用于捕获出现的异常对象,Model对象用于使用request域来存储异常信息反馈到前端页面 public String testMath(Exception e,Model model){ //类似于xml配置中配置的exceptionAttribute属性 model.addAttribute("a",e); //类似于xml配置文件中exceptionMappings属性集配置的目标页面 return "mathexception"; } }
本文学习路线是根据尚硅谷2021.8月发布的SpringMVC学习视频学习,整理了里面的知识点,有些地方加入了自己的理解。知识点2相比于知识点1更多的是知识点的学习,不像初次遇见SpringMVC那种学习的感觉,同时知识点2的知识点相对分散,但每个点都比较重要。