XML,Dom4j、综合案例
反射
认知:反射技术就是对类进行解剖,解剖出"构造器"、"成员变量"、"成员方法"
- 构造器: 可以实例化对象
- 成员变量:可以赋值、取值
- 成员方法:调用方法
- 大白话:不使用new关键字,可以实例化对象,可以访问对象中的成员
反射技术,通常应用于:框架
反射技术的程序书写步骤:
- 获取Class对象
- 获取构造器
- 获取成员(成员方法、成员变量)
- 实例化对象 | 调用方法 | 给成员变量赋值取值
使用反射技术:
- 核心点:Class类
- Class类是什么呢?
- JVM中只能执行.class字节码文件(Java程序中会存在大量的.class文件)
- .class文件是通过
类加载器
读取到内存中,并基于这个.class文件创建出:Class对象- Class对象,就是指一个.class文件
- Class类的作用
- 通过Class对象,获取"构造器"、"成员方法"、"成员变量"
- Class类是什么呢?
- 步骤:
1、获取Class对象
Class cls = 类名.class; Class cls = 对象名.getClass(); Class cls = Class.forName("类的全限定名"); //回顾:同步方法(线程), 非静态方法默认有一个同步锁:this // 静态方法的同步锁:类名.class
2、基于Class对象,可以获取:构造器、成员方法、成员变量
构造器:Constructor类
Constructor c = Class对象.getConstructor();//无参构造器 Constructor c = Class对象.getConstructor(String.class);//有参构造器
成员方法:Method类
Method m = Class对象.getMethod("方法名");//获取指定方法名的无参方法 Method m = Class对象.getMethod("方法名",int.class);
成员变量:Field类
Field f = Class对象.getDeclaredField("属性名"); //针对私有成员:成员变量、成员方法、构造器,需要取消权限检查 f.setAccessible(true);//true就表示关闭本次权限检查
3、使用构造器,调用
newInstance(...)
方法,来实例化对象4、使用成员方法,调用
invoke(...)
方法,来调用方法入栈执行5、使用成员变量,调用
set(...)
方法、get(...)
方法,对变量进行赋值、取值
- 核心点:Class类
注解
认知:
- 注解单独使用,没有任何意义。
- 通常注解会配合反射技术一起使用,常用于框架技术
注解的定义:
public @interface 注解名{ 数据类型 属性名(); 数据类型 属性名() default 默认值; 数据类型[] 属性名(); } 注解中的数据类型可以有哪些: 1. 8种基本数据类型 2. 枚举 3. String 4. Class 5. 注解 6. 以上所有类型的一维数组形式
注解的使用:
@注解名 public class 类{ @注解名(属性名=值) private String 成员变量; @注解名 //@Test public void 成员方法(int 参数){ } }
元注解:
- 作用:限定注解的使用位置、注解的生命周期
- @Target //指定注解的使用位置
- @Retention //设定注解的生命周期
- SOURCE : 只能在源码中使用 。 例:@Override
- CLASS :可以用于源码中、字节码文件中 【默认】
- RUNTIME : 可以用于源码、字节码文件、程序运行 例:@Test
注解解析(注解存在的意义)
注解解析的步骤: 1、获取一个对象(构造器Constructor、成员变量Field、成员方法Method) 2、判断对象上是否有指定的注解 有: 获取对象上的注解对象 对象上有注解后,获取注解对象中的属性值 //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[] 方法中的实参 ){ //业务逻辑 } } )
动态代理:
- 代理程序中的某个类中的功能,为该功能进行增强
动态代理实现的关键步骤:
- 被代理类(例:UserServiceImpl),必须有实现接口
- 创建被代理类对象(例: new UserServiceImpl() ),交给代理对象使用
动态代理的实现:
- JDK已经提供了现成的代理对象的生成
- Proxy类
- 静态方法:newProxyInstance(类加载器 , 接口数组 , 处理器)
- Proxy创建一个子类
- 子类: 必须和被代理类实现相同的父接口
- 子类编译后是一个.class文件, 需要使用类加载器,加载.class文件到内存中
- Proxy类
XML,Dom4j、综合案例
今日内容
XML
Dom4j(解析xml文件)
综合案例
教学目标
第一章 XML
1 XML概述
目标
- 什么是XML
什么是XML
- 英文:eXtensible Markup Language 可扩展的标记语言,由各种标记(标签,元素)组成。
- 可扩展:所有的标签都是自定义的,可以随意扩展的。如:<abc/>,<姓名>
- 标记语言:整个文档由各种标签组成。清晰,数据结构化!
- XML是通用格式标准,全球所有的技术人员都知道这个东西,都会按照XML的规范存储数据,交互数据!!
XML作用
作用:总体上来说,就是为了存储维护数据的。
数据交换:不同的计算机语言之间,不同的操作系统之间,不同的数据库之间,进行数据交换。
配置文件:在后期我们主要用于各种框架的配置文件基本天天见。
比如 连接池:c3p0-config.xml 日志:logback.xml
小结
xml是什么?
- 可扩展的标记语言
- 标记:标签。例:
- xml文件是由N多个标签组成的
- 可扩展的标记语言
主要有哪两个作用?
- 存储数据
- 配置文件(主流)
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,"男");
效果
步骤
选择当前项目鼠标右键新建
新建一个File命名时,以 .xml结尾。这个文件就是xml文件
编写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>
通过浏览器解析XML的内容
- 注:XML以后通过Java来进行解析,很少直接在浏览器上显示。
小结:
- xml文件,通常以.xml作为后缀名
- xml文件中的首行必须书写: <?xml version="1.0" encoding="UTF-8" ?> (固定写法)
- xml文件通常是由成对标签(开始标签、结束标签)组成
3 XML的组成:声明和元素
目标
- XML文档中的声明
- XML文档中的元素
XML组成
- 声明
- 元素(标签)
- 属性
- 注释
- 转义字符【实体字符】
- CDATA 字符区
文档声明
<?xml version="1.0" encoding="utf-8" ?> 固定格式
IDEA会自动提示。
文档声明必须为<?xml开头,以?>结束
文档声明必须从文档的1行1列位置开始,==必须在xml文档中的首行首列==
文档声明中常见的两个属性:
- version:指定XML文档版本。必须属性,这里一般选择1.0;
- encoding:指定当前文档的编码,可选属性,默认值是utf-8;
元素(标签、标记)
格式1: <person> 标签体 </person> 有标签体的标签 (双标签)
格式2: <person/> 没有标签体的标签 (空标签) (单标签)
元素是XML文档中最重要的组成部分;
普通元素的结构由开始标签、元素体、结束标签组成。【格式1】
元素体:元素体可以是元素,也可以是文本,例如:
<person> 标签中可以包含另一个标签 <name>张三</name> </person>
空元素:空元素只有标签,而没有结束标签,但元素必须自己闭合,例如:
<sex/>
元素命名
- 区分大小写
- 不能使用空格,不能使用冒号
- 不建议以XML、xml、Xml开头
- 标签名不能数字开头,可以有数字
- 可以使用下划线
可以保持与Java命名标识符一样的规则
==格式化良好的XML文档,有且仅有一个根元素。==
错误演示:
元素没有结束
元素大写小写不一致
xml中多个根
小结
声明有哪两个常用的属性?
- version
- encoding
一个XML有几个根元素?
- 有且只能有一个根元素
XML标签命名不能有什么符号?
- 不能使用关键字xml、XML
- 不能有空格、不能有冒号
- 不能以数字作为开头
4 XML的组成:属性、注释和转义字符
属性的语法
<person id="110" id2="11">
属性是元素的一部分,它必须出现在元素的开始标签中
属性的定义格式:
属性名=“属性值”
,其中属性值必须使用单引或双引号括起来一个元素可以有0~N个属性,但一个元素中不能出现同名属性
属性名不能使用空格 , 建议不要使用冒号等特殊字符,且必须以字母开头
建议以Java的标识符定义规则做参考
<person id="123">
<name>张三</name>
</person>
注释
<!-- 注释内容 -->
<!--
注释内容 1
注释内容 2
-->
XML的注释与HTML相同,既以<!--
开始,-->
结束。不能嵌套。
Java中注释:
// 单行
/* */ 多行注释
/** */ 文档注释
XML注释:
<!-- 注释内容 -->
<!--<person>注释</person>--> <!-- 快捷键:Ctrl+/ :可以将整行进行注释-->
<person>三生三世</person> <!-- 快捷键:Ctrl+Shift+/:局部注释-->
转义字符[实体字符]
XML中的实体字符与HTML一样。因为很多符号已经被文档结构所使用,所以在元素体或属性值中想使用这些符号就必须使用实体字符
字符 | 预定义的转义字符 | 说明 |
---|---|---|
< | < | 小于(less than) |
> | > | 大于(greater than) |
" | " | 双引号(quotation) |
' | ' | 单引号(apostrophe) |
& | & | 和号(ampersand ) |
注意:严格地讲,在 XML 中仅有字符 "<"和"&" 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。
转义字符应用示例:
假如您在 XML 文档中放置了一个类似 "<" 字符,那么这个文档会产生一个错误,这是因为解析器会把它解释为新元素的开始。因此你不能这样写:
<message>if salary < 1000 then </message>
为了避免此类错误,需要把字符 "<" 替换为实体引用,就像这样:
<message>if salary < 1000 then</message>
小结
属性必须出现在标签哪个位置?
和开始标签绑定在一起,书写在开始标签元素的后面
<person id="属性值"> </person>
同一个标签是否可以有同名的属性?
- 不可能。
- 允许有多个属性,属性之间使用空格分隔,但不能出现相同名称的属性
为什么要有转义字符(实体字符)?
- 在xml文件中,一些特定的符号已经被xml使用了。例:> & 等
- 希望在xml文档中,使用特定的符号,需要用:转义字符
- < =>
<
- & =>
&
- < =>
注释的快捷?
- ctrl + /
5 XML的组成:字符区(了解)
当大量的转义字符出现在xml文档中时,会使XML文档的可读性大幅度降低。这时如果使用CDATA段就会好一些。
CDATA (Character Data)字符数据区,格式如下:
<![CDATA[
文本数据 < > & ; " "
]]>
- CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)
- CDATA 部分由
`<![CDATA[`
开始,由`]]>`
结束;
例如:
<![CDATA[
if salary < 1000 then
]]
快捷模板:CD 回车
注意:
CDATA 部分不能包含字符串 "]]>"。也不允许嵌套的 CDATA 部分。
标记 CDATA 部分结尾的 "]]>" 不能包含空格或折行。
小结:
- 字符区的特点:
- 原样显示(书写的内容不会被xml解析器解析)
6 XML约束:DTD约束
目标
- XML有哪两种约束
- 了解DTD约束
1. XML约束
在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
常见的xml约束:DTD、Schema
注意:我们对于约束的要求是能通过已写好的约束文件编写xml文档.
2. 约束体验
体验效果说明:当编写xml文档时不符合指定dtd约束时,进行提示xml编写错误,如下图:
体验步骤:
步骤1:复制bookshelf.dtd文件
步骤2:bookshelf.dtd文件内容如下
<!ELEMENT 书架 (书+)> <!-- 声明了根元素,并指定了根元素下必须有的一个子元素 -->
<!-- 声明子元素 书 -->
<!ELEMENT 书 (书名,作者,售价)><!--约束元素书的子元素必须为书名、作者、售价-->
<!-- , 表示必须按照元素的顺序出现 -->
<!ELEMENT 书名 (#PCDATA)> <!-- #PCDATA : 表示文本内容 -->
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>
步骤三:新建books.xml,代码如下
引用 <!DOCTYPE 根元素 SYSTEM "dtd约束文件的路径">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE 书架 SYSTEM "bookshelf.dtd"><!--指定使用bookshelf.dtd文件约束当前xml文档-->
<书架>
<书>
<书名>JavaWeb开发教程</书名>
<作者>张孝祥</作者>
<售价>100.00元</售价>
</书>
<书>
<书名>三国演义</书名>
<作者>罗贯中</作者>
<售价>100.00元</售价>
<测试>hello</测试><!--不符合约束,书的子元素必须为书名、作者、售价-->
</书>
</书架>
步骤四:idea开发工具books.xml的dtd约束验证不通过的效果如下
3. DTD学习要求
在企业实际开发中,我们很少自己编写DTD约束文档,通常情况下通过框架提供的DTD约束文档编写对应的XML文档。所以这一知识点的要求是可以根据DTD约束文档内容编写XML文档。
小结:
在xml文件中引入DTD约束文件
<!DOCTYPE 根元素 SYSTEM "约束文件的路径">
7 XML约束:Schema约束
目标
了解Schema约束
1 概念
Schema 语言也也叫做 XSD(XML Schema Definition)。
其本身也是XML格式文档,但Schema文档扩展名为xsd,而不是xml。
Schema 功能更强大,数据类型约束更完善。 比DTD强大,是DTD代替者。
2 约束体验
体验效果说明:体验schema约束XML文档中对元素体数据类型的约束。
效果如下:
DTD约束无法对具体数据类型进行约束,所以开发工具没有任何错误提示,如下效果:
实现步骤
步骤1:复制schema约束文件bookshelf.xsd,其中已对售价约束了数据类型,代码如下
<?xml version="1.0" encoding="UTF-8" ?>
<!--
传智播客schema教学实例文档.将注释中的以下内容复制到要编写的xml的声明下面
复制内容如下:
<书架 xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn bookshelf.xsd"
>
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itcast.cn"
elementFormDefault="qualified">
<xs:element name='书架' >
<!-- complexType : 复杂元素
xml文档中的某个标签,有子标签或有属性,该标签就为:复杂元素
例: <书架> <书>
简单元素:
没有子标签、没有属性。 例:书名、作者、售价
-->
<xs:complexType>
<xs:sequence maxOccurs='unbounded' >
<xs:element name='书' >
<xs:complexType>
<xs:sequence>
<xs:element name='书名' type='xs:string' />
<xs:element name='作者' type='xs:string' />
<xs:element name='售价' type='xs:double' />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
步骤2:新建books2.xml使用schema约束文件bookshelf.xsd,代码如下
<?xml version="1.0" encoding="UTF-8"?>
<书架
xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn bookshelf.xsd"
><!--指定schema文档约束当前XML文档-->
<书>
<书名>JavaScript网页开发</书名>
<作者>张孝祥</作者>
<售价>abc</售价>
</书>
</书架>
步骤3:开发工具提示效果
3 名称空间
一个XML文档最多可以使用一个DTD文件,但一个XML文档中使用多个Schema文件,若这些Schema文件中定义了相同名称的元素时,使用的时候就会出现名字冲突。这就像一个Java文件中使用了import java.util.*
和import java.sql.*
时,在使用Date类时,那么就不明确Date是哪个包下的Date了。
同理 , 在XML文档中就需要通过名称空间(namespace)来区分元素和属性是来源于哪个约束中的。
名称空间就在在根元素后面的内容 , 使用xmlns到引入约束 。
当一个XML文档中需要使用多个Schema文件的时候 , 有且仅有一个使用缺省的 , 其他的名称空间都需要起别名 。
参考资料中的 applicationContext.xml文件(spring框架的配置文件)
xmlns="http://www.itcast.cn"
<!-- 缺省的名称空间.使用此约束中的元素的时候只需要写元素名即可 例如:<书></书> -->
xmlns:aa="http://java.sun.com"
<!-- aa就是此约束的别名,使用此约束中的元素的时候就需要加上别名 例如:<aa:书></aa:书> -->
总之名称空间就是用来处理元素和属性的名称冲突问题,与Java中的包是同一用途。如果每个元素和属性都有自己的名称空间,那么就不会出现名字冲突问题,就像是每个类都有自己所在的包一样,那么类名就不会出现冲突。
4 schema学习要求
虽然schema功能比dtd强大,但是编写要比DTD复杂,同样以后我们在企业开发中也很少会自己编写schema文件。
xml编写与约束内容已经完成了,根据xml的作用我们了解到,无论是xml作为配置文件还是数据传输,我们的程序都要获取xml文档中的数据以便我们进行具体的业务操作,接下来我们就要学习XML解析技术Dom4j。
小结:
怎么引入xsd 文档
<?xml version="1.0" encoding="UTF-8" ?>
<根元素 xmlns="命名空间"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="命名空间 文件路径"
>
</根元素>
schemaLocation:指定命名空间所在的文件
命名空间在约束文件的:targetNamespace中
第二章 Dom4j
1 XML解析
1.1 解析概述
当将数据存储在XML后,我们就希望通过程序获取XML的内容。我们使用Java基础所学的IO知识是可以完成的,不过需要非常繁琐的操作才可以完成,且开发中会遇到不同问题(只读、读写)。
人们为不同问题提供不同的解析方式,使用不同的解析器进行解析,方便开发人员操作XML。
1.2 解析方式和解析器
开发中比较常见的解析方式有三种,如下:
DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象
a)优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
b)缺点:XML文档过大,可能出现内存溢出
SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都触发对应的事件。
a)优点:处理速度快,可以处理大文件
b)缺点:只能读,逐行后将释放资源,解析操作繁琐。
PULL:Android内置的XML解析方式,类似SAX。(了解)
解析器,就是根据不同的解析方式提供具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包
常见的解析器
小结
xml的常用解析方式:
- DOM
- SAX
- PULL
基于解析方式,常用的解析工具包: Dom4j
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/
通常我们会在项目中创建lib文件夹,将需要依赖的库放在这里。
库导入方式:
在IDEA中,选择项目鼠标右键--->弹出菜单-->open Module settings”-->Dependencies-->+-->JARs or directories... 找到dom4j-1.6.1.jar,成功添加之后点击"OK" 即可。
直接右键选择:Add as Library
小结
dom4j的解析思想,先把xml文档加载到内存中,从而得到一个DOM树,并创建一个Document对象去维护dom树。
利用Document对象,就可以去解析DOM树(解析XML文件)
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的步骤:
- 创建SaxReader对象,调用read方法关联xml文件,得到一个Document对象
- 通过Document对象,获取根元素
- 获取根元素之后,就可以层层深剥,运用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值,以及书本名称,作者名称和价格.
步骤分析:
- 创建一个SaxReader对象,调用read方法加载一个xml文件获得文档对象
- 通过文档对象,获取根元素
- 通过根元素一层一层的进行解析子元素。
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>
步骤分析:
- 先创建一个Book类对应book元素
- 创建一个ArrayList集合用来存储解析后的book对象
- 创建SaxReader对象,调用read方法加载xml文件,得到文档对象
- 通过文档对象获取根元素,然后层层解析
代码实现:
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 Dom4J结合XPath解析XML(了解)
3.1 介绍
XPath使用路径表达式来选取XML/HTML 文档中的元素节点或属性节点。节点是通过沿着路径 (path) 来选取的。XPath在解析XML/HTML文档方面提供了一独树一帜的路径思想。
3.2 XPath使用步骤
步骤1:导入jar包(dom4j和jaxen-1.1-beta-6.jar)
步骤2:通过dom4j的SaxReader获取Document对象
步骤3: 利用Xpath提供的api,结合xpath的语法完成选取XML文档元素节点进行解析操作。
Node接口中存在以下方法:
方法 | 作用 |
---|---|
List<Element> selectNodes("路径表达式") | 获取符合表达式的元素集合 |
Element selectSingleNode("路径表达式") | 获取符合表达式的唯一元素 |
我们熟知的Document,Element等都是Node的子类型,因此也能使用上述selectNode的方法。如下图
3.3 XPath语法(了解)
XPath也是一种解析XML或者HTML的方式。
XPath表达式,就是用于选取XML文档中节点的表达式字符串。获取XML文档节点元素一共有如下4种XPath语法方式:
- 绝对路径表达式方式 例如: /元素/子元素/子子元素...
- 相对路径表达式方式 例如: 子元素/子子元素.. 或者 ./子元素/子子元素..
- 全文搜索路径表达式方式 例如: //子元素//子子元素
- 谓语(条件筛选)方式 例如: //元素[@attr]
在xml解析得到的一个文档对象中,Document,Element,Attribute,Text ,都是接口Node的子类型。
Node中存在两个方法,可以结合xpath使用查询节点:
List<Node> selecteNodes(路径) :将所有满足路径的节点给获取出来
Node selecteSingleNode(路径) :获取单个满足路径的节点
xpath使用步骤:
- 创建SAXReader对象,调用read方法,关联xml文件。得到Document对象
- 就可以使用Document对象来调用方法传入xpath路径
3.3.1 绝对路径表达式(了解)
格式:
String xpath="/根元素/子元素/子子元素...";
绝对路径是以“/”开头,一级一级描述标签的层级路径就是绝对路径,这里注意不可以跨层级
演示需求(将素材中的Contact.xml拷贝到项目中)
采用绝对路径获取从根节点开始逐层的查找name节点列表并打印信息 String path="/contactList/contact/name";
实现步骤:
先创建SAXReader对象,调用read方法,将Contact.xml关联,得到Document对象
定义绝对路径
调用selectedNodes
public class Demo01 {
public static void main(String[] args) throws DocumentException {
//采用绝对路径获取从根节点开始逐层的查找name节点列表并打印信息
//1. 先创建SAXReader对象,调用read方法,将Contact.xml关联,得到Document对象
SAXReader sr = new SAXReader();
Document doc = sr.read("day15/xml/Contact.xml");
//2. 定义绝对路径
String xpath = "/contactList/contact/name";
//3. 调用selectedNodes
List<Node> nameNodes = doc.selectNodes(xpath);
for (Node nameNode : nameNodes) {
String text = nameNode.getText();
System.out.println("text = " + text);
}
}
}
text = 潘金莲
text = 武松
text = 武大狼
3.2 相对路径表达式(了解)
相对路径介绍
格式:
String xpath2="./子元素/子子元素"; // "./"代表当前元素路径位置
需求:
先采用绝对路径获取 contact 节点 再采用相对路径获取下一级name子节点并打印信息。
public class Demo01 {
public static void main(String[] args) throws DocumentException {
//先采用绝对路径获取 contact 节点 再采用相对路径获取下一级name子节点并打印信息。
//1. 先创建SAXReader对象,调用read方法,将Contact.xml关联,得到Document对象
SAXReader sr = new SAXReader();
Document doc = sr.read("day15/xml/Contact.xml");
//先使用绝对路径,获取contact
List<Node> contactNodes = doc.selectNodes("/contactList/contact");
for (Node contactNode : contactNodes) {
//使用相对路径获取name,并获取文本打印
Node node = contactNode.selectSingleNode("./name");
System.out.println("node.getText() = " + node.getText());
}
}
}
node.getText() = 潘金莲
node.getText() = 武松
node.getText() = 武大狼
3.3 全文搜索路径表达式(了解)
全文搜索路径介绍
格式:
String xpath1="//子元素//子子元素";
“/”符号,代表逐级写路径
“//”符号,不用逐级写路径,可以直接选取到对应的节点,是全文搜索匹配的不需要按照逐层级
举例 说明 //contact 找contact元素,无论元素在哪里 //contact/name 找contact,无论在哪一级,但name一定是contact的子节点 //contact//name contact无论在哪一种,name只要是contact的子孙元素都可以找到 //name 需求:直接全文搜索所有的 name元素并打印
public class Demo01 { public static void main(String[] args) throws DocumentException { //需求:直接全文搜索所有的 name元素并打印 //1. 先创建SAXReader对象,调用read方法,将Contact.xml关联,得到Document对象 SAXReader sr = new SAXReader(); Document doc = sr.read("day15/xml/Contact.xml"); List<Node> nodes = doc.selectNodes("//name"); for (Node node : nodes) { System.out.println("node.getText() = " + node.getText()); } } }
结论:
/ 表示根节点 (不能跨层级)
// 表示全文任意节点 (可以跨层级)
3.4 谓语(条件筛选 了解)
介绍
谓语,又称为条件筛选方式,就是根据条件过滤判断进行选取节点
格式:
String xpath1="//元素[@属性名]";//查找元素对象,全文中只要含有该属性名的元素 String xpath2="//元素[@属性名=value]";//查找元素对象,全文中只要含有该属性,并指定的值
需求:查找含有id属性的contact元素
public class Demo01 {
public static void main(String[] args) throws DocumentException {
//查找含有id属性的contact元素
SAXReader sr = new SAXReader();
Document doc = sr.read("day15/xml/Contact.xml");
//List<Node> nodes = doc.selectNodes("//contact[@id]");//获取含有属性id的元素
List<Node> nodes = doc.selectNodes("//contact[@id=1]");//获取含有属性并且值为1的元素
for (Node node : nodes) {
//将节点,先转换Element
Element contact = (Element) node;
System.out.println("contact.elementText(\"name\") = " + contact.elementText("name"));
}
}
}
**小结**
- 在xpath解析xml方式中,最核心的就是使用了"路径表达式"
- xpath是利用"路径表达式"实现对xml文件的解析
- 路径表达式的语法:
1. 绝对路径表达式
~~~
/根元素/子元素/子子元素/.... (一层一层查找)
~~~
2. 相对路径表达式
~~~
./子元素/子子元素/.....
~~~
3. 全文搜索路径表达式
~~~
//元素 (会跨层级进行全文查找)
~~~
4. 条件筛选表达式
~~~
//元素[@属性名]
//元素[@属性名=属性值]
~~~
## 第三章 综合案例
学习目标:
- 理解所谓的框架是如何实现的,如何使用框架
### 1、需求
需求:自定义dao层jdbc框架
- 为了方便程序员操作数据库,让程序员更关注于sql代码层面和业务层面
### 2、案例效果



使用到的技术:
- 反射
- 注解
- 动态代理
- xml解析:xpath
### 3、案例分析
自定义jdbc框架开发步骤:
1、通过软配置方式,和数据库连接
- 解析xml配置文件,获得:driver、url、username、password
- C3P0连接池
- 根据配置文件中的参数,创建连接池对象
2、创建@Select注解
- 解析@Select注解中value值:`select查询语句`
3、创建映射接口Mapper
- 把从@Select注解中解析出来的`select查询语句`,赋值给Mapper中的sql成员变量
4、创建SqlSession类
- 提供getMapper()方法,用来获取代理对象
- 说明:程序员在获取到代理对象后,利用代理对象调用某个方法时,会被代理对象拦截处理
### 4、自定义JDBC框架-代码实现
#### 4.1、Configuration
- 作用:读取配置文件
- 配置文件名称被固定了:config.xml
~~~java
/*配置类
1. 解析XML文件
2. 创建C3P0连接池
*/
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.01: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
~~~java
//查询时使用的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String[] value();
}
~~~
@ResultType
~~~java
//查询结果的封装类型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ResultType {
String value();
}
~~~
#### 4.3、映射类:Mapper
- 作用:把通过解析@Select注解中的属性值(sql语句),存储到Mapper对象中
~~~java
public class Mapper {
private String sql;//存储sql语句
public Mapper() {
}
public Mapper(String sql) {
this.sql = sql;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
~~~
#### 4.4、SqlSession类
- 作用:
1. 解析@Select、@ResultType注解
2. 和数据库交互
3. 把数据结果封装到指定的对象中
~~~java
@SuppressWarnings("all")
public class SqlSession {
/**
* 动态代理
*/ //clazz = UserMapper.class
//clazz = StudentMapper.class
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 *******/
//config.getInterName() 获取接口的全路径名称 例:com.itheima.UserMapper
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];//select * from user
//给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();
//通用型JDBC框架:考虑查询的结果类型是不同
//为了保障结果类型统一, 把所有的结果类型当作Class对象
Class cl = Class.forName(resultType);
//获取数据库连接池对象
DataSource ds = config.getDataSource();
//利用连接池对象,获取Connection对象
Connection conn = ds.getConnection();
//调用自定义方法: 执行sql查询语句
//参数1:Connection对象
//参数2:要执行的sql语句(select.....)
//参数3:select查询语句的结果要封装到指定的类型对象中
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 ...
//得到成员变量的名字
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、数据表
~~~mysql
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、创建实体类
~~~java
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接口
~~~java
public interface UserMapper {
//编写SQL语句
//查询所有用户
@Select("select * from user")
@ResultType("com.itcast.pojo.User")
public abstract List queryAllUser();
}
~~~
#### 5.4、配置文件:config.xml
~~~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、测试类
~~~java
public class Test1 {
@Test
public void testSelect() {
//使用jdbc框架中提供的API
//实例化
SqlSession sqlSession = new SqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.queryAllUser();
for (User u : users) {
System.out.println(u);
}
}
}
~~~