Day05.XML、Dom4j、综合案例

反射

  • 认知:反射技术就是对类进行解剖,解剖出"构造器"、"成员变量"、"成员方法"
    • 构造器: 可以实例化对象
    • 成员变量:可以赋值、取值
    • 成员方法:调用方法
    • 大白话:不使用new关键字,可以实例化对象,可以访问对象中的成员
  • 反射技术,通常应用于:框架
  • 使用反射技术:
    • 核心点:Class类
      • Class类是什么呢?
        • JVM中只能执行.class字节码文件(Java程序中会存在大量的.class文件)
        • .class文件是通过类加载器读取到内存中,并基于这个.class文件创建出:Class对象
          • Class对象,就是指一个.class文件
      • Class类的作用
        • 通过Class对象,获取"构造器"、"成员方法"、"成员变量"
    • 步骤:
      • 1、获取Class对象
      • 2、基于Class对象,可以获取:构造器、成员方法、成员变量
        • 构造器:Constructor类
        • 成员方法:Method类
        • 成员变量:Field类
      • 3、使用构造器,调用newInstance(...)方法,来实例化对象
      • 4、使用成员方法,调用invoke(...)方法,来调用方法入栈执行
      • 5、使用成员变量,调用set(...)方法、get(...)方法,对变量进行赋值、取值

注解

  • 认知:

    • 注解单独使用,没有任何意义。
    • 通常注解会配合反射技术一起使用,常用于框架技术
  • 注解的定义:

    • public @interface 注解名{
          数据类型 属性名();
          数据类型 属性名() default 默认值;
          数据类型[] 属性名();
      }
      
  • 注解的使用:

    • @注解名
      public class 类{
          
          @注解名(属性名=值)
          private String 成员变量;
          
          @注解名    //@Test
          public void 成员方法(int 参数){
              
          }   
      }
      
  • 元注解:

    • 作用:限定注解的使用位置、注解的生命周期
    • @Target //指定注解的使用位置
    • @Retention //设定注解的生命周期
  • 注解解析(注解存在的意义)

    • //API:
      //判断某个对象(类、接口、成员变量、构造器、成员方法)上是否有使用注解
      boolean flag = isAnnotationPresent(注解.class)
          
      //获取某个对象上的注解对象
      注解名  对象 =   getAnnotation(注解.class)   
          
          
      数据类型 变量 = 注解对象.属性;    
      
  • 示例:

    • //前置铺垫:
      一张数据表   对应    类
      一行记录     对应    对象    
      一个字段     对应    成员变量
          
      create table t_student
      (
         sname varchar(20),
         sage  int
      );
      
      //类
      class Student{
          private String name;
          private int age;
      }
          
      
      //JDBC程序: (程序员自己编写)
      连接数据库
      创建数据库操作对象
      发送sql语句: select ...
      处理结果集
           while(rs.next()){
               
              age = rs.getInt("sage");
              name =rs.getString("sname");
               
               Student stu = new Student();
               stu.setName( name );
               stu.setAge( age );     
           }
      
      
      //框架技术: 反射+注解   (别人已经完成的)
      //1、自定义一些注解
      public @interface Entity{  //实体
          String table();  //表名
      }   
      public @interface property{ //属性
          String name();
      }
      //2、解析@Entity、@Property注解
       扫描某些包下的的类,发现这些类上有@Entity@Property注解,就会利用反射
       利用反射技术: 获取 Student.class 对象 (Class对象)
                    解析 类上的@Entity注解
       利用反射技术: 获取所有的成员变量 : 
                    解析所有成员变量上的@Property注解
                    
      
           
      
      //JDBC程序中使用框架技术中注解 ( 程序员自己编写 )
      
      @Entiry(table="t_student")  //表示当前的Student类和数据表t_student关联
      class Student{
          
          @Property(name="sname") //表示当前的成员变量name和字段sname关联
          private String name;
          
          @Property(name="sage") //表示当前的成员变量age和字段sage关联
          private int age;
      }
      
      
      

动态代理

  • 动态代理,提供了一个代理的对象,有了代理对象后,当访问某个方法时,会被代理对象拦截(拦截后可以对方法进行前增强、后增强【代理对象不会破坏原有方法的代码】)

  • 动态代理的特点:

    • 动态的创建.class文件
    • 动态的加载.class文件到内存中(创建Class对象)
      • Proxy类需要一个"类加载器"
    • 对程序员来讲,不需要书写代理类
  • 动态代理的代码实现:

    • 代理对象 = Proxy.newProxyInstance(类加载器 , 父接口 , 处理器)
      
    • 类加载器: 动态的加载.class文件
      
      父接口 : 代理类和被代理类需要拥有共同的父接口
          
      处理器: 代理对象拦截了方法后,对方法进行前增强、后增强,是由处理器来书写逻辑    
      
    • 代理对象  = Proxy.newProxyInstance(
                   类.class.getClassLoader(), //类加载器
                   被代理类.class.getInterfaces(), //父接口
                   
                   new InvocationHandler(){
                       public Object invoke(
                                             Object 代理对象, 
                                             Method 被拦截的方法对象 ,
                                             Object[] 方法中的实参
                                            ){
                           
                           
                           //业务逻辑
                       }
                          
                   }
                 )
      

XML,Dom4j、综合案例

今日内容

  • XML

  • Dom4j(解析xml文件)

  • 综合案例

教学目标

今日重点

1.书写xml
2.理解如何解析xml

第一章 XML

1 XML概述

目标

  1. 什么是XML

什么是XML

  1. 英文:eXtensible Markup Language 可扩展的标记语言,由各种标记(标签,元素)组成。
  2. 可扩展:所有的标签都是自定义的,可以随意扩展的。如:<abc/>,<姓名>
  3. 标记语言:整个文档由各种标签组成。清晰,数据结构化!
  4. XML是通用格式标准,全球所有的技术人员都知道这个东西,都会按照XML的规范存储数据,交互数据!!

XML作用

作用:总体上来说,就是为了存储维护数据的。

  1. 数据交换:不同的计算机语言之间,不同的操作系统之间,不同的数据库之间,进行数据交换。
    1552221268464

  2. 配置文件:在后期我们主要用于各种框架的配置文件基本天天见。

比如我们很快会学到连接池:c3p0-config.xml

1552221310087

小结

  1. xml是什么?

    • 可扩展的标记语言
      • 标记:标签。例:
    • xml文件是由N多个标签组成的
  2. 主要有哪两个作用?

  • 存储数据
  • 配置文件

2 编写第1个XML文件

需求

编写xml文档,用于描述人员信息,person代表一个人员,id是人员的属性代表人员编号。人员信息包括age年龄、name姓名、sex性别信息。

使用Java类去描述:

class Person{
  String id;
  int age;
  String name;
  String sex;
}

Person p = new Person("1","张三",18,"男");

效果

1552352318788

步骤

  1. 选择当前项目鼠标右键新建

    新建一个File命名时,以 .xml结尾。这个文件就是xml文件

1552221478628

  1. 编写person.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <peopel>
        <person>
            <id>1</id>
            <name>张三</name>
            <age>18</age>
            <sex></sex>
        </person>
        
        <person>
            <id>2</id>
            <name>李四</name>
            <age>20</age>
            <sex></sex>
        </person>
    
    </peopel>
    
  2. 通过浏览器解析XML的内容

    1552221534649

  • 注:XML以后通过Java来进行解析,很少直接在浏览器上显示。

小结:

  • xml文件,通常以.xml作为后缀名
  • xml文件中的首行必须书写:
  • xml文件通常是由成对标签(开始标签、结束标签)组成

3 XML的组成:声明和元素

目标

  1. XML文档中的声明
  2. XML文档中的元素

XML组成

  1. 声明
  2. 元素(标签)
  3. 属性
  4. 注释
  5. 转义字符【实体字符】
  6. CDATA 字符区

文档声明

<?xml version="1.0" encoding="utf-8" ?>    固定格式
  1. IDEA会自动提示。

  2. 文档声明必须为<?xml开头,以?>结束

  3. 文档声明必须从文档的1行1列位置开始,必须在xml文档中的首行首列

  4. 文档声明中常见的两个属性:

    • version:指定XML文档版本。必须属性,这里一般选择1.0;
    • encoding:指定当前文档的编码,可选属性,默认值是utf-8;

元素(标签、标记)

格式1:  <person> 标签体 </person>   有标签体的标签
格式2:  <person/>  没有标签体的标签 (空标签)
  1. 元素是XML文档中最重要的组成部分;

  2. 普通元素的结构由开始标签、元素体、结束标签组成。【格式1】

  3. 元素体:元素体可以是元素,也可以是文本,例如:

    <person> 
      标签中可以包含另一个标签  
      <name>张三</name>  
    </person>
    
  4. 空元素:空元素只有标签,而没有结束标签,但元素必须自己闭合,例如:

    <sex/>
    
  5. 元素命名

    • 区分大小写
    • 不能使用空格,不能使用冒号
    • 不建议以XML、xml、Xml开头
    • 标签名不能数字开头,可以有数字
    • 可以使用下划线

    可以保持与Java命名标识符一样的规则

  6. 格式化良好的XML文档,有且仅有一个根元素。

错误演示:

元素没有结束

1552352828511

元素大写小写不一致

1552352880848

xml中多个根

1552352959286

小结

  1. 声明有哪两个常用的属性?

    • version
    • encoding
  2. 一个XML有几个根元素?

    • 有且只能有一个根元素
  3. XML标签命名不能有什么符号?

    • 不能使用关键字xml、XML
    • 不能有空格、不能有冒号
    • 不能以数字作为开头

4 XML的组成:属性、注释和转义字符

属性的语法

<person id="110">
  1. 属性是元素的一部分,它必须出现在元素的开始标签中

  2. 属性的定义格式:属性名=“属性值”,其中属性值必须使用单引或双引号括起来

  3. 一个元素可以有0~N个属性,但一个元素中不能出现同名属性

  4. 属性名不能使用空格 , 建议不要使用冒号等特殊字符,且必须以字母开头

    建议以Java的标识符定义规则做参考

<person id="123">
	<name>张三</name>
</person>

注释

<!-- 注释内容 -->

<!-- 
注释内容 1
注释内容 2
-->

XML的注释与HTML相同,既以<!--开始,-->结束。不能嵌套。

Java中注释:

// 单行
/* */ 多行注释
/** */ 文档注释

XML注释:

<!-- 注释内容 -->
<!--<person>注释</person>-->  <!-- 快捷键:Ctrl+/ :可以将整行进行注释-->
<person>三生三世</person> <!-- 快捷键:Ctrl+Shift+/:局部注释-->

转义字符[实体字符]

​ XML中的实体字符与HTML一样。因为很多符号已经被文档结构所使用,所以在元素体或属性值中想使用这些符号就必须使用实体字符

1552353386585

字符 预定义的转义字符 说明
< &lt; 小于(less than)
> &gt; 大于(greater than)
" &quot; 双引号(quotation)
' &apos; 单引号(apostrophe)
& &amp; 和号(ampersand )

注意:严格地讲,在 XML 中仅有字符 "<"和"&" 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。

转义字符应用示例:

​ 假如您在 XML 文档中放置了一个类似 "<" 字符,那么这个文档会产生一个错误,这是因为解析器会把它解释为新元素的开始。因此你不能这样写:

<message>if salary < 1000 then </message>

为了避免此类错误,需要把字符 "<" 替换为实体引用,就像这样:

<message>if salary &lt; 1000 then</message>

小结

  1. 属性必须出现在标签哪个位置?

    • 和开始标签绑定在一起,书写在开始标签元素的后面

    • <person id="属性值">
      </person>
      
  2. 同一个标签是否可以有同名的属性?

    • 不可能。
    • 允许有多个属性,属性之间使用空格分隔,但不能出现相同名称的属性
  3. 为什么要有转义字符(实体字符)?

    • 在xml文件中,一些特定的符号已经被xml使用了。例:> & 等
    • 希望在xml文档中,使用特定的符号,需要用:转义字符
      • < => &lt;
      • & => &amp;
  4. 注释的快捷?

    • ctrl + /

5 XML的组成:字符区(了解)

当大量的转义字符出现在xml文档中时,会使XML文档的可读性大幅度降低。这时如果使用CDATA段就会好一些。

CDATA (Character Data)字符数据区,格式如下:

<![CDATA[
	文本数据   < >  & ; " "
]]>
  1. CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)
  2. CDATA 部分由 <![CDATA[开始,由 ]]> 结束;

例如:

<![CDATA[
    if salary < 1000 then
]]

快捷模板:CD 回车

image-20200103105134226

注意:

​ CDATA 部分不能包含字符串 "]]>"。也不允许嵌套的 CDATA 部分。

​ 标记 CDATA 部分结尾的 "]]>" 不能包含空格或折行。

小结:

  • 字符区的特点:
    • 原样显示(书写的内容不会被xml解析器解析)

6 DTD约束(能够看懂即可)

1 什么是DTD

DTD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。

2 DTD约束的实现和语法规则(看懂dtd约束,书写符合规范的xml文件)

开发中,我们不会自己编写DTD约束文档,通常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。

需求:接下来我们创建一个dtd约束文档,然后按照约束文档书写符合规范的xml文件。

我们先新建一个books.xml文件。

第一步:在项目下面创建一个dtd的文件夹,然后选中文件夹,鼠标右击,新创键一个books.xml文件

第二步:我们先书写books.xml文件中的内容:

<?xml version="1.0" encoding="UTF-8"?>
<books>
	<book>
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
	</book>
	<book>
		<name>java从入门到精通</name>
		<author>黑旋风</author>
		<price>88.8</price>
	</book>
</books>

经过上述四步我们就将books.xml文件书写完毕,接下来我们开始书写DTD约束。

关于DTD约束我们能够看懂即可。如下所示就是上述books.xml文件引入了DTD约束。

简单的DTD约束就写好了,如下所示:

我们直接将如下约束复制到我们上述书写好的books.xml文件中即可,能够读懂即可。

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
	1.在xml内部定义dtd约束的声明 : <!DOCTYPE 根元素 [元素声明]>
	2.xml的元素(标签)的声明: <!ELEMENT 元素名称 (元素内容)>
		<!ELEMENT books (book)> 表示books标签下是book标签
	    <!ELEMENT book (name,author,price)> 表示的是book标签下出现的name,author,price子标签
	    <!ELEMENT name (#PCDATA)> 表示name标签中出现的内容是文本
 -->
 <!DOCTYPE books [
 	<!--约束根标签 book* 表示books标签下可以有多个book子标签
 		* + ? 和正则表达式中表示的意思是一样的
		* :  0 1 n
		+ : 1 n
		? :  0 1
 	-->
 	<!ELEMENT books (book*)>
 	<!--约束book标签-->
 	<!ELEMENT book (name,author,price)>
 	<!--约束name,author,price标签 
 		但是这三个标签下就是文本了
 		#PCDATA 表示标签下内容是文本
 	-->
 	<!ELEMENT name (#PCDATA)>
 	<!ELEMENT author (#PCDATA)>
 	<!ELEMENT price (#PCDATA)>
 ]>
<books>
	<book>
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
	</book>
	<book>
		<name>java从入门到精通</name>
		<author>黑旋风</author>
		<price>88.8</price>
	</book>
</books>

说明:

1)xml中出现的标签,也叫做元素。那么我们书写的约束可以规范xml中到底能出现哪些标签。除此之外都不能出现。

所以xml中出现的标签都需要我们开发者在dtd约束中声明一下。只有声明了这个标签,xml中才能出现这个标签。如果约束中没有声明,那么xml中就不能出现。

所以,xml中出现的标签需要使用如下的语法(也就是xml中元素的声明语法)。

在xml内部定义dtd约束的声明 :

 <!DOCTYPE 根元素 [元素声明]>

xml的元素(标签)的声明:

<!ELEMENT 元素名称 (元素内容)>

元素名:自定义。

元素内容包括:符号、数据类型和标签。

常见符号:? * + () |

常见数据类型:#PCDATA 表示内容是文本,不能是子标签。

标签:就是正常子标签。

2)由于 顺序是name,author,price,所以下面的顺序也得是:

<book>
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
</book>

前后顺序不能换。

接下来对上述代码的标签添加属性,比如给book属性添加一个address地址,表示将书存放到哪里。

添加属性之后的代码如下所示:

<!DOCTYPE books [
 	<!--约束根标签 book* 表示books标签下可以有多个book子标签
 		* + ? 和正则表达式中表示的意思是一样的
 	-->
 	<!ELEMENT books (book*)>
 	<!--约束book标签-->
 	<!ELEMENT book (name,author,price)>
 	<!--约束name,author,price标签 
 		但是这三个标签下就是文本了
 		#PCDATA 表示标签下内容是文本
 	-->
 	<!ELEMENT name (#PCDATA)>
 	<!ELEMENT author (#PCDATA)>
 	<!ELEMENT price (#PCDATA)>
 	<!ATTLIST book address CDATA "图书馆"
 					id      ID    #REQUIRED
 			>
 ]>
<books>
	<book address="藏经阁" id="a1">
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
	</book>
	<book id="a2">
		<name>java从入门到精通</name>
		<author>黑旋风</author>
		<price>88.8</price>
	</book>
</books>

对上述声明属性进行解释说明:

元素名称: 表示该属性使用在哪个标签上;

属性名称: 表示在标签上添加的属性名字;

属性类型: 添加的属性类型。

属性类型有如下几种:

​ 类型 描述

​ **CDATA 值为字符数据 (character data) **

​ (en1|en2|..) 此值是枚举列表中的一个值

​ **ID 值为唯一的 id **

默认值: 表示最开始给属性的默认值。

​ 值 解释

​ 值 属性的默认值

​ **#REQUIRED 属性值是必需的 **

​ #IMPLIED 属性不是必需的

​ #FIXED value 属性值是固定的

7 Schema约束(能够看懂即可)

与dtd约束一样,schema它也是用来约束xml文件的。schema约束书写的时候,它遵守xml的语法规则。在书写schema的时候,就和书写xml文件的文档结构一样。

注意:书写schema文件的时候,它的文件扩展名是xsd。

1 书写schema约束

1、首先创建一个xml文件。然后根据xml文件书写符合规范的schema约束。

创建books.xml文件:

代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<books>
	<book>
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
	</book>
	<book>
		<name>java从入门到精通</name>
		<author>黑旋风</author>
		<price>88.8</price>
	</book>
</books>

2、接下来我们需要创建一个schema文件:

步骤:

第一步:首先进入文件创建窗口,选中工程鼠标右击,new-->File,然后进入如下页面:

第二步:输入创建的文件的名称和schema文件的后缀名xsd;

第三步:因为schema约束文件本身就是xml,所以声明xml文件的头适用于schema约束的文件。

第四步:将如下的内容复制到上述已经创建好的books.xsd中。然后我们就可以书写schema约束了。

<schema xmlns="http://www.w3.org/2001/XMLSchema" 
		   targetNamespace="http://www.example.org/books"
		   elementFormDefault="qualified">
</schema>

说明:

1)schema约束的结构和xml文件书写规范是一样的,表示对xml的声明,是根标签;

2)xmlns="http://www.w3.org/2001/XMLSchema" 表示此schema文件受到w3组织的指定的约束;

3)targetNamespace="http://www.example.org/books",叫做名称空间,这个相当于java中包的作用,区分不同约束中的不同标签。当需要被当前这个schema文件约束的xml文件,需要通过当前这个名字引入当前schema文件。

4)elementFormDefault="qualified",如果值为qualified,那么当前schema中所有的标签默认都是放在名称空间中的。如果值为unqualified,那么除了schema中的根标签在名称空间即 http://www.example.org/books 包中,其他的标签都不会在这个包中。在开发中,我们都是书写默认值qualified。

上述了解完成之后,接下来我们读一下一个完整的schema约束:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
		targetNamespace="http://www.example.org/books"
		elementFormDefault="qualified">
		<!-- 在此处书写schema约束语法 -->
		<element name="books">
			<complexType><!-- 声明books是复杂标签 -->
				<sequence><!-- 通过sequence标签指定子标签的顺序 -->
					<element name="book" maxOccurs="unbounded">
						<complexType>
							<sequence>
								<element name="name" type="string"></element>
								<element name="author" type="string"></element>
								<element name="price" type="double"></element>
							</sequence>
							<attribute name="address"></attribute>
						</complexType>
					</element>
				</sequence>
			</complexType>
		</element>
</schema>

说明:

1.xml中出现的标签需要使用这样的语法来定义。即先清楚xml中需要多少个标签,在Schema文件中就书写多少个element标签。

所以xml中第一个出现的books标签需要使用来声明。

element标签中的 name属性 就是xml中 可以书写的标签的名字。

2.为了方便schema约束的书写,我们将xml中的标签简单的分为2大类:

​ a) 简单标签 : 标签中只有文本数据;

​ b) 复杂标签:标签中有子标签或者属性加上文本数据;

在element标签中需要使用complexType声明当前的element标签name属性指定的是一个复杂标签。

如果是简单标签可以使用simpleType。

3.上述在books.xml文件中我们发现books标签是复杂标签,针对复杂标签,需要在当前的标签中书写子标签来限制当前复杂标签中的其他内容。

所以我们需要使用标签来声明books标签是一个复杂标签。并且针对复杂标签中出现的子标签,我们需要使用标签来声明子标签出现的顺序。

A)books标签中出现的子标签是book,由于book也是一个标签,所以我们也需要使用标签来声明xml中的book;

B)并且book标签也是一个复杂标签,所以我们需要使用标签来声明;

C)book标签中也有子标签,所以还需要使用来声明子标签的顺序;

D)最后发现book标签在books.xml出现了多次,所以需要给加上maxOccurs="unbounded"属性。表示book标签可以出现很多次。(没有次数的限制)

大于等于1次

5.最后在里面写上book标签中出现的3个name,author,price子标签的声明。并且针对book标签中出现的属性。我们需要使用

这样的语法来声明。注意标签的位置。

在books.xml文件中增加一个address属性:

所以在books.xsd即schema约束中添加一个属性。注意标签的位置。

2 在xml文件中引入schema约束

在books.xml文件中引入schema约束的步骤:

第一步:首先鼠标放在根标签books后面,打个空格,然后将如下内容复制到books后面

代码如下:

<books xmlns="default namespace"
		xsi:schemaLocation="{namespace} {location}"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

然后引入schema约束的图解

最终完整代码:

<?xml version="1.0" encoding="UTF-8"?>
<books xmlns="http://www.example.org/books"
		xsi:schemaLocation="http://www.example.org/books books.xsd"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		>
	<book address="藏经阁">
		<name>面试宝典</name>
		<author>锁哥</author>
		<price>78.8</price>
	</book>
	<book>
		<name>java从入门到精通</name>
		<author>黑旋风</author>
		<price>88.8</price>
	</book>
</books>

说明:

1)xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance",表示当前xml文件是一个受schema约束的实例,这里不需要改变;

2)xmlns="http://www.example.org/books",schema约束文件中的targetNamespace的值,表示在books.xml文件中引入名称空间;

3)xsi:schemaLocation="http://www.example.org/books books.xsd",

schema约束文件中的targetNamespace的值 schema约束文件的路径

第二章 Dom4j

1 XML解析

1.1 解析概述

​ 当将数据存储在XML后,我们就希望通过程序获取XML的内容。我们使用Java基础所学的IO知识是可以完成的,不过需要非常繁琐的操作才可以完成,且开发中会遇到不同问题(只读、读写)。

人们为不同问题提供不同的解析方式,使用不同的解析器进行解析,方便开发人员操作XML。

1.2 解析方式和解析器

开发中比较常见的解析方式有三种,如下:

  1. DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象

    a)优点:元素与元素之间保留结构关系,故可以进行增删改查操作。

    b)缺点:XML文档过大,可能出现内存溢出

  2. SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都触发对应的事件。

    a)优点:处理速度快,可以处理大文件

    b)缺点:只能读,逐行后将释放资源,解析操作繁琐。

  3. PULL:Android内置的XML解析方式,类似SAX。(了解)

解析器,就是根据不同的解析方式提供具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包

常见的解析器

1552305195234

2 Dom4j的基本使用

2.1 DOM解析原理及结构模型

解析原理

将整个XML文档加载到内存,生成一个DOM树,并获得一个Document对象,通过Document对象就可以对DOM树进行操作。以下面books.xml文档为例。

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="0001"> 
        <name>JavaWeb开发教程</name>
        <author>张孝祥</author>
        <sale>100.00元</sale>
    </book>
    <book id="0002">
        <name>三国演义</name>
        <author>罗贯中</author>
        <sale>100.00元</sale>
    </book>
</books>

结构模型

DOM中的核心概念就是节点,在XML文档中的元素、属性、文本,在DOM中都是节点!所有的节点都封装到了Document对象中。

结论:使用Document对象,就可以去访问DOM树中的每一个节点

引入dom4j的jar包

去官网下载 zip 包。http://www.dom4j.org/
1552305595501

通常我们会在项目中创建lib文件夹,将需要依赖的库放在这里。

库导入方式:

  1. 在IDEA中,选择项目鼠标右键--->弹出菜单-->open Module settings”-->Dependencies-->+-->JARs or directories... 找到dom4j-1.6.1.jar,成功添加之后点击"OK" 即可。

  2. 直接右键选择:Add as Library

    image-20200719205315975

小结

dom4j的解析思想,先把xml文档加载到内存中,从而得到一个DOM树,并创建一个Document对象去维护dom树。

利用Document对象,就可以去解析DOM树(解析XML文件)

回顾上午内容:

  • xml文档
    • xml是由一些标签组成
    • xml的后缀名:.xml
    • xml文档的组成内容:
      • 文档声明(固定的)
      • 标签(标记、元素)
      • 属性
      • 注释
      • 转义字符
      • 字符区
    • xml:可扩展的标记语言。所有的标记都是自定义的(书写的随意性比较高)
    • xml约束:约束xml文档
      • dtd约束
      • schema约束
        • 比dtd约束更灵活、更强大(针对数据的类型也可以约束)
  • xml解析
    • 对xml文档的内容进行解析,解析出xml中的一些数据
    • 解析方式:
      • dom4j
        • 实现原理:
          • 把整个xml文档加载到内存中,并生成一个DOM树,基于dom树创建一个Document对象
          • 利用Document对象来对xml进行解析
      • xpath

2.2 常用的方法

dom4j 必须使用核心类SaxReader加载xml文档获得Document,通过Document对象获得文档的根元素,然后就可以操作了。

SAXReader对象
方法 作用
SAXReader sr = new SAXReader(); 构造器
Document read(String url) 加载执行xml文档
Document对象
方法 作用
Element getRootElement() 获得根元素
Element对象
方法 作用
List<Element> elements(String ele ) 获得指定名称的所有子元素。可以不指定名称
Element element(String ele) 获得指定名称第一个子元素。
String getName() 获得当前元素的元素名
String attributeValue(String attrName) 获得指定属性名的属性值
String elementText(Sting ele) 获得指定名称子元素的文本值
String getText() 获得当前元素的文本内容

小结

解析xml的步骤:

  1. 创建SaxReader对象,调用read方法关联xml文件,得到一个Document对象
  2. 通过Document对象,获取根元素
  3. 获取根元素之后,就可以层层深剥,运用Element相关的API进行解析其子元素

2.3 方法演示

复制资料下的常用xml中"books.xml",内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="0001">
        <name>JavaWeb开发教程</name>
        <author>张孝祥</author>
        <sale>100.00元</sale>
    </book>
    <book id="0002">
        <name>三国演义</name>
        <author>罗贯中</author>
        <sale>100.00元</sale>
    </book>
</books>

注意:为了便于解析,此xml中没有添加约束

解析此文件,获取每本书的id值,以及书本名称,作者名称和价格.

步骤分析:

  1. 创建一个SaxReader对象,调用read方法加载一个xml文件获得文档对象
  2. 通过文档对象,获取根元素
  3. 通过根元素一层一层的进行解析子元素。
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

public class Demo01 {
    public static void main(String[] args) throws DocumentException {
        //1. 创建一个SaxReader对象,调用read方法加载一个xml文件获得文档对象
        SAXReader sr = new SAXReader();
        Document doc = sr.read("day15/xml/book.xml");

        //2. 通过文档对象,获取根元素
        Element rootElement = doc.getRootElement();

        //3. 通过根元素一层一层的进行解析子元素。
        //获取所有的子元素
        List<Element> bookElements = rootElement.elements("book");

        for (Element bookElement : bookElements) {
            //System.out.println(bookElement);
            //解析属性
            String id = bookElement.attributeValue("id");
            System.out.println("id = " + id);
            //获取子元素文本
            String name = bookElement.elementText("name");
            String author = bookElement.elementText("author");
            String sale = bookElement.elementText("sale");
            System.out.println("name = " + name);
            System.out.println("author = " + author);
            System.out.println("sale = " + sale);



            System.out.println("----------------------");
        }
        
    }
}

需求二:

将xml中文件数据解析成为java对象,每个book解析为一个book类型的对象。然后将book对象放到一个集合中存储。

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book id="0001">
        <name>JavaWeb开发教程</name>
        <author>张孝祥</author>
        <sale>100.00元</sale>
    </book>
    <book id="0002">
        <name>三国演义</name>
        <author>罗贯中</author>
        <sale>100.00元</sale>
    </book>
</books>

步骤分析:

  1. 先创建一个Book类对应book元素
  2. 创建一个ArrayList集合用来存储解析后的book对象
  3. 创建SaxReader对象,调用read方法加载xml文件,得到文档对象
  4. 通过文档对象获取根元素,然后层层解析

代码实现:

public class Demo02 {
    public static void main(String[] args) throws DocumentException {
        //定义一个集合用来存储解析的Book对象
        ArrayList<Book> books = new ArrayList<>();



        //1. 创建一个SaxReader对象,调用read方法加载一个xml文件获得文档对象
        SAXReader sr = new SAXReader();
        Document doc = sr.read("day15/xml/book.xml");

        //2. 通过文档对象,获取根元素
        Element rootElement = doc.getRootElement();

        //3. 通过根元素一层一层的进行解析子元素。
        //获取所有的子元素
        List<Element> bookElements = rootElement.elements("book");

        for (Element bookElement : bookElements) {
            //System.out.println(bookElement);
            //解析属性
            String id = bookElement.attributeValue("id");
            System.out.println("id = " + id);
            //获取子元素文本
            String name = bookElement.elementText("name");
            String author = bookElement.elementText("author");
            String sale = bookElement.elementText("sale");

            //将解析的字符串封装成为对象,放到集合
            Book book = new Book(id,name,author,sale);
            books.add(book);

        }


        //将集合遍历,打印book对象
        for (Book book : books) {
            System.out.println("book = " + book);
        }

    }
}

class Book{
    private String id;
    private String name;
    private String author;
    private String sale;

    public Book() {
    }

    public Book(String id, String name, String author, String sale) {
        this.id = id;
        this.name = name;
        this.author = author;
        this.sale = sale;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getSale() {
        return sale;
    }

    public void setSale(String sale) {
        this.sale = sale;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", sale='" + sale + '\'' +
                '}';
    }
}

3 使用xpath技术结合DOM4J技术读取xml文件(了解)

1.概念介绍

问题:通过上面的案例我们发现有个小问题.就是获取标签的过程太过繁琐。我们需要一层一层的去获取。假设xml嵌套了50层的话,对于我们开发者来说是非常繁琐的。所以我们希望应该有这样的技术,一下子直接就能获取我们所需要的标签对象。这就是我们接下来需要学习的xpath技术。

xpath技术 也是 W3C 组织制定的快速获取 xml文件中某个标签的技术。

xpath其实就像在文件系统中定位文件,所以我们学习xpath主要学习xpath的路径表达式。

2.XPath使用步骤

步骤1:导入jar包(dom4j-1.6.1.jar(dom4j核心包)和jaxen-1.1-beta-6.jar(xpath依赖的包))

image-20200506193939941

步骤2:通过dom4j的SaxReader获取Document对象

image-20200506194111624

步骤3:使用document常用的api结合xpath的语法完成选取XML文档元素节点进行解析操作。

说明:

  • Node叫做节点,DOM里面所有的类型都是Node的子类
    • 比如Document Element Attribute 都是 Node的子类
  • Node中的两个方法可以使用XPath:
方法 作用
List selectNodes("表达式") 获取符合表达式的元素集合
Element selectSingleNode("表达式") 获取符合表达式的唯一元素

3.XPath语法(了解)

在学习如何使用XPath之前,我们先将课后资料中的tianqi.xml放到项目根目录下,然后我们在演示如何使用xpath。

3.1全文搜索路径表达式方式 掌握
  • 代表无论中间有多少层,直接获取所有子元素中满足条件的元素
    • 一个“/”符号,代表逐级写路径
    • 2个“//”符号,不用逐级写路径,可以直接选取到对应的节点,是全文搜索匹配的不需要按照逐层级 重点
  • 需求1:获取天气预报里的所有湿度,不论有多少层
  • 需求2:获取广州里面的3个最高温度
//代表无论中间有多少层,直接获取所有子元素中满足条件的元素
    @Test
    public void demo03() throws Exception {
        //全文搜索    //
        SAXReader reader = new SAXReader();
        Document document = reader.read("tianqi.xml");
        //获取天气预报里的所有湿度,不论有多少层
        List<Node> list = document.selectNodes("//湿度");

        //遍历打印
        for (Node node : list) {
            System.out.println(node.getText());
        }
        System.out.println("--------------");

        //获取广州里面的3个最高温度
        List<Node> list1 = document.selectNodes("/天气预报/广州//最高温度");
        for (Node node : list1) {
            System.out.println(node.getText());
        }
    }

第三章 综合案例

学习目标:

  • 理解所谓的框架是如何实现的,如何使用框架

1、需求

需求:自定义dao层jdbc框架

  • 为了方便程序员操作数据库,让程序员更关注于sql代码层面和业务层面

2、案例效果

image-20210610210837862

image-20210610210613141

image-20210610211138286

使用到的技术:

  • 反射
  • 注解
  • 动态代理
  • xml解析:xpath

3、案例分析

自定义jdbc框架开发步骤:

1、通过软配置方式,和数据库连接

  • 解析xml配置文件,获得:driver、url、username、password
  • 德鲁伊连接池
    • 根据配置文件中的参数,创建连接池对象

2、创建@Select注解

  • 解析@Select注解中value值:select查询语句

3、创建映射类Mapper

  • 把从@Select注解中解析出来的select查询语句,赋值给Mapper中的sql成员变量

4、创建SqlSession类

  • 提供getMapper()方法,用来获取代理对象
    • 说明:程序员在获取到代理对象后,利用代理对象调用某个方法时,会被代理对象拦截处理

4、自定义JDBC框架-代码实现

4.1、Configuration

/*配置类
  1. 解析XML文件
  2. 创建德鲁伊连接池
*/
public class Configuration {
    /* 定义数据库连接对象相关属性 */
    private String driver;//驱动
    private String url;//连接地址
    private String username;//登录名
    private String password;//密码

    /* Mapper接口的全名称 */
    private String interName;

    /* 数据库连接池对象 */
    private DataSource dataSource;

    /* 映射类对象 */
    private Mapper mapper = new Mapper();


    //无参构造方法
    public Configuration() {
        try {
            //解析"config.xml"文件
            SAXReader reader = new SAXReader();
            InputStream is = Configuration.class.getClassLoader().getResourceAsStream("config.xml");
            Document doc = reader.read(is);
            //调用自定义方法: 将核心配置文件中的数据封装到Configuration类的属性中
            loadConfigXml(doc);

            //调用自定义方法: 初始化数据库连接池对象
            createDataSource();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //初始化数据库连接池对象
    private void createDataSource() throws Exception {
        //创建c3p0核心类对象
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        //使用对象调用方法将四大连接参数给数据库连接池
        cpds.setDriverClass(driver);
        cpds.setJdbcUrl(url);
        cpds.setUser(username);
        cpds.setPassword(password);

        //将cpds赋值给成员变量ds
        this.dataSource = cpds;//数据库连接池对象
    }


    //将核心配置文件中的数据封装到Configuration类属性中
    private void loadConfigXml(Document doc) {
        /*
        //使用document对象调用方法获取property标签:
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/itheima"/>
        <property name="username" value="root"/>
        <property name="password" value="itheima"/>
        */
        List<Node> list = doc.selectNodes("//property");

        //遍历List集合
        for (Node node : list) {
            //强制转换
            Element e = (Element) node;
            //获取property标签的name属性值
            String name = e.attributeValue("name");//双引号中的name是property标签的name属性 driver
            //获取property标签的value属性值
            String value = e.attributeValue("value");//双引号中的value是property标签的value属性 com.mysql.jdbc.Driver
           
            //将value的值赋值给成员变量
            switch (name) {
                case "driver":
                    this.driver = value;//数据库驱动
                    break;
                case "url":
                    this.url = value;//连接url
                    break;
                case "username":
                    this.username = value;//登录名
                    break;
                case "password":
                    this.password = value;//密码
                    break;
            }
        }

        //<package name="xxx.xxx.UserMapper"></package>
        Node node = doc.selectSingleNode("//package");
        Element e = (Element) node;
        //Mapper接口的全名称
        this.interName = e.attributeValue("name");//"xxx.xxx.UserMapper"
    }


    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getInterName() {
        return interName;
    }

    public void setInterName(String interName) {
        this.interName = interName;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Mapper getMapper() {
        return mapper;
    }

    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }
}

4.2、注解

@Select

//查询时使用的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    String[] value();
}

@ResultType

//查询结果的封装类型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ResultType {
    String value();
}

4.3、映射类:Mapper

package cn.itcast.config;

public class Mapper {
    private String sql;//存储sql语句
    private String resultType;//结果类型

    public Mapper() {
    }

    public Mapper(String sql) {
        this.sql = sql;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
}

4.4、SqlSession类

@SuppressWarnings("all")
public class SqlSession {
    /**
     * 动态代理
     */
    public <T> T getMapper(Class<T> clazz) {
        //类加载器: 负责加载代理类到内存中
        ClassLoader classLoader = SqlSession.class.getClassLoader();

        //父接口
        Class[] interfaces = {clazz};


        T mapperProxy = (T) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {

            //在调用方法时,代理对象执行invoke方法,返回List
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //创建核心配置类对象
                Configuration config = new Configuration();


                /******** 解析 @Select@ResultType *******/
                Class clazz = Class.forName(config.getInterName());

                Method[] methods = clazz.getMethods();
                //遍历数组
                for (Method m : methods) {
                    //判断是否有注解
                    boolean boo = m.isAnnotationPresent(Select.class);

                    boolean boo2 = m.isAnnotationPresent(ResultType.class);

                    if (boo && boo2) {
                        //获取@Select注解对象
                        Select select = m.getAnnotation(Select.class);
                        //获取属性值
                        String[] value = select.value();//{"select * from user"}
                        String sql = value[0];
                        //给Mapper对象中的sql成员变量赋值
                        config.getMapper().setSql(sql);

                        //获取@ResultType注解对象
                        ResultType resultType = m.getAnnotation(ResultType.class);
                        String type = resultType.value();//获取属性值
                        config.getMapper().setResultType(type);
                    }
                }
                /*******************************/


                //获取映射对象
                Mapper mapper = config.getMapper();
                //利用映射对象,获取sql语句
                String sql = mapper.getSql();

                //利用映射对象,获取类型
                String resultType = mapper.getResultType();
                Class cl = Class.forName(resultType);


                //获取数据库连接池对象
                DataSource ds = config.getDataSource();
                //利用连接池对象,获取Connection对象
                Connection conn = ds.getConnection();

                //调用自定义方法: 执行sql查询语句
                List list = queryForList(conn, sql, cl);

                return list;
            }
        });

        //代理对象返回给getMapper的调用者     UserMapper mapper = sqlSession.getMapper(UserMapper.class);//mapperProxy
        return mapperProxy;
    }
   public List queryForList(Connection conn, String sql, Class clazz) throws SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        List userList = new ArrayList();

        //通过连接对象得到预编译的语句对象
        PreparedStatement ps = conn.prepareStatement(sql);

        //执行SQL语句,得到结果集
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            //获取构造方法对象,并实例化
            Object user = clazz.getConstructor().newInstance();

            //获取所有的成员变量(包含私有成员变量)
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) { //field可以是:id name passwd age gender adddate
                //得到成员变量的名字
                String name = field.getName();

                //暴力破解: 取消权限检查
                field.setAccessible(true);

                //rs.getObject(name) 表示根据数据表的列名取出数据表中的列值 因为User类中的成员变量名必须和数据表列名一致
                //例如: name 的值是birthday 那么这里 rs.getObject(name)---》rs.getObject("age")获取数据表的年龄20
                Object table_value = rs.getObject(name);

                //void set(Object obj, Object value)给成员变量赋值,参数1:对象名 参数2:要赋的值
                field.set(user, table_value);

            }
            //user对象添加到集合中
            userList.add(user);
        }

        //释放资源
        rs.close();
        ps.close();
        conn.close();

        //返回集合
        return userList;
    }
}   

5、自定义JDBC框架的使用

5.1、数据表

CREATE TABLE user (
  id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  name varchar(30) DEFAULT NULL,
  passwd varchar(20) DEFAULT NULL,
  age int(3) DEFAULT NULL,
  gender varchar(2) DEFAULT NULL,
  adddate date DEFAULT NULL
);

# 测试数据
INSERT INTO user VALUES (null, 'itcast', '123123', '10', '男', '2020-12-11');

5.2、创建实体类

public class User {
    private int id;
    private String name;
    private String passwd;
    private int age;
    private String gender;
    private Date adddate;

    public User() {
    }

    public User(int id, String name, String passwd, int age, String gender, Date adddate) {
        this.id = id;
        this.name = name;
        this.passwd = passwd;
        this.age = age;
        this.gender = gender;
        this.adddate = adddate;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Date getAdddate() {
        return adddate;
    }

    public void setAdddate(Date adddate) {
        this.adddate = adddate;
    }


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", passwd='" + passwd + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", adddate=" + adddate +
                '}';
    }
}

5.3、UserMapper接口

public interface UserMapper {

    //查询所有用户
    @Select("select * from user")
    @ResultType("com.itcast.pojo.User")
    public abstract List queryAllUser();

}

5.4、配置文件:config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!--数据源-->
    <dataSource>
        <!--驱动-->
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <!--地址-->
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/itheima"/>
        <!--用户名-->
        <property name="username" value="root"/>
        <!--密码-->
        <property name="password" value="itheima"/>
    </dataSource>

    <!--加载映射接口-->
    <mappers>
        <package name="com.itcast.mapper.UserMapper"></package>
    </mappers>

</configuration>

5.5、测试类

public class Test1 {
    @Test
    public void testSelect() {
        //实例化
        SqlSession sqlSession = new SqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> users = mapper.queryAllUser();

        for (User u : users) {
            System.out.println(u);
        }
    }
}

今日作业

1.书写xml: person book

2.解析xml

3.熟悉综合案例流程:反射 动态代理 解析注解

4.预习

posted @   忘了鱼尾纱的猫  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
  1. 1 刘哈哈与大先生 刘心&大鹏
  2. 2 我们打着光脚在风车下跑,手上的狗尾巴草摇啊摇 等一下就回家 / -艾兜
  3. 3 哎呦 毛不易
  4. 4 夜、萤火虫和你 AniFace
我们打着光脚在风车下跑,手上的狗尾巴草摇啊摇 - 等一下就回家 / -艾兜
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词:等一下就回家/艾兜

作曲:等一下就回家/艾兜

混音:裴济逸

编曲:HYPER MUSIC

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

让我忘记时间to the midnight

the sun came out 把所有染成金色的

风风风让它吹过来

至少年轻我还记得

oh 找一个地方落下

躺在谷仓里

和你讲着小话

什么风都吹不倒它

它就像是活的

知道哪是它的家

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

我们打着光脚在那风车下跑

等一下就回家怎么才到半山腰

就让那些烦恼都随风去吧

随着稻香飘过的地方耶哎呦喂

喜欢那时候风言风语

总是习惯悲中带着笑

喜欢被无视的童言无忌

被风车带走不在

风是从哪儿来手上的狗尾巴草摇的更剧烈

稻穗也晃起来我紧握着你的手把它拍成照片

我们俩转 就像大风车

早该逃离这我转转 把云卷散了

下个地方 风筝睡醒了

乘着它走吧 飘飘 等着大风车

像在画一幅油画

陶醉你的笑容

就沿着风车走吧

不用 猜忌 下个地点

点击右上角即可分享
微信分享提示