超通俗易懂的Servlet入门教程

语言: CN / TW / HK

不怕千万人阻挡,就怕自己投降。


文章目录


概念:运行在服务器端的小程序
servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。将来我们自定义一个类,实现servlet接口,复写方法。所以servlet就是实现了Servlet接口的类。


01.Servlet快速入门

1.创建javaEE项目

2.定义一个类,实现servlet接口
public class servletDemo1 implements servlet

3.实现接口中的抽象方法

4.配置Servlet,在xml中配置。

<servlet><servlet-name>demo1</ servlet-name><servlet-class>cn.itcast.web. servlet.ServletDemo1</servlet-class>< / servlet><servlet-mapping><servlet-name>demo1</ servlet-name><url-pattern>/demo1</url-pattern>< / servlet-mapping>

执行原理:
1,当服务器接受到客户端浏览器的请求后,会解析请求uRL路径,获取访问的servlet的资源路径2.查找web.xml文件,足否有对应的<url-pattern>标签体内容。
3.如果有,则在找到对应的<servlet-class>全类名
4. tomcat会将字节码文件加载进内存,并且创建其对象
5,调用其方法。



servlet中的生命周期方法∶
1.被创建:执行init方法,只执行一次
对象被创建后再执行Servlet方法。

servlet什么时候被创建?

默认情况下,第一次被访问时,servlet被创建。可以配置其执行servlet的创建时机。

<servlet>标签下配置

1,第一次被访问时,创建
<load-on-startup>的值为负数。

2.在服务器启动时,创建
<load-on-startup>的值为0或正整数。

Servlet的init方法,只执行一次,说明一个servlet在内存中只存在一个对象,Servlet是单例的。

多个用户同时访问时,可能存在线程安全问题。

解决∶尽量不要在servlet中定义成员变量。即使定义了成员变量,也不要对修改值。

2.提供服务:执行service方法,执行多次。

每次访问Servlet时,Service方法都会被调用一次。

3.被销毁:执行destroy方法,只执行一次。

servlet被销毁时执行,服务器关闭时,Servlet被销毁。

只有服务器正常关闭时,才会执行destroy方法。

destroy方法在servlet被销毁之前执行,一般用于释放资源。

02.Servlet3.0注解配置

java6支持Serlet3.0.

java8支持Serlet4.0.

好处:支持注解配置。

可以不需要web.xml。

步骤:
1.创建JavaEE项目,选择Servelt3.0以上的,可以不创建web.xml。

2.定义一个类,实现Servlet接口。

3.复写方法。

4.在类上使用@WebServlet注解,进行配置。

因为注解是加在类上的,所以不需要关心类名,只需要关心资源路径。

@WebServlet(urlPatterns="/demo")//可以配置多个路径public class ServletDemo implement Servlet{}

或者

@WebServlet("/demo")//可以配置多个路径public class ServletDemo implement Servlet{}

IDEA与tomcat的相关配置
1.IDEA会为每一个tomcat部署的项目单独建立一份配置文件.
查看控制台的log : Using CATALINA_BASE:“c: \users\fay.Intelli]Idea2018.1\system\tomcatl_itcast"

2.工作空间项目和 tomcat部署的web项目。

tomcat真正访问的是"“tomcat部署的web项目”",“tomcat部署的web项目"对应着”"工作空间项目”的web目录下的所有资源。

WEB-INF目录下的资源不能被浏览器直接访问。

03.GenericServlet&HttpServlet(Serlvet的体系结构)

GenericServlet是Servlet的子类(子);
HttpServlet是GenericServlet的子类(孙);

问题提出:有时候我们只需要重写Servlet的service方法。然而我们继承Servlet类必须实现Servlet的所有抽象方法。这样就与我们意愿相左。有什么解决办法呢?

解决办法】:GenericServlet为Servlet的子类,并且已经(空)实现了Servlet的4个方法,只有service方法没有实现。

因此我们可以写一个类,继承GenericServlet抽象类。实现service方法即可。

将来我们在定义自己的serlvet类时,可以继承抽象类GenericServlet即可,如果需要使用到其他方法,重些该方法即可。

可以看出,此时代码书写已经很方便,但是我们以后开发并不使用这种方式。

那我们使用哪种方式呢?

【HttpServlet】:为什么要使用这个类呢?
那我们得问自己,我们实现service方法干嘛呢?

