目录

一 、页面导航的方式

1. 转发到一个jsp页面

1.1 字符串方式转发

1.2 ModelAndView转发

2. 重定向到一个jsp页面

2.1 字符串方式重定向

2.2 ModelAndView重定向方式

3. 重定向或者转发到控制器

3.1 转发到控制器

3.2 重定向到控制器

二、异常处理

1. @ExceptionHandler 注解

​2. 实现步骤

2.1 自定义异常类

2.2 编写控制器

2.3 编写error.jsp、idError.jsp、nameError.jsp页面

2.4 测试

3.优化

三、拦截器

3.1 介绍

3.2 自定义拦截器

3.3 配置拦截器

四、文件上传与下载

4.1 文件上传

4.2 文件下载

五、RESTful风格

5.1 REST概念

5.2 RESTful概念

5.3 API设计/URL设计 (设计原则)

5.3.1 动词+宾语

5.3.2 宾语必须是名词

5.3.3 避免多级URL

5.4 HTTP状态码

5.4.1 状态码2xx

5.4.2 状态码3xx

5.4.3 状态码4xx

5.4.4 状态码5xx

5.5 服务器响应

5.6 案例

5.6.1 RESTful风格的查询

5.6.2 RESTful风格的添加

5.6.3 RESTful风格的更新

5.6.4 RESTful风格的删除

5.6.5 RESTful风格的更新和删除遇到的问题

5.7 自己封装响应结果

5.7.1 封装的实体类

5.7.2 修改控制器中的返回值

5.7.3 修改前端页面


一 、页面导航的方式

页面导航分为两种:1、转发 2、重定向

springMVC有以下两种方式实现页面的转发或重定向:
1、返回字符串
2、使用ModelAndView

在SpringMVC中两种导航进行页面导航的时候使用不同的前缀指定转发还是重定向
前缀: 转发: forward:url     默认
            重定向: redirect:url

准备工作:创建一个新的控制器NavigationController.java:

package com.lxy.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("navigation")
public class NavigationController {}

1. 转发到一个jsp页面

1.1 字符串方式转发

在NavigationController中添加方法:

    //使用字符串转发@RequestMapping("test01-1")public String test011(HttpServletRequest request){System.out.println("test01-1.do--------");request.setAttribute("teamName","湖人");//return "ok"; //默认方式:由视图解析器处理之后将逻辑视图转换为物理资源路径return "forward:/jsp/ok.jsp";//当添加了forward的前缀后,视图解析器中的前后缀就失效了,必须自己编写绝对路径}

1.2 ModelAndView转发

在NavigationController中添加方法:

    //使用ModelAndView转发@RequestMapping("test01-2")public ModelAndView test012(){ModelAndView mv = new ModelAndView();mv.addObject("teamName","热火");//mv.setViewName("ok");//默认方式:由视图解析器处理之后将逻辑视图转换为物理资源路径mv.setViewName("forward:/jsp/ok.jsp");return mv;}

添加一个前端页面jsp文件夹下ok.jsp页面

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-22 0022Time: 下午 04:07:52To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>ok</title>
</head>
<body><h3>ok---------------------${requestScope.teamName}</h3><h2>从地址栏中获取的参数值: teamName=${param.teamName},teamId=${param.teamId}</h2>
</body>
</html>

2. 重定向到一个jsp页面

2.1 字符串方式重定向

在NavigationController中添加方法:

    //使用字符串重定向@RequestMapping("test02-1")public String test021(HttpServletRequest request){request.setAttribute("teamName","湖人");//页面上无法获取到存储到request作用域中的值,请求中断了return "redirect:/jsp/ok.jsp";//当添加了forward的前缀后,视图解析器中的前后缀就失效了,必须自己编写绝对路径}

由于是重定向方式跳转页面,所以request中的值会丢失。转发到ok.jsp页面时,request中的值不会显示 。

2. 2 ModelAndView重定向方式

在NavigationController中添加方法:

    //使用ModelAndView重定向@RequestMapping("test02-2")public ModelAndView test022(){ModelAndView mv = new ModelAndView();mv.addObject("teamName","huangfeng");mv.addObject("teamId","1002");//存储在request作用域中的值以参数的形式追加在URL后面mv.setViewName("redirect:/jsp/ok.jsp");return mv;}

但是使用ModelAndView来重定向的话,存储在request作用域中的值以参数的形式追加在URL后面 。

3. 重定向或者转发到控制器

3.1 转发到控制器

