Spring MVC的主要知识介绍

本文使用spring mvc的版本为5.3.7

软件世界迭代迅速,尽量使用新版本,大致使用方法不会变,具体变化可查看源码找找对应的新方法

1. WEB的主要运行流程

spring mvc是作为一个WEB框架使用,主要的作用就是在我们指定的网址上根据不同的页面需求而返回对应的页面信息。

如浏览器中输入地址:https://我们的主网址/index.html

  1. 这里用户试图获取对应网址下的index.html页面
  2. 那么当浏览器将这一请求发送过来时,附带着说明我需要这样一个文件
  3. 我们的WEB框架就会根据该需求,识别出需要的是一个index.html文件,并将本地资源中的index.html文件作为回复发送给浏览器,附带着告诉它这是什么类型的文件
  4. 接着浏览器就会按照html文件格式,读取文件并渲染显示在页面中。

大体上,Spring MVC需要做的主要工作就是上述的流程,而这种识别工作就交给被标注了@Controller注解的类来完成。

但是我们的页面很多不是那种仅用于读取的静态页面,而是涉及到数据的交换,此时则需要结合数据库工作,目前常用的就是利用Mybatis作为数据库的操作工具,帮助我们存储和读取数据。


而涉及到spring框架时,常看到的就是容器,IOC。

上述就是,我们使用Spring MVC主要的目的和重要的技术点。而具体到实际的项目,则需要考虑一些细枝末节,对产品做优化,不至于粗糙。

所谓的MVC是Model-View-Controller,controller负责判断url请求并分配到对应的负责方法,model就是这个方法,方法内部将各种工作完成后,将结果或直接将任务交给页面负责,即view,由此将一套任务进行分配,各司其职。

2. 代码的主要内容

前面了解了这一框架的用处,那么实际的使用则需要注重功能的组织。

读者可以阅读开源项目 ,该项目简单同时包含了springmvc较为完整的功能实现,非常适合初学者参考。

3. 项目构建

我们可以利用maven或gradle引入javax.servlet-apispring-webmvc

首先,整个项目需要中央调度器,要求在web.xml中告诉tomcat之类的服务器生成一个DispatchServlet的对象【更严格的说,tomcat只能算是Servlet容器】,如果是通过类创建调度器,则要求tomcat支持servlet3.0+,基本现在官网能下载的版本都是支持的(基本上6.0以上的版本就OK)。

3.1 xml构建

大致的源代码文件框架如下:

关于其中的xml文件编写,读者搜索关于springmvc配置的结果,基本上都是这方面的资料。而且,xml配置属于以前的传统方式,本文更注重现有的纯java配置,但这要求使用的servlet版本至少是3.0,如果不是太老旧的,应该是没问题的。

可以了解一下,毕竟现如今存在的一些项目还是使用着上述的大体结构。

3.2 java Bean配置

上述的XML文件配置,对于很多人而言觉得非常繁琐,而且还必须在指定的位置指定的文件名内写大量各种标签,从spring3.1开始,我们可以通过写普通的java类来初始化调度器。

现在,可以将项目交给Tomcat运行,例如在IDEA中设置运行的各种属性时,在 deployment中添加自己的项目时需要的带有 exploded,下面的 Application context可以随便填一些字符串,但要记得前面的 /不能少,例如写一个 /suiyi,运行后【记得对应的目录下有你的页面文件】,访问 http://localhost:8080/suiyi/views/test,即可。

3. Mapping

前文中,我们已经基本实现了一个spring mvc项目,其中主要的@controller,@requestmapping,@getmapping也都有出场。代码中的使用基本上可以让读者明白大致的使用目的。本节,则需要进一步介绍更为细致的操作,其实也很简单。

