SpringMVC_1

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanhang89/article/details/78453365

SpringMVC

入门程序

ItemController是一个普通的java类,不需要实现任何接口,只需要在类上添加@Controller注解即可。@RequestMapping注解指定请求的url,其中“.action”可以加也可以不加。在ModelAndView对象中,将视图设置为“/WEB-INF/jsp/itemList.jsp”

@Controller
public class ItemsController {

    //指定url到请求方法的映射
    //url中输入一个地址,找到这个方法.例如:localhost:8081/springmvc0523/list.action
    @RequestMapping("/list")
    public ModelAndView  itemsList() throws Exception{
        List<Items> itemList = new ArrayList<>();

        //商品列表
        Items items_1 = new Items();
        items_1.setName("联想笔记本_3");
        items_1.setPrice(6000f);
        items_1.setDetail("ThinkPad T430 联想笔记本电脑!");

        Items items_2 = new Items();
        items_2.setName("苹果手机");
        items_2.setPrice(5000f);
        items_2.setDetail("iphone6苹果手机!");

        itemList.add(items_1);
        itemList.add(items_2);

        //模型和视图
        //model模型: 模型对象中存放了返回给页面的数据
        //view视图: 视图对象中指定了返回的页面的位置
        ModelAndView modelAndView = new ModelAndView();

        //将返回给页面的数据放入模型和视图对象中
        modelAndView.addObject("itemList", itemList);

        //指定返回的页面位置
        modelAndView.setViewName("itemList");

        return modelAndView;

    }
}

Spring配置文件

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://code.alibabatech.com/schema/dubbo 
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

        <!-- 配置@Controller注解扫描 -->
        <context:component-scan base-package="cn.itheima.controller"></context:component-scan>

        <!-- 如果没有显示的配置处理器映射器和处理器适配那么springMvc会去默认的dispatcherServlet.properties中查找,
        对应的处理器映射器和处理器适配器去使用,这样每个请求都要扫描一次他的默认配置文件,效率非常低,会降低访问速度,所以要显示的配置处理器映射器和
        处理器适配器 -->

        <!-- 注解形式的处理器映射器 -->
<!--         <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"></bean> -->
        <!-- 注解形式的处理器适配器 -->
<!--         <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"></bean> -->

        <!-- 配置最新版的注解的处理器映射器 -->
<!--         <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> -->
        <!-- 配置最新版的注解的处理器适配器 -->
<!--         <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> -->

    <!-- 注解驱动:
        作用:替我们自动配置最新版的注解的处理器映射器和处理器适配器
     -->
    <mvc:annotation-driven></mvc:annotation-driven>


    <!-- 配置视图解析器 
    作用:在controller中指定页面路径的时候就不用写页面的完整路径名称了,可以直接写页面去掉扩展名的名称
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 真正的页面路径 =  前缀 + 去掉后缀名的页面名称 + 后缀 -->
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>

配置DispatchServlet

<!-- 前端控制器 -->
<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>

流程

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet通过HandlerAdapter处理器适配器[调用处理器]
  5. 执行处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户

组件说明

  1. DispatcherServlet:前端控制器

    用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

  2. HandlerMapping 处理器映射器

    HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  3. Handler: 处理器

    Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。

    由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。

  4. HandlAdapter: 处理器适配器

    通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

  5. View Resolver: 视图解析器

    ViewResolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

    View视图:

    一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

说明:在springmvc的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。

需要用户开放的组件有

RequestMappingHandlerMapping

注解式处理器映射器,对类中标记@ResquestMapping的方法进行映射,根据ResquestMapping定义的url匹配ResquestMapping标记的方法,匹配成功返回HandlerMethod对象给前端控制器,HandlerMethod对象中封装url对应的方法Method。

从spring3.1版本开始,废除了DefaultAnnotationHandlerMapping的使用,推荐使用RequestMappingHandlerMapping完成注解式处理器映射。

RequestMappingHandlerAdapter

springmvc使用< mvc:annotation-driven >自动加载RequestMappingHandlerMapping和RequestMappingHandlerAdapter,可用在springmvc.xml配置文件中使用< mvc:annotation-driven>替代注解处理器和适配器的配置。

视图解析器

视图解析器的配置:

<beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <propertyname="viewClass"
        value="org.springframework.web.servlet.view.JstlView"/>
    <propertyname="prefix"value="/WEB-INF/jsp/"/>
    <propertyname="suffix"value=".jsp"/>
</bean>

InternalResourceViewResolver:支持JSP视图解析
viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,所以classpath中必须包含jstl的相关jar 包。此属性可以不设置,默认为JstlView。
prefix 和suffix:查找视图页面的前缀和后缀,最终视图的址为:
前缀+逻辑视图名+后缀,逻辑视图名需要在controller中返回ModelAndView指定,比如逻辑视图名为hello,则最终返回的jsp视图地址 “WEB-INF/jsp/hello.jsp”

整合Mybatis

Dao层:

​ 1、SqlMapConfig.xml,空文件即可。需要文件头。

​ 2、applicationContext-dao.xml。

​ a) 数据库连接池

​ b) SqlSessionFactory对象,需要spring和mybatis整合包下的。

​ c) 配置mapper文件扫描器。

Service层:

​ 1、applicationContext-service.xml包扫描器,扫描@service注解的类。

​ 2、applicationContext-trans.xml配置事务。

表现层:

Springmvc.xml

​ 1、包扫描器,扫描@Controller注解的类。

​ 2、配置注解驱动。

​ 3、视图解析器

Web.xml

​ 配置前端控制器。

案例

按照上面的过程配置:
SqlMapConfig.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

db.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springmvc?characterEncoding=utf-8
jdbc.username=root
jdbc.password=admin

log4j.properties:

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

ApplicationContext-dao:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:db.properties" />
    <!-- 数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="10" />
        <property name="maxIdle" value="5" />
    </bean>

    <!-- mapper配置 -->
    <!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 数据库连接池 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 加载mybatis的全局配置文件 -->
        <property name="configLocation" value="classpath:SqlMapConfig.xml" />
    </bean>

    <!-- 配置Mapper扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.itheima.dao"/>
    </bean>

</beans>

ApplicationContext-trans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>

    <!-- 切面 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
            pointcut="execution(* cn.itheima.service.*.*(..))" />
    </aop:config>

</beans> 

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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- @Controller注解扫描 -->
    <context:component-scan base-package="cn.itheima.controller"></context:component-scan>

    <!-- 注解驱动:
            替我们显示的配置了最新版的注解的处理器映射器和处理器适配器 -->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

    <!-- 配置视图解析器 
    作用:在controller中指定页面路径的时候就不用写页面的完整路径名称了,可以直接写页面去掉扩展名的名称
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 真正的页面路径 =  前缀 + 去掉后缀名的页面名称 + 后缀 -->
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!-- 配置自定义转换器 
    注意: 一定要将自定义的转换器配置到注解驱动上
    -->
    <bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <!-- 指定自定义转换器的全路径名称 -->
                <bean class="cn.itheima.controller.converter.CustomGlobalStrToDateConverter"/>
            </set>
        </property>
    </bean>

</beans>

Controller(主要):

@Controller
public class ItemsController {

    @Autowired
    private ItemsService itmesService;

    @RequestMapping("/list")
    public ModelAndView itemsList() throws Exception{
        List<Items> list = itmesService.list();

        ModelAndView modelAndView = new ModelAndView();

        modelAndView.addObject("itemList", list);
        modelAndView.setViewName("itemList");

        return modelAndView;
    }

    /**
     * springMvc中默认支持的参数类型:也就是说在controller方法中可以加入这些也可以不加,  加不加看自己需不需要,都行.
     *HttpServletRequest
     *HttpServletResponse
     *HttpSession
     *Model
     */
    @RequestMapping("/itemEdit")
    public String itemEdit(HttpServletRequest reuqest, 
             Model model) throws Exception{

        String idStr = reuqest.getParameter("id");
        Items items = itmesService.findItemsById(Integer.parseInt(idStr));

        //Model模型:模型中放入了返回给页面的数据
        //model底层其实就是用的request域来传递数据,但是对request域进行了扩展.
        //页面通过${item.XXXX}获取item对象的属性值。
        //如果使用Model则可以不使用ModelAndView对象,Model对象可以向页面传递数据,View对象则可以使用String返回值替代。不管是Model还是ModelAndView,其本质都是使用Request对象向jsp传递数据。
        model.addAttribute("item", items);

        //如果springMvc方法返回一个简单的string字符串,那么springMvc就会认为这个字符串就是页面的名称
        return "editItem";
    }

    //springMvc可以直接接收基本数据类型,包括string.spirngMvc可以帮你自动进行类型转换.
    //controller方法接收的参数的变量名称必须要等于页面上input框的name属性值
    //public String update(Integer id, String name, Float price, String detail) throws Exception{

    //spirngMvc可以直接接收pojo类型:要求页面上input框的name属性名称必须等于pojo的属性名称
    //注意:提交的表单中不要有日期类型的数据,否则会报400错误。如果想提交日期类型的数据需要用到后面的自定义参数绑定的内容。
    @RequestMapping("/updateitem")
    public String update(Items items) throws Exception{
        itmesService.updateItems(items);

        return "success";
    }

    //如果Controller中接收的是Vo,那么页面上input框的name属性值要等于vo的属性.属性.属性.....
    @RequestMapping("/search")
    public String search(QueryVo vo) throws Exception{
        System.out.println(vo);
        return "";
    }
}

需要的转换器:

/**
 * S - source:源
 * T - target:目标
 * @author zj
 *
 */
public class CustomGlobalStrToDateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String source) {
        try {
            Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(source);
            return date;
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

}

相关的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 }/search.action" method="post">
查询条件:
<table width="100%" border=1>
<tr>
<!-- 如果Controller中接收的是Vo,那么页面上input框的name属性值要等于vo的属性.属性.属性..... -->
<td>商品名称:<input type="text" name="items.name"/></td>
<td>商品价格:<input type="text" name="items.price"/></td>
<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">
<tr>
    <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 }/itemEdit.action?id=${item.id}">修改</a></td>

</tr>
</c:forEach>

</table>
</form>
</body>

</html>

编辑列表:

<%@ 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> 
    <!-- 上传图片是需要指定属性 enctype="multipart/form-data" -->
    <!-- <form id="itemForm" action="" method="post" enctype="multipart/form-data"> -->
    <form id="itemForm" action="${pageContext.request.contextPath }/updateitem.action" method="post">
        <input type="hidden" name="id" value="${item.id }" /> 修改商品信息:
        <table width="100%" border=1>
            <tr>
                <td>商品名称</td>
                <td><input type="text" name="name" value="${item.name }" /></td>
            </tr>
            <tr>
                <td>商品价格</td>
                <td><input type="text" name="price" value="${item.price }" /></td>
            </tr>

            <tr>
                <td>商品生产日期</td>
                <td><input type="text" name="createtime"
                    value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" /></td>
            </tr>
            <%-- 
            <tr>
                <td>商品图片</td>
                <td>
                    <c:if test="${item.pic !=null}">
                        <img src="/pic/${item.pic}" width=100 height=100/>
                        <br/>
                    </c:if>
                    <input type="file"  name="pictureFile"/> 
                </td>
            </tr>
             --%>
            <tr>
                <td>商品简介</td>
                <td><textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
                </td>
            </tr>
            <tr>
                <td colspan="2" align="center"><input type="submit" value="提交" />
                </td>
            </tr>
        </table>

    </form>
</body>

</html>

参数传递

当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。从Request取参数的方法可以进一步简化。

@RequestMapping("/itemEdit")
public String itemEdit(Integer id, Model model) {
    Items items = itemService.getItemById(id);
    //向jsp传递数据
    model.addAttribute("item", items);
    //设置跳转的jsp页面
    return"editItem";
}

支持的数据类型:

参数类型推荐使用包装数据类型,因为基础数据类型不可以为null

整形:Integer、int

字符串:String

单精度:Float、float

双精度:Double、double

布尔型:Boolean、boolean

说明:对于布尔类型的参数,请求的参数值为true或false。

处理器方法:

public String editItem(Model model,Integer id,Boolean status) throwsException

请求url:

http://localhost:8080/xxx.action?id=2&status=false

@RequestParam

使用@RequestParam常用于处理简单类型的绑定。

value:参数名字,即入参的请求参数名字,如value=“item_id”表示请求的参数区中的名字为item_id的参数的值将传入;

required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;

HTTP Status 400 -Required Integer parameter 'XXXX' is not present
public String editItem(@RequestParam(value="item_id",required=true) String id) {
}

猜你喜欢

转载自blog.csdn.net/guanhang89/article/details/78453365