共计 18161 个字符,预计需要花费 46 分钟才能阅读完成。
在编码一段时间之后,再从新看之前学过的框架,发现有新的认知,像 Spring,我就重新学习了一把:
- 欢迎光临 Spring 时代 - 绪论
- 欢迎光临 Spring 时代(一) 上柱国 IOC 列传
- 代理模式 -AOP 绪论
- 欢迎光临 Spring 时代(二) 上柱国 AOP 列传
这次重学的是 Spring MVC,以前学习 Spring MVC 的时候是在 B 站看的视频,的确不错,让我疾速入门了,然而我还是感觉不是很零碎,感觉知识点四分五裂,我心愿用我的形式将这些形式串联起来。在学习本篇之前,须要有 Java EE 和 Spring Framework 的根底,如果没有 Java EE 的根底,能够参看 JavaWeb 视频教程,如果没有 Spring Framework 的根底,请参看我下面的文章,如果感觉四篇有点多,能够先只看 欢迎光临 Spring 时代(一) 上柱国 IOC 列传,如果能够的话,还是倡议先看完下面四篇文章来看这四篇文章。
原生 Servlet 时代的问题
简略的说 Servlet 是 Java 中的一个接口,位于 javax.servlet 下。咱们来看一下 Servlet 上的正文:
A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients,usually across HTTP, the HyperText Transfer Protocol.
Servlet 是一个运行在 Web 服务器中的一个小 Java 程序,Servlet 接管和解决 Web 客户端的申请,通常用于 Http 协定。
咱们通常用的是它的实现类 HttpServlet,而后重写 doGet 和 doPost 办法,像上面这样:
WebServlet(name ="/servlet")
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.getParameterNames();
req.getParameter("username");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 解决 post 申请
super.doPost(req, resp);
}
}
它有哪些痛点呢? 第一个就是取参数的问题,取参数只有这一种形式,参数一多就有点让人心烦了,不是那么的合乎面向对象的思维,程序员除了关怀业务逻辑的,还要关注如何获取参数,确实 HttpServletRequest 外面搁置的参数很多了,很全很全:
那 getParameterNames 去哪里了?HttpServletRequest 是一个接口,getParameterNames 从 ServletRequest 继承而来。
是否让程序员更加专一于业务逻辑一点呢,让取参数变的更简略一些,比方我想用学生类对象接管,又比方我须要的参数只有一个 Long 类型的 id,接管参数的时候是否就间接让用 Long id 来接管呢。这是咱们提出的第一个问题,取参数是否变的简略一些。除了取参数,在 Ajax 技术曾经广泛利用的明天,是否让我响应前端的 Ajax 申请在简略一些呢?在原生 Servlet,咱们解决 Ajax,咱们在响应中的设置编码,设置响应的类型,而后通过一些 Java 的 JSON 库将咱们的对象转成 JSON 模式,发送给前端,大抵上要这么做:
Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Student student = new Student(13, "223", null);
Gson gson = new Gson();
resp.setContentType("application/json;charset=utf-8");
resp.setCharacterEncoding("UTF-8");
System.out.println(req.getMethod());
System.out.println("解决 get 申请");
PrintWriter writer = resp.getWriter();
writer.println(gson.toJson(student));
}
这是咱们提出的第二个问题。还有文件上传下载,原生 Servlet 的文件名乱码问题,异样的对立解决,零碎出了错间接把出错日志返回到网页上?用户一脸懵逼,这是产生了什么? 咱们是否对立的解决一下异样呢?比方服务端出了问题,就间接给个提醒,服务端产生了谬误,请分割零碎工程师。这样是不是更敌对一些呢。
总结一下
咱们这里再总结一下,咱们应用原生 Servlet 进行 Web 编程所遇到的痛点:
- 解决参数麻烦,不合乎面向对象准则
- 解决 Ajax 麻烦,而且有些流程是通用的,然而还要反复编写
- 上传和下载文件中文名乱码
- 没有提供对立的异样解决机制
这四点让咱们在关注于业务的逻辑的同时,也要在取参数上花上一番功夫,这些问题是应用原生 Servlet 广泛都会遇到的,
很天然咱们在碰到这些问题之后会对原生的 Servlet 进行扩大,那么既然大家都会用到,那么就会有人或社区针对下面的问题,提出解决方案,个别咱们称这种类型的框架为 Web 框架,在 Java Web 畛域比拟罕用的有:
- Spring MVC
Spring 公司出品,其实人家叫 Spring Web MVC,不过咱们更习惯叫它 Spring MVC,性能弱小,能够和 Spring Framework 无缝集成。
- Apache Struts 2.x
Apache 出品,不过当初来看没有 Spring MVC 性能高,Spring MVC 应用更为宽泛
- Apache Tapestry 5.x
Apache 出品,这个倒是头一次见,我也是在重新学习 Spring MVC 的时候,去翻官网文档才看到的这个框架,查了一下,发现材料不多。这里作为理解吧。
本篇咱们介绍的就是 Spring MVC,各位同学在学习的时候,肯定留神领会思维,技术倒退的是很快的,如果疲于学习各种框架,你会发现吞没在技术的陆地中,那什么是通往此岸的船呢?那就是思维,解决问题,可能思维是雷同的,然而具体实现上可能会有一些小差别,这也就是学一通百。这也是我在文章结尾会花很大的力量,介绍这种技术呈现的起因以及思维,介绍框架的应用很容易,然而介绍思维相对来说就更难一些。
这里我还想顺带聊一下如何学习常识,个别来如果官网文档写的不错的话,我就会间接去看官网文档,因为相对来说官网文档更为权威,也是一手材料。我在重学 Spring MVC,去翻了一下 Spring MVC 的官网文档,发现写的还是不错的,有兴致的同学也能够翻翻官网文档。
- 点击链接 https://spring.io/projects/sp… 进入 Spring 官网。
筹备工作
本篇咱们还是应用 maven 来搭建我的项目,如果不懂 maven 还想下 jar 包,在 欢迎光临 Spring 时代(一) 上柱国 IOC 列传一文中我也介绍了下载 jar 包的办法,这里不再赘述,欢迎光临 Spring 时代(一) 上柱国 IOC 列传一文所须要的依赖咱们还要接着用,新须要的依赖如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
先用起来
下面介绍的 Java Web 畛域的 Web 框架解决下面咱们提出的问题就是通过扩大原生 Servlet 来做的,那如何应用 Spring MVC 提供的一系列扩大 Servlet 呢?那么首先就要让 Tomcat 启动的时候晓得,也就是在 web.xml 进行配置:
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始 dispatcherServlet 须要的参数 contextConfigLocation,DispatcherServlet 有这个属性 -->
<init-param>
<!-- 加载配置文件, 指明配置文件的地位 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<!-- 元素标记容器是否应该在 web 应用程序启动的时候就加载这个 servlet,(实例化并调用其 init()办法)。-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!-- /: 拦挡所有申请 -->
<!-- /user: 拦挡所有以 /user 结尾的申请 -->
<!-- /user/abc.do: 只拦挡该申请 -->
<!-- .action: 只拦挡.action 的申请 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
咱们看下 contextConfigLocation 在 DispatcherServlet 哪里,首先到 DispatcherServlet 去看:
发现没找到,咱们去它的父类找:
下面咱们初始化 DispatcherServlet 的时候指明了配置文件的地位,那么配置文件中要配置什么呢? 除了数据库连接池之类的,咱们回顾一下应用原生 Servelt+jsp 进行编程的时候,咱们还能管制页面的跳转,尽管在前后端拆散时代,这个页面的跳转被前端仔拿走了,然而 Spring MVC 还是提供了这样的性能,Spring 中称之为视图解析器,所以还要在配置文件中将这个配置文件中加进来,也就是纳入到 IOC 容器管辖。写到这里仿佛有点 Spring 整合 Spring MVC 的滋味了,然而在 Java Web 畛域 Spring 是治理对象核心,不论用什么框架你都得将外围对象纳入到 IOC 容器中,以便咱们优雅的应用。所以在开篇咱们强调学习本篇要有 Spring 的根底。
在配置文件中须要配置的:
<!-- 配置扫描器 -->
<context:component-scan base-package="org.example"/>
<!-- 配置视图解析器 -->
<bean id = "internalResourceViewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name = "prefix" value = "view/"></property>
<property name = "suffix" value = ".jsp"></property>
</bean>
学过 Spring 的人会晓得配置扫描器的作用,将加上了 Spring 提供注解的类 (@Controller、@Service、@Repository、@Component) 退出到 IOC 容器中,那配置的这个视图解析器是做什么的呢?该类就用来管制页面的跳转,那这个前缀是什么意思呢? 如果 Spring MVC 认为你返回的是一个视图 (也就是页面) 的话,如上面:
// 该注解将该类变为解决 HTTP 申请的类
Controller
public class StudentController {
// 将解决申请形式为 GET,URI 为 servlet 的申请
@RequestMapping(value = "/servlet", method = RequestMethod.GET)
public String testGet() {System.out.println("hello world");
return "success";
}
// 将解决申请形式为 post,URI 为 servlet 的申请
@RequestMapping(value = "/servlet", method = RequestMethod.POST)
public String testPost() {System.out.println("hello world");
return "success";
}
}
返回值是 String,Spring MVC 会认为你要跳转到 view/success.jsp 页面。上面咱们在 webapp 下建一个名为 success.jsp。
接下来咱们测试一下:
梳理一下
咱们要应用 Spring MVC 提供的扩大 Servlet,首先须要在 web.xml 配置文件让 Spring MVC 的 DispatcherServlet 接管咱们的所有申请,为了做到这一点,咱们须要让 Tomcat 在启动的时候就初始化 DispatcherServlet。
当一个类上领有 @Controller 注解,则该类在 Spring MVC 框架中就被视为一个可能解决申请的类,该类中领有 @RequestMapping 的办法,将可能解决 HTTP 申请。@RequestMapping 中的 value 属性用于指定 URI,如果 HTTP 申请的 URI 和该办法上 @RequestMapping 的 value 属性吻合,那么申请将进入该办法。@RequestMapping 中的 method 用于限度申请形式,method 值要求是 RequestMethod 类型,RequestMethod 是 Method。
因为在 Spring MVC 在设计之初,前后端拆散时代还没到来,那个时候 Java 还能管制页面的跳转,所以 Spring MVC 用 internalResourceViewResolver 做视图解析。然而本篇并不会介绍多少无关页面跳转,动态资源解决的货色,这些货色曾经不再适宜这个前后端拆散时代。
简化取参数的形式
仿佛 Spring MVC 下解决申请的类只是变的简介了一些,并没有让咱们感触到取参数的简化啊。咱们接下来就通过几个注解来领会一下 Spring MVC 下优雅的取参数形式。
间接接对象
Spring MVC 能够将前端的参数间接变为一个对象,在参数名能对应的状况下,首先咱们筹备一个简略的对象:
// 为了节俭篇幅, 这里引入了 Lombok 插件, 通过这些注解就能够产生构造函数,get、set 办法、重写 toString 办法
Data
Getter
Setter
@ToString
public class User {
private String userName;
private String password;
}
@Controller
public class StudentController {
@RequestMapping(value = "/obj", method = RequestMethod.GET)
public String testOBj(User user) {System.out.println(user);
return "success";
}
}
测试一下:
测试后果
@RequestParam
@Controller
public class StudentController {@RequestMapping(value = "/servlet", method = RequestMethod.GET)
public String testGet(@RequestParam(value = "name") String userName, @RequestParam(value = "password") String password) {System.out.println("username:" + userName);
System.out.println("password:" + password);
return "success";
}
}
如果 HTTP 申请的参数名和办法的参数名雷同,在申请进入该办法的时候,Spring MVC 的申请办法的办法参数能够间接接管。
如果 HTTP 的申请和 Controller 中的办法形参不雷同,也就是不同名,可通过 @RequestParam 作为桥梁,通过 value 属性将申请参数映射到指定参数变量上,然而形参上有 @RequestParam,则默认该形参必须有,否则会报错。
咱们测试一下:
能够通过 @RequestParam 的 required 属性设置为 false,勾销要求该参数必须有的设定。默认状况 @RequestParam 要求的参数必须要有。
@RequestHeader 和 @CookieValue
- @RequestHeader: 实现申请头 (header) 数据到处理器性能解决办法参数上的绑定
- @CookieValue: 实现 Cookie 数据到处理器性能解决办法的办法参数上的绑定
咱们首先看下申请头里有什么:
而后再捋一捋 Session 和 Cookie 之间的关系:
因为 HTTP 协定是无状态的协定,所以服务端须要记录用户的状态时,就须要用某种机制来辨认具体的用户,这个机制就是 Session。这个机制就是 Session。典型的场景比方购物车,当你电脑下单按钮时,因为 HTTP 协定的无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创立了特定的 Session,用于标识特定的用户,这个 Session 是保留在服务端的。
服务端在辨认到特定的用户的时候,就须要 Cookie 退场了。每次 HTTP 申请的时候,客户端都会发送相应的 Cookie 信息到服务端。实际上大多数的利用都是用 Cookie 来实现 Session 跟踪的,第一次创立 Session 的时候,服务端会在 HTTP 协定中通知客户端,须要在 Cookie 外面记录一个 Session Id,当前每次申请把这个回话 ID 发送到服务器,我就晓得你是谁了。
而后咱们看下 Cookie 中有什么:
示例:
@Controller
public class StudentController {@RequestMapping(value = "info", method = RequestMethod.GET)
public void test(@RequestHeader("User-Agent") String useAgent, @CookieValue("JSESSIONID") String jsesionId) {System.out.println(useAgent);
System.out.println(jsesionId);
}
}
@RequestBody
咱们晓得 get 申请,申请参数搁置在 URI 之后,没有申请体,post 申请的申请参数能够放在 URI 之后,也能够放在申请体中。
下面咱们曾经能够看到,如果申请参数是 get,且参数名和申请办法中形参的属性名能对的上,那 Spring MVC 就会将申请参数帮咱们封装为一个对象,那对于 post 申请呢? 我将申请参数搁置在申请体中,咱们就须要通过 @RequestBody 就能够将 post 申请发送的 json 格局的数据,转为 Java 中的对象。
简化文件上传
原生 Servlet 的上传
前台代码:
<form action="upload" method = "post" enctype = "multipart/form-data">
用户名:<input type = "text" name = "userName"/>
明码:<input type = "text" name = "password"/>
文件:<input type = "file" name = "pic">
<input type = "submit" value = "提交">
</form>
在原生 Servlet 时代,咱们通过 Apache 提供的组件做文件上传,后盾的逻辑大抵是这样的:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 对立编码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=UTF-8");
boolean isMultipartContent = ServletFileUpload.isMultipartContent(req);
// 表单中有上传文件,method 中必须有 enctype 这个属性
if (isMultipartContent) {FileItemFactory fileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
List<FileItem> items = servletFileUpload.parseRequest(req);
for (FileItem item : items) {if (item.isFormField()){// 解决表单的非文件字段}else{
// 将上传文件写入到文件服务器中
item.write(new File("服务器的门路 /"));
}
}
}
}
Spring MVC 下的应用 Apache 组件进行文件上传
- 应用 Apache 组件的上传就变成了这样
首先在配置文件中,咱们要配置文件解析器:
<bean id = "commonsMultipartResolver" class = "org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<!-- 单位是 B 1048576 = 1024 * 1024 -->
<value>1048576</value>
</property>
</bean>
// 应用 MultipartFile 当形参,SpringMVC 会主动将表单中的文件对象注入到该参数中
// SpringMVC 也会尝试将表单的非文件对象放入 User 对象中
// post 申请咱们个别习惯性将文件对象放进申请体中
@RequestMapping(value = "mvcUpload" , method = RequestMethod.POST)
public void testUpload1(@RequestBody User u , MultipartFile pic){}
忘了讲,这两个 Apache 组件的依赖如下:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
Servlet3 实现文件上传
首先在 web.xml 中配置上传的地位:
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 初始 dispatcherServlet 须要的参数 contextConfigLocation,DispatcherServlet 有这个属性 -->
<init-param>
<!-- 加载配置文件, 指明配置文件的地位 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<!-- 元素标记容器是否应该在 web 应用程序启动的时候就加载这个 servlet,(实例化并调用其 init()办法)。-->
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>c:temp</location>
<max-file-size>1048576</max-file-size>
<!-- 单个数据大小 -->
<file-size-threshold>10240</file-size-threshold>
</multipart-config>
</servlet>
这里强调一下,要留神 Servlet 的版本:
而后在 IOC 容器中退出:
<bean id = "multipartResolver" class = "org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
后端代码不变。
简化文件下载
这里其实感觉没有简化多少,用原生的 Servlet 也不是那么麻烦。Spring MVC 这里又给咱们提供了一种抉择而已。
上面是示例:
@RequestMapping(value = "testUpload")
public void downloadFile(Long id, HttpServletResponse resp, HttpServletRequest request) throws IOException {
// 依据 id 去查文件的地位
resp.setContentType("application/x-msdownload");
String userAgent = request.getHeader("User-Agent");
if (userAgent.contains("IE")) {resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("文件名", StandardCharsets.UTF_8.name()));
} else {resp.setHeader("Content-Disposition", "attachment;filename=" + new String("文件名".getBytes("UTF-8"), StandardCharsets.ISO_8859_1.name()));
}
Files.copy(Paths.get("文件夹", "文件名"), resp.getOutputStream());
}
@RequestMapping(value = "testUpload2")
public ResponseEntity<byte[]> testUpload(Long id, HttpServletResponse resp, HttpServletRequest request) throws IOException {
// 依据 id 去查文件的地位
resp.setContentType("application/x-msdownload");
String userAgent = request.getHeader("User-Agent");
HttpHeaders httpHeaders = new HttpHeaders();
if (userAgent.contains("IE")) {resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("文件名", StandardCharsets.UTF_8.name()));
} else {resp.setHeader("Content-Disposition", "attachment;filename=" + new String("文件名".getBytes("UTF-8"), StandardCharsets.ISO_8859_1.name()));
}
Files.copy(Paths.get("服务器上的文件夹", "文件名"), resp.getOutputStream());
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
byte[] array = FileUtils.readFileToByteArray(new File("文件夹", "文件名"));
return new ResponseEntity<byte[]>(array,httpHeaders, HttpStatus.CREATED);
}
对 Restful 格调的反对
什么是 Restful 格调
Restfu 是一种软件格调,严格意义上 Restfu 是一种编码格调,简略的说,通常状况咱们设计的后端接口能够对应四种操作,即增删查改,这也是服务端工程师常常被称作 CRUD 仔的起因,那么怎么让增删查改和申请形式绑定在一起呢? 对 Restful 格调不相熟的敌人可能会说,不是只有两种申请形式吗?怎么对应四种操作呢?事实上 HTTP 协定规定的申请形式可不止有四种,有人说是十四种,然而我在火狐开发者文档上只查到八种,然而浏览器只反对两种,所以咱们用原生 Servlet 进行编程的时候,只重写了 HttpServlet 的 doGet 和 doPost 办法。这八种申请形式有四种咱们能够拎进去看看:
- DELETE
- GET
- POST
- PUT
粗略的说,Restful 格调中规定 DELETE 代表删除某个资源,GET 代表向服务端获取某个资源,POST 代表向服务器新增一个资源,PUT 代表向服务器申请更新一个资源。这样的设计理念,就不必开发者在设计接口的时候,在接口中退出动词来标识这个接口要实现的动作,这样更有利于保护,贯彻繁多设计职责,后续的开发人员从接口就能看进去这个申请是做什么的,这也是 Restful 格调流行的起因。
然而我在翻阅火狐的开发者文档的时候,发现文档在介绍浏览器对八种申请形式都是反对的:
然而我在网上翻了许多材料,都是说浏览器只反对 GET 和 POST 的申请,认真想了想,这兴许跟浏览器的版本有关系吧。不同版本反对的 HTTP 协定是不同的,HTTP1.1 之前,申请形式只有 GET 和 POST 两种,HTTP1.1 协定新增了五种申请形式:
- OPTIONS
- HEAD
- PUT
- DELETE
- TRACE
浏览器反对是反对,然而客户端发动申请的时候,怎么让服务端晓得你用的是 PUT 和 DELETE 申请的,通用的做法就是在表单代码中减少暗藏属性,把 POST 申请包装成 PUT 申请。
另外顺便提一句,火狐浏览器的开发者文档写的不错,有介绍 HTTP 协定的,想学 HTTP 协定,找不到权威的参考资料的能够去看下,文末的参考资料有链接。
对 Restful 的反对
@ResponseBody
在现在的前后端拆散时代,跳转页面曾经被前端管制,前后端通信的形式也是通过 Ajax 实现部分刷新,然而在 SpringMVC 还是有跳转视图的概念,那怎么告知 Spring MVC,我不要跳转页面,我就是给前端页面发送了数据呢? 就是通过在申请办法上加
@ResponseBody
来实现的。像上面这样:
@RestController
如果你感觉每个办法加 @ResponseBody 比拟办法,能够在类上加上这个注解,加上
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
见名知义,反对不同的申请形式,用法和
@RequestMapping
一样。将这五个注解统称为申请注解。
@PathVariable
服务端应用申请注解后,能够相似 {参数名} 站位符传参,此时须要通过 @PathVariable 注解能够将 URL 中站位符参数绑定到控制器解决办法的形参中。
此时客户端的申请地址如: http://localhost:8080/studySpringFrameWork_war_exploded/delete/1
服务端代码:
@RequestMapping(value = "delete/{id}", method = RequestMethod.GET)
public String getInfo(@PathVariable(value = "id") String id) {System.out.println("id:" + id);
return id;
}
{参数值} 和 @PathVariable(value = “id”) 要维持统一。
对立的异样解决
简介
Spring MVC 解决异样有三种形式:
(1) 应用 Spring MVC 提供的简略异样处理器 SimpleMappingExceptionResolver
(2) 现 Spring 的异样解决接口 HandlerExceptionResolver 自定义本人的异样处理器
(3) 应用@ExceptionHandler
+ @ControllerAdvice
注解实现异样解决
最顶层的异样解决就是 HandlerExceptionResolver,继承结构图如下:
个别咱们罕用的是第一种和第三种解决形式。
第一种解决形式
第一种形式具体的实操就是在配置文件中配置,要对哪些异样进行解决,跳转到哪些页面,像上面这样:
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异样解决页面, 当该异样类型产生时跳转到 error 页面 -->
<property name="defaultErrorView" value="error"/>
<!-- 定义异样解决页面用来获取异样信息的变量名, 默认名为 exception-->
<property name="exceptionAttribute" value="ex"/>
<!-- 定义须要非凡解决的异样, 有两种配置形式 1 种是 value 标签形式 2.props 标签形式 还是配置类更好, 无需关怀这些细节 -->
<property name="exceptionMappings">
<!-- value 标签多个异样用逗号隔开 -->
<!-- <value>-->
<!-- java.lang.ArithmeticException=error,-->
<!-- java.lang.NullPointerException=exception-->
<!-- </value>-->
<!-- props 标签, 每个 prop 标签的 key 代表解决一种异样,prop 标签中代表具体要跳转的页面 -->
<props>
<prop key="java.lang.ArithmeticException">
error
</prop>
</props>
</property>
</bean>
第三种解决形式
// 这个注解会解决所有办法的异样
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
// 该注解中申明要解决哪些异样
@ExceptionHandler({Exception.class})
public String error(Exception ex , Model model){
// 向申请域中放入异样信息
model.addAttribute("errorMsg",ex.getMessage());
// 跳转至 error 页面
return "error";
}
}
拦截器
简介
在用户的申请达到申请办法之前会进入拦截器,咱们能够在拦截器外面做一些通用操作,比方鉴权,判断用户登录之类的。折让我想到了 AOP,也能够做到在办法之前、办法执行之后执行。那拦截器和 AOP 有什么区别呢? 某种意义上拦截器能够算在 AOP 外面,然而拦截器又不能算在 AOP 外面,咱们简略的讲一下拦截器的原理,用户的申请在达到 DispacherServlet 之后,由 DispacherServlet 散发申请,DispacherServlet 会查看该申请是否被拦截器拦挡,如果被拦挡,那么先将该申请交给拦截器,如下图所示:
而 AOP 则是通过动静代理来做的,在运行的时候生成须要加强办法的类的子类来做到的。拦截器算是在整个 Spring MVC 执行流程的一环,只能拦 URL,AOP 则更为粗疏。执行程序上,拦截器先于 AOP 执行。
怎么用?
实现 org.springframework.web.servlet.HandlerInterceptor 接口。咱们先大抵的看一下这个办法:
preHandle 在申请到处理器之前执行,postHandle 在解决申请的办法执行结束之后,视图渲染之前执行。afterCompletion 视图渲染之后执行。这里的视图是什么意思呢? 也就是 Spring MVC 的 V 视图层,页面渲染完就是页面造成结束
而后在配置文件或配置类中配置拦截器即可。
首先实现 HandlerInterceptor 接口:
public class MyHandlerInterceptor implements HandlerInterceptor {
/**
* 返回 true 代表将申请交给真正的控制器
* 返回 false 代表将申请放给下一个拦截器, 如果找不到下一个拦截器,
* 则该申请不会达到真正的拦截器
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("在办法执行之前执行................");
return true;
}
/**
* 在处理器办法执行之后执行
* ModelAndView 中寄存办法返回的数据和要跳转的视图
* 办法未胜利执行, 不会触发此办法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("在办法执行之后执行................");
}
/**
* 在页面渲染之后获取
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 打印办法的异样信息
ex.printStackTrace();
System.out.println("在视图渲染结束执行之后执行................");
}
}
配置文件中配置:
<mvc:interceptors>
<mvc:interceptor>
<!-- /*: 只能拦挡一级门路 -->
<!-- /**: 能够拦挡一级或多级门路 -->
<mvc:mapping path = "/**"/>
<mvc:exclude-mapping path="login"/>
<bean class="org.example.mvc.MyHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
补充介绍
Spring MVC 的 M(model)、V(view)、C(Controller),下面咱们仿佛就将关注点放在 C 上了,也就是接管参数,解决参数了。其实 M 也讲了,就是用 @ResponseBody
向前端返回 JSON 格局的数据,还有 V 咱们这里也并没有细讲,起因在于这些并不罕用了,服务端曾经不能管制住页面的跳转了,对于返回数据也次要是向前端返回 JSON 格局的数据。下面仿佛有提到过 Model,这是一个在跳转对应页面后取数据的类,在 Controller 取完数据之后,将数据放入 Model,而后指明跳转到哪个页面,而后在对应的页面取出 Model 中存储的数据,Model 是 Spring MVC 提供给咱们的类,除此之外还有 ModelAndView,既能存放数据又能寄存视图,当返回类型是这个的时候,SpringMVC 会 ModelAndView 取视图跳转,而后咱们就能够在对应的页面取数据了。然而这些并不罕用,所以着墨不多,本文重点关注的都是在前后端拆散时代,高频罕用的,其实这里原本想介绍 Spring MVC 对立解决乱码的,就是在 web.xml 中配置一个过滤器:
<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>
然而不晓得咋会事,兴许是我用的 Spring MVC 版本太新了,这个乱码问题就被解决了。如果有同学在编码的时候发现有中文乱码问题的话,能够在 web.xml 把下面的配置加一下。
写在最初
每次重学框架都会有新的认知,一不小心又把篇幅拉的太大了,总结一下本文讲了什么,本文次要讲了 Spring MVC 的高频应用点,Spring MVC 对原生的 Servlet 进行了加强,为了应用 Spring MVC 提供的加强的 Servlet,咱们须要让 Spring MVC 的 DispatcherServlet 接管所有的申请,这也就是在 Tomcar 的配置文件 web.xml 配置在启动的时候就初始化 DispatcherServlet,因为 Spring MVC 刚设计之初,服务端还能管制页面跳转,所以咱们还要在配置文件中配置视图解析器,也就是 InternalResourceViewResolver。而后咱们就能领会到 Spring MVC 提供给咱们的遍历,简化取参数的形式让咱们更专一于业务逻辑,对立的异样解决让咱们的页面不至于间接把错误信息间接输入到页面上,简略的获取前端文件的形式,拦截器能够让咱们对立进行鉴权,以及对 RESTful 格调的敌对反对。心愿对大家会有所帮忙。
参考资料
- SpringMVC 视频教程 颜群
- COOKIE 和 SESSION 有什么区别?
- RESTful API 设计指南
- HTTP 协定中的 14 种申请办法
- 火狐开发者文档
- HTTP 8 种申请形式介绍
- rfc2068-http1.1 规范文档