如果读者觉得复杂,那是因为没有意识到某些功能对开发的简化。我们在使用浏览器时,

  • 最常用的就是简单的请请求一个页面,
  • 若是搜索内容,则uri还包含一个参数;
  • 如果是在页面填一些表格之类的,浏览器也需要将这个表格的相关信息发送过去;
  • 对于同一个uri的请求,如果请求的内容类型不同,返回的结果可能是下载一个文件,也可能就是单纯的页面;
  • 类似地,我们有时也许要特意地指定我们发送的内容,如果图片的具体格式,别把jpeg当作gif给处理了;
  1. 路径匹配规则

    在开始具体内容前,先补充一下,上述代码中的 *的符号的意义。

    • ?:匹配任意一个字符
    • *:匹配任意多个字符
    • **:匹配多层路径
  2. 路径匹配 ( @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping )

    其中,@RequestMapping是我们常用的一种,类似上面代码中的使用,是要内部对应的uri格式内容匹配到了,则有对应该注解的方法负责该uri。

    但是,浏览器对于请求也有不同的方式,GET, POST。@RequestMapping默认是接受GET方式。

  3. 内容类型

    在这些Mapping注解中,可以使用 consumesproduces指定内容在发送和接受时的内容类型。

  4. 请求头

    所谓请求头示例如下:

    在这些注解中可以使用 headers指定对应的请求头

  5. 参数

    这应该是常用且非常重要的,即判断传递进来的参数是否符合要求,也可以获取对应的参数。

    如果希望将控制类中获得的数据显示在页面中,简单来说,就是在页面文件中写 ${内写数据名},数据名就是对应控制类方法中的变量名或对象名,其中对象也完全可以使用类似 对象名.属性指定对应内部属性值。


–––过场–—

现在,我们基本上知道了spring mvc大体的使用方式【当然了,还远远不够】。就像是开车上路,前面的知识让我们学会了启动,刹车,踩油门。但好的司机,会把握细节,开得平稳潇洒,什么路段怎么开,车子出毛病怎么修。

因此,我们需要暂停学技术,简单了解一下,spring mvc的一些内部机制。

java的web编程,说到底是编写servlet【这是Server和Applet的组合词,Applet是以前java的web客户端技术,现在基本不用了】,它的意思大致为服务端的小程序,可生成动态的web页面,是作为客户请求和后端服务的中间层。【即前面的controller能接受浏览器的请求,即客户端请求,同时能够显示后端做出的回复】(所以前面说了tomcat本质上就是一个servlet的容器)

servlet对应的java接口或类,主要为javax.servlet包实现了接口,其中javax.servlet.http包提供了派生出的用于处理HTTP请求的抽象类和一半的工具类,我们主要也就是使用这一类型。

如果想自己常见一种servlet,可以自行实现javax.servlet.Servlet接口,但需要实现里面的很多方法。也可以继承一个抽象类GenericServlet,只需要覆写里面的service方法即可。当然,我们最主要的还是继承HttpServlet,其中的主要方法为doGet()doPost()

为了运行我们编写的servlet【我们上面编写的项目实际就是一个servlet,当加载到Tomcat后,它会自动识别目录webapp,并找到里面的web.xml文件读取当前servlet的配置信息。

前文中提及到,项目创建初期要先创建一个中央调度器(Dispatcher),用于将客户端的请求进行分发,而实际上spring的这个调度器最后还是把任务交给了java的RequestDispatcher,位于javax.servlet包中,是一个接口,只是Tomcat有它关于该接口的实现类。

大致了解了底层的一些实际机制后,我们需要了解一下最直观的页面。这里常用的就是jsp文件,全称是Java server pages,属于动态网页开发技术。我们可以在里面写HTML语言作为静态内容,也可以使用标签<% %>在%中间插入java代码,更多的可自行了解jsp中java代码的使用,主要需要了解的就是JSTL和EL表达式【不过现在jsp算是一种老技术了(似乎java开发的这类东西总是要被别人替代,但仍然很有用),而且spring框架推荐使用freemarker、thymeleaf等模板编写页面,因此需要了解jsp,但要学会这些新的技术】(由于jsp支持java代码,所以它本质上不是一个单纯的页面文件,而是伪装成页面的servlet)

通过上述的描述,我们最终可以获得一个认识,所谓的web框架,就是借着java的servlet完成客户端与服务端的消息传递。其余的琐碎工作就是,如何管理这些servlet(Tomcat),如何显示内容(页面)。

下图为spring mvc内部处理请求的基本流程。

1.请求
12.相应
2.请求
3.处理器执行链
4.处理器执行链
7.ModelAndView
5.执行
6.ModelAndView
8.ModelAndView
9.View
10.调用
11.执行
浏览器
DispatcherServlet 中央调度器
HandleMapping 处理器映射器
HandleAdaptor 处理器适配器
Controller 处理器
ViewResolver 视图解析器
View 视图

具体的运行过程是:我们创建的各种controller对象都保存在一个实现了handleExecutionChain的对象中,通过映射器得到对应的控制器,再将控制器交给适配器运行其中的方法,最后的结果由视图解析器确定视图文件位置,最后调用页面文件,回复浏览器的请求。


4. 文件获取

4.1 静态文件

通过控制器类,我们可以获取对应目录下的页面文件,这源于我们对uri的请求有对应的处理机制,但是对应的静态文件并没有这样的控制器类逐一负责,诸如jscss文件等都属于静态文件,页面文件内部可能会直接调用进行页面渲染等工作。

我们只需要在 DispatcherConfig类中添加:

4.2 上传文件

上传文件相比于索取页面,是一个反向的操作,自然需要在springmvc的框架中设置一下,则需要在本文中负责springmvc入口的DispatcherConfig类中添加内容。

首先,在页面中上传文件的常规操作是利用表单并使用POST方式进行传输,如果使用了GET方式,则上传的内容将转换为字符串像参数一样跟在uri后面一起传输,而POST则将传输内容放在请求体中。

而在这里进行文件上传的表单,则是一种multipart表单,意为将表单分成多个块,并以二进制流进行传输。

因此,需要告诉框架我们需要处理这样的multipart表单。只要添加如下的代码即可。

首先需要添加依赖 commons-fileupload

此时,我们仅仅是有了上传文件的底子,还缺少控制类,

下面我们给出一个较为完整且简单的示例:

首先是大体的文件结构

upfile.jsp中包含了负责上传文件的表单,并指定点击上传后,将调用你的uri/fileupload,并触发对应的控制器类,方法内完成文件的存储,并返回一个新的页面,用以显示文件上传情况。

4.3 跳转

在控制类中,我们通常根据用户的请求而返回对应的页面文件,但是当我们的整个业务发生了变化,原有的工作转移到其它地址负责,而之前的地址不想再多余在里面写相关的方法实现,于是我们就希望直接将用户的请求转移到新的地址上由对应的地址负责。则使用redirectforward

关于redirect,由于通过用户的浏览器实现跳转,导致信息缺失,为了保证能携带信息进行跳转,框架准备了flash属性,用于临时存储指定的信息,当完成跳转后,这部分信息传递给对应的控制器,原本的信息将自动删除。更具体的是,内容信息使用了map结构进行存储,称为flashmap,另外还有flashmapmanger用于管理flashmap。

但我们实际使用,则代码如下:

 

4.4 文件下载

类似于静态文件的获取,我们可以把文件放在 webapp的任意目录下,这里做一些简化,首先需要一个负责下载文件的页面【当然,也可以在已有的页面中,增加个链接】

我们只需要再增加一个控制器,负责 downloadfile的uri,

user.withoutpasswordview

5. 数据

类似于前面所述的,利用对象进行数据存储的操作,由于大型项目中由于各部分工作重点不同,处理数据所需要的数据格式不一定相同,比如单纯需要数据可以使用对象传递,而负责页面渲染而言,更希望直接传递过来的是模型类型的数据。

其它的,由于需要的数据格式不同,也要求我们需要进行数据格式转换,对于一些常用场景,我们可以使用注解简单地完成任务。

5.1 JSON格式

json格式由于非常简洁,在WEB前端中已成为常用的数据类型,也普遍用于前后端的数据交换【由于前后端可能不共享java对象,因此需要共同支持的数据格式】。这里先简单介绍几种关于json格式的第三方库。

这里主要介绍几种json库:GsonFastjsonJackson

其中Jackson需要倒入3个依赖包:jackson-corejackson-annotationsjackson-databind

具体使用可阅读对应库的文档,主要的操作就是将原有的对象转换为字符串或数组格式。

如果单纯使用,自然需要利用不同依赖对应的处理方法,将对象进行转化,但是既然使用到了框架,则不需要我们做这些工作【凡是机械,没有额外需要创造的工作,框架基本都会替我们完成大部分工作】。

json格式本身是javascript的数据格式,因此为了真正地使用这种数据,则需要配合ajax之类的工具进行显示。暂且不谈。

5.2 格式转换

使用格式转换不只是将某一种对象转换为另一种对象,而是可以按照你给定的格式将传进来的数据转换为指定的格式,例如一串字符串可以转为对应的对象。

这里先举一个最简单的日期格式,我们给定一个日期 格式的字符串,它可以转化为对应的日期对象,

上述是最纯粹的字符串转日期,框架有更为广泛的转化,名为属性编辑器,将这些内部操作封装起来,

spring已实现了各种属性编辑器,读者可在org.springframework.beans.propertyeditors包中查阅已有的类。

上述的属性编辑器是在原有用于设计界面的类上进行操作的,原本的类中存在许多额外的方法,为简化,3.0之后,提供了转换器接口 Convert,不同的转换器类需要实现该接口,而spring又提供了一个转化器容器接口ConversionService,该接口的实现有:DefaultConversionService【简单的实现,可利用不同的转换器进行不同类型的转换】、DefaultFormattingConversionService【在前面简单实现的基础上,增加国际化的格式化和解析】。

spring框架本身也已经实现了很多类型转换的类,具体的可在包 org.springframework.core.convert.support中查看,在我们使用时,不需要指定转换器,而是由容器自己判断,如:

如果自定义转换器,示例如下:

5.3 封装、绑定

5.3.1 封装

BeanWrapperImpl可以吸纳给定的对象,完成属性的装载和提取,虽然本质上就是普通的setter/getter操作,但是相对于不同对象具体方法名的多样,使用封装后,我们的方法中,可以传入任意对象,并且能够以同样的方法完成属性操作。

具体的操作可见下面的代码:

假如我们需要传递不同的对象作为参数,这种做法就可以极大简化了不同对象属性的操作。

假如,属性中包含一些特有的类型,如Date,我们希望赋值的时候转化为对应的类型,即格式转换,这里有简单的实现,

5.3.2 绑定

所谓的绑定其实就是针对特定的格式进行转换,本质还是格式转换。

6. 登录

读者在一些网站中可能会发现,例如购物网站,或论坛,有时我们作为游客查看一些页面没问题,而当我们视图添加商品,或试图评论,或下载某些文件等,由于这些功能被网站设置为需要注册用户才能使用,则我们的页面被强制转到了登录或注册页面。

6.1 拦截器

上述的强制登录手段,则涉及到了网站对请求的拦截技术,所谓的拦截,即在我们已经实现的功能之上,在请求和我们的回复之间插入一个额外的工作。

如果读者是遵循建议先阅读SpringMVC的话,可能不了解面向切面编程(AOP),这种拦截其实就是AOP,对实现的方法在外部进行扩充或判断是否执行。

【这也是我建议先阅读Spring MVC的原因,与其预先了解大量基础的知识,不如先尝试着实现功能,不然程序员从学习计算机开始得先去学习数理逻辑】

拦截器的实现的框架:

拦截的样子大概是下面的样子:

其次。为了告诉框架何时需要拦截,需要在关于调度器的配置文件中覆写对应的方法

到这里基本上拦截器就差不多了,具体的操作就是看情况随便写了。

6.2 过滤器

过滤器本身是servlet自大的功能,不是spring额外实现的功能,自然不是调度器能够管理的,如果需要配置,也需要在之前的MyWebAppInitializer类中配置。

配置过滤器我这里有两种方法,先介绍简单的:

上述就是非常简单有效的方法,如果希望复杂点,可以在MyWebAppInitializer类中进行配置。

 

 

在SpringBoot中的使用方法

其实,没必要为Springboot专门列出一章,因为Springboot本来就是建构在spring之上的,基本的使用相同,不同点,基本就在于前期的一些简单配置上。

首先我们创建一个springboot的项目,可以直接使用IDEA中的spring的初始化功能,在选择组件时,选择其中的web即可,以防万一的话,也可以选择其中的thymeleaf引擎组件。

创建完毕后,基本上就完成了一个springboot的mvc项目的创建。

不同于上面spring项目中,对于各种设置都要大动干戈,原本前面的各种属性配置可能需要一个专门的xml文件或java类负责处理,而这里全部都方法一个属性文件中。

在resources目录中,可能IDEA会自动创建一个application.properties文件,这就是一个可以设置属性的文件 ,只是现在我们比较常用的是yaml格式的。因此读者可以手动将那个文件修改一个后缀名,为yml即可。

之所以yml文件比较流行,在于属性会阶梯型地展开,多个属性将共用同样的前缀。不像properties那样,每个属性名都要写完整。

但,文件中对应的属性名都是一样的,这个读者不需要忧虑。

比如,我们要设置对应的端口号,

 

最后,再准备一个启动类,估计IDEA也会帮我们设置好的,

目录中,可能会有一个写好的java类,名字估计和你设置的项目名类似,后面有个Applicaiton的字样。

如果没有,你可以自己随便写个这样的类。【当然,名字具体是什么无关紧要,主要好区分】

类似于下面的代码

这个类,既可以帮我们启动项目,有充当了一个配置类。

这个类就保持这样就好了,尽量别往里面放其它代码,虽然是个配置类,也别把@Bean的内容往里写,保持代码的干净。

 

至于,其它的各种控制类的编写,都是与上面的介绍相同的。

 

 

阅读完之后,可接着看看Mybatis的使用,毕竟二者经常放在一起使用