在编码一段时间之后,再从新看之前学过的框架,发现有新的认知,像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规范文档
发表回复