我们在写service方法体时,需要判断前端给后台的请求方式,并根据其获取数据。每次都需要判断,有没有一种方式,可以简洁的达到效果呢?
HttpServlet抽象类,帮我们把这些事情都做好了,我们只需要重写HttpServlet对应得方法即可。
如:

doGet(){//重写部分};doPost(){//重些部分};

因此我们将来需要屏蔽请求方式的处理逻辑,继承HttpServlet就显得格外好用。

HttpSerlvet:对http协议的一种封装,简化操作
因此我们以后开发:就不再定义类继承GenericServlet实现service方法。而是定义类继承HttpServlet重写doGet(),或doPost()方法。

Servlet的urlpartten配置
因为urlpattten是一个数组,所以可以为其配置多个资源路径

@WebServlet({"/d4","dd4","ff"})public class ServletDemo extends HttpServlet{}

一个servlet可以设置多个访问路径。
路径的定义规则

1./xxx2./xxx/xxx(多层路径)(/xxx/*)


3.*.do

04.HTTP

概念:Hyper Text Transfer Protocol超文本协议。
传输协议:定义了,客户端和服务器端通信超时,发送数据的格式。

请求和响应一一对应
无状态:每次请求之间相互独立,不能交互数据。
历史版本:
1.0:每一次请求响应都会建立新的连接(http1.0)
1.1:复用连接(http1.1)。



请求消息数据格式
1.请求行
请求方式 请求url 请求协议/版本
GET /index.html HTTP/1.1


HTTP协议有7种请求方式。
GET
(1.请求参数在请求行中,在url后。
(2.请求的url的长度有限制。
(3.不安全
POST:
(1.请求参数在请求体中。
(2.请求的url的长度没有限制。
(3.相对安全







2.请求头
请求头名称:请求头值
常见的请求头:
主机
User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息,可以获取其信息解决浏览器兼容性问题。



Accept:告诉服务器,我可以解析的格式。

Referer:告诉服务器,当前请求从哪里来。
作用:方式其他人盗链。统计工作。

Connection:表示连接可以复用。

3.请求空行
空行(分割POST的请求头和请求体)
4.请求体
POST才有请求体,封装了POST的请求参数。


响应消息数据格式

1.响应行
协议及版本 响应状态码 状态码描述

状态码:
1xx:服务器接收客户端消息,但没有接收完成,等待一段时间后,发送1xx状态码。
2xx:成功

3xx:302(重定向),304访问缓存
在这里插入图片描述
4xx:客户端错误,405没有对应的方法。(doGet,doPost)

5xx:服务器端错误,500代码错误,505服务器不支持客户端使用的HTTP版本。

2.响应头
头名称:值
常见的响应头:
Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式。
Content-disposition:服务器告诉客户端以什么格式打开响应体数据。
值:in-line默认值,在当前页面内打开。
attachment:以附件形式打开响应体,文件下载。
3.响应空行






4.响应体
传输的数据。

05.Request对象

1.request对象继承体系结构:
ServletRequest —接口
继承
HttpServletRequest —接口
实现
org.apache.catalina.connector.RequestFacade 类(tomcat实现了HttpServletRequest接口)




2.request和response的原理

在这里插入图片描述
1.request和response对象是由服务器创建的。我们只是使用他们。

2.request对象是来获取请求消息,response对象是来设置响应消息。

3.request的功能

3.1获取请求消息数据

获取请求行数据

**1.1获取请求方式**

String getMethod();//了解即可

**1.2获取虚拟目录**

String getContextPath();//重要

**1.3获取Servlet路径:**

String getServletPath();

**1.4获取get方式请求参数:**

String getQueryString();//了解

**1.5获取请求URI**

String getRequestURI(); 如:/day1/demo2

**1.6获取请求URL**

StringBuffer getRequestURL(); 如: http://localhost/day1/demo2

**1.7获取协议及版本**

String getProtocol();

**1.8获取客户机的IP地址**

String getRemoteAddr();

URI:统一资源标识符。(范围更大)
URL:统一资源定位符。
获取请求头数据

String getHeader(String name);//通过请求头的名称获取请求头的值
Enumeration<String>  getHeaderNames();//获取所有的请求头名称
@WebServlet("/ServletTest2")public class ServletTest2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取所有请求头名称
        Enumeration<String> headNames = request.getHeaderNames();
        //遍历
        while (headNames.hasMoreElements()){
            String name = headNames.nextElement();
            //根据名称获取请求头的值
            String value = request.getHeader(name);
            System.out.println(name+"---"+value);
        }
    }}

获取请求体数据
步骤:
1.获取流对象

BufferedReader getReader() ;//获取字符输入流,只能操作字符数据
ServletInputStream getInputStream();//获取字节输入流,可以操作所有
类型的数据(文件上传)

2.再从流对象中拿数据

@WebServlet("/ServletTest3")public class ServletTest3 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        //1.获取字符流
        BufferedReader br = request.getReader();
        //读取数据
        String line = null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }}

3.2.其他功能
1.获取请求参数的通用方式(get和post都可以)

String getParameter(String name):根据参数名获取参数值
String[] getParameterValues(String name):根据参数名获取参数值的数组
(常用于复选框)
Enumeration<String> getParameterNames():获取所有请求参数名称。
Map<String,String[]> getParameterMap():获取所有参数的map集合。

优势:屏蔽了get和post方法的不同,代码只需要写一份,再另一方法中调用另一个已经实现的方法即可,满足get和post请求。

获取请求参数中文乱码的问题处理:
get方式:tomcat8已经将get方式乱码问题解决了。
post方式:会乱码。

解决方案:在获取参数前,设置流的编码。
request.setCharacterEncoding(“utf-8”);

//设置编码request.setCharacterEncoding("utf-8");//获取请求参数String username = request.getParameter("username");

2.请求转发
在这里插入图片描述

一种在服务器内资源跳转的方式。
步骤:

1.通过request对象获取请求转发器对象:
RequestDispatcher getRequestDispatcher(String path)2.使用RequestDispatcher对象来进行转发:forward(ServletRequest request,ServletResponse response)

特点
浏览器地址栏路径没有发生变化。

只能访问当前服务器内部资源中。

转发是一次请求,多个资源使用同一个请求。
3.共享数据
在这里插入图片描述

域对象:一个有作用范围的对象,可以在范围内共享数据。
request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据。
方法:

setAttribute(String name,Object obj);存储数据
Object getAttitude(String name);通过键获取值removeAttribute(String name)通过键移除值

例子:

@WebServlet("/ServletTest4")public class ServletTest4 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletTest4被访问了");
        //存储数据到request域中
        request.setAttribute("name","MengYangchen");
        RequestDispatcher getquestDispatcher = request.getRequestDispatcher("/ServletTest5");//没有虚拟路径(项目)
        getquestDispatcher.forward(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doPost(request,response);
    }}
@WebServlet("/ServletTest5")public class ServletTest5 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletTest5被访问了");
        //获取数据
        Object name = request.getAttribute("name");
        System.out.println(name);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

结果:
在这里插入图片描述

4.获取ServletContext(对象)
返回ServletContext对象

ServletContext getServletContext()

06.Request案例(登录)

用户登录案例需求:
1.编写login.html登录页面
username &password两个输入框

2.使用Druid数据库连接池技术,操作mysql,day14数据库中user表

3.使用JdbcTemplate技术封装JDBC

4.登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您

5.登录失败跳转到FailServlet展示:登录失败,用户名或密码错误。

07.Response对象

1.请求消息:客户端发送给服务器的数据。
2.服务器端发送给客户端的数据。

功能:设置响应消息。
1.设置响应行。

void setStatus(int sc)  设置状态码

2.设置响应头。

void setHeader(String name, String value)

3.设置响应体。
获取输出流

PrintWriter getWriter()  字符输出流
ServletOutputStream getOutputStream()  字节输出流

使用输出流,将数据输出到客户端浏览器。

Response案例

1.完成重定向
在这里插入图片描述

@WebServlet("/ServletResponseTest1")public class ServletResponseTest1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("response1被访问了");
        //重定向,访问ServletResponseTest1,会自动跳转到ServletResponseTest2
        //1.设置状态码302/*        response.setStatus(302);
        //2.设置响应头location
        response.setHeader("location","/First/ServletResponseTest2");
        */

        //简单重定向方法
        response.sendRedirect("/First/ServletResponseTest2");//有虚拟路径
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}
@WebServlet("/ServletResponseTest2")public class ServletResponseTest2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("response2被访问了");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

转发的特点:
1.转发地址栏路径不变。
2.转发只能访问当前服务器下的资源。
3.转发是一次请求。
4.路径不需要带虚拟路径。
重定向的特点:
1.地址栏发生改变。
2.重定向可以访问其他站点(服务器)的资源。
3.重定向是两次请求。
4.路径需要虚拟路径。








2.服务器输出字符数据到浏览器
乱码原因:编码解码用的编码不同。
在这里插入图片描述

2.1.获取字符输出流。

//设置输出流编码response.setCharacterEncoding("utf-8");//告诉浏览器,服务器发送消息体数据的编码,建议浏览器使用该编码解码response.setHeader("content-type","text/html;charset=utf-8");//简单形式,设置编码response.setContentType("text/html;charset=utf-8");PrintWrite out = response.getWriter();//tomcat返回的对象[ISO-8859-1]。

2.2输出数据(不需要刷新)
out.write();

3.服务器输出字节数据到浏览器
3.1获取字节输出流

3.2输出数据

response.setContentType("text/html;charset=utf-8");ServletOutputStream out = response.getOutputStream();out.writer("你好".getBytes("utf-8"));

4.验证码
1.本质:图片
2.目的:防止恶意表单注册。
随机生成。
后端:servlet:



@WebServlet("/IdentifyingCodeServlet")public class IdentifyingCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int width = 100;
        int height = 50;
        //1.创建一对象,在内存中的图片(验证码图片对象)
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        //2.美化图片
        //2.1填充背景色
        Graphics g = image.getGraphics();
        g.setColor(Color.pink);
        g.fillRect(0,0,100,50);//填充矩形

        //2.2画边框
        g.setColor(Color.yellow);
        g.drawRect(0,0,width-1,height-1);

        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        //生成随机角标
        Random ran  = new Random();

        for(int i=1;i<=4;i++){
            int index = ran.nextInt(str.length());
            char ch = str.charAt(index);//随机字符
            //2.3写验证码
            g.setColor(Color.blue);
            g.drawString(""+ch,width/5*i,height/2);
        }
        //2.4画干扰线,防识别
        //随机生成坐标点
        for(int i=0;i<8;i++){
            int x1 =ran.nextInt(width);
            int x2 =ran.nextInt(width);
            int y1 =ran.nextInt(height);
            int y2 =ran.nextInt(height);
            g.setColor(Color.green);
            g.drawLine(x1,y1,x2,y2);
        }

        //3.将图片输出到页面展示
        ImageIO.write(image,"jpg",response.getOutputStream());
  

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

前端页面:

<body><h1>账号注册</h1><form action="/First/ServletTest3" method="post">
    <input type="text" placeholder="请输入用户名" name="username"><br>
    <input type="password" placeholder="请输入密码" name="password"><br>
    <input type="submit"  value="注册"><br>
    <img id="checkCode" src="/First/IdentifyingCodeServlet"/>
    <a id="change" href="">看不清换一张</a></form>
    <script>
        //1.给超链接或图片绑定单击事件
        //2.重新设置图片的src属性
        window.onload = function () {

            var a  =document.getElementById("change");
            //1.获取图片对象
            var img  = document.getElementById("checkCode");
            //2.绑定单击事件
            img.onclick = function () {
                //加时间戳
                var date = new Date().getTime();

                img.src = "/First/IdentifyingCodeServlet?"+date;//欺骗缓存,因为路径不变,浏览器会自动去找缓存,而不是服务器
            }
            a.onclick = function () {
                var date = new Date().getTime();
                img.src = "/First/IdentifyingCodeServlet?"+date;//欺骗缓存,因为路径不变,浏览器会自动去找缓存,而不是服务器
            }
        }
    </script></body>

路径的分类

1.相对路径
如:./index.html
不以/开头,以./开头的路径(可以省略不写)。
规则:找到当前资源和目标资源之间的相对位置关系。
./表示当前目录;…/上一级目录。
2.绝对路径
如:http://localhost/First/ServletResponseTest2
简略写法:/First/ServletResponseTest2
以/开头。
规则:判断定义的路径是给谁用的。
1.给客户端浏览器使用:需要加虚拟目录(超链接…项目的访问路径)
2.给服务器端使用(转发…不需要加虚拟目录)
【问题提出】在重定向写路径是我们采用的是直接书写路径,以后一旦更改了虚拟目录。所有代码将需要该过来。











【解决方案】动态获取虚拟目录。

//动态获取虚拟目String contextPath = request.getContextPath();//简单的重定向方法response.sendRedirect(contextPath+"ServletResponseTest2");

前端代码采取jsp获取虚拟目录。

08.ServletContext对象

1.概念:代表整个web应用,可以和程序的容器(服务器)来通信。

2.如何获取ServletContext对象。

1.通过request获取
request.getServletContext();2.通过HttpServlet获取this.getServletContext();//因为我们继承了HttpServlet

两者获取的ServletContext是相等的。

2.功能:
获取MIME类型。
MIME类型:在互联网通信过程中定义的一种文件数据类型。
格式:大类型/小类型 text/html


//通过HttpServlet获取SevletContext context = this.getServletContext();//定义文件名称String filename = "a.jpg";//获取MIME类型String mimeType = context.getMimeType(filename);System.out.println(mimeType);

域对象:共享数据。

1.setAttribute(String name,Object value)2.getAttribute(String name)3.removeAttribute(String name);

ServletContext对象范围:所有用户所有请求的数据。

@WebServlet("/ServletContextTest")public class ServletContextTest extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //通过httpServlet获取
        ServletContext context  = this.getServletContext();

        //设置共享数据
        context.setAttribute("name","Is me!");

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}
@WebServlet("/ServletContextTest2")public class ServletContextTest2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //通过httpServlet获取
        ServletContext context  = this.getServletContext();

        //获取共享数据
        Object a = context.getAttribute("name");
        response.setContentType("utf-8");
        PrintWriter out = response.getWriter();
        out.print(a);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

