SpringBoot(05) -- Web开发-- 请求参数处理

SpringBoot2学习笔记

源码地址

四、Web开发

4.3)请求参数处理

4.3.1)rest使用与原理

Rest【@RequestMapping】风格支持(使用HTTP请求方式动词来表示对资源的操作

  • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户

  • 现在: /user GET-获取用户 DELETE- 删除用户 PUT-**修改用户 POST-**保存用户

  • 核心Filter;HiddenHttpMethodFilter

  • 用法: 表单method=post,隐藏域 _method=put

  • SpringBoot中手动开启

4.3.1.1)请求映射示例

工程SpringBootDemo3中修改HelloController.java,代码如下:

@RestController
public class HelloController {
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUser() {
        return "GET-张三";
    }
​
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String saveUser() {
        return "POST-张三";
    }
​
​
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser() {
        return "PUT-张三";
    }
​
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String deleteUser() {
        return "DELETE-张三";
    }
}

修改配置文件application.yaml,代码如下:

spring:
  mvc:
    # 开启 HiddenHttpMethodFilter
    hiddenmethod:
      filter:
      #开启页面表单的Rest功能,选择性开启,无页面交互即可关闭【前后端分离】
        enabled: true

修改首页index.html,代码如下:

测试REST风格;
<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>

测试,启动工程,浏览器访问首页:http://localhost:8080/

 4.3.1.2)Rest原理

相关源码如下:

WebMvcAutoConfiguration.java中的 OrderedHiddenHttpMethodFilter方法,源码如下:

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

HiddenHttpMethodFilter.java中的 doFilterInternal方法,源码如下:

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }
​
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

Rest原理(表单提交要使用REST的时候):

表单提交会带上_method=PUT

请求过来被HiddenHttpMethodFilter拦截,执行下列操作:

请求是否正常,并且是POST

获取到_method的值

兼容以下请求【PUT.DELETE.PATCH】

原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值;

过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的

执行过程如下图:

 Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,则无需Filter

扩展点:如何把 _method 修改成自定义的?

新建配置类 WebConfig.java,代码如下:

@Configuration
public class WebConfig {
​
    @Bean
    // 开发者实现 HiddenHttpMethodFilter()方法,将 _method修改为 _m
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

修改首页index.html,代码如下:

<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input name="_m" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>

测试:delete添加"m",可以执行;put未添加 "m",不可以执行,页面如下所示

 4.3.2)请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch() 开始

SpingBoot中的调用顺序如下图:

 DispatcherServlet.java中的doDispatch() 源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
​
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
​
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
​
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
​
                // 找到当前请求使用哪个Handler(Controller的方法)处理
                mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx
                ...

getHandler() 源码如下:

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            Iterator var2 = this.handlerMappings.iterator();
​
            while(var2.hasNext()) {
                HandlerMapping mapping = (HandlerMapping)var2.next();
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
​
        return null;
    }

执行过程中,this.handlerMappings【处理器映射有5个】,如下图所示:

 RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

 所有的请求映射【每个请求由谁负责处理】都在HandlerMapping中:

  1. SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,访问 /能访问到index.html;

  2. SpringBoot自动配置了默认的 RequestMappingHandlerMapping

  3. 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息:

    • 如果有就找到这个请求对应的handler

    • 如果没有就是下一个 HandlerMapping

4.3.3)SpringMVC处理Web请求

4.3.3.1)注解

4.3.3.1.1)@PathVariable

@PathVariable:获取到路径中变量的值

工程SpringBootDemo3中修改ParameterTestController.java,代码如下:

@RestController
public class ParameterTestController {
​
    // @PathVariable:获取到路径中变量的值
    //  car/3/owner/lisi
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar(@PathVariable("id") Integer id,
                                      @PathVariable("username") String name,
                                      @PathVariable Map<String, String> pv) {
        Map<String, Object> map = new HashMap<>();
        map.put("路径中变量id ",id);
        map.put("路径中变量name ",name);
        map.put("路径中所有变量Map",pv);
        return map;
    }

修改首页index.html,代码如下:

测试基本注解:
<ul>
    <a href="car/3/owner/lisi">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
</ul>

测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi,页面如下所示:

4.3.3.1.2)@RequestHeader

@RequestHeader:获取请求头信息中的值

修改ParameterTestController.java,代码如下:

@GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar(@PathVariable("id") Integer id,
                                      @PathVariable("username") String name,
                                      @PathVariable Map<String, String> pv,
                                      @RequestHeader("User-Agent") String userAgent,
                                      @RequestHeader Map<String, String> header) {
        Map<String, Object> map = new HashMap<>();
        map.put("路径中变量id ", id);
        map.put("路径中变量name ", name);
        map.put("路径中所有变量Map", pv);
        map.put("请求头中变量userAgent", userAgent);
        map.put("请求头中所有变量Map", header);
        return map;
    }
