SpringMVC框架之第三篇

2.高级参数绑定(重点)
            2.1.数组类型
            数组类型的参数可以传递一批相同的数据到Controller的方法中。
            2.1.1.需求
            批量删除:在商品列表页面选中多个商品,然后删除。
            2.1.2.需求分析
            此功能要求商品列表页面中的每个商品前有一个checkbook,选中多个商品后点击删除按钮把商品id传递给Controller,根据商品id删除商品信息。
            功能分解
            前端:1)能选中多个商品;2)能提交选中的多个商品
            后端:1)能接收到选中商品的id;2)进行删除处理
            2.1.3.演示代码
            1.Jsp
            可以重新创建一个专门演示批量删除的画面【itemListDelBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后在表格的最前面增加一列checkbox。
            【itemListDelBatch.jsp】
            <%@ page language="java" contentType="text/html; charset=UTF-8"
                pageEncoding="UTF-8"%>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
            <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
            <html>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>查询商品列表</title>
            </head>
            <body>
                <form action="${pageContext.request.contextPath }/delAll.action" method="post">
                    查询条件:
                    <table width="100%" border=1>
                        <tr>
                            <td><input type="submit" value="批量删除"/></td>
                        </tr>
                    </table>
                    商品列表:
                    <table width="100%" border=1>
                        <tr>
                            <td></td>
                            <td>商品名称</td>
                            <td>商品价格</td>
                            <td>生产日期</td>
                            <td>商品描述</td>
                            <td>操作</td>
                        </tr>
                        <c:forEach items="${itemList }" var="item">
                        <tr>
                            <td><input type="checkbox" name="ids" value="${item.id }" /></td>
                            <td>${item.name }</td>
                            <td>${item.price }</td>
                            <td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                            <td>${item.detail }</td>
                            <td><a href="${pageContext.request.contextPath }/toEdit.action?id=${item.id}">修改</a></td>
                        </tr>
                        </c:forEach>
                    </table>
                </form>
            </body>
            </html>
            2.Controller
            【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
                @RequestMapping("/listForDel")
                public ModelAndView getListForDel() throws Exception {
                    List<Items> itemsList = itemsService.getItemsList();
                    ModelAndView modelAndView = new ModelAndView();
                    // request.setAttribute(key, value)
                    // 底层仍然是把属性名和属性值放到request对象中
                    // jsp页面永远是从request对象中取得数据的
                    modelAndView.addObject("itemsList", itemsList);
                    modelAndView.setViewName("items/itemListDelBatch");
                    return modelAndView;
                }

            然后再定义一个执行删除的方法(这里主要是学习如何传参数,不做具体的删除操作)
            方式一:直接传递数组参数
            ·传参规范:页面上input框的name属性值必须等于接收时数组参数的变量名称。
                /**
                 * 演示接收数组(直接接收数组)
                 */
                @RequestMapping("/delAll")
                public String delAll(Integer[] ids) throws Exception {
                    // 批量删除:可以循环这个ids数组,逐条删除即可。
                    return "success";
                }

            方式二:在Vo中传递数组参数
            【QueryVo.java】
            package cn.baidu.pojo;

            import java.util.List;

            public class QueryVo {

                private Integer[] ids;

                // getter/setter方法。。。。。。
            }    

            ·传参规范:页面上input框的name属性值必须等于接收时Vo中数组类型属性的变量名称。
                /**
                 * 演示接收数组(用Vo传递数组)
                 */
                @RequestMapping("/delAll")
                public String delAll(QueryVo vo) throws Exception {
                    // 批量删除:可以循环这个Vo中的ids数组,逐条删除即可。
                    return "success";
                }

            2.1.4.传参规范
            总结上面两种规范:Input框的name与Controller方法参数中的数组名要相同。(无论数组定义在方法形参中还是定义在形参Vo中)
            2.2.List集合类型
            可以利用List集合类型的参数传递多条数据进行批量处理。比如批量更新。
            2.2.1.需求
            批量更新:实现商品数据的批量修改。
            2.2.2.需求分析
            要想实现商品数据的批量修改,需要在商品列表中可以对商品信息进行修改,并且可以批量提交修改后的商品数据。提交的数据应该是一个List。
            功能分解:
                前端:1)列表改成input输入框;2)定义改好的input框的name属性;
                后端:1)能接收到提交过来的更新数据;2)批量更新处理
            2.2.3.演示代码
            1.接收商品列表的pojo
            注意:SpringMVC不能直接传递List集合类型的参数,必须包装在Vo中。这是SpringMVC框架的强制要求。
            【QueryVo.java】
            package cn.baidu.pojo;

            import java.util.List;

            public class QueryVo {

                private List<Items> updateItemsList;

                // setter/getter方法。。。。。。。

            }    

            2.Jsp
            可以重新创建一个专门演示批量更新的画面【itemListUpdBatch.jsp】,利用原来的itemList.jsp拷贝一个,然后将表格中的项目都改成input输入框,可以直接修改数据。
            【itemListUpdBatch.jsp】代码如下:
            <%@ page language="java" contentType="text/html; charset=UTF-8"
                pageEncoding="UTF-8"%>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
            <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
            <html>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>查询商品列表</title>
            </head>
            <body>
            <form action="${pageContext.request.contextPath }/updateAll.action" method="post">
            查询条件:
            <table width="100%" border=1>
            <tr>
            <td><input type="submit" value="批量修改"/></td>
            </tr>
            </table>

            商品列表:
            <table width="100%" border=1>

            <tr>
                <td>商品名称</td>
                <td>商品价格</td>
                <td>生产日期</td>
                <td>商品描述</td>
                <td>操作</td>
            </tr>

            <c:forEach items="${itemList }" var="item" varStatus="status"> <!-- status.index表示集合的下标,从0开始 -->
            <tr>
                <td><input type="text" name="updateItemsList[${status.index }].name" value="${item.name }" /></td>
                <td><input type="text" name="updateItemsList[${status.index }].price" value="${item.price }" /></td>
                <td><input type="text" name="updateItemsList[${status.index }].createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" /></td>
                <td><input type="text" name="updateItemsList[${status.index }].detail" value="${item.detail }" /></td>

                <td><input type="hidden" name="updateItemsList[${status.index }].id" value="${item.id}" />
                    <a href="${pageContext.request.contextPath }/toEdit.action?id=${item.id}">修改</a>
                </td>
            </tr>
            </c:forEach>
            </table>
            </form>
            </body>

            </html>
            附:
            varStatus属性常用参数总结下:
            ${status.index}      输出行号,从0开始。
            ${status.count}      输出行号,从1开始。
            ${status.current}   当前这次迭代的(集合中的)项
            ${status.first}  判断当前项是否为集合中的第一项,返回值为true或false
            ${status.last}   判断当前项是否为集合中的最后一项,返回值为true或false
            begin属性、end属性、step属性分别表示:起始序号,结束序号,跳跃步伐。

            3.Controller
            【ItemsController.java】先定义一个方法用于itemListDelBatch.jsp页面的显示:
                @RequestMapping("/listForUpd")
                public ModelAndView getListForUpd() throws Exception {
                    List<Items> itemsList = itemsService.getItemsList();
                    ModelAndView modelAndView = new ModelAndView();
                    // request.setAttribute(key, value)
                    // 底层仍然是把属性名和属性值放到request对象中
                    // jsp页面永远是从request对象中取得数据的
                    modelAndView.addObject("itemsList", itemsList);
                    modelAndView.setViewName("items/itemListUpdBatch");
                    return modelAndView;
                }

            再定义一个方法,用来更新处理。这里不做具体的更新,我们主要学习如何接收List参数。
                /**
                 * 演示接收List
                 */
                @RequestMapping("/updateAll")
                public String updateAll(QueryVo vo) throws Exception {
                    System.out.println(vo);

                    // 批量修改:遍历List,逐个修改。
                    return "success";
                }

            2.2.4.传参规范
            1.SpringMVC不能直接传递List集合类型的参数,必须包装在java bean中。这是SpringMVC框架的强制要求。
            2.页面上input框的name属性值必须等于Controller方法形参java bean中List属性名 + [集合下标] + . + List泛型中的属性名。
            例如:
            <input type="text" name="itemsUpdLst[${status.index }].name" value="${item.name }" />

            3[email protected](重点)
            注解@RequestMapping控制着url到请求方法的映射,对url能访问到Controller中正确的响应方法起到了至关重要的作用。
            3.1.使用在方法上
            标记url到请求方法的映射,即通过一段url找到Controller中对应的方法。这个在以前的示例中已经练习了。
            3.2.使用在类上
            官方的说法叫做窄化请求映射,其实就是为了防止你和你的同事起的url重名,在类上多给url加了一层目录。
            【ItemsController.java】的修改:
            @Controller
            @RequestMapping("/items")
            public class ItemsController {
            。。。。。。
            }
            访问地址从【http://localhost:8080/ssm-1/list.action】变成了【http://localhost:8080/ssm-1/items/list.action】,多了一层【/items】目录。
            注意:
            此时SpringMVC.xml中的视图解析器的前缀的开头要加斜杠/WEB-INF/jsp,如果写成WEB-INF/jsp就会被SpringMVC认为是相对路径,直接拼在类上面@RequestMapping中url的后面。
            3.3.限制请求类型
            1.Http请求类型:post、get、put、delete等,不过put、delete现在已经很少使用了。
            2.post与get:
            必须明确指定是post时,才是post请求;否则默认是get请求。
            在浏览器中输入url提交的请求是get请求。
            3[email protected]使用方式:
            a)默认方式:之前使用@RequestMapping都是它的默认使用方式,默认的@RequestMapping支持所有的Http请求类型。
            b)正常方式:@RequestMapping(value=”具体url路径”, method=某一种http请求类型)
            指定了Http请求类型就限制只能用指定的请求类型提交请求。
            c)多请求方式:@RequestMapping(value=”具体url路径”, method={请求类型1, 请求类型2,……})
            4.示例:
            ◆限定只允许GET方法访问:
                    @RequestMapping(value=”/list”, method = RequestMethod.GET)
            ◆限定只允许POST方法访问:
                    @RequestMapping(value=”/list”, method = RequestMethod.POST)
            ◆GET和POST方法都可以:
                    @RequestMapping(value=”/list”, method = {RequestMethod.POST, RequestMethod.GET })
                    或
                    @RequestMapping(”/list”)

            以商品列表画面的访问为例,是通过get方式访问的,因此可以在@RequestMapping的method属性中设置请求的类型是【RequestMethod.GET】:
                @RequestMapping(value="/list", method=RequestMethod.GET)
                public ModelAndView itemsList() throws Exception {

                    List<Items> itemsList = itemsService.findItemsList();

                    // 1. 设置返回页面需要的数据    2. 指定返回页面的地址
                    ModelAndView modelAndView = new ModelAndView();

                    // 1. 设置返回页面需要的数据
                    modelAndView.addObject("itemList", itemsList);

                    // 2. 逻辑视图名称的设置(就是视图文件的文件名)
                    modelAndView.setViewName("itemList");

                    return modelAndView;
                }
                尝试是否能访问成功。
                然后把【RequestMethod.GET】改成【RequestMethod.POST】,再试一次,会报405错误:
                    HTTP Status 405 - Request method ‘GET‘ not supported
                这说明请求访问受限了。
                同样如果用POST方法访问【RequestMethod.GET】修饰的URL也会报405错误:
                    HTTP Status 405 - Request method ‘POST‘ not supported
            3.4.用途
            大家是不是有一个疑问,感觉这个功能多余,还不如不限制。是的,如果在传统web系统中这个限制功能使用的很少。但在RESTful的url中十分有用。
            4.Controller方法的返回值(重点)
            提到Controller方法的返回值主要指两方面内容:
            1.怎样指定返回页面的路径?
            2.怎样指定返回页面的数据?
            4.1.ModelAndView模型和视图
            可以调用里面的方法指定页面的地址;还可以调用里面的方法指定返回给页面的数据。
            这个在第一天的内容中已经介绍过了。这里就不多说了。
            4.2.字符串
            4.2.1.返回普通的字符串
            Controller方法如果返回的是普通字符串,那就是视图文件的逻辑视图名或者是视图文件的物理地址。这个在第一天的代码示例中已经介绍过了,这里不多说了。
            对于处理好的数据可以借助于默认的参数Model将数据返回给页面。
            4.2.2.请求转发与重定向
            1.请求转发和重定向的特征(也是区别):
            请求转发时浏览器中URL不发生改变,说明还是在同一个请求中,因此request对象是一个,request域中的数据可以带到转发后的方法中。
            重定向时浏览器中的URL发生改变,说明是重新发起了一个请求,因此request对象不是一个,重定向前request域中的数据不能带到重定向后的方法中。
            4.2.3.请求转发字符串
            1.SpringMVC中请求转发的字符串特征:
            返回的字符串中以【forward:】开头,后面跟转发的URL路径。
            这个URL必须是Controller方法上@RequestMapping注解配置的URL。这样就可以转发到对应的方法中进行处理然后返回给页面。
            2.对代码的改造:
            比如:我们可以设计在商品的详细修改页面点击【保存】后,又重新回到详细页面。用请求转发的方式转发到商品详细页面的检索方法【itemEdit】中。
            具体改造代码【ItemsController.java】:
                /**
                 * 演示请求转发:
                 *     在Controller方法返回的字符串中以【forward:】开头为请求转发,后面跟转发的url路径
                 */
                @RequestMapping("/update")
                public String updateItems(Items items, Model model) throws Exception {
                    itemsService.updateItems(items);
                    // 将转发后商品详细信息查询需要的主键id传递过去。
                    // 用model可以,因为在底层model仍然是把数据放到了request域中。
                    model.addAttribute("id", items.getId());
                    // 因为jsp是从request对象中接收参数,因此也可以直接用request对象直接传值。
                    // 也可以直接用request对象直接传。

                    // 请求转发
                    return "forward:toEdit.action";
                }

            因为请求转发前后的request对象是同一个,所以也可以直接用request对象直接传值:
                /**
                 * 演示请求转发:
                 *     在Controller方法返回的字符串中以【forward:】开头为请求转发,后面跟转发的url路径
                 */
                @RequestMapping("/update")
                public String updateItems(Items items, Model model, HttpServletRequest request) throws Exception {
                    itemsService.updateItems(items);
                    // 也可以直接用request对象直接传。
                    request.setAttribute("id", items.getId());

                    // 请求转发
                    return "forward:toEdit.action";
                }    

            4.2.4.重定向字符串
            1.重定向的字符串特征:
            返回的字符串中以【redirect:】开头,后面跟重定向的URL路径。
            这个URL必须是Controller方法上@RequestMapping注解配置的URL。这样就可以转发到对应的方法中进行处理然后返回给页面。
            2.对代码的改造:
            还是上面的例子我们也可以用重定向来做,但重定向前和后是两个独立的request域,因此传递主键id时不能使用request对象来传递,只能通过model对象完成,底层model会把数据赋给新的request域中。
            具体代码【ItemsController.java】改造:
            可以先试一试看看用request对象是不是真的传递不过去。
                /**
                 * 演示重定向:
                 *     在Controller方法返回的字符串中以【redirect:】开头的为重定向,后面跟重定向的url路径
                 */
                @RequestMapping("/update")
                public String updateItems(Items items, HttpServletRequest request, Model model) throws Exception {
                    itemsService.updateItems(items);
                    request.setAttribute("id", items.getId());
                    return "redirect:toEdit.action";
                }
                此处用request设置了id,然后可以debug到【itemEdit】方法中看id能否传过去,结果是id无法传过去。
            使用model对象传值:
                /**
                 * 演示重定向:
                 *     在Controller方法返回的字符串中以【redirect:】开头的为重定向,后面跟重定向的url路径
                 */
                @RequestMapping("/update")
                public String updateItems(Items items, Model model) throws Exception {
                    itemsService.updateItems(items);
                    // 将重定向后商品信息详细查询需要的主键id传递过去。
                    model.addAttribute("id", items.getId());
                    return "redirect:toEdit.action";
                }
            此处用Model设置了id,然后可以debug到【itemEdit】方法中看id能否传过去,结果id被传递到了【itemEdit】方法中。
            这就是为什么SpringMVC不推荐直接用request对象传值的原因,它自己定义了一个用于传值的对象Model,这样就不怕跨不同request对象的重定向传值问题了。
            也有人说还可以用session传递,这个没错,是可以传的,但是session在实际工作中是不可以随便往里面放数据的,session需要有项目负责人统一规划可以往里面放的数据,比如用户登录信息。因此不可以使用session。
            4.2.5.请求转发和重定向的路径写法为什么不以/开头
            1.这就是url的相对路径和绝对路径的问题。
            以【localhost:8080/ssm-1/items/toEdit.action】为例说明问题:
            url相对路径:相对于当前目录下的路径,示例中的当前目录是/items,当对于它下的相对路径是/toEdit.action
                url绝对路径:从项目名后开始的完整的url路径:/items/toEdit.action
            2.SpringMVC怎么才能区分出相对路径还是绝对路径呢?
            SpringMVC规定:在请求转发和重定向关键字后以斜杠/开头的url为绝对路径,不以斜杠/开头的是相对路径。
            由此可知:
            请求转发的相对路径:forward:toEdit.action
            请求转发的绝对路径:forward:/items/toEdit.action
            重定向的相对路径:redirect:toEdit.action
            重定向的绝对路径:redirect:/items/toEdit.action
            3.两种路径的使用范围:
            url相对路径:
            在Controller类上有@RequestMapping并且是在此Controller内部方法间跳转时可使用。
            url绝对路径:
            任何时候都可以使用,但是在跳转到其他Controller方法的时候必须用绝对路径。
                如果实在记不住:就一律用绝对路径。
            4.3.void
            如果使用void为返回值,那么就需要使用原生的HttpServletRequest,HttpServletResponse等,这样就不走SpringMVC的视图解析器,这样就破坏了SpringMVC的体系结构,所以一般不要使用。这里只是提一下。
            返回值为void一般在ajax的时候使用,用response.getWriter().write("。。。")返回数据
            示例代码【ItemsController.java】
                /**
                 * 演示void
                 */
                @RequestMapping("/update")
                public void updateItems(Items items, HttpServletRequest request, HttpServletResponse response) throws Exception {
                    itemsService.updateItems(items);
                    request.setAttribute("id", items.getId());
                    request.getRequestDispatcher("/WEB-INF/jsp/editItem.jsp").forward(request, response);
                    response.setCharacterEncoding("utf-8");
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write("json串");
                }

原文地址:https://www.cnblogs.com/haizai/p/11668003.html

时间: 2024-11-10 07:30:53

SpringMVC框架之第三篇的相关文章

Django框架之第三篇模板语法

Django框架之第三篇模板语法(重要!!!) 一.什么是模板? 只要是在html里面有模板语法就不是html文件了,这样的文件就叫做模板. 二.模板语法分类 一.模板语法之变量:语法为 {{ }}: 在 Django 模板中遍历复杂数据结构的关键是句点字符  .(也就是点) views.py def index(request): name = "hello haiyan" i = 200 l = [11,22,33,44,55] d = {"name":&quo

(C/C++)基于SharpUI控件库的插件式框架开发--第三篇框架基础服务库

一个框架基础的东西,一般也是操作的最基础的类,比如char.int.bool等,有时出现内存泄露的问题导致错误的抛出,但是C++开发有的时候就算是抛出异常,那也是靠经验来积累才能非常快速准确的找出错误所在,这就需要在框架中需要添加日志管理的接口,日志管理的好处就是开发者自身在找异常时提供参考,另一个就是如果用户操作时出现问题,也可将日志反馈,帮助快速解决问题:总之了为了更好的扩展完善我的框架,我详细列一下这个基础服务库(XPCore)包含内容: 虽说sharpui控件库内封闭好string类,但

MyBatis框架之第三篇

8.Spring与Mybatis整合 框架的整合就是软件之间的集成,它很抽象,因此在做整合之前先想好思路.规划好思路然后按照思路一步一步的做就可以实现框架的整合. 8.1.SM整合思路 8.1.1.思路分析 SM整合是中后端框架的整合: Spring框架作为一个优秀的容器级框架在系统整合中一直充当着业务控制中心的作用.是后端的核心.管理着系统中重要的资源.重要的核心类的对象.业务层对象以及持久层Dao对象. MyBatis作为一个优秀的持久层框架,其优势当然是访问数据库的具体工作,所以交出对重要

SpringMVC框架之第四篇

5.SpringMVC异常处理 5.1.异常分类 1.可预知异常: Java编译时可检测异常,例如:IOException.SQLException等. 自定义异常(继承Exception父类的自定义类即为自定义异常). 2.不可预知异常: Java运行时异常,例如:NullPointerException.IndexOutOfBoundsException等. 5.2.SpringMVC异常处理 在JavaEE项目的开发中,不管是持久层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过

Java SpringMVC框架学习(三)springMVC的执行流程

具体执行逻辑如下: 浏览器提交请求到中央调度器. 中央调度器将请求转给处理器映射器. 处理器映射器根据请求, 找到请求对应的处理器, 并将其封装为处理器执行链返回给中央调度器. 中央调度器根据处理器执行链中的处理器, 找到能够执行该处理器的适配器. 适配器调用执行处理器. 处理器将处理结果以及要跳转的视图封装到一个对象ModelAndView中, 并将其返回给处理器适配器. 适配器将结果返回给中央调度器. 中央调度器调用视图解析器, 将ModelAndView中的视图名封装为视图对象. 视图解析

Django框架之第三篇模板语法(重要!!!)

一.什么是模板? 只要是在html里面有模板语法就不是html文件了,这样的文件就叫做模板. 二.模板语法分类 一.模板语法之变量:语法为 {{ }}: 在 Django 模板中遍历复杂数据结构的关键是句点字符  .(也就是点) views.py def index(request): name = "hello haiyan" i = 200 l = [11,22,33,44,55] d = {"name":"haiyan","age

手写MyBatis,纯手工打造开源框架(第四篇:决胜千里)- 第272篇

说明 MyBatis版本:3.5.1 相关历史文章(阅读本文之前,您可能需要先看下之前的系列) Spring Boot MyBatis最全教程:你值得拥有MyBatis能脱离Spring吗一图纵览MyBatis的工作原理从源码看MyBatis,竟如此简单MyBatis的Mapper是什么`垃圾` 手写MyBatis,纯手工打造开源框架(第一篇:风云再起) 手写MyBatis,纯手工打造开源框架(第二篇:君临天下) 手写MyBatis,纯手工打造开源框架(第三篇:运筹帷幄) 前言        运

Spring框架学习(三) SpringMVC

SpringMVC是Spring中用于开发MVC项目的一个框架. 关于MVC Model-View-Controller,曾经以为构成了一整个应用程序,不过这篇文章里的说明,让我的看法有了一些变化,MVC可以是应用的上层,而在M层之下,还可以有类似于Repository.UnitOfWord等数据访问层与Controller层交互. SpringMVC SpringMVC做了这几件事: 定义了请求入口处理程序DispatcherServlet,并由它来分发请求到不同的Controller 定义C

SpringMVC框架之第二篇

6.参数绑定(重点) Springmvc作为表现层框架,是连接页面和service层的桥梁,它负责所有请求的中转.怎么从请求中接收参数是重点,这也体现了我们刚开始说的Springmvc的第一个作用:“接收请求中的参数”. 接收的参数具体有哪些类型呢?6.1~6.5是绝大部分参数绑定的类型,还有两种第二天课程讲. 6.1.简述参数绑定的类型 1.默认支持的参数类型:HttpServletRequest,HttpServletResponse,HttpSession,Model 2.简单类型:Str