基于Servlet的3.1以上注解方式完成上传功能。

简介: 基于Servlet的3.1以上注解方式完成上传功能。

基于Servlet完成的文件上传和下载

注意:这里采用的是servlet的注解方式,即要求在Servlet3.1版本以上。另,Tomcat7.0版本以上

建议最低采用如图所示环境进行开发!

开发环境如图:

采用 Jdk1.8 + Tomcat 9.0 + JavaEE 8.0 、开发工具采用的是IntellJ IDEA

1.创建实体类

package  cn.javabs.demo.entity;
/**
 *  资源文件 =  资源路径 +  资源名称
 */
public class Source {
    private   int    id;
    private   String author;
    private   String sourceName;//资源名称
    private   String sourcePath;//资源路径
    private   int downCount; // 下载的次数
    private   String createTime  ;
 
    @Override
    public String toString() {
        return "Source{" +
                "id=" + id +
                ", author='" + author + '\'' +
                ", sourceName='" + sourceName + '\'' +
                ", sourcePath='" + sourcePath + '\'' +
                ", downCount=" + downCount +
                ", createTime='" + createTime + '\'' +
                '}';
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public void setAuthor(String author) {
        this.author = author;
    }
 
    public String getSourceName() {
        return sourceName;
    }
 
    public void setSourceName(String sourceName) {
        this.sourceName = sourceName;
    }
 
    public String getSourcePath() {
        return sourcePath;
    }
 
    public void setSourcePath(String sourcePath) {
        this.sourcePath = sourcePath;
    }
 
    public int getDownCount() {
        return downCount;
    }
 
    public void setDownCount(int downCount) {
        this.downCount = downCount;
    }
 
    public String getCreateTime() {
        return createTime;
    }
 
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
}

2.1 创建工具类 - 数据库连接池

package cn.javabs.demo.utils;
 
 
import com.alibaba.druid.pool.DruidDataSourceFactory;
 
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
 
/**
 * DruidUtils数据库连接池工具类设计
 * @author Mryang
 */
public class DruidUtils {
    public static DataSource dataSource;
 
    static {
        try {
            String myFile = "druid.properties";
            InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream(myFile);
            Properties props = new Properties();
            props.load(is);
            dataSource = DruidDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            throw  new RuntimeException(e);
        }
    }
 
    /**
     * 获取数据源
     * @return
     */
    public static  DataSource getDataSource(){
        return  dataSource;
    }
 
    /**
     * 通过数据源获取连接
     * @return
     */
    public static Connection getConnection(){
        try {
            return  dataSource.getConnection();
        } catch (SQLException e) {
            throw  new RuntimeException(e);
        }
    }
}
 

2.2 创建工具类配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/testSource_demo
username=root
password=sorry

3.1 创建DAO接口

package cn.javabs.demo.dao;
 
import cn.javabs.demo.entity.Source;
 
import java.util.List;
 
public interface SourceDao {
    /**
     * 添加资源  --- 上传
     * @param source
     * @return
     */
    int saveSource(Source source);
 
    /**
     * 删除资源
     * @param id
     * @return
     */
    int removeSource(int id);
 
    /**
     * 修改资源
     * @param source
     * @return
     */
    int editSource(Source source);
 
    /**
     * 查询全部资源
     * @return
     */
    List<Source> getAllSources();
 
    /**
     * 根据编号查询资源
     * @param id
     * @return
     */
    Source getSourceById(int id);
 
    /**
     * 根据模糊得注意名称查询资源 - 目前不开发本功能
     * @param sourcename
     * @return
     */
    List<Source>  getSourceByLikeName(String sourcename);
}

3.2 创建DAO接口的实现类

package cn.javabs.demo.dao.impl;
 
import cn.javabs.demo.dao.SourceDao;
import cn.javabs.demo.entity.Source;
import cn.javabs.demo.utils.DruidUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
 
import java.sql.SQLException;
import java.util.List;
 
public class SourceDaoImpl implements SourceDao {
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
 
    @Override
    public int saveSource(Source source) {
        try {
            int result = qr.update("insert into source(author,sourceName,sourcePath,downCount,createTime) values (?,?,?,?,?)",
                    source.getAuthor(),
                    source.getSourceName(),
                    source.getSourcePath(),
                    source.getDownCount(),
                    source.getCreateTime()
            );
            return result;
        } catch (SQLException e) {
            throw new  RuntimeException(e);
        }
    }
 
    @Override
    public int removeSource(int id) {
        try {
            return qr.update("delete from source" ,id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
 
    @Override
    public int editSource(Source source) {
        try {
            return qr.update("update source set author = ? ,sourceName = ?,sourcePath = ?,downCount = ?,createTime = ? where id = ?" ,
                    source.getAuthor(),
                    source.getSourceName(),
                    source.getSourcePath(),
                    source.getDownCount(),
                    source.getCreateTime(),
                    source.getId()
            );
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
 
    @Override
    public List<Source> getAllSources() {
        try {
            return qr.query("select * from source" ,new BeanListHandler<Source>(Source.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
 
    @Override
    public Source getSourceById(int id) {
        try {
            return qr.query("select * from source where id  = ?" ,new BeanHandler<Source>(Source.class),id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
 
    @Override
    public List<Source> getSourceByLikeName(String sourcename) {
        return null;
    }
}

4.1 创建Service接口

package cn.javabs.demo.service.impl;
 
import cn.javabs.demo.dao.SourceDao;
import cn.javabs.demo.dao.impl.SourceDaoImpl;
import cn.javabs.demo.entity.Source;
import cn.javabs.demo.service.SourceService;
 
import java.util.List;
import java.util.UUID;
 
public class SourceServiceImpl implements SourceService {
 
    SourceDao sourceDao = new SourceDaoImpl();
    /**
     * 添加资源
     *
     * @param source
     * @return
     */
    @Override
    public int addSource(Source source) {
        return sourceDao.saveSource(source);
    }
 
 
    /**
     * 删除资源
     *
     * @param id
     * @return
     */
    @Override
    public int delSource(int id) {
        return sourceDao.removeSource(id);
    }
 
    /**
     * 修改资源
     *
     * @param source
     * @return
     */
    @Override
    public int updateSource(Source source) {
        return sourceDao.editSource(source);
    }
 
    /**
     * 查询所有资源
     *
     * @return list
     */
    @Override
    public List<Source> findAllSources() {
        String stringId = UUID.randomUUID().toString();
        return sourceDao.getAllSources();
    }
 
    /**
     * 根据id查询资源
     *
     * @param id
     * @return
     */
    @Override
    public Source findSourceById(int id) {
        return sourceDao.getSourceById(id);
    }
 
    /**
     * 模糊查询
     *
     * @param sourcename
     * @return
     */
    @Override
    public List<Source>  findSourceByLikeName(String sourcename) {
        return sourceDao.getSourceByLikeName(sourcename);
    }
 
}

4.2 创建Service接口的实现类

package cn.javabs.demo.service.impl;
 
import cn.javabs.demo.dao.SourceDao;
import cn.javabs.demo.dao.impl.SourceDaoImpl;
import cn.javabs.demo.entity.Source;
import cn.javabs.demo.service.SourceService;
 
import java.util.List;
import java.util.UUID;
 
public class SourceServiceImpl implements SourceService {
 
    SourceDao sourceDao = new SourceDaoImpl();
    /**
     * 添加资源
     *
     * @param source
     * @return
     */
    @Override
    public int addSource(Source source) {
        return sourceDao.saveSource(source);
    }
 
 
    /**
     * 删除资源
     *
     * @param id
     * @return
     */
    @Override
    public int delSource(int id) {
        return sourceDao.removeSource(id);
    }
 
    /**
     * 修改资源
     *
     * @param source
     * @return
     */
    @Override
    public int updateSource(Source source) {
        return sourceDao.editSource(source);
    }
 
    /**
     * 查询所有资源
     *
     * @return list
     */
    @Override
    public List<Source> findAllSources() {
        String stringId = UUID.randomUUID().toString();
        return sourceDao.getAllSources();
    }
 
    /**
     * 根据id查询资源
     *
     * @param id
     * @return
     */
    @Override
    public Source findSourceById(int id) {
        return sourceDao.getSourceById(id);
    }
 
    /**
     * 模糊查询
     *
     * @param sourcename
     * @return
     */
    @Override
    public List<Source>  findSourceByLikeName(String sourcename) {
        return sourceDao.getSourceByLikeName(sourcename);
    }
 
}

5. 创建Servlet

package cn.javabs.demo.servlet;
 
import cn.javabs.demo.entity.Source;
import cn.javabs.demo.service.SourceService;
import cn.javabs.demo.service.impl.SourceServiceImpl;
import org.apache.commons.beanutils.BeanUtils;
 
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
 
/**
 * 资源管理
 *  |-- 上传
 *  |-- 下载
 */
@WebServlet("/sourceServlet")
@MultipartConfig(maxFileSize = 1024*50*1024)
public class SourceServlet extends HttpServlet {
 
    Source source = new Source();
    SourceService sourceService = new SourceServiceImpl();
 
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet( request,  response);
    }
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
//        response.setContentType("text/html;charset=utf-8");
 
        String op = request.getParameter("op");
        switch (op){
            case "uploadSource":
                uploadSource(request,response);
                break;
            case "downloadSource":
                downloadSource(request,response);
                break;
            case "delSource":
                delSource(request,response);
                break;
            case "findAllSources":
                findAllSources(request,response);
                break;
            default:
                System.out.println("没有找到指定参数");
        }
 
 
    }
 
    /**
     * 删除资源
     * @param request
     * @param response
     */
    private void delSource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String id = request.getParameter("id");
        int sourceId = Integer.parseInt(id);
        int resultRows  =  sourceService.delSource(sourceId);
        if (resultRows > 0){
            request.setAttribute("msg","资源删除成功!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
        }else{
            request.setAttribute("msg","删除资源失败,请核查相关信息!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
        }
    }
 
    /**
     * 下载资源
     * @param request
     * @param response
     */
    private void downloadSource(HttpServletRequest request, HttpServletResponse response) {
        String id = request.getParameter("id");
        int sourceId = Integer.parseInt(id);
        source  =  sourceService.findSourceById(sourceId);
        // 如果有资源,就可以进行下载
        if (source != null){
            // 获取资源文件的路径和名称
            String sourcePath = source.getSourcePath();
            String sourceName = source.getSourceName();
 
            String fileName = sourcePath + sourceName;
 
            System.out.println("fileName:" + fileName);
 
            File file = new File(fileName);
            if (file.exists()){
                try {
                    FileInputStream fileInputStream = new FileInputStream(file);
                    String filename = URLEncoder.encode(file.getName(), "utf-8");
                    byte[] b = new byte[fileInputStream.available()];
                    fileInputStream.read(b);
                    response.setCharacterEncoding("utf-8");
                    response.setHeader("Content-Disposition","attachment; filename="+filename+"");
                    //获取响应报文输出流对象
                    ServletOutputStream out =response.getOutputStream();
                    //输出
                    out.write(b);
                    out.flush();
                    out.close();
                } catch (Exception e) {
                    throw  new RuntimeException(e);
                }
            }
        }
    }
 
    /**
     * 查询所有资源
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void findAllSources(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Source> sources = null;
        sources = sourceService.findAllSources();
        System.out.println("sources:"+sources);
 
        Iterator<Source> it = sources.iterator();
        while (it.hasNext()){
            source = it.next();
 
            if (source.getCreateTime().contains(".")){
                int index = source.getCreateTime().indexOf(".");
                String newTime = source.getCreateTime().substring(0, index);
                source.setCreateTime(newTime);
                // sources.add(source);
            }
        }
        System.out.println(sources.size());
        if (sources.size()>0 && sources != null){
            request.setAttribute("sources",sources);
            request.getRequestDispatcher("/sourceList.jsp").forward(request,response);
        }else{
            request.setAttribute("msg","新闻查询失败!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
        }
 
    }
 
    /**
     * 上传资源
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void uploadSource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 定义路径
        String realPath = this.getServletContext().getRealPath("/upload/");
 
        // 2. 如果该文件夹不存在、则帮我创建出来
        File file = new File(realPath);
        if (!file.exists()){
            file.mkdirs();
        }
        Part sourceName = null;
 
        try {
            // 3. 获取上传文件
            sourceName = request.getPart("sourceName");
        } catch (Exception e) {
            throw new RuntimeException("\"文件上传失败,请上传小于5kb的文件\""+e);
        }
 
 
        // 4. 获取上传文件的文件名
        String fileName = sourceName.getSubmittedFileName();
 
        //4.2 更改上传文件名称:
        String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date());
        // 如果文件名称存在
        if (!fileName.equals("") || fileName != null){
            fileName = currentDate + "_" + fileName;
        }
 
        sourceName.write(realPath+"/"+fileName);
 
        source.setSourcePath(realPath);
        source.setSourceName(fileName);
 
        try {
            BeanUtils.populate(source,request.getParameterMap());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
 
        System.out.println("[Source:]" + source);
 
        int resultRows = sourceService.addSource(source);
 
        System.out.println("4545");
 
        if (resultRows > 0){
            request.setAttribute("msg","添加资源成功!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
        }else{
            request.setAttribute("msg","资源添加失败,请核查相关信息!");
            request.getRequestDispatcher("/message.jsp").forward(request,response);
        }
 
 
    }
}


6.设计上传页面

6.1 添加资源 - 上传页面

<%--
  Created by IntelliJ IDEA.
  User: Mryang
  Date: 2019/6/16
  Time: 16:53
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/admin/resource/css/style.css"/>
</head>
<body>
<div class="formbody">
    <div class="formtitle"><span>资源信息</span></div>
    <form action="${pageContext.request.contextPath}/sourceServlet?op=uploadSource" method="post" enctype="multipart/form-data">
 
        <ul class="forminfo">
            <li>
                <label>上传人</label>
                <input class="dfinput" type="text" name="author" id="entityauthor"
                       class="dfinput" data-rule-required="true"/>
            </li>
            <li>
                <label>资源文件</label>
                <input class="dfinput" type="file" name="sourceName" id="entitysourceName"
                       class="dfinput" data-rule-required="true"/>
            </li>
        </ul>
    </form>
</div>
</body>
</html> 

6.2 查询资源 - 下载文件

<%--
  Created by IntelliJ IDEA.
  User: Mryang
  Date: 2019/6/16
  Time: 17:19
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
    String contextPath = request.getContextPath();
    pageContext.setAttribute("basePath", contextPath);
%>
<html>
<head>
    <title>用户列表</title>
    <script src="${pageContext.request.contextPath}/admin/resource/js/jquery-1.7.2.min.js" type="text/javascript"
            charset="utf-8"></script>
    <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/admin/resource/css/style.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $(".click").click(function () {
                $(".tip").fadeIn(200);
            });
            $(".find").click(function () {
                // $("#listform").submit();
                $("#listform").click(function () {
                    window.location.href = "${basePath}/navigationServlet?op=findNavigationByLikeName&name=" + $("#unm").val();
                });
            });
            $(".tiptop a").click(function () {
                $(".tip").fadeOut(200);
            });
            $(".sure").click(function () {
                <%--window.location = "${basePath}${opServlet}?op=delete&id=" + $(".sure_value").val();--%>
                window.location.href = "${basePath}/navigationServlet?op=delNavigation&id=" + $(".sure_value").val();
                $(".tip").fadeOut(100);
            });
            $(".cancel").click(function () {
                $(".tip").fadeOut(100);
            });
        });
 
        function delete_confirm(e, id) {
            $(".tip").fadeIn(200);
            $(".sure_value").val(id);
        }
 
        function gotoPage(page) {
            $("#pageNo").val(page);
            $("#listform").submit();
        }
    </script>
</head>
<body>
<div class="place">
    <span>位置:</span>
    <ul class="placeul">
        <li><a href="#">导航栏管理</a></li>
        <li><a href="#">导航栏列表</a></li>
    </ul>
</div>
<form action="${basePath}/userServlet?op=findUserByLikeName&username=" method="post" id="listform">
    <div class="rightinfo">
        <div class="tools">
            <ul class="toolbar">
                <li style="padding-right: 0px;">
                    &nbsp;&nbsp; 导航栏名称:<input type="text" name="author" id="unm" class="dfinput" style="width: 150px;">
                </li>
                <li class="find">
                    <span><img src="${basePath}/admin/resource/img/t06.png"/> </span>查询
                </li>
            </ul>
        </div>
        <table class="tablelist">
            <thead>
            <tr>
                <th>上传人</th>
                <th>文件名称</th>
                <th>下载次数</th>
                <th>创建时间</th>
                <th width="150px">操作</th>
            </tr>
            </thead>
            <tbody>
            <c:forEach items="${sources}" var="item">
                <tr>
                    <td>
                            ${item.author }
                    </td>
                    <td>
                            ${item.sourceName }
                    </td>
                    <td>
                            ${item.downCount }
                    </td>
                    <td>
                            ${item.createTime }
                    </td>
                    <td>
                        <a class="tablelink"
                           href="${pageContext.request.contextPath}/sourceServlet?op=downloadSource&id=${item.id}">【
                            下载】</a>
                        <a class="tablelink"
                           href="${pageContext.request.contextPath}/navigationServlet?op=updateNavigationView&id=${item.id}">【修改】</a>
                        <a class="tablelink" href="javascript:delete_confirm(this,'${item.id}')">【 删除】</a>
                    </td>
                </tr>
            </c:forEach>
            </tbody>
        </table>
        <div class="tip">
            <div class="tiptop">
                <span>提示信息</span><a></a>
            </div>
 
            <div class="tipinfo">
                <span><img src="${basePath}/admin/resource/img/ticon.png"/> </span>
                <div class="tipright">
                    <p>是否确认对信息的删除 ?</p>
                    <cite>如果是请点击确定按钮 ,否则请点取消。</cite>
                </div>
            </div>
            <div class="tipbtn">
                <input type="hidden" class="sure_value" value=""/>
                <input name="" type="button" class="sure" value="确定"/>
                &nbsp;
                <input name="" type="button" class="cancel" value="取消"/>
            </div>
        </div>
    </div>
</form>
<script type="text/javascript">
    $('.tablelist tbody tr:odd').addClass('odd');
</script>
<%-- <table border="1" width="438">
     <c:forEach items="${list}"  var="l">
         <tr>
             <td>${l.id}</td>
             <td>${l.username}</td>
             <td>${l.birthday}</td>
             <td>
                 <a href="JavaScript:delUser('${l.id}')">删除</a>
                 <a href="${pageContext.request.contextPath}/userServlet?op=editUser&id=${l.id}">修改</a>
             </td>
         </tr>
     </c:forEach>
     <c:if test="${username!=admin}">
         <script>
             alert(98);
         </script>
                 <a href="${pageContext.request.contextPath}/userServlet?op=editUser&id=${l.id}">修改</a>
     </c:if>
 </table>--%>
</body>
</html>

6.3 提示页面

<%--
  Created by IntelliJ IDEA.
  User: Mryang
  Date: 2019/6/25
  Time: 14:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>提示信息</title>
</head>
<body>
    <h1>${msg}</h1>
</body>
</html>

7. 下载全套代码

GitHub演示代码下载:https://github.com/yangsir1688/UploadAndDownload_Demo

目录
相关文章
|
1月前
|
Java 应用服务中间件 数据库
Servlet实现注册登录列表页面及其相互跳转功能
Servlet实现注册登录列表页面及其相互跳转功能
49 1
|
1月前
|
前端开发 Java Maven
Eclipse里使用Servlet实现简单的登录功能
Maven是一款非常方便的Java开发插件,它可以自动管理好开发过程中需要的jar包,提升开发者们的开发效率。在这里,我手把手教给大家如何新建一个Maven项目,并实现简单的用户登录功能。
109 0
|
23小时前
|
XML 数据格式
XML配置Servlet文件,不使用注解配置路径的方法
XML配置Servlet文件,不使用注解配置路径的方法
|
8天前
|
Java Apache
基于servlet完成文件上传功能
基于servlet完成文件上传功能
18 0
|
8天前
|
SQL 数据可视化 数据库
基于jsp+servlet的javaweb实现最基本的用户注册登陆注销功能
基于jsp+servlet的javaweb实现最基本的用户注册登陆注销功能
10 0
|
1月前
|
存储 Java 应用服务中间件
Servlet执行流程&生命周期&方法介绍&体系结构、Request和Response的功能详解(2)
Servlet执行流程&生命周期&方法介绍&体系结构、Request和Response的功能详解
28 2
|
1月前
|
Web App开发 XML Java
Servlet执行流程&生命周期&方法介绍&体系结构、Request和Response的功能详解(1)
Servlet执行流程&生命周期&方法介绍&体系结构、Request和Response的功能详解
37 2
|
1月前
|
安全 前端开发 Java
10:基于Servlet模拟用户登录功能的实现与解析-Java Web
10:基于Servlet模拟用户登录功能的实现与解析-Java Web
102 3
|
10月前
|
应用服务中间件
Servlet - 匹配模式加注解源码分析
Servlet - 匹配模式加注解源码分析
58 0
|
1月前
Servlet3.0+环境下使用注解注册Servlet、Filter和Listener组件
Servlet3.0+环境下使用注解注册Servlet、Filter和Listener组件
47 2