在NavigationController中添加方法:

    @RequestMapping("test03-1")public ModelAndView test031(){ModelAndView mv = new ModelAndView();System.out.println("test03-1---转发到控制器");mv.addObject("teamName","huangfeng");mv.addObject("teamId","1002");//存储在request作用域中mv.setViewName("forward:/navigation/test01-1");return mv;}

由于转发到test01-1中,test01-1中也设置过request.setAttribute(), 而ModelAndView的addObject()方法相当于request.setAttribute(); 所以request中存储的值被覆盖掉了。

3.2 重定向到控制器

在NavigationController中添加方法:

    @RequestMapping("test03-2")public ModelAndView test032(){ModelAndView mv = new ModelAndView();System.out.println("test03-2---重定向到控制器");mv.addObject("teamName","huangfeng");mv.addObject("teamId","1002");//存储在request作用域中的值以参数的形式追加在URL后面mv.setViewName("redirect:/navigation/test01-1");return mv;}

由于是重定向到test01-1中,不仅地址栏会发生改变,request中存储的数据也会丢失,但是这些存储的数据会以参数的形式追加在URL后面。 

二、异常处理

SpringMVC框架常用@ExceptionHandler 注解处理异常。

1. @ExceptionHandler 注解

@ExceptionHandler 可以将一个方法指定为异常处理方法。

被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。

对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中.

 2. 实现步骤

2.1 自定义异常类

目录结果为:

TeamException.java:

public class TeamException extends  Exception{public TeamException() {}public TeamException(String message) {super(message);}
}

TeamIdException.java:

public class TeamIdException extends  TeamException{public TeamIdException() {}public TeamIdException(String message) {super(message);}
}

TeamNameException.java:

public class TeamNameException extends  TeamException{public TeamNameException() {}public TeamNameException(String message) {super(message);}
}

2.2 编写控制器

编写异常控制器ExController:

package com.lxy.controller;import com.lxy.exception.TeamIdException;
import com.lxy.exception.TeamNameException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
@RequestMapping("ex")
public class ExController {@RequestMapping("test01/{id}/{name}")public ModelAndView test01(@PathVariable("id") Integer teamId,@PathVariable("name") String teamName) throws TeamIdException, TeamNameException {ModelAndView mv = new ModelAndView();if(teamId<=1000){throw new TeamIdException("teamId不合法!必须在1000之上");}if("test".equals(teamName)){throw new TeamNameException("teamName不合法!不能用test");}System.out.println(10/0);  //这里是其他的异常mv.setViewName("ok");return mv;}// 包含所有能处理的异常类型@ExceptionHandler(value = {TeamIdException.class,TeamNameException.class,Exception.class})public ModelAndView exHandler(Exception ex){ModelAndView mv=new ModelAndView();mv.addObject("msg",ex.getMessage());if(ex instanceof TeamIdException) {mv.setViewName("idError");}else if (ex instanceof TeamNameException){mv.setViewName("nameError");}else {mv.setViewName("error");}return mv;}
}

2.3 编写error.jsp、idError.jsp、nameError.jsp页面

error.jsp:

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 05:04:44To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>error</title>
</head>
<body>
<h2>默认错误页面 ----error---${msg}</h2>
</body>
</html>

idError.jsp:

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 05:05:02To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>idError</title>
</head>
<body>
<h2>id错误页面-----idError---${msg}</h2>
</body>
</html>

nameError.jsp:

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 05:05:17To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>nameError</title>
</head>
<body>
<h2>name错误页面----nameError---${msg}</h2>
</body>
</html>

2.4 测试

http://localhost:8080/ex/test01/1001/tes

分别改变传的id值和name值 看看到底异常类能不能实现

因为上面放开了10/0的代码 所以会报错

3.优化

一般将异常处理方法专门定义在一个类中,作为全局的异常处理类。

使用注解@ControllerAdvice,就是“控制器增强”,是给控制器对象增强功能的。使用
@ControllerAdvice 修饰的类中可以使用@ExceptionHandler。

当使用@RequestMapping 注解修饰的方法抛出异常时,会执行@ControllerAdvice 修饰的类中的异常处理方法。

@ControllerAdvice 注解所在的类需要进行包扫描,否则无法创建对象。

在springmvc.xml中添加包扫描:

<context:component-scan base-package="com.lxy.exception"/>

 在exception包中添加GlobalExceptionHandler类 专门处理异常类:

把之前加了@ExceptionHandler的方法删除掉。

package com.lxy.exception;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = TeamIdException.class)public ModelAndView exHandler1(Exception ex){ModelAndView mv = new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("idError");return mv;}@ExceptionHandler(value = TeamNameException.class)public ModelAndView exHandler2(Exception ex){ModelAndView mv = new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("nameError");return mv;}@ExceptionHandler(value = Exception.class)public ModelAndView exHandler3(Exception ex){ModelAndView mv = new ModelAndView();mv.addObject("msg",ex.getMessage());mv.setViewName("error");return mv;}
}

添加完上述后来输入地址测试一下:

三、拦截器

3.1 介绍

SpringMVC 中的 拦截器( Interceptor)是非常重要的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。

拦截的时间点在“处理器映射器HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器HandlerAdaptor执行处理器之前”。在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链HandlerExecutionChain,并返回给了前端控制器。

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

上图解释:

preHandle(request,response, Object handler):
该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。


postHandle(request,response, Object handler,modelAndView):
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。


afterCompletion(request,response, Object handler, Exception ex):
当 preHandle()方法返回true时,会将该方法放到专门的方法栈中,等到对请求进行响应的所工作完成之后才执行该方法。即该方法是在前端控制器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。
afterCompletion最后执行的方法,清除资源,例如在Controller方法中加入数据

3.2 自定义拦截器

创建一个拦截器包interceptor,添加一个拦截类MyInterceptor

package com.lxy.interceptor;import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {//执行时间: 控制器方法执行之前 在ModeAndView返回之前//使用场景: 登录验证//返回值: true表示放行 false表示拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle----");return true;}//执行时间: 控制器方法执行之后 在ModeAndView返回之前,由机会修改返回值//使用场景: 日记记录,记录登录的ip和时间@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle---------");}//执行时间: 控制器方法执行之后 在ModeAndView返回之后,没有机会修改返回值//使用场景: 全局资源的一些操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion------------");}
}

再添加一个拦截器MyInterceptor2:

package com.lxy.interceptor;import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class MyInterceptor2 implements HandlerInterceptor {//执行时间: 控制器方法执行之前 在ModeAndView返回之前//使用场景: 登录验证//返回值: true表示放行 false表示拦截@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle2----");return true;}//执行时间: 控制器方法执行之后 在ModeAndView返回之前,由机会修改返回值//使用场景: 日记记录,记录登录的ip和时间@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle2---------");}//执行时间: 控制器方法执行之后 在ModeAndView返回之后,没有机会修改返回值//使用场景: 全局资源的一些操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion2------------");}
}

3.3 配置拦截器

在springmvc.xml中添加如下配置:

    <!--配置拦截器--><mvc:interceptors><!--按顺序配置多个拦截器--><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.lxy.interceptor.MyInterceptor" id="myInterceptor"></bean></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.lxy.interceptor.MyInterceptor2" id="myInterceptor2"></bean></mvc:interceptor></mvc:interceptors>

随便访问个简单的控制器http://localhost:8080/param/hello.do来测试一下:

可以看出拦截器类似于栈来输出结果的,先进的后出,后进的执行完才能弹栈。

四、文件上传与下载

Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现.

Spring中有一个MultipartResolver的实现类:CommonsMultipartResolver

在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver。

4.1 文件上传

(1)添加依赖:

        <!--文件上传--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version></dependency>

(2) springmvc.xml文件中配置MultipartResolver:

    <!--文件上传--><bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!--文件上传大小的限制 以字节B为单位 设置5M--><property name="maxUploadSize" value="5242880"/><property name="defaultEncoding" value="utf-8"/></bean>

(3)添加一个前端页面fileHandle.jsp页面表单

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 06:25:40To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>文件操作</title>
</head>
<body><form action="/file/upload" method="post" enctype="multipart/form-data">请选择文件:<input type="file" name="myFile"/><br/><button type="submit">上传文件</button></form>
</body>
</html>

(4)创建一个文件夹用来保存和接收上传的文件

(5)在controller中添加上传的java代码

package com.lxy.controller;import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.UUID;/*** ClassName:FileController* 文件的控制器*/
@Controller
@RequestMapping("file")
public class FileController {@RequestMapping("upload")public String upload(@RequestParam("myFile") MultipartFile myFile, HttpServletRequest request){//获取文件的原始名称String originalFilename = myFile.getOriginalFilename();//实际开发中,一般都要将文件重新命名进行存储//存储到服务器的文件名称=随机的字符串+根据实际名称获取到源文件的后缀String fileName= UUID.randomUUID().toString().replace("-","")+originalFilename.substring(originalFilename.lastIndexOf("."));System.out.println(fileName);//文件存储路径String realPath = request.getServletContext().getRealPath("/uploadFile")+"/";try {myFile.transferTo(new File(realPath+fileName));//真正的文件上传到服务器指定的位置System.out.println("文件上传成功!"+realPath+fileName);}catch (IOException e){e.printStackTrace();}return "ok";}@RequestMapping("hello")public String hello(){return "fileHandle";}
}

测试一下:

(6)对上传的文件进行一些优化

如果你需要限定文件的大小和类型,那么就需要写一个拦截器在controller方法执行前判断一下大小和类型,上面有拦截器的配置方法,需要在springmvc中注册拦截器,还要在

multipartResolver中添加一些文件上传的大小限制。

 springmvc.xml中注册拦截器:

    <!--配置拦截器--><mvc:interceptors><!--按顺序配置多个拦截器--><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.lxy.interceptor.MyInterceptor" id="myInterceptor"></bean></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.lxy.interceptor.MyInterceptor2" id="myInterceptor2"></bean></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.lxy.interceptor.FileInterceptor" id="fileInterceptor"></bean></mvc:interceptor></mvc:interceptors>
    <!--文件上传--><bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!--文件上传大小的限制 以字节B为单位 设置5M--><property name="maxUploadSize" value="5242880"/><property name="defaultEncoding" value="utf-8"/></bean>

在interceptor包中添加FileInterceptor类:

package com.lxy.interceptor;import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Iterator;
import java.util.Map;/*** ClassName:FileInterceptor* 文件后缀处理的拦截器*/
public class FileInterceptor implements HandlerInterceptor {/*** 在文件上传之前判断文件后缀是否合法* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断是否是文件上传的请求boolean flag = true;if(request instanceof MultipartHttpServletRequest){MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();//遍历文件Iterator<String> iterator = fileMap.keySet().iterator();while (iterator.hasNext()){String key = iterator.next();MultipartFile file = multipartRequest.getFile(key);String originalFilename = file.getOriginalFilename();String hz = originalFilename.substring(originalFilename.lastIndexOf("."));//判断后缀是否合法if(!hz.toLowerCase().equals(".png") && !hz.toLowerCase().equals(".jpg")){flag=false;request.getRequestDispatcher("/jsp/fileTypeError.jsp").forward(request,response);}}}return flag;}
}

根据上面代码可看出如果上传的文件后缀名不对会跳转到一个页面,这个页面是文件类型错误时才能跳转的一个页面fileTypeError.jsp。

编写fileTypeError.jsp:

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 07:11:11To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>typeError</title>
</head>
<body><h1>文件上传的类型有误!后缀必须是.png或者是.jpg</h1>
</body>
</html>

4.2 文件下载

(1)修改下前端页面:

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 06:25:40To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>文件操作</title>
</head>
<body><form action="/file/upload" method="post" enctype="multipart/form-data">请选择文件:<input type="file" name="myFile"/><br/><button type="submit">上传文件</button></form><form action="/file/download" method="post" enctype="multipart/form-data"><button type="submit">下载图片</button></form>
</body>
</html>

(2)修改 FileController类:

package com.lxy.controller;import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.UUID;/*** ClassName:FileController* 文件的控制器*/
@Controller
@RequestMapping("file")
public class FileController {@RequestMapping("download")public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {//指定文件的路径String path=request.getServletContext().getRealPath("/uploadFile")+"/652385df38204aad950c20567238ff89.jpg";//创建响应 的头信息的对象HttpHeaders headers = new HttpHeaders();//标记以流的方式作出响应headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);//以附件的形式响应给用户headers.setContentDispositionFormData("attachment", URLEncoder.encode("652385df38204aad950c20567238ff89.jpg","utf-8"));File file = new File(path);ResponseEntity<byte[]> resp= new ResponseEntity<>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);return resp;}@RequestMapping("upload")public String upload(@RequestParam("myFile") MultipartFile myFile, HttpServletRequest request){//获取文件的原始名称String originalFilename = myFile.getOriginalFilename();//实际开发中,一般都要将文件重新命名进行存储//存储到服务器的文件名称=随机的字符串+根据实际名称获取到源文件的后缀String fileName= UUID.randomUUID().toString().replace("-","")+originalFilename.substring(originalFilename.lastIndexOf("."));System.out.println(fileName);//文件存储路径String realPath = request.getServletContext().getRealPath("/uploadFile")+"/";try {myFile.transferTo(new File(realPath+fileName));//真正的文件上传到服务器指定的位置System.out.println("文件上传成功!"+realPath+fileName);}catch (IOException e){e.printStackTrace();}return "ok";}@RequestMapping("hello")public String hello(){return "fileHandle";}
}

五、RESTful风格

5.1 REST概念

REST(英文:Representational State Transfer,简称REST,意思:表述性状态转换,描述了一个架构样式的网络系统,比如web应用)。

它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务端交互类的软件。基于这个风格设计的软件可以更简介,更有层次,更易于实现缓存等机制。

它本身并没有什么使用性,其核心价值在于如何设计出符合REST风格的网络接口。

5.2 RESTful概念

REST指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就RESTful。

RESTful的特性:

资源(Resources):互联网所有的事物都可以被抽象为资源 。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。

表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。

状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,所以就是“表现层状态转换”。

具体来说就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

原来操作资源的方式是写全称:

http://localhost:8080/getExpress.do?id=1 
http://localhost:8080/saveExpress.do 
http://localhost:8080/updateExpress.do 
http://localhost:8080/deleteExpress.do?id=1

原来用的方式当然没有问题,但是如果有更简洁的方式就更好了,此时就是RESTful风格。

使用RESTful操作资源:
GET /expresses #查询所有的快递信息列表
GET /express/1006 #查询一个快递信息
POST /express #新建一个快递信息
PUT /express/1006 #更新一个快递信息(全部更新)
PATCH /express/1006 #更新一个快递信息(部分更新)
DELETE /express/1006 #删除一个快递信息

5.3 API设计/URL设计 (设计原则)

5.3.1 动词+宾语

RESTful 的核心思想就是客户端的用户发出的数据操作指令都是"动词 + 宾语"的结构。例如GET
/expresses 这个命令,GET是动词,/expresses 是宾语。

动词通常就是五种 HTTP 方法,对应 CRUD 操作。
        GET:读取(Read)
        POST:新建(Create)
        PUT:更新(Update)
        PATCH:更新(Update),通常是部分更新
        DELETE:删除(Delete)

PS: 1、根据 HTTP 规范,动词一律大写。
2、一些代理只支持POST和GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖http原来的方法。使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.(重点 后面会解释)

5.3.2 宾语必须是名词

宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。
比如,/expresses 这个 URL 就是正确的。

以下这些URL都是不推荐的,因为带上了动词,不是推荐写法。
/getAllExpresses
/getExpress
/createExpress
/deleteAllExpress

5.3.3 避免多级URL

例如:

如果资源中有多级分类,也不建议写出多级的URL。例如要获取球队中某个队员,有人可能这么写:
GET /team/1001/player/1005
这种写法的语义不够明确,所以推荐使用查询字符串做后缀,改写为
GET /team/1001?player=1005.

再例如查询所有的还未取出的快递,你该如何编写呢?
GET /expresses/statu 不推荐
GET /expresses?statu=false 推荐

5.4 HTTP状态码

客户端的用户发起的每一次请求,服务器都必须给出响应。响应包括 HTTP 状态码和数据两部分。
HTTP 状态码就是一个三位数,分成五个类别。这五大类总包含了100多种状态码(不需要全都记住,不用紧张哈,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。

五类状态码分别如下:
        1xx:相关信息
        2xx:操作成功
        3xx:重定向
        4xx:客户端错误
        5xx:服务器错误
PS:API 不需要1xx状态码,所以这个类别直接忽略。

5.4.1 状态码2xx

200 状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

GET: 200 OK 表示一切正常
POST: 201 Created 表示新的资源已经成功创建
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content 表示资源已经成功删除

5.4.2 状态码3xx

API 用不到 301 状态码(永久重定向)和 302 状态码(暂时重定向, 307 也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况

API 用到的 3xx 状态码,主要是 303 See Other ,表示参考另一个 URL。它与 302 和 307 的含义一样,也是"暂时重定向",区别在于 302 和 307 用于 GET 请求,而 303 用于 POST 、 PUT 和 DELETE 请求。收到 303 以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办

我们只需要关注一下304状态码就可以了
304 : Not Modified 客户端使用缓存数据 

5.4.3 状态码4xx

4xx 状态码表示客户端错误。

400 Bad Request:服务器不理解客户端的请求,未做任何处理。
401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。
403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。
404 Not Found:所请求的资源不存在,或不可用。
405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内。
410 Gone:所请求的资源已从这个地址转移,不再可用。
415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是客户端要求返回 XML 格式。
422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。
429 Too Many Requests:客户端的请求次数超过限额。

5.4.4 状态码5xx

5xx 状态码表示服务端错误。一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。

500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。
503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

5.5 服务器响应

服务器返回的信息一般不推荐纯文本,而是建议大家选择JSON 对象,因为这样才能返回标准的结构化数据。
所以,服务器回应的 HTTP 头的 Content-Type 属性要设为 application/json 。客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的 ACCEPT 属性也要设成
application/json 。

当发生错误的时候,除了返回状态码之外,也要返回错误信息。所以我们可以自己封装要返回的信息。

5.6 案例

5.6.1 RESTful风格的查询

(1)添加一个前端页面restful.jsp

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 09:52:22To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>restful</title>
</head>
<script src="/js/jquery-1.11.1.js"></script>
<body>
<form id="myForm" action="" method="post">球队ID:<input type="text" name="teamId" id="teamId" /><br/>球队名称:<input type="text" name="teamName" /><br/>球队位置:<input type="text" name="teamLocation" /><br/><button type="button" id="btnGetAll">查询所有GET</button><button type="button" id="btnGetOne">查询单个GET</button><button type="button" id="btnPost">添加POST</button><button type="button" id="btnPut">更新PUT</button><button type="button" id="btnDel">删除DELETE</button>
</form>
<p id="showResult"></p>
</body>
</html>
<script>//页面加载完毕之后给按钮绑定事件$(function () {//给 查询所有GET 按钮绑定单击事件$("#btnGetAll").click(function () {//发起异步请求$.ajax({type: "GET",url: "/teams", //RESTful风格的API定义data: "",dataType:"json",success: function(list) {var str = "";for (var i = 0; i < list.length; i++) {var obj = list[i];str += obj.teamId + "----" + obj.teamName + "----" + obj.teamLocation+ "<br/>";}$("#showResult").html(str);}});});//给 查询单个GET 按钮绑定单击事件$("#btnGetOne").click(function () {//发起异步请求$.ajax({type: "GET",url: "/team/"+$("#teamId").val(), //RESTful风格的API定义data: "",dataType:"json",success: function(obj){if(obj==""){$("#showResult").html("没有复合条件的数据!");}else {$("#showResult").html(obj.teamId + "----" + obj.teamName + "----" + obj.teamLocation + "<br/>");}}});});});
</script>

(2)编写ResultfulController控制器

package com.lxy.controller;import com.lxy.pojo.Team;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.ArrayList;
import java.util.List;@Controller
public class RestfulController {private static List<Team> teamList;static {teamList=new ArrayList<>(3);for(int i=1;i<=3;i++){Team team=new Team();team.setTeamId(1000+i);team.setTeamName("湖人"+i);team.setTeamLocation("洛杉矶"+i);teamList.add(team);}}/*** 查询所有的球队* @return*/@RequestMapping(value = "teams",method = RequestMethod.GET)@ResponseBodypublic List<Team> getAll(){System.out.println("查询所有GET--发起的请求---------");return teamList;}/*** 根据id 查询单个的球队* @return*/@RequestMapping(value = "team/{id}",method = RequestMethod.GET)@ResponseBodypublic Team getOne(@PathVariable("id")int id){System.out.println("查询单个GET--发起的请求---------");for (Team team : teamList) {if(team.getTeamId()==id){return team;}}return null;}// 发送hello请求时可以跳转到restful.jsp页面@RequestMapping("hello")public String hello(){return "restful";}
}

5.6.2 RESTful风格的添加

在result.jsp页面中添加btnPOST的绑定事件:

        //给 添加POST 按钮绑定单击事件$("#btnPost").click(function () {alert($("#myForm").serialize());//发起异步请求$.ajax({type: "POST",url: "/team", //RESTful风格的API定义data: $("#myForm").serialize(),//表单的所有数据以?&形式追加在URL后面 /team?teamId=1006&teamName=kuaichuan&location=lasdataType:"json",success: function(msg){//alert( "Data Saved: " + msg ); $("#showResult").html(msg);}});});

在控制器ResultfulController中添加对应的方法:

/**
* 添加一个球队
*/
@RequestMapping(value = "team",method = RequestMethod.POST)
@ResponseBody
public String add(Team team){System.out.println("添加POST--发起的请求---------");teamList.add(team);return "201";
}

5.6.3 RESTful风格的更新

在result.jsp页面中添加btnPUT的绑定事件:

        $("#btnPut").click(function(){alert($("#myForm").serialize());$.ajax({type: "POST",url: "/team/"+$("#teamId").val(),data: $("#myForm").serialize()+"&_method=PUT",dataType:"json",//headers:{"X-HTTP-Method-Override":"GET"},success: function(msg){$("#showResult").html(msg);}});});

在这里可以看出type使用的是POST,但是为什么不像上面一样使用他们自己的PUT呢?

答:

因为:在Ajax中,采用Restful风格PUT和DELETE请求传递参数无效,传递到后台的参数值为null。

为什么传递到后台参数为null呢?

答:

Tomcat封装请求参数的过程:
1.将请求体中的数据,封装成一个map
2.request.getParameter(key)会从这个map中取值
3.SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进request.getParamter();
AJAX发送PU或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。
Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。

解决方案:

在下面讲解!

在控制器ResultfulController中添加对应的方法:

    @RequestMapping(value = "/team/{id}",method = RequestMethod.PUT)@ResponseBodypublic String update(@PathVariable("id") int id,Team newTeam){System.out.println(id);for (Team team : teamList) {if(team.getTeamId()==id){team.setTeamName(newTeam.getTeamName());team.setTeamLocation(newTeam.getTeamLocation());return "204";}}return "404";}

5.6.4 RESTful风格的删除

在result.jsp页面中添加btnDEL的绑定事件:

        //给 删除DELETE 按钮绑定单击事件$("#btnDel").click(function () {alert($("#myForm").serialize());//发起异步请求$.ajax({type: "POST",url: "/team/"+$("#teamId").val(), //RESTful风格的API定义data: "_method=DELETE",success: function(msg){//alert( "Data Saved: " + msg );$("#showResult").html(msg);}});});

在控制器ResultfulController中添加对应的方法:

    /*** 根据ID删除一个球队*/@RequestMapping(value = "team/{id}",method = RequestMethod.DELETE)@ResponseBodypublic String del(@PathVariable("id")int id){System.out.println("删除DELETE--发起的请求---------");for (Team team1 : teamList) {if(team1.getTeamId()==id){teamList.remove(team1);return "204";}}return "500";}

5.6.5 RESTful风格的更新和删除遇到的问题

(1)遇到的问题:

正常写的情况下type里写PUT或DELETE

在Ajax中,采用Restful风格PUT和DELETE请求传递参数无效,传递到后台的参数值为null

(2)产生的原因:

Tomcat封装请求参数的过程:

1.将请求体中的数据,封装成一个map

2.request.getParameter(key)会从这个map中取值

3.SpringMvc封装POJO对象的时候,会把POJO中每个属性的值进行request.getParamter();
AJAX发送PU或者DELETE请求时,请求体中的数据通过request.getParamter()拿不到。
Tomcat一检测到是PUT或者DELETE就不会封装请求体中的数据为map,只有POST形式的请求才封装请求为map。

(3)解决方案:

1.前端页面中的ajax发送请求的时候在url中加 &_method=”PUT” 或者 &_method=”DELETE” 即可

 我们之前知道,在data中的所有数据会追加在url后面,并且每条数据之间以&符号连接

 以上可知&_method=PUT一定也会追加在url后面,

2. web.xml中配置过滤器
配置的时候多个过滤器需要注意顺序

    <!-- 使用Rest风格的URI 将页面普通的post请求转为指定的delete或者put请求原理:在Aajx中发送post请求后,带_method参数,将其修改为PUT,或者DELETE请求--><filter><filter-name>httpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class></filter><filter-mapping><filter-name>httpMethodFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>

在这里我们观察HiddenHttpMethodFilter类:

测试结果:

5.7 自己封装响应结果

5.7.1 封装的实体类

在vo包下添加一个AjaxResultVO<T>类:

package com.lxy.vo;import java.util.List;/*** 自己封装的返回的结果类*/
public class AjaxResultVO<T> {private Integer code;//返回的状态码private String msg;//返回的信息 (一般错误的信息或者异常的信息)private List<T> list;//返回的数据由可能是集合private T obj;//返回的数据有可能是对象public AjaxResultVO() {code=200;msg="ok";list=null;obj=null;}public AjaxResultVO(Integer code, String msg) {this.code = code;this.msg = msg;}public AjaxResultVO(Integer code, String msg, List<T> list) {this.code = code;this.msg = msg;this.list = list;}public AjaxResultVO(Integer code, String msg, T obj) {this.code = code;this.msg = msg;this.obj = obj;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public List<T> getList() {return list;}public void setList(List<T> list) {this.list = list;}public T getObj() {return obj;}public void setObj(T obj) {this.obj = obj;}
}

5.7.2 修改控制器中的返回值

package com.lxy.controller;import com.lxy.pojo.Team;
import com.lxy.vo.AjaxResultVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.ArrayList;
import java.util.List;@Controller
public class RestfulController {private static List<Team> teamList;static {teamList=new ArrayList<>(3);for(int i=1;i<=3;i++){Team team = new Team();team.setTeamId(1000+i);team.setTeamName("湖人"+i);team.setTeamLocation("洛杉矶"+i);teamList.add(team);}}@RequestMapping(value = "team/{id}",method = RequestMethod.DELETE)@ResponseBodypublic AjaxResultVO<Team> del(@PathVariable("id") int id){System.out.println("删除DELETE----发起的请求--");for (Team team1 : teamList) {if(team1.getTeamId()==id){teamList.remove(team1);return new AjaxResultVO<>();}}return new AjaxResultVO<>(500,"要删除的id不存在~~");}@RequestMapping(value = "team/{id}",method = RequestMethod.PUT)@ResponseBodypublic AjaxResultVO<Team> update(@PathVariable("id") int id,Team team){System.out.println("更新PUT----发起的请求--");for (Team team1 : teamList) {if(team1.getTeamId()==id){team1.setTeamLocation(team.getTeamLocation());team1.setTeamName(team.getTeamName());return new AjaxResultVO<>();}}return new AjaxResultVO<>(500,"要更新的id不存在~~");}@RequestMapping(value = "team",method = RequestMethod.POST)@ResponseBodypublic AjaxResultVO<Team> add(Team team){System.out.println("添加POST----发起的请求--");teamList.add(team);return new AjaxResultVO<>(200,"ok");}@RequestMapping(value = "team/{id}",method = RequestMethod.GET)@ResponseBodypublic AjaxResultVO<Team> getOne(@PathVariable("id") int id){System.out.println("查询单个GET----发起的请求--");for (Team team : teamList) {if(team.getTeamId()==id){return new AjaxResultVO<>(200,"ok",team);}}return null;}@RequestMapping(value = "teams",method = RequestMethod.GET)@ResponseBodypublic AjaxResultVO<Team> getAll(){System.out.println("查询所有GET----发起的请求--");return new AjaxResultVO<>(200,"ok",teamList);}@RequestMapping("hello")public String hello(){return "restful";}
}

5.7.3 修改前端页面

<%--Created by IntelliJ IDEA.User: AdministratorDate: 2022-04-23 0023Time: 下午 09:52:22To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>restful</title>
</head>
<script src="/js/jquery-1.11.1.js"></script>
<body>
<form id="myForm" action="" method="post">球队ID:<input type="text" name="teamId" id="teamId" /><br/>球队名称:<input type="text" name="teamName" /><br/>球队位置:<input type="text" name="teamLocation" /><br/><button type="button" id="btnGetAll">查询所有GET</button><button type="button" id="btnGetOne">查询单个GET</button><button type="button" id="btnPost">添加POST</button><button type="button" id="btnPut">更新PUT</button><button type="button" id="btnDel">删除DELETE</button>
</form>
<p id="showResult"></p>
</body>
</html>
<script>$(function (){$("#btnDel").click(function (){alert($("#myForm").serialize());$.ajax({type:"POST",url:"/team/"+$("#teamId").val(),data:"_method=DELETE",//表单的所有数据以? &形式追加在URL后面dataType:"json",success:function (vo){if(vo.code==200){$("#showResult").html("删除成功!");}else {$("#showResult").html(vo.msg);}}});});$("#btnPut").click(function (){alert($("#myForm").serialize());$.ajax({type:"POST",url:"/team/"+$("#teamId").val(),data:$("#myForm").serialize()+"&_method=PUT",//表单的所有数据以? &形式追加在URL后面dataType:"json",success:function (vo){if(vo.code==200){$("#showResult").html("更新成功!");}else {$("#showResult").html(vo.msg);}}});});$("#btnPost").click(function (){alert($("#myForm").serialize());$.ajax({type:"POST",url:"/team",data:$("#myForm").serialize(),//表单的所有数据以? &形式追加在URL后面dataType:"json",success:function (vo){if(vo.code==200){$("#showResult").html("添加成功!");}else {$("#showResult").html(vo.msg);}}});});//给 查询所有GET 按钮绑定单机事件$("#btnGetAll").click(function (){$.ajax({type:"GET",url:"/teams",data:"",dataType:"json",success:function (vo){if(vo.code==200){var list = vo.list;var str="";for(var i=0;i<list.length;i++){var obj=list[i];str+=obj.teamId+"---"+obj.teamName+"---"+obj.teamLocation+"<br/>";}$("#showResult").html(str);}else {$("#showResult").html(vo.msg);}}});});//给 查询单个GET 按钮绑定单机事件$("#btnGetOne").click(function (){$.ajax({type:"GET",url:"/team/"+$("#teamId").val(),data:"",dataType:"json",success:function (vo){if(vo.code==200){var obj = vo.obj;if(obj==""){$("#showResult").html("没有复合条件的数据!");}else{$("#showResult").html(obj.teamId+"---"+obj.teamName+"---"+obj.teamLocation+"<br/>");}}else {$("#showResult").html(vo.msg);}}});});});
</script>

如果您还有什么疑问或解答有问题,可在下方评论,我会及时回复。 

如果您看完后对您有帮助的话,请给博主点个赞支持一下吧

SSM_SpringMVC(下)相关推荐

  1. 数据库(DataBase)

    MySQL 简介 数据库 数据库:DataBase,简称 DB,存储和管理数据的仓库 数据库的优势: 可以持久化存储数据 方便存储和管理数据 使用了统一的方式操作数据库 SQL 数据库.数据表.数据的 ...

  2. golang通过RSA算法生成token,go从配置文件中注入密钥文件,go从文件中读取密钥文件,go RSA算法下token生成与解析;go java token共用

    RSA算法 token生成与解析 本文演示两种方式,一种是把密钥文件放在配置文件中,一种是把密钥文件本身放入项目或者容器中. 下面两种的区别在于私钥公钥的初始化, init方法,需要哪种取哪种. 通过 ...

  3. Java 获取当前时间之后的第一个周几,java获取当前日期的下一个周几

    Java 获取当前时间之后的第一个周几,java获取当前日期的下一个周几 //获得入参的日期 Calendar cd = Calendar.getInstance(); cd.setTime(date ...

  4. 过滤Linux下不同大小的文件,linux查找当前目录下 M/G 大小的文件,删除Linux下指定大小的文件

    过滤Linux下不同大小的文件,linux查找当前目录下 M/G 大小的文件,删除Linux下指定大小的文件 find ./ -type f -size +1G| xargs rm 在清理系统日志文件 ...

  5. linux环境下nacos的安装+启动,阿里云服务器安装nacos

    nacos安装+启动(linux环境): 基础:安装java环境 官网下载压缩包:如 nacos-server-1.2.1.tar.gz 放在自定义目录下 # 解压 tar -xvf nacos-se ...

  6. Linux下创建硬链接,文件访问为空,提示:xxxx: 符号连接的层数过多

    Linux下创建软链接|硬链接,文件访问为空,提示:x x x: 符号连接的层数过多. 原因:创建符号链接的时候未使用绝对路径,无论是源文件路径还是目标路径,都需要使用绝对路径. 如: ln -s / ...

  7. 设置select下拉框不可修改的→“四”←种方法

    设置select下拉框为不可修改的几种方法: 因为select的特殊性,导致它不能像input表单一样简单地设置一个readonly来限制修改,所以,我们需要进行别的操作! 1.为下拉框添加样式,可以 ...

  8. Linux/docker下oracle开启监听,开启自动启动

    写在前头: 之前呢,使用docker安装了oracle,但它默认是会关闭的.使用了几天以后突然连接异常了,报的问题是oracle监听有问题了,我知道了是oracle服务自动关闭了,监听也跟着关了.所以 ...

  9. Linux下docker安装配置oracle,oracle创建用户并远程连接,实测可用!

    最近在给同学弄毕业设计的数据库,因为oracle在个人电脑上极不稳定,所以他的电脑数据库崩溃了,这时候我就在docker上为他拉了一个oracle,解决了问题. docker的安装共有以下几步,实测没 ...

最新文章

  1. java 关键字final static
  2. Intellij idea 14 创建简单的Web项目
  3. linux软件可以在所有发行版运行吗,Linux通用的跨发行版的3大软件包管理器
  4. Go Web编程--应用数据库
  5. 机器学习之方差与偏差(bias-variance)
  6. 《SOA In the real world》第一章译稿(含下载)
  7. 色彩专题——蓝色(blue)_8号招牌档
  8. xcode 左边导航栏中,类文件后面的标记“A”,M,?……等符号的含义???(转)...
  9. MATLAB程序(入门)
  10. 【Qt学习之路】我的Qt历程
  11. Python基础总结
  12. 小米平板4刷 linux,小米平板4_刷开发版_获取root
  13. 程序猿头头(async与await的原理)
  14. python 自己选择excel保存的位置
  15. mysql求和语句月份,sql – 按月求和并将月份作为列
  16. whistle-安卓手机配置代理
  17. Carrey的第一篇博客
  18. 第九章 更自由,更开放,大数据的机遇和挑战
  19. 解决PPT不能插入页码问题
  20. Windows下使用SSD检测

热门文章

  1. delphi 侧边栏_Delphi10.3的ListView学习(大图标)
  2. 手机测试充电宝软件,记者随机测试5款产品 “有共享充电宝半小时只充了11%”...
  3. umi Mock数据
  4. Linux学习笔记——vim显示行号(临时/永久)
  5. Gbase8s数据库创建复合索引
  6. 《前端小课》· 前端图书的一次“革命”
  7. win7 mysql 安全_巧妙解决Win7忘记Mysql密码的问题
  8. jquery validation engine ajax验证,jquery.validationEngine 验证 AJAX 不起作用解决方法
  9. 手把手教你修改毕业论文中页眉页脚页码
  10. 论文阅读: 【landmark】MakeItTalk: Speaker-Aware Talking-Head Animation