springmvc全称是spring web mvc,是spring框架一部分,是一个mvc的框架,和struts2一样是一个表现层框架。
原理简写:DispatcherServlet-->映射器-->适配器-->视图解析器-->页面
3.2:编写配置文件
在web.xml中配置如下内容:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> </web-app>
其中servlet是用来拦截请求,交给spring管理;init-param来加载配置文件;url-pattern常用*.do或*.action
在src目录下或者自己创建一个config资源目录下创建springmvc.xml文件,引入如下约束信息:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
创建一个springmvc的handler类,来处理请求,相当于servlet。
package com.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class SpringMvc implements Controller{ @Override public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("index.jsp");//设定视图,相对的是web return modelAndView; } }
在springmvc.xml中配置:
<!-- 把SpringMvc类交给springmvc管理 --> <bean id="springmvc" class="com.controller.SpringMvc"></bean> <!-- 配置适配器 --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <!-- 配置处理器映射器 --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!--key表示的是访问的路径--> <prop key="/springmvc.action">springmvc</prop> </props> </property> </bean>
使用注解就不需要说明那么麻烦,也是在springmvc.xml中配置。
注解的处理器映射器:
spring3.1版本之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
3.1版本之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
注解的处理器适配器:
spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
<!-- 注解处理器映射器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <!-- 注解处理器适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
以上代码可以简写为两行:
<!--配置注解处理器映射器和适配器--> <mvc:annotation-driven></mvc:annotation-driven>
注意,还要进行包扫描:
<!--包扫描--> <context:component-scan base-package="com.controller"/>
然后新建一个handler类:
package com.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class SpringMvc2{ //配置注解映射,一定要加/ @RequestMapping("/show.action") public ModelAndView show(){ ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("index2.jsp"); return modelAndView; } }
新建一个index.jsp,在里面写入内容,然后去访问springmvc.action,效果如下:
两种方式配置的效果是一样的,常用注解配置。
一个类中可以有多个映射,可以有多个方法。
ModelAndView主要是为了跳转页面,因此这个方法也可以简写。
1.直接返回一个路径字符串
@RequestMapping("/show2.action") public String show2(){ return "index2.jsp"; }
2.访问内部私有的资源,外部不能访问
//使用程序访问私有的资源 @RequestMapping("/show3.action") public String show3(){ return "WEB-INF/index3.jsp"; }
3.在springmvc.xml中配置前缀和后缀
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 指定视图路径前缀 --> <property name="prefix" value="/view/" /> <!-- 指定视图路径后缀 --> <property name="suffix" value=".jsp" /> </bean>
配置了前缀和后缀之后,返回的路径就很简单,不需要前缀和后缀,但是有局限性,不经常使用。
4.编码处理:设置一个编码过滤器,参数等不在乱码。web.xml中配置:
<filter> <filter-name>CharacterEncodingFilter</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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
springmvc接收请求的key/value串(比如:id=2&type=101),经过类型转换,将转换后的值赋值给controller方法的形参,这个过程就叫参数绑定。
在controller方法形参中如下类型是可以直接绑定成功,springmvc框架给以下类型的参数直接赋值:
HttpServletRequest:通过request对象获取请求信息
HttpServletResponse:通过response处理响应信息
HttpSession:通过session对象得到session中存放的对象
Model/ModelMap:ModelMap是Model接口的实现类,通过Model或ModelMap向页面传递数据
@RequestMapping("/login4.action") public void login4(HttpServletRequest request, HttpServletResponse response) throws IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); response.sendRedirect("loginSuccess.jsp"); }
其他都雷同,比较麻烦,不推荐使用
Integer、string、boolean、float。。。
绑定规则:对于基本类型参数绑定,当请求的参数的名称和controller方法的形参名一致时可以绑定成功。针对当前类型做类转换,当类型明确时使用明确的类型,当类型不明确时,可以使用string类型,需要时再强转。
@RequestParam:取别名
@RequestBody:把请求体中的数据封装到对象中,请求体中的内容必须是json格式
如果请求的参数的名称和controller方法的形参名不一致时,如何绑定?
就要使用@RequestParam进行绑定:@RequestParam(value="ids") Integer id ,将请求的参数名为ids的值绑定方法形参的id上,Value可以省略.
@RequestMapping("/login4.action") public void login4(@RequestParam(value="id") int id2) { System.out.println(id2);
多个参数
@RequestMapping("/login2.action") public void login(String username,String password){ System.out.println(username+" "+password); }
简单pojo:pojo中都基本类型
Login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form method="get" action="login.action"> <input type="text" name="username"> <input type="text" name="password"> <input type="submit"> </form> </body> </html>
User类
package com.entity; public class User { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "username=‘" + username + ‘\‘‘ + ", password=‘" + password + ‘\‘‘ + ‘}‘; } }
获取内容
@RequestMapping("/login3.action") public void login2(User user){ System.out.println(user); }
一个类中不仅有简单数据类型,还有对象属性。那么页面需要使用相应的类标识属性名,如下:
<form method="get" action="login.action"> <input type="text" name="name"> <input type="text" name="user.username"> <input type="text" name="user.password"> <input type="submit"> </form>
实体类
package com.entity; public class OtherUser { private String name; private User user; public String getName() { return name; } public void setName(String name) { this.name = name; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "OtherUser{" + "name=‘" + name + ‘\‘‘ + ", user=" + user + ‘}‘; } }
获取内容
@RequestMapping("/login5.action") public void login5(OtherUser otherUser){ System.out.println(otherUser); }
<form method="get" action="login.action"> <input type="checkbox" name="hobby" value="篮球">篮球 <input type="checkbox" name="hobby" value="足球">足球 <input type="checkbox" name="hobby" value="羽毛球">羽毛球 <input type="submit"> </form>
获取选中的内容
@RequestMapping("/login.action") public void login3(String[] hobby){ for (String s : hobby) { System.out.println(s); } }
requestMapping注解的作用:对controller方法进行映射。
URL路径映射:requestMapping指定url,对controller方法进行映射。
@RequestMapping("/login.action")
化请求映射:为了更好的管理url,为了避免url冲突,可以在class上使用requestMapping指定根url。
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/login3.action") public void login2(User user){ System.out.println(user); }}
访问时使用user/login3.action。在开发时候,需要提前进行url规划,以避免后期修改url后,需要大量修改页面上的url地址。
请求方法限定:通过requestMapping限制http的请求方法,可以提高系统安全性。
@RequestMapping(value="/login2.action",method = RequestMethod.GET) public void login(String username,String password){ System.out.println(username+" "+password); }
如果没有通过指定的方式来访问该请求就会出现如下错误
6、Controller方法返回值
controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定逻辑视图名。
@Override public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("index.jsp"); return modelAndView; }
类似原始serlvet 的开发。
响应结果的三种方法:
1、使用request转发页面,如下:
request.getRequestDispatcher("页面路径").forward(request, response);
2、也可以通过response页面重定向:
response.sendRedirect("url")
3、也可以通过response指定响应结果,例如响应json数据如下:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");
1.页面转发方式
格式是:forward:转发地址(不能写http://,只能写action的地址)
特点:转发的上一个请求request和要转发的地址共用request,转发后浏览器的地址是不变化。
@RequestMapping("/login2.action") public String login2(){ return "forward:index.jsp"; }
2.页面重定向方式
格式是:redirect:重定向地址(比如:http://.....)
特点:重定的上一个请求request和要重定的地址不公用request,重定后浏览器的地址是变化的。
@RequestMapping("/login2.action") public String login3(){ return "redirect:index.jsp"; }
3.表示逻辑视图名
返回一个string如果即不是转发格式,也不是重定向的格式,就表示一个逻辑视图名。
@RequestMapping("/login2.action") public String login(){ return "index.jsp"; }
数据回显就是把数据传给页面,在jsp页面可以直接通过${}来获取内容
@Override public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView modelAndView=new ModelAndView(); //传递参数给页面,类似于域对象 modelAndView.addObject("msg","这是mvc"); modelAndView.setViewName("index.jsp"); return modelAndView; }
在请求转发时常常使用。
@RequestMapping("/login6.action") public String login6(Model model){ model.addAttribute("msg","我是model传递过来的数据"); return "index.jsp"; }
//把方法的返回值封装到model中,携带到要跳转的页面中 //添加注解后这个方法是全局的,不管任何一个方法执行都会调用这个方法,不常用 @ModelAttribute public User getUser(){ User user=new User(); user.setUsername("hhh"); user.setPassword("123"); return user; } //在jsp页面可以使用${user.username}的方式取到值
如下代码会把传递过来的user放到model中携带到要跳转的界面
//将传入的参数作为参数返回到页面 @RequestMapping("/login2.action") public String login2(@ModelAttribute("user") User user){ return "index.jsp"; } //在jsp页面可以使用${user.username}的方式取到值
当使用return “index.jsp”传递数据给页面时,可以使用request方法,上面已经介绍了,下面介绍重定向传递数据。重定向传递数据使用RedirectAttributes类,使用方式见下面代码:
addAttribute可以把参数传递给页面,但是只是拼接在地址栏中
@RequestMapping("/login.action") public String login(RedirectAttributes attributes){ attributes.addAttribute("msg","我是addAttribute"); return "redirect:index.jsp"; }
addFlashAttribute把参数传递给页面,存放在session的map中,使用完毕后自动清空session。如果要获取参数,不能直接获取,要使用@ModelAttribute
@RequestMapping("/login.action") public String login(RedirectAttributes attributes){ attributes.addFlashAttribute("msg","我是addFlashAttribute"); return "redirect:index.jsp"; }
以上两种方式虽然都可以传递参数到页面,但是页面不能显示这些数据,因此需要其他方式结合来使用。
第一种方式:内部跳转(分发器)
@Controller public class Login { @RequestMapping("/login.action") public String login(RedirectAttributes attributes){ attributes.addFlashAttribute("msg","我是addFlashAttribute"); //这里不直接跳转到页面,而是内部跳转,然后再跳转到页面 return "redirect:login_jsp.action"; } @RequestMapping("login_jsp.action") public String login(@ModelAttribute("msg")String msg){ System.out.println(msg); return "index.jsp"; } }
第二种方式:直接将数据存放到session中,类似于之前的servlet中重定向的传值,获取到数据后清空session.
当异常发生时跳转到错误的页面,给用户相应的提示
1、自定义一个异常类用于抛出异常
package com.controller; public class MyException extends Exception { //自定义异常,继承Exception,重写父类中的部分方法 public MyException() {} public MyException(String message) { super(message); } }
2、创建一个异常处理器,实现ExceptionResolver接口,并交给springmvc管理
package com.controller; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class HandlerException implements HandlerExceptionResolver { //创建一个异常处理器,实现ExceptionResolver接口 //只要任何一层发生异常,就会调用下面的方法 @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { ModelAndView modelAndView=new ModelAndView(); //判断是自定义异常还是系统异常 if(e instanceof MyException){ modelAndView.addObject("msg",e.getMessage()); }else{ modelAndView.addObject("msg","服务器飞啦,请稍后再试"); } modelAndView.setViewName("error.jsp"); return modelAndView; } }
3、写一个类,测试异常
package com.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class Test { @RequestMapping("test.action") public void test() throws MyException { //这里发生自定义异常,用于某些特定异常发生时处理异常 MyException myException=new MyException("我是自定义异常"); throw myException; } @RequestMapping("test2.action") public void test2(){ //这里会发生系统异常 int i=10/0; } }
4、错误页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body><center> <img src="http://img1.imgtn.bdimg.com/it/u=2272026066,3668786831&fm=26&gp=0.jpg"><br> ${msg}</center> </body> </html>
1.导入所需要的jar
2.创建上传文件的jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <!--文件上传必须按照下面的格式写,缺一不可--> <form action="upload.action" enctype="multipart/form-data" method="post"> <input type="file" name="file"> <input type="submit"> </form> </body> </html>
3.创建接收文件上传的controller
package com.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Controller public class UploadController { @RequestMapping("/upload.action") public String upload(MultipartFile file) throws IOException { //多文件上传MultipartFile[] file,遍历这个数组 if(file!=null&& file.getOriginalFilename()!=null&&!file.getOriginalFilename().equals("")){ //获取文件的名字 String fileName = file.getOriginalFilename(); //获取文件的后缀名 int index = fileName.lastIndexOf("."); //UUID这个方法是给文件命名,不重名 String newFileName =UUID.randomUUID()+fileName.substring(index); File NewFile=new File("E:\\upload",newFileName); //把上传的文件的内容保写到保存的位置 file.transferTo(NewFile); } return "success.jsp"; } }
4.在springmvc.xml中配置文件解析器
<!-- Multipart解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件的最大尺寸为5MB --> <property name="maxUploadSize"> <value>5242880</value> </property> </bean>
5.配置虚拟目录
将文件上传到固定的物理目录下,通过虚拟目录 访问物理目录 下的图片。
在tomcat上配置虚拟目录,访问虚拟目录可以访问虚拟目录对应物理目录
在server.xml添加以下代码:
<Context docBase="F:\upload" path="/upload/files"/>
@RequestBody注解将json数据转成java对象 ..test(@RequestBody User user){}
@ResponseBody注解实现将java对象转成json输出
导入jackson包或fastjson包,springmvc默认是使用jackson包。
1)导入jackson包并配置springmvc.xml
如果使用xml配置适配器和映射器,需要如下代码:
<!--注解适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean> </list> </property> </bean>
如果使用注解配置适配器和映射器,就不要再配置。
2)导入fastjson包并配置springmvc.xml
<!--配置注解处理器映射器和适配器--> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes" value="application/json"/> <property name="features"> <array> <value>WriteMapNullValue</value> <value>WriteDateUseDateFormat</value> </array> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
1.register.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <script src="${pageContext.request.contextPath}/js/jquery-1.11.0.min.js"></script> </head> <body> <input type="text" name="username" id="username"><font id="msg"></font> <script> $(function(){ $("#username").blur(function () { var username=$(this).val(); $.post("regist.action",{"username":username},function (data) { $("#msg").html(data.message); }); }); }) </script> </body> </html>
2.UserController类
package com.controller; import com.entity.JsonResult; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class UserController { @RequestMapping("regist.action") @ResponseBody public JsonResult regist(String username){ if("zys".equals(username)){ return new JsonResult(false,"用户名已存在"); }else{ return new JsonResult(true,"验证通过"); } } }
3.注解介绍
@Controller
//是将这个类交给springmvc管理 @RestController
//它是等于@Controller+@ResponseBody,当这个类中的方法全部有返回值时可以直接使用,省去两个注解,它会在每个方法前面自动加@ResponseBody那么这个类只能做返回,不能页面跳转
RESTful,即Representational State Transfer的缩写,表现层状态转化。所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息,应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
url模板映射:@RequestMapping(value="/ user/{id}"):{×××}占位符,请求的URL可以是“/user/1”或“/user/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量。
@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。
在Controller中添加如下代码:
@RequestMapping("/test3/{id}") public void test3(@PathVariable("id")int id){ System.out.println("id"+id); }
在浏览器进行测试,控制台会打印id47
注意:使用restful时,web.xml中url-pattern必须是/,不能是其他的,那么这样又会拦截一些需要的静态资源,所以需要在springmvc.xml中配置来加载静态资源:
<mvc:resources mapping="/js/**" location="/js/"></mvc:resources> <mvc:resources mapping="/css/**" location="/css/"></mvc:resources> mapping代表映射文件目录,**表示当前以及子类的所有文件夹,*只是当前文件夹
restful的使用(了解)
//restful四种方式的使用(了解) @RequestMapping(value = "/user/{abc}",method = RequestMethod.GET) public void add(@PathVariable("abc") int id){ System.out.println("获取数据"+id); } @RequestMapping(value = "/user/{abc}",method = RequestMethod.DELETE) public void delete(@PathVariable("abc") int id){ System.out.println("删除数据"+id); } @RequestMapping(value = "/user/{abc}",method = RequestMethod.PUT) public void update(@PathVariable("abc") int id){ System.out.println("更新数据"+id); } @RequestMapping(value = "/user/{abc}",method = RequestMethod.POST) public void newRes(@PathVariable("abc") int id){ System.out.println("新建数据"+id); }
springmvc提供拦截器实现对Handler进行面向切面编程,可以Handler执行之前、之后、之中添加代码,这种方式就是切面编程。
1.定义一个类实现HanlderInterceptor接口
package com.controller; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Myintercept implements HandlerInterceptor { //preHandle:在Handler执行之前调用 @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("pre..."); //return true代表放行 //return flase代表拦截 return true; } //postHandle:在Handler中方法执行一半时调用(return ModelAndView前),可以更改跳转的视图 @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("post..."); } //afterCompletion:在Handler执行完毕之后调用,可以用于异常的处理 @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { System.out.println("after..."); } }
2.在springmvc.xml中配置拦截器
<!--配置拦截器--> <mvc:interceptors> <!--设置一个拦截路径,也可以写多个 <mvc:interceptor>拦截多个路径--> <mvc:interceptor> <!--path里面是要拦截的路径--> <mvc:mapping path="/test.action"/> <!--把Myintercept交给springmvc管理--> <bean class="com.controller.Myintercept"></bean> </mvc:interceptor> </mvc:interceptors>
dao层:mybatis+spring
service层:spring
controller层:springmvc+spring
整合步骤:
原文:https://www.cnblogs.com/zys2019/p/11439836.html