修改首页i
ndex.html,代码如下:
测试基本注解:
<ul>
    <a href="car/3/owner/lisi">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
</ul>

测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi,页面如下所示:

 4.3.3.1.3)@RequestParam

@RequestParam:获取请求参数信息中的值

修改ParameterTestController.java,代码如下:

    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar(@RequestParam("age") Integer age,
                                      @RequestParam("inters") List<String> inters,
                                      @RequestParam Map<String, String> params) {
        Map<String, Object> map = new HashMap<>();
        map.put("请求参数中变量age", age);
        map.put("请求参数中集合inters", inters);
        map.put("请求参数中所有变量Map", params);
        return map;
    }

修改首页index.html,代码如下:

测试基本注解:
<ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
</ul>

测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game,页面如下所示:

 4.3.3.1.4)@CookieValue

@CookieValue:获取cookie信息中的值

修改ParameterTestController.java,代码如下:

    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar( @CookieValue("_ga") String _ga,
                                       @CookieValue("_ga") Cookie cookie) {
        Map<String, Object> map = new HashMap<>();
        map.put("cookie信息中变量_ga", _ga);
        map.put("cookie信息的所有内容", cookie);
        System.out.println(cookie.getName() + "===>" + cookie.getValue());
        return map;
    }

修改首页index.html,代码如下:

测试基本注解:
<ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
</ul>

测试:工程启动,浏览器访问首页http://localhost:8080/,点击超链接“car/{id}/owner/{username}”,页面跳转到:http://localhost:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game,页面如下所示:

 控制台输出:

_ga===>GA1.1.1657823623.1653548910

4.3.3.1.5)@RequestBody

@RequestBody:获取请求体信息中的值

修改ParameterTestController.java,代码如下:

    // @RequestBody:获取请求体信息中的值
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content) {
        Map<String, Object> map = new HashMap<>();
        map.put("请求体信息的所有内容", content);
        return map;
    }

修改首页index.html,代码如下:

<br/>
<form action="/save" method="post">
    测试@RequestBody获取数据 <br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
</form>

测试:工程启动,浏览器访问首页http://localhost:8080/,输入用户名和邮箱信息,页面跳转到:http://localhost:8080/save,页面如下所示:

4.3.3.1.6)@RequestAttribute

@RequestAttribute:获取request域中保存的属性【request域中设置属性用于页面转发时可以获取当前请求的数据】

新建RequestController.java,代码如下:

@Controller
public class RequestController {
​
    // request域中保存属性
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        //转发到  /success请求
        return "forward:/success";
    }
​
    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                       @RequestAttribute(value = "code",required = false)Integer code,
                       HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");
        Map<String,Object> map = new HashMap<>();
        // 方式一:@RequestAttribute获取request中的属性值
        map.put("@RequestAttribute获取request",msg1);
        // 方式二:原生HttpServletRequest获取request中的属性值
        map.put("原生HttpServletRequest获取request",msg);
        return map;
    }
}

测试:工程启动,浏览器访问跳转页 :http://localhost:8080/goto,页面如下所示:

 4.3.3.1.7)@MatrixVariable

@MatrixVariable:矩阵变量

获取请求中的变量值:

  1. @RequestParam:/cars/{path}?xxx=xxx&aaa=ccc;

  2. 矩阵变量【; 表示矩阵变量】:/cars/sell;low=34;brand=byd,audi,yd

矩阵变量需要在SpringBoot中手动开启

根据RFC3986的规范,矩阵变量应当绑定在路径变量中

若是有多个矩阵变量,应当使用英文符号;进行分隔

若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可

手动开启矩阵变量

  1. 方式一:实现WebMvcConfigurer接口,重写configurePathMatch方法,设置RemoveSemicolonContent为false;修改配置类WebConfig.java,代码如下:

@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
​
    // 手动开启矩阵变量方式一:实现WebMvcConfigurer接口,重写configurePathMatch方法,设置RemoveSemicolonContent为false
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除;后面的内容。矩阵变量功能就可以生效【手动开启矩阵变量】
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
  1. 方式二:重写WebMvcConfigurer方法定制化SpringMVC的功能,设置RemoveSemicolonContent为false,修改配置类WebConfig.java,代码如下:

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    // 手动开启矩阵变量方式二:重写WebMvcConfigurer方法定制化SpringMVC的功能,设置RemoveSemicolonContent为false
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}    

修改ParameterTestController.java,代码如下:

    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path) {
        Map<String, Object> map = new HashMap<>();
        map.put("矩阵变量中的变量low", low);
        map.put("矩阵变量中的变量brand", brand);
        map.put("路径中变量path", path);
        return map;
    }

修改首页index.html,代码如下:

测试基本注解:
<ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
    <li>@RequestBody(获取请求体[POST])</li>
    <li>@RequestAttribute(获取request域属性)</li>
    <li>@MatrixVariable(矩阵变量)</li>
</ul>
<br/>
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a><br/>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a><br/>

测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击超链接“1)@MatrixVariable(矩阵变量)”,页面跳转至:http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd ,点击超链接“2)@MatrixVariable(矩阵变量)”,页面跳转至:http://localhost:8080/cars/sell;low=34;brand=byd;brand=audi;brand=yd,页面如下所示:

 如果有多条访问请求,请求中的参数如何确定是那一条请求对应的?【pathVar用来区分不同的请求】

修改ParameterTestController.java,代码如下:

    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age", pathVar = "empId") Integer empAge) {
        Map<String, Object> map = new HashMap<>();
        map.put("bossAge", bossAge);
        map.put("empAge", empAge);
        return map;
    }

修改首页index.html,代码如下:

<a href="/cars/sell;low=34;brand=byd,audi,yd">1)@MatrixVariable(矩阵变量)</a><br/>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">2)@MatrixVariable(矩阵变量)</a><br/>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a><br/>

测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击超链接“@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}”,页面跳转至:http://localhost:8080/boss/1;age=20/2;age=10,页面如下所示:

4.3.3.2)Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (WebRequest.class.isAssignableFrom(paramType) ||
                ServletRequest.class.isAssignableFrom(paramType) ||
                MultipartRequest.class.isAssignableFrom(paramType) ||
                HttpSession.class.isAssignableFrom(paramType) ||
                (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
                Principal.class.isAssignableFrom(paramType) ||
                InputStream.class.isAssignableFrom(paramType) ||
                Reader.class.isAssignableFrom(paramType) ||
                HttpMethod.class == paramType ||
                Locale.class == paramType ||
                TimeZone.class == paramType ||
                ZoneId.class == paramType);
    }

4.3.3.3)复杂参数

MapModel(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

修改RequestController.java,代码如下:

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg", required = false) String msg,
                       @RequestAttribute(value = "code", required = false) Integer code,
                       HttpServletRequest request) {
        Object msg1 = request.getAttribute("msg");
        Map<String, Object> map = new HashMap<>();
        // 方式一:@RequestAttribute获取request中的属性值
        map.put("@RequestAttribute获取request", msg1);
        // 方式二:原生HttpServletRequest获取request中的属性值
        map.put("原生HttpServletRequest获取request", msg);
​
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");
        map.put("hello",hello);
        map.put("world",world);
        map.put("message",message);
        return map;
    }
​
    @GetMapping("/params")
    public String testParam(Map<String, Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response) {
        map.put("hello", "world666");
        model.addAttribute("world", "hello666");
        request.setAttribute("message", "HelloWorld");
​
        Cookie cookie = new Cookie("c1", "v1");
        response.addCookie(cookie);
        return "forward:/success";
    }

测试:工程启动,浏览器访问首页 :http://localhost:8080/params,页面如下所示:

 Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map

ModelAndViewContainer.java的源码如下:

public class ModelAndViewContainer {
    private boolean ignoreDefaultModelOnRedirect = false;
    @Nullable
    private Object view;
    private final ModelMap defaultModel = new BindingAwareModelMap();
    @Nullable
    private ModelMap redirectModel;
    private boolean redirectModelScenario = false;
    ...        
    }

mavContainer.getModel(); 获取到值的

    public ModelMap getModel() {
        if (this.useDefaultModel()) {
            return this.defaultModel;
        } else {
            if (this.redirectModel == null) {
                this.redirectModel = new ModelMap();
            }
            return this.redirectModel;
        }
    }

如下图:

 4.3.3.4)自定义对象参数

可以自动类型转换与格式化,可以级联封装。

新建Bean对象 Person.java,代码如下:

@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}

新建Bean对象 Pet.java,代码如下:

@Data
public class Pet {
    private String name;
    private Integer age;
}

修改ParameterTestController.java,代码如下:

    @PostMapping("/saveuser")
    public Person saveuser(Person person) {
        return person;
    }

修改首页index.html,代码如下:

测试封装POJO;
<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    <!--级联属性-->
    宠物姓名:<input name="pet.name" value="阿猫"/><br/>
    宠物年龄:<input name="pet.age" value="5"/>
    <input type="submit" value="保存"/>
</form>

测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击“保存”按钮,页面跳转至:http://localhost:8080/saveuser;页面如下所示:

 自定义Converter,不使用级联属性

修改首页index.html,代码如下:【直接点击“保存”按钮会报错400】

测试封装POJO;
<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    <!--自定义Converter-->
    宠物: <input name="pet" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>

自定义Converter,修改配置文件WebConfig.java,代码如下:

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            // 自定义对象参数 --》自定义Converter,不使用级联属性
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if (!StringUtils.isEmpty(source)) {
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

测试:工程启动,浏览器访问首页 :http://localhost:8080/,点击“保存”按钮,页面跳转至:http://localhost:8080/saveuser;页面如下所示:

猜你喜欢

转载自blog.csdn.net/jianghao233/article/details/125125005