访问http://localhost/First/ServletContextTest2得到结果:
在这里插入图片描述

获取文件的真实路径
在web服务器的真实路径。
在这里插入图片描述

1.方法:String getRealPath(String path)

配置文件所在地方不同,参数书写不同。

String realPath = context.getRealPath("/a.txt");//web目录下资源访问String realPath = context.getRealPath("/WEB-INF/a.txt");//WEB-IN目录下资源访问String realPath = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下资源访问
@WebServlet("/ServletContextTest3")public class ServletContextTest3 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String realPath = context.getRealPath("/WEB-INF/classes/a.txt");
        System.out.println(realPath);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

09.文件下载案例

文件下载需求

1.页面显示超链接

2.点击超链接后弹出下载提示框

3.完成图片文件下载。
分析:
1.超链接指向的资源如果能过被浏览器解析,则在浏览器中显示,如果不能解析,则弹出下载提示框。不满足需求。

2.任何资源都必须弹出下载提示框。

3.使用响应头设置资源的打开方式。

步骤:
1.定义页面,编辑超链接href属性,指向servlet,传递资源名称filename

2.定义servlet
2.1获取文件名称
2.2使用字节输入流加载文件进内存.
2.3指定response的响应头:content-disposition:attachment;filename=xxx.2.4将数据写出到response输出流。


前端:

<body><p align="center">优质资源网站</p><a href="/First/res/img/1.jpg">表情包(未处理)</a><br><a href="/First/ServletDownLoad?filename=1.jpg">表情包</a><br><a href="/First/ServletDownLoad?filename=2.jpg">表情包2</a></body></html>

后端:

@WebServlet("/ServletDownLoad")public class ServletDownLoad extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获取请求参数,文件名称
        String filename = request.getParameter("filename");
        //2.使用字节输入流加载文件进入内存
        //2.1找到文件服务器路径
        ServletContext context = this.getServletContext();
        String realPath = context.getRealPath("/res/img/"+filename);
        //2.2用字节流关联
        FileInputStream fis = new FileInputStream(realPath);

        //3.设置reponse响应头
        //设置响应数据类型:context-type
        String mimeType = context.getMimeType(filename);
        response.setHeader("content-type",mimeType);
        //设置打开方式:content-disposition
        response.setHeader("content-disposition","attachment;filename="+filename);

        //4.将输入流的数据写出到输出流中
        ServletOutputStream sos = response.getOutputStream();
        byte[] buff = new byte[1024*4];
        int len =0;
        while ((len=fis.read(buff))!=-1){
            sos.write(buff,0,len);
        }
        fis.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }}

中文文件名的问题
【解决思路】
1.获取客户端使用的浏览器版本信息
2.根据不同的版本信息,设置filename不同的编码方式。


//1.获取user-agent请求头String agent = request.getHeader("user-agenet");//2.使用工具类方法编码文件名(自己从网上下载工具栏导入在自己创建的工具包)filename = DownLoadUtils.getFileName(agent,filename);

别害怕顾虑,想到就去做,这世界就是这样,当你把不敢去实现梦想的时候梦想就会离你越来越远,当你勇敢地去追梦的时候,全世界都会来帮你。

在这里插入图片描述
在这里插入图片描述


分享到: