Spring MVC的主要知识介绍1. WEB的主要运行流程2. 代码的主要内容3. 项目构建3.1 xml构建3.2 java Bean配置3. Mapping–––过场–—4. 文件获取4.1 静态文件4.2 上传文件4.3 跳转4.4 文件下载5. 数据5.1 JSON格式5.2 格式转换5.3 封装、绑定5.3.1 封装5.3.2 绑定6. 登录6.1 拦截器6.2 过滤器在SpringBoot中的使用方法
本文使用spring mvc的版本为5.3.7
软件世界迭代迅速,尽量使用新版本,大致使用方法不会变,具体变化可查看源码找找对应的新方法
spring mvc是作为一个WEB框架使用,主要的作用就是在我们指定的网址上根据不同的页面需求而返回对应的页面信息。
如浏览器中输入地址:https://我们的主网址/index.html
index.html页面index.html文件,并将本地资源中的index.html文件作为回复发送给浏览器,附带着告诉它这是什么类型的文件大体上,Spring MVC需要做的主要工作就是上述的流程,而这种识别工作就交给被标注了@Controller注解的类来完成。
但是我们的页面很多不是那种仅用于读取的静态页面,而是涉及到数据的交换,此时则需要结合数据库工作,目前常用的就是利用Mybatis作为数据库的操作工具,帮助我们存储和读取数据。
而涉及到spring框架时,常看到的就是容器,IOC。
上述就是,我们使用Spring MVC主要的目的和重要的技术点。而具体到实际的项目,则需要考虑一些细枝末节,对产品做优化,不至于粗糙。
所谓的MVC是Model-View-Controller,controller负责判断url请求并分配到对应的负责方法,model就是这个方法,方法内部将各种工作完成后,将结果或直接将任务交给页面负责,即view,由此将一套任务进行分配,各司其职。
前面了解了这一框架的用处,那么实际的使用则需要注重功能的组织。
读者可以阅读开源项目 ,该项目简单同时包含了springmvc较为完整的功能实现,非常适合初学者参考。
我们可以利用maven或gradle引入javax.servlet-api,spring-webmvc。
首先,整个项目需要中央调度器,要求在web.xml中告诉tomcat之类的服务器生成一个DispatchServlet的对象【更严格的说,tomcat只能算是Servlet容器】,如果是通过类创建调度器,则要求tomcat支持servlet3.0+,基本现在官网能下载的版本都是支持的(基本上6.0以上的版本就OK)。
大致的源代码文件框架如下:
xxxxxxxxxx├───java│ └───自定义的│ └───包名│ ├───controller(自己随便命名,控制类的包)│ └───···│ └───···├───resources│ └───springmvc.xml//因为难受而自定义的另一个配置文件└───webapp └───resources └───WEB-INF//这里面的文件对用户是不开放的 └───web.xml//传统做法,需要借助这个文件作为项目的启动入口,现在我们可以直接创建java类取代 └───dispatcher-servlet.xml//springmvc的配置文件 //这个名字是随着自己定义的调度器名称而改变(是默认的格式)关于其中的xml文件编写,读者搜索关于springmvc配置的结果,基本上都是这方面的资料。而且,xml配置属于以前的传统方式,本文更注重现有的纯java配置,但这要求使用的servlet版本至少是3.0,如果不是太老旧的,应该是没问题的。
可以了解一下,毕竟现如今存在的一些项目还是使用着上述的大体结构。
上述的XML文件配置,对于很多人而言觉得非常繁琐,而且还必须在指定的位置指定的文件名内写大量各种标签,从spring3.1开始,我们可以通过写普通的java类来初始化调度器。
xxxxxxxxxx//继承并实现后,将创建出Dispatcher对象public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{ protected Class<?>[] getRootConfigClasses() { return new Class[] { springConfig.class }; } protected Class<?>[] getServletConfigClasses() { return new Class[] { DispatcherConfig.class }; } protected String[] getServletMappings() { return new String[] { "/" }; //告诉dispatcher仅可以捕获仅有"/"的uri,如果使用了"/*"则会将其它uri交给dispatcher直接负责, 则绕过了控制器类,导致无法成功访问 }}xxxxxxxxxx//指明是配置类public class springConfig {}xxxxxxxxxx//配置调度器("控制器所在的包")public class DispatcherConfig implements WebMvcConfigurer { public void configureViewResolvers(ViewResolverRegistry registry) {//视图解析器 registry.jsp("/WEB-INF/jsp/", ".jsp");//假设页面文件都放在了jsp目录下 //控制类返回的字符串一一般都会认为是逻辑名,将在前后加上这些前后缀 //更深入的,可以通过prefix,suffix,viewClass指定前后缀,和加载视图的类 }}xxxxxxxxxx//本质上就是个特别的@Bean,表明这是一个负责uri的控制器类("/views/*")//随意搞些花样,指view/*表示view/之后跟任何字符串,该地址都会交给这个类负 责。而且这个注解既可以放在类上,也可以放在方法上public class TestController { ("test") public String test(){ return "index";//返回一个名为index的jsp文件,该文件位于视图解析器前缀的目录下 //随意写一个自己的页面文件名称,别忘了自己写好页面文件,就是一个html语言写的jsp文件 //以后可以按照spring给定的模板引擎写相应的页面文件,大体上都是在html之上做些手脚 }}现在,可以将项目交给Tomcat运行,例如在IDEA中设置运行的各种属性时,在 deployment中添加自己的项目时需要的带有 exploded,下面的 Application context可以随便填一些字符串,但要记得前面的 /不能少,例如写一个 /suiyi,运行后【记得对应的目录下有你的页面文件】,访问 http://localhost:8080/suiyi/views/test,即可。
前文中,我们已经基本实现了一个spring mvc项目,其中主要的@controller,@requestmapping,@getmapping也都有出场。代码中的使用基本上可以让读者明白大致的使用目的。本节,则需要进一步介绍更为细致的操作,其实也很简单。
如果读者觉得复杂,那是因为没有意识到某些功能对开发的简化。我们在使用浏览器时,
- 最常用的就是简单的请请求一个页面,
- 若是搜索内容,则uri还包含一个参数;
- 如果是在页面填一些表格之类的,浏览器也需要将这个表格的相关信息发送过去;
- 对于同一个uri的请求,如果请求的内容类型不同,返回的结果可能是下载一个文件,也可能就是单纯的页面;
- 类似地,我们有时也许要特意地指定我们发送的内容,如果图片的具体格式,别把jpeg当作gif给处理了;
路径匹配规则
在开始具体内容前,先补充一下,上述代码中的 *的符号的意义。
路径匹配 ( @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping )
其中,@RequestMapping是我们常用的一种,类似上面代码中的使用,是要内部对应的uri格式内容匹配到了,则有对应该注解的方法负责该uri。
但是,浏览器对于请求也有不同的方式,GET, POST。@RequestMapping默认是接受GET方式。
xxxxxxxxxx("uri格式",method=RequestMethod.POST)//对应的就是POST请方式//其中uri格式可以包括多个,即不同的uri地址都可以被一个方法或类负责//不同的方法则可以使用类似@PostMapping代替
(value={"第一个uri格式","第二个",...},method=你需要的方式(其中,GET可以不写))//不同的请求方式,则可以简化为对应的@GetMapping,@PostMapping等//其中,put和delete方式很少用内容类型
在这些Mapping注解中,可以使用 consumes和 produces指定内容在发送和接受时的内容类型。
xxxxxxxxxx(...,consumes="application/json",produces="application/json;charset=UTF-8")//上述代码,指示内容提交给服务器是json格式,当然还可以是其它格式,可自行查询,如 "text/plain",如果内容格式可以随意,但就是 "text/plain"不行,那就使用 "!text/plain"表示。//produces指示了返回的内容也是json格式,并指定了编码格式为 UTF-8。同样可以根据需要改变格式。请求头
所谓请求头示例如下:
xxxxxxxxxxHost localhost:8080Accept text/html,application/xhtml+xml,application/xml;q=0.9Accept-Language fr,en-gb;q=0.7,en;q=0.3Accept-Encoding qzip,defiateAccept-Charset ISO-8859-1,UTF-8;q=0.7,*;q=0.7Keep-Alive 300
在这些注解中可以使用 headers指定对应的请求头
xxxxxxxxxx(...,headers="Host=localhost:8080")//类似的,可以指定header中某些值必须符合某些要求//官方文档中举例,如果对应的类型不是指定的值,自然无法匹配(value = "/something", headers = "content-type=text/*")
//另外,如果我们希望可以使用请求头中的某些值,则类似下述方法("uri格式")public 方法(("请求头中的类型,如Keep-Alive") 对应的数据类型,如 Long 随意设定一个变量名 就弄个 keepAlive ){ //方法实现}参数
这应该是常用且非常重要的,即判断传递进来的参数是否符合要求,也可以获取对应的参数。
xxxxxxxxxx(value="/uri/{bianliang}")public 方法( 类型 bianliang){方法实现}//从uri中获取对应的变量,参数与对应的变量名相同
/*--------------------------------------------------------------*///另外,如果一次传进来大量的参数,而且使用了类似map的形式,即用等号赋值,形成键值对//这里使用陈学明的《Spring +Spring MVC+Mybatis 整合开发实战》中的例子/* 路径为 /depts/dept001;att1=values/users/user001;att1=value11;att2=value2 我们真正需要获得的是那些带有等号的键值对,这些map结构的内容与路径其它部分用`;`分隔*///用@MatrixVariable提取这些值(value="/depts/{deptId/uers/{userId}}")public 方法( MultiValueMap<String,String> bianliang1, //上述匹配得到的值为 // {att1=[value1,value11],att2=[value2]} (pathVar="userId") MultiValueMap<String,String> bianliang2 //上述使用pathVar指定了获取userId位置的变量,因此匹配值为 // {att1=[value11],att2=[value2]}){方法实现}
/*----------------------------------------------------------------*/("uri格式")public 方法 ( (value="bianliang",required=false) 类型 bianliang //指示要求传入一个名为bianliang的参数值,默认不传就报异常, //这里可以用required=false取消 //且负责参数与方法中的参数名是相同的){方法实现}
/*----------------------------------------------------------------*/("uri格式/{属性名1}/{属性名2}")//或者直接用纯粹的参数传递形式@RequestMapping("uri格式"),在浏览器中输入的时候,如下//uri?属性名1=某某&属性名2=某某,同样可以达到效果public 方法 ( 类型 对象名 //更常用的是,如果一次需要接受多个变量,我们则使用一个java对象负责接受 //这个对象的类,内部将具有与参数名相同的属性,这样就会框架会自动将参数存到对应的属性中 //@Valid指示用来判断对象是否合法){方法实现}如果希望将控制类中获得的数据显示在页面中,简单来说,就是在页面文件中写 ${内写数据名},数据名就是对应控制类方法中的变量名或对象名,其中对象也完全可以使用类似 对象名.属性指定对应内部属性值。
现在,我们基本上知道了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内部处理请求的基本流程。
具体的运行过程是:我们创建的各种controller对象都保存在一个实现了handleExecutionChain的对象中,通过映射器得到对应的控制器,再将控制器交给适配器运行其中的方法,最后的结果由视图解析器确定视图文件位置,最后调用页面文件,回复浏览器的请求。
通过控制器类,我们可以获取对应目录下的页面文件,这源于我们对uri的请求有对应的处理机制,但是对应的静态文件并没有这样的控制器类逐一负责,诸如js,css文件等都属于静态文件,页面文件内部可能会直接调用进行页面渲染等工作。
我们只需要在 DispatcherConfig类中添加:
xxxxxxxxxxpublic void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); //当用户在页面中请求的uri符合 `/resouces/**`,则对应的文件将从resources目录开始查找,这里的 // resource是指在webapp目录下的 //作为WEB框架,资源的获取基本都是以目录 `webapp`为根目录 /*假设webapp的目录结构如下 └───webapp └───WEB-INF └───resources └───tupian.jpg └───form.css └───jquery └───1.6 └───jquery.js */ /*例如,在页面文件中请求css文件和js文件,以及插入图片 <link href="<c:url value="/resources/form.css" />" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="<c:url value="/resources/jquery/1.6/jquery.js" />"> </script> <img src="${pageContext.request.contextPath }/resources/tupian.jpg" /> //愿意的话,还可以指定图片的尺寸大小 */}上传文件相比于索取页面,是一个反向的操作,自然需要在springmvc的框架中设置一下,则需要在本文中负责springmvc入口的DispatcherConfig类中添加内容。
首先,在页面中上传文件的常规操作是利用表单并使用POST方式进行传输,如果使用了GET方式,则上传的内容将转换为字符串像参数一样跟在uri后面一起传输,而POST则将传输内容放在请求体中。
xxxxxxxxxx<!--上传文件的表单,其实就是正常的表单,就是用了上传文件的功能而已--><form method="POST" enctype="multipart/form-data" action="指定的uri格式,假设为 fileupload"> <!--enctype用来指定请求内容的MIME编码类型,默认是”application/x-www-form-urlencoded--> <!--action指定后端服务的地址,即等会设定的控制类管理的uri格式--> <!--action中的uri如果前面有"/",则代表这里的uri的前缀是我们的域名,不加,则从当前页面的根开始--> <!--加入当前页面是 `http://www.baidu.com/suibian/yemian`, 加了"/"则请求的uri为 `http://www.baidu.com/fileupload`, 否则是 `http://www.baidu.com/suibian/fileupload`--> <input type="file" name="upfile"> <input type="submit" value="Upload"></form>而在这里进行文件上传的表单,则是一种multipart表单,意为将表单分成多个块,并以二进制流进行传输。
因此,需要告诉框架我们需要处理这样的multipart表单。只要添加如下的代码即可。
首先需要添加依赖
commons-fileupload
xxxxxxxxxxpublic MultipartResolver multipartResolver() { return new CommonsMultipartResolver(); //CommonsMultipartResolver继承自抽象类CommonsFileUploadSupport //CommonsFileUploadSupport内部包含了,限制上传文件大小的方法,文件默认编码等方法}此时,我们仅仅是有了上传文件的底子,还缺少控制类,
xxxxxxxxxxpublic class FileUploadController { ("/fileupload") public void processUpload(("页面中上传文件的名字") MultipartFile file, Model model) {方法实现}}下面我们给出一个较为完整且简单的示例:
首先是大体的文件结构
xxxxxxxxxx├───java│ └───org│ └───example│ └───DispatcherConfig.java│ └───MyWebAppInitializer.java//上述的内容即可│ └───springConfig.java//上述的内容即可│ └───controller│ └───UpfileController.java│ └───ResultController.java├───resources└───webapp└───resources└───WEB-INF└───html└───upload└───upfile.jsp└───result.jsp
xxxxxxxxxx("org.example.controller")public class DispatcherConfig implements WebMvcConfigurer {public void configureViewResolvers(ViewResolverRegistry registry) {registry.jsp("/WEB-INF/html/", ".jsp");}public MultipartResolver multipartResolver(){return new CommonsMultipartResolver();}}xxxxxxxxxx<!--upfile.jsp--><% page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>文件上传</title></head><body><form method="POST" enctype="multipart/form-data" action="fileupload"><!--action 对应的是将要调用的uri,用于触发对应的控制器--><input type="file" name="upfile"><!--上面的name可自定义,后面的控制类中获取的file需要注意就是这个名字--><input type="submit" value="Upload"></form></body></html>xxxxxxxxxx/*UpfileController.java*/public class FileUploadController {("/fileupload")public String fileupload(("upfile") MultipartFile file, Model model){//获取之前upfile.jsp上传的名为upfile的文件if (!file.isEmpty()) {try {File dir = new File("E:/tmp" + File.separator + "tmpFiles");//File.separator用于生成一个分隔符,用于创建一个名为"E:/tmp/tmpFiles"的目录if (!dir.exists()) dir.mkdirs();//此时才真正创建了目录// 准备好一个文件壳子File serverFile = new File(dir.getAbsolutePath() +File.separator + file.getOriginalFilename());file.transferTo(serverFile);//将实际的文件诸如到壳子里,获得实际的文件model.addAttribute("message","你的文件"+file.getOriginalFilename()+"已上传成功!");} catch (Exception e) {model.addAttribute("message","You failed to upload " +file.getOriginalFilename() + " => " +e.getMessage());}} else {model.addAttribute("message","You failed to upload "+ file.getOriginalFilename() +" because the file was empty.") ;}return "upload/result";}}xxxxxxxxxx<!--result.jsp--><% page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>文件上传状况</title></head><body><div>${message}</div></body></html>upfile.jsp中包含了负责上传文件的表单,并指定点击上传后,将调用
你的uri/fileupload,并触发对应的控制器类,方法内完成文件的存储,并返回一个新的页面,用以显示文件上传情况。
在控制类中,我们通常根据用户的请求而返回对应的页面文件,但是当我们的整个业务发生了变化,原有的工作转移到其它地址负责,而之前的地址不想再多余在里面写相关的方法实现,于是我们就希望直接将用户的请求转移到新的地址上由对应的地址负责。则使用redirect或forward。
xxxxxxxxxx("/uri")public String tiaozhuan(){ return "redirect:/新uri"; //此时将以当前页面的父级uri为起点查询新uri,浏览器会接受这个新地址并主动发送请求 /*如果没有加上”/“,则代表返回的是某个页面文件,需要注意的是WEB-INF目录下的文件无法被浏览器直接获 得,对应的文件需要放在webapp其它目录下 也可以使用 return new RedirectView("页面文件"),默认以webapp目录为起点,因此,需要写入完整的路径,但是大工程尽量不同,这属于硬编码耦合。*/ /*如果不希望浏览器主动发送请求,而是服务器自己完成这些工作,那就使用forward return "forward:/新uri"; */ //如果需要访问全新的页面,那就使用完成的地址,即包含http这些协议字眼}关于redirect,由于通过用户的浏览器实现跳转,导致信息缺失,为了保证能携带信息进行跳转,框架准备了flash属性,用于临时存储指定的信息,当完成跳转后,这部分信息传递给对应的控制器,原本的信息将自动删除。更具体的是,内容信息使用了map结构进行存储,称为flashmap,另外还有flashmapmanger用于管理flashmap。
但我们实际使用,则代码如下:
xxxxxxxxxxpublic String 方法名(RedirectAttributes attr){ //其它实现内容 attr.addFlashAttribute("key值", "对应信息");//这里可以看出实际存储的就是map结构 //也可以创建一个map提前存储对应的信息,通过 `addAllAttributes` 一次性存储 return "跳转地址";}
类似于静态文件的获取,我们可以把文件放在 webapp的任意目录下,这里做一些简化,首先需要一个负责下载文件的页面【当然,也可以在已有的页面中,增加个链接】
xxxxxxxxxx<% page contentType="text/html;charset=UTF-8" language="java" %><html><head> <title>下载文件</title></head><body><div id="global"> <p><a href="downloadfile">Download file</a></p></div></body></html>我们只需要再增加一个控制器,负责 downloadfile的uri,
xxxxxxxxxxpublic class downloadController { (value = "/downloadfile") public void download(HttpServletRequest request,HttpServletResponse response){ String datadir = request.getServletContext().getRealPath("/resources"); Path file = Paths.get(datadir, "data.txt"); //这里设置为在`webapp/resources`下存放一个data.txt文件 if (Files.exists(file)) { response.setContentType("text/plain");//不同文件的类型有所不同 response.addHeader("Content-Disposition","attachment;filename="+ "data.txt"); try { Files.copy(file, response.getOutputStream());//将文件放入流中进行返回 } catch (IOException e) { e.printStackTrace(); } } }}user.withoutpasswordview
类似于前面所述的,利用对象进行数据存储的操作,由于大型项目中由于各部分工作重点不同,处理数据所需要的数据格式不一定相同,比如单纯需要数据可以使用对象传递,而负责页面渲染而言,更希望直接传递过来的是模型类型的数据。
其它的,由于需要的数据格式不同,也要求我们需要进行数据格式转换,对于一些常用场景,我们可以使用注解简单地完成任务。
json格式由于非常简洁,在WEB前端中已成为常用的数据类型,也普遍用于前后端的数据交换【由于前后端可能不共享java对象,因此需要共同支持的数据格式】。这里先简单介绍几种关于json格式的第三方库。
这里主要介绍几种json库:Gson,Fastjson,Jackson。
其中Jackson需要倒入3个依赖包:jackson-core,jackson-annotations,jackson-databind。
具体使用可阅读对应库的文档,主要的操作就是将原有的对象转换为字符串或数组格式。
如果单纯使用,自然需要利用不同依赖对应的处理方法,将对象进行转化,但是既然使用到了框架,则不需要我们做这些工作【凡是机械,没有额外需要创造的工作,框架基本都会替我们完成大部分工作】。
首先,我们仍然需要为项目添加对应的json依赖包,最好是Gson或Jackson,因为框架似乎对它们具有较好的支持。
其次也是最后,当我们希望将数据按照json格式输出,在原有具有类型输出的方法上添加注解@ResponseBody。
json格式本身是javascript的数据格式,因此为了真正地使用这种数据,则需要配合ajax之类的工具进行显示。暂且不谈。
使用格式转换不只是将某一种对象转换为另一种对象,而是可以按照你给定的格式将传进来的数据转换为指定的格式,例如一串字符串可以转为对应的对象。
这里先举一个最简单的日期格式,我们给定一个日期 格式的字符串,它可以转化为对应的日期对象,
xxxxxxxxxxSimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");Date date=format.parse("日期字符串");//这要求字符串符合指定的格式,即类似2021-05-01上述是最纯粹的字符串转日期,框架有更为广泛的转化,名为属性编辑器,将这些内部操作封装起来,
xxxxxxxxxxpublic class MyDate extends PropertyEditorSupport{//随意自定义一个类名 public void SetAsText(String text) throws IllegalArgumentException{ SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd"); Date date=null; try{ date=format.parse(text); }catch(ParseException e){} setValue(date); }}/* 接下来使用这个属性编辑器*/PropertyEditorSupport editor=new Mydate();editor.setAsText(日期字符串);Date date=(Date) editor.getValue();spring已实现了各种属性编辑器,读者可在org.springframework.beans.propertyeditors包中查阅已有的类。
上述的属性编辑器是在原有用于设计界面的类上进行操作的,原本的类中存在许多额外的方法,为简化,3.0之后,提供了转换器接口 Convert,不同的转换器类需要实现该接口,而spring又提供了一个转化器容器接口ConversionService,该接口的实现有:DefaultConversionService【简单的实现,可利用不同的转换器进行不同类型的转换】、DefaultFormattingConversionService【在前面简单实现的基础上,增加国际化的格式化和解析】。
spring框架本身也已经实现了很多类型转换的类,具体的可在包 org.springframework.core.convert.support中查看,在我们使用时,不需要指定转换器,而是由容器自己判断,如:
xxxxxxxxxxpublic 方法(){ ConversionService conversionService=new DefaultConversionService(); Date date=conversionService.convert("2021/05/01", Date.class); System.out.println(date);}如果自定义转换器,示例如下:
xxxxxxxxxxpublic class myconvert implements Converter<String, User> { //我们这里的转换器,用于从字符串中提取User对象的名字和其中的日期 //所谓的User类只有简单的name和birthday两个属性 public User convert(String source) { User user=new User(); String[] result=source.split(","); Date birthday=null; try{ birthday=(new SimpleDateFormat("yyyy-MM-dd")).parse(result[1]); }catch(ParseException e){} user.setName(result[0]); user.setBirthday(birthday); return user; } public static void main(String[] args) throws ParseException { DefaultConversionService conversionService=new DefaultConversionService(); conversionService.addConverter(new myconvert());//将我们的转换器加入容器中 User user=conversionService.convert("学生名字,2021-05-01",User.class); //字符串将两种属性用逗号分隔 System.out.println(user); //下面,我们顺便再介绍json的格式转换, Gson gson=new Gson(); String user_string=gson.toJson(user);//转化成json,方便我们确定是否是我们指定的对象格式 System.out.println(user_string); User user1=gson.fromJson(user_string,User.class);//这里从json格式反向转为对应的对象 System.out.println(user1); }}BeanWrapperImpl可以吸纳给定的对象,完成属性的装载和提取,虽然本质上就是普通的setter/getter操作,但是相对于不同对象具体方法名的多样,使用封装后,我们的方法中,可以传入任意对象,并且能够以同样的方法完成属性操作。
具体的操作可见下面的代码:
xxxxxxxxxx//假设我们定义User类,并包含name,age属性//【要记得实现setter/getter方法】方法(){ User user=new User(); BeanWrapperImpl beanWrapper=new BeanWrapperImpl(); beanWrapper.setBeanInstance(user); beanWrapper.setBeanInstance(student); PropertyValue[] propertyValues={new PropertyValue("name", "lisi"), new PropertyValue("age", "26")}; //PropertyValue用于保存属性与对应的赋值 for (PropertyValue s:propertyValues){ beanWrapper.setPropertyValue(s);//装载对应的属性赋值 //如果属性不多,想直接赋值,可以直接setPropertyValue("属性名",赋值) } Object name= beanWrapper.getPropertyValue("name"); System.out.println(name.toString()+ beanWrapper.getPropertyValue("age").toString());}假如我们需要传递不同的对象作为参数,这种做法就可以极大简化了不同对象属性的操作。
假如,属性中包含一些特有的类型,如Date,我们希望赋值的时候转化为对应的类型,即格式转换,这里有简单的实现,
xxxxxxxxxxclass 类名{ (pattern="yyyy+mm+dd") private Date date;//之后,在赋值的时候,对应的日期字符串需要符合该格式,并且会自动转换为Date类型}所谓的绑定其实就是针对特定的格式进行转换,本质还是格式转换。
DataBinder
xxxxxxxxxx方法(){//这里假设user包含name和date属性,其中date为Date类型 User user=new User(); DataBinder databinder=new DataBinder(user); databinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); MutablePropertyValues propertyValues=new MutablePropertyValues(); propertyValues.add("name","8989"); propertyValues.add("date","8989-12-12"); databinder.bind(propertyValues); BindingResult bindingResult=databinder.getBindingResult(); user=(User) bindingResult.getTarget(); System.out.println((new Gson()).toJson(user));}读者也发现了,上面的代码中格式转换使用Formatter为后缀的方法实现,而不是converter的方法,其实二者都是同样的功能,只不过在这里,我们需要专门使用formatter的方法。
另外,我们需要保证指定的字符串格式是正确的,否则程序是无法识别出来的。
如果我们希望实现我们自己指定的转换方式,也只需要学习DateFormatter的实现,即实现接口Formatter,完善对应的方法。这里我们再额外实现一下,上面的converter已经实现过字符串转User,主要的实现代码也就是那些。
首先,我们假设在一个Person类中,包含User的属性为user,把上面的代码稍微调整调整类和对象,然后,就需要增加关于user的代码
xxxxxxxxxxpropertyValues.add("user","user 1 ,2021-05-01");为此,我们额外实现一个Formatter类,UserFormatter,增加一个转换器
xxxxxxxxxxdatabinder.addCustomFormatter(new UserFormatter());现在就是实现UserFormatter
xxxxxxxxxxpublic class UserFormatter implements Formatter<User> { /*其中Formatter对应的源码为 public interface Formatter<T> extends Printer<T>, Parser<T> {} 进一步的,我们发现就是需要实现两个方法 `print` 和 `parse` */ public String print(User user, Locale locale) { return (new Gson()).toJson(user);//这里随便实现,就是返回一个String值,到时候 } public User parse(String source, Locale locale) throws ParseException { //就是之前转换的代码 User user=new User(); String[] result=source.split(","); Date date=null; try{ date=(new SimpleDateFormat("yyyy-MM-dd")).parse(result[1]); }catch(ParseException e){} user.setName(result[0]); user.setDate(date); return user;}}WebDataBinder
这里的绑定主要针对浏览器发来的请求参数。
前面我们提及过,可以通过uri传递参数,这里则视图将这些参数由框架直接转化为我们希望的对象等。读者也应该记得前面的参数最下面也提及了,通过指定参数位置,可以直接转化为方法指定的对象。【实际框架的调度器底层就已经利用WebDataBinder】。
而我们只需要使用注解@InitBinder指定即可,在其中加载指定的转换器,将把符合对应格式的参数进行转换。
xxxxxxxxxx("随便指定对象名")public void initBinderAddr(WebDataBinder binder) { binder.setFieldDefaultPrefix("随便什么对象名.");}//这里主要针对的是表单传入参数,在表单中,通过设置对应数据的name,符合指定的前缀,后面跟着的不同的属性名//例如/*<form action="uri" method="post"> <input type="text" name="user.name" > <input type="text" name="user.age" > <input type="submit" value="提交">*///此时,对应的属性将假如到指定方法参数中,如/*@ResponseBody@RequestMapping("/user")public User userinit11(@ModelAttribute("user") User user){ //"user"需要对应的是上面"随便指定对象名" return user;}*///当对象中包含其它类型的属性,我们就可以使用自己的转换器将传入参数的字符串转换为我们需要的类型对象但是上述的表单行为,实际使用很不方便,可以对用户填写某些信息时,对个别数据做转换,但对于项目本身方法之间的通信而言,前后端的交流更多的还是使用json等格式进行通信【比如将对象转化为对应的json进行传递】,此时就需要更高一级的转换器来负责。
在前面我们已经不自觉的使用了,就是@ResponseBody,我们仅仅添加了这个注解,就可以将生成的对象转化为json返回给服务器 。
作为response仅仅是作为后端的响应类型,我们现在更希望从请求request中获取数据。首先介绍框架中对于请求和响应的类:RequestEntity, ResponseEntity,都是HttpEntity的子类。
当配置好json的依赖包,且返回的请求体是一个对象的json格式,则RequestEntity的请求体能够自动转化为对应的对象。下面首先看一下,响应是怎么发送的【注意这不是浏览器的请求,仍然是服务器内部的】。
xxxxxxxxxxResponseEntity<String> responseEntity (RequestEntity<String> requestEntity){ String str="随便写点"; //这里指定请求体是个字符串,可以随便设置成其它对象,当然最好是把自己的对象转化为json格式的字符串 HttpHeaders response=new HttpHeaders(); ResponseEntity<String> responseEntity=new ResponseEntity<>(str,response, HttpStatus.OK);//设置的依此是请求体,请求头,状态码 return responseEntity;}/*至于如何从响应中读取对象,则异常简单,导致没必要多余写上面*/方法(RequestEntity<指定的对象类型> requestEntity){ 对象类型 对象名=requestEntity.getBody();//此时就从请求体中获得对象了}为模拟浏览器的请求以及后端的提取操作,我们设置某一页面触发向另一个地址发送请求,并获得对应的响应:
xxxxxxxxxx/*后端用于返回响应的页面*/public class jsonbodyController { ("/jsonbody") public ResponseEntity<User> jsonBody(RequestEntity<User> request){ User user=request.getBody(); ResponseEntity<User> responseEntity=new ResponseEntity<>(user, new HttpHeaders(), HttpStatus.OK); return responseEntity; }}/*发送请求的页面*/public class httpbodyController { //为方便,我们这里将在页面以json格式显示对象结果 ("/body") public User body(){ String str=null; String url="http://localhost:8080/m/jsonbody"; //m是启动tomcat对应的Appliation context RestTemplate restTemplate=new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); User user=new User("sdsdd00","44545"); String body=(new Gson()).toJson(user); HttpEntity<String> entity = new HttpEntity<String>(body, headers); ResponseEntity<User> user1=restTemplate.exchange(url, HttpMethod.GET, entity, User.class); User user2=user1.getBody(); return user; }}
读者在一些网站中可能会发现,例如购物网站,或论坛,有时我们作为游客查看一些页面没问题,而当我们视图添加商品,或试图评论,或下载某些文件等,由于这些功能被网站设置为需要注册用户才能使用,则我们的页面被强制转到了登录或注册页面。
上述的强制登录手段,则涉及到了网站对请求的拦截技术,所谓的拦截,即在我们已经实现的功能之上,在请求和我们的回复之间插入一个额外的工作。
如果读者是遵循建议先阅读SpringMVC的话,可能不了解面向切面编程(AOP),这种拦截其实就是AOP,对实现的方法在外部进行扩充或判断是否执行。
【这也是我建议先阅读Spring MVC的原因,与其预先了解大量基础的知识,不如先尝试着实现功能,不然程序员从学习计算机开始得先去学习数理逻辑】
拦截器的实现的框架:
xxxxxxxxxxpublic class SessionCheckInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //这里的判断只是示例,不一定需要写,总之,内部的实现完全看情况随便写, //但这里需要注意最后返回一个bool值 //你可以判断任意你需要确认的内容,并做出你任意想做的操作,如转移到其它页面,或直接报异常,都可以 if(request.getSession().getAttribute("username")==null){ response.sendRedirect("/login.jsp");//在这里,可以强制我们转到登录页面 } return true; //返回true后,请求将转移到postHandle,如果返回false,则表示当前的方法不通过,无法继续下面的方法而结束 } //上面完成了真正实现方法前的准备工作 //下述方法,将真正执行对应的方法,并可能对request或response进行处理 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {} //最后,在请求结束后,可能需要完成资源释放的工作 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}拦截的样子大概是下面的样子:

其次。为了告诉框架何时需要拦截,需要在关于调度器的配置文件中覆写对应的方法
xxxxxxxxxx public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SessionCheckInterceptor()). addPathPatterns("/指定uri格式"); //凡是遇到这种uri都会启动拦截 //如果有很多不同的uri格式需要拦截,也可以以List形式放入}到这里基本上拦截器就差不多了,具体的操作就是看情况随便写了。
过滤器本身是servlet自大的功能,不是spring额外实现的功能,自然不是调度器能够管理的,如果需要配置,也需要在之前的MyWebAppInitializer类中配置。
配置过滤器我这里有两种方法,先介绍简单的:
xxxxxxxxxx//声明为Bean,spring就会自动注册(urlPatterns = "/filter/*")//servlet自带的注解,这里一个*就可以了,两个*反而没什么用//当然WebFilter还有其它功能,如果真的有需要,可以看看源码中是否有自己需要的public class SessionFilter extends HttpFilter { protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { res.sendRedirect("完整的uri");//原本请求转化为重定向到其它地址 chain.doFilter(req,res);//不能少,这是让整个请求流程继续的命令,否则无法继续执行了 }}上述就是非常简单有效的方法,如果希望复杂点,可以在MyWebAppInitializer类中进行配置。
xxxxxxxxxxpublic void onStartup(ServletContext servletContext) throws ServletException { String filterName = "myfilter";//自定义一个名字 FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, new SessionFilter()); //此时我们的过滤器的类就不要加那些注解了 filterRegistration.addMappingForUrlPatterns( EnumSet.of(DispatcherType.REQUEST), false, "/filter/*"); //其中isMatchAfter是说明是否在ServletContext声明的说有过滤器完成映射后在进行操作 //简单说就是是否愿意吃剩饭,这里当然是不愿意了,虽然就注册了一个过滤器 super.onStartup(servletContext);//别忘了,继续进行启动}
其实,没必要为Springboot专门列出一章,因为Springboot本来就是建构在spring之上的,基本的使用相同,不同点,基本就在于前期的一些简单配置上。
首先我们创建一个springboot的项目,可以直接使用IDEA中的spring的初始化功能,在选择组件时,选择其中的web即可,以防万一的话,也可以选择其中的thymeleaf引擎组件。
创建完毕后,基本上就完成了一个springboot的mvc项目的创建。
不同于上面spring项目中,对于各种设置都要大动干戈,原本前面的各种属性配置可能需要一个专门的xml文件或java类负责处理,而这里全部都方法一个属性文件中。
在resources目录中,可能IDEA会自动创建一个application.properties文件,这就是一个可以设置属性的文件 ,只是现在我们比较常用的是yaml格式的。因此读者可以手动将那个文件修改一个后缀名,为yml即可。
之所以yml文件比较流行,在于属性会阶梯型地展开,多个属性将共用同样的前缀。不像properties那样,每个属性名都要写完整。
但,文件中对应的属性名都是一样的,这个读者不需要忧虑。
比如,我们要设置对应的端口号,
xxxxxxxxxxserver port端口号
最后,再准备一个启动类,估计IDEA也会帮我们设置好的,
目录中,可能会有一个写好的java类,名字估计和你设置的项目名类似,后面有个Applicaiton的字样。
如果没有,你可以自己随便写个这样的类。【当然,名字具体是什么无关紧要,主要好区分】
类似于下面的代码
xxxxxxxxxxpublic class DemoApplication{public static void main(String[] args){SpringApplicaton.run(DemoApplication.class,args);}}这个类,既可以帮我们启动项目,有充当了一个配置类。
这个类就保持这样就好了,尽量别往里面放其它代码,虽然是个配置类,也别把@Bean的内容往里写,保持代码的干净。
至于,其它的各种控制类的编写,都是与上面的介绍相同的。
阅读完之后,可接着看看Mybatis的使用,毕竟二者经常放在一起使用