[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfodZkLj-1691793219502)(./assets/%E7%8E%84%E5%AD%90Share%20-%20Java%20%E5%BC%80%E5%8F%91%E4%B9%8B%E6%A1%86%E6%9E%B6%E5%9F%BA%E7%A1%80%E6%8A%80%E6%9C%AF.png)]
玄子Share - Java 开发之框架基础技术
目录
一、反射
1.1 反射介绍
Java反射:在编译时不确定哪个类被加载,而在程序运行时才加载、探知、使用
1.1.1 Java 程序的运行过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPRhLuhe-1691793219503)(./assets/%E8%BF%90%E8%A1%8C%E6%9C%BA%E5%88%B6.png)]
1.1.2 反射及其作用
反射是指能够在运行时,观察并修改自己运行时(Runtime)行为的特性 Java 反射机制主要提供了以下的一些功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的属性和方法
- 在运行时调用任意一个对象的方法
1.2 反射技术常用 API
- java.lang.Class 类可获取类和类的成员信息
- java.lang.reflect.Constructor 类可调用类的构造方法
- java.lang.reflect.Field 类可访问类的属性
- java.lang.reflect.Method 类可调用类的方法
1.2.1 使用反射的基本步骤
- 导入
java.lang.reflect.*
- 获得需要操作的类的 java.lang.Class 对象
- 调用Class的方法获取 Field、Method 等对象
- 使用反射 API 操作实例成员
1.3 反射的入口类 Class
Class 类是 Java 反射机制的起源和入口
- 每个类都有自己向关的 Class 对象
- 提供了获取类信息的相关方法
Class 类存放类的结构信息
- 类名
- 父类﹑接口
- 构造方法﹑方法﹑属性
- 注解
- ……
1.3.1 获取 Class 实例
获取 Class 实例的常用方式
// 方法1:对象.getClass()
Class clazz = new Student().getClass();
// 方法2:类.class
Class clazz = Student.class;
// 方法3:Class.forName()
Class clazz = Class.forName("xxx.xxx.Student");
// 方法4:基本数据类型包装类.TYPE
Class<Integer> clazz = Integer.TYPE;
1.4 反射获取信息
1.4.1 获取类的基本信息
方法 | 说明 |
---|---|
String getName() | 以字符串形式返回该类型的名称 |
String getSimpleName() | 以字符串形式返回该类型的简称 |
Package getPackage() | 获取该类型所在的包 |
Class getSuperclass() | 返回该类型的超类的 Class 实例 |
Class[] getInterfaces() | 返回该类型所实现的全部接口的 Class 实例 |
int getModifiers() | 返回该类型的所有修饰符,由 public、protected、private、final、staic、abstract 等对应的 int 常量组成,返回的整数应使用 Modifier 工具类来解码,才可以判断修饰符的构成 |
Class[] getDeclaredClasses() | 返回该类型中包含的全部内部类的 Class 实例 |
Class getDeclaringClass() | 返回该类型所在的外部类的 Class 实例 |
- 演示案例
public class Xz01 {
public static void main(String[] args) throws ClassNotFoundException {
Class<Student> c1 = Student.class;
Class<?> c2 = new Student().getClass();
Class<?> c3 = Class.forName("xuanzi.Student");
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c1 == c2);
// hashCode 编码以及 内存地址一致表示同一个 Class 对象
System.out.println("获取全类名(包含包路径):" + c1.getName());
System.out.println("获取类名(仅类名称):" + c1.getSimpleName());
System.out.println("获取包名(仅包名):" + c1.getPackage().getName());
System.out.println("获取类加载器:" + c1.getClassLoader());
System.out.println("获取父类路径:" + c1.getSuperclass().getName());
System.out.println("获取类访问修饰符:" + c1.getModifiers());
System.out.println("表示 PUBLIC:" + Modifier.PUBLIC);
}
}
// 1324119927
// 1324119927
// 1324119927
// true
// 获取全类名(包含包路径):xuanzi.Student
// 获取类名(仅类名称):Student
// 获取包名(仅包名):xuanzi
// 获取类加载器:jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b
// 获取父类路径:java.lang.Object
// 获取类访问修饰符:1
// 表示 PUBLIC:1
1.4.2 获取构造方法信息
方法 | 说明 |
---|---|
Constructor getConstructor(Class… params) | 返回该类型指定参数列表的 public 构造方法,构造方法的参数列表与 params 所指定的类型列表所匹配 |
Constructor[] getConstructors() | 返回该类型的所有 public 构造方法 |
Constructor getDeclaredConstructor(Class… params) | 返回该类型的指定参数列表的构造方法,访问级别不限 |
Constructor[] getDeclaredConstructors() | 返回该类型的所有构造方法,访问级别不限 |
- 演示案例
public class Xz02 {
public static void main(String[] args) throws NoSuchMethodException {
Class<Student> c1 = Student.class;
Constructor<Student> constructor = c1.getConstructor();
Constructor<?>[] constructors = c1.getConstructors();
Constructor<Student> declaredConstructor = c1.getDeclaredConstructor();
Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
Constructor<?> declaredConstructors1 = c1.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(Arrays.toString(constructors));
System.out.println(declaredConstructor);
System.out.println(Arrays.toString(declaredConstructors));
System.out.println(declaredConstructors1);
System.out.println("================================");
for (Constructor<?> cc : declaredConstructors) {
int modifier = cc.getModifiers();
String xiu = "";
if ((modifier & Modifier.PUBLIC) == Modifier.PUBLIC) {
xiu = "PUBLIC";
} else if ((modifier & Modifier.PROTECTED) == Modifier.PROTECTED) {
xiu = "PROTECTED";
} else if ((modifier & Modifier.FINAL) == Modifier.FINAL) {
xiu = "FINAL";
} else if ((modifier & Modifier.ABSTRACT) == Modifier.ABSTRACT) {
xiu = "ABSTRACT";
} else if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE) {
xiu = "PRIVATE";
} else {
xiu = "未知";
}
System.out.println(xiu);
}
System.out.println("================================");
Class<?>[] parameterTypes = declaredConstructors1.getParameterTypes();
if (parameterTypes.length == 0) {
System.out.println("无参");
} else {
for (int i = parameterTypes.length - 1; i >= 0; i--) {
System.out.println(parameterTypes[i].getSimpleName());
}
}
System.out.println("================================");
}
}
// public xuanzi.Student()
// [public xuanzi.Student(java.lang.String,int,char), public xuanzi.Student()]
// public xuanzi.Student()
// [public xuanzi.Student(java.lang.String,int,char), public xuanzi.Student(), protected xuanzi.Student(java.lang.String), private xuanzi.Student(java.lang.String,int)]
// protected xuanzi.Student(java.lang.String)
// ================================
// PUBLIC
// PUBLIC
// PROTECTED
// PRIVATE
// ================================
// String
// ================================
1.4.3 获取属性信息
方法 | 说明 |
---|---|
Field getField(String name) | 返回该类型中指定名称的 public 属性,name 参数用于指定属性名称 |
Field[] getFields() | 返回该类型中所有 public 属性 |
Field getDeclaredField(String name) | 返回该类型中指定名称的属性,与属性的访问级别无关 |
Field[] getDeclaredFields() | 返回该类型中的全部属性,与属性的访问级别无关 |
- 演示案例
public class Xz03 {
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
Class<Student> c1 = Student.class;
Field[] fields = c1.getFields();
Field name = c1.getField("name");
Field declaredName = c1.getDeclaredField("name");
Field[] declaredFields = c1.getDeclaredFields();
System.out.println(Arrays.toString(fields));
System.out.println(name);
System.out.println(declaredName);
System.out.println(Arrays.toString(declaredFields));
System.out.println("================================");
for (Field df : declaredFields) {
int modifier = df.getModifiers();
String xiu = "";
if ((modifier & Modifier.PUBLIC) == Modifier.PUBLIC) {
xiu = "PUBLIC";
} else if ((modifier & Modifier.PROTECTED) == Modifier.PROTECTED) {
xiu = "PROTECTED";
} else if ((modifier & Modifier.FINAL) == Modifier.FINAL) {
xiu = "FINAL";
} else if ((modifier & Modifier.ABSTRACT) == Modifier.ABSTRACT) {
xiu = "ABSTRACT";
} else if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE) {
xiu = "PRIVATE";
} else {
xiu = "未知";
}
System.out.println(xiu);
}
System.out.println("================================");
for (Field declaredField : declaredFields) {
System.out.print(declaredField.getModifiers() + " - ");
System.out.print(declaredField.getName() + " - ");
System.out.print(declaredField.getType() + " - ");
System.out.println();
}
System.out.println("================================");
}
}
// [public java.lang.String xuanzi.Student.name, public int xuanzi.Student.age]
// public java.lang.String xuanzi.Student.name
// public java.lang.String xuanzi.Student.name
// [public java.lang.String xuanzi.Student.name, public int xuanzi.Student.age, private char xuanzi.Student.gender]
// ================================
// PUBLIC
// PUBLIC
// PRIVATE
// ================================
// 1 - name - class java.lang.String -
// 1 - age - int -
// 2 - gender - char -
// ================================
1.4.4 获取方法信息
方法 | 说明 |
---|---|
Method getMethod(String name, Class… params) | 返回该实例中指定的 public 方法,name 参数用于指定方法名称,params 参数指定参数列表 |
Method[] getMethods() | 返回该实例中所有 public 方法 |
Method getDeclaredMethod(String name, Class… params) | 返回该实例中指定的方法,与方法的访问级别无关 |
Method[] getDeclaredMethods() | 返回该实例中的全部方法,与方法的访问级别无关 |
- 演示案例
public class Xz04 {
public static void main(String[] args) throws NoSuchMethodException {
Class<Student> c1 = Student.class;
Method[] methods = c1.getMethods();
Method getStudent = c1.getMethod("setName", String.class);
Method[] declaredMethods = c1.getDeclaredMethods();
Method declaredMethod = c1.getDeclaredMethod("getGender");
System.out.println(getStudent);
System.out.println(Arrays.toString(methods));
System.out.println(Arrays.toString(declaredMethods));
System.out.println(declaredMethod);
System.out.println("================================");
for (Method dm : declaredMethods) {
int modifier = dm.getModifiers();
String xiu = "";
if ((modifier & Modifier.PUBLIC) == Modifier.PUBLIC) {
xiu = "PUBLIC";
} else if ((modifier & Modifier.PROTECTED) == Modifier.PROTECTED) {
xiu = "PROTECTED";
} else if ((modifier & Modifier.FINAL) == Modifier.FINAL) {
xiu = "FINAL";
} else if ((modifier & Modifier.ABSTRACT) == Modifier.ABSTRACT) {
xiu = "ABSTRACT";
} else if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE) {
xiu = "PRIVATE";
} else {
xiu = "未知";
}
System.out.println(xiu);
}
System.out.println("================================");
for (Method declaredField : declaredMethods) {
System.out.print((declaredField.getModifiers() == Modifier.PUBLIC ? "PUBLIC" : "PRIVATE") + " - ");
System.out.print(declaredField.getName() + " - ");
System.out.print(declaredField.getReturnType().getSimpleName() + " - ");
System.out.print(Arrays.toString(declaredField.getParameterTypes()) + " - ");
System.out.print(Arrays.toString(declaredField.getExceptionTypes()) + " - ");
System.out.println();
}
System.out.println("================================");
}
}
// public void xuanzi.Student.setName(java.lang.String)
// [public java.lang.String xuanzi.Student.getName(), public java.lang.String xuanzi.Student.toString() throws java.lang.IllegalArgumentException,java.lang.ArithmeticException, public void xuanzi.Student.setName(java.lang.String), public int xuanzi.Student.getAge(), public void xuanzi.Student.setAge(int), public void xuanzi.Student.setGender(char), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
// [public java.lang.String xuanzi.Student.getName(), public java.lang.String xuanzi.Student.toString() throws java.lang.IllegalArgumentException,java.lang.ArithmeticException, public void xuanzi.Student.setName(java.lang.String), private char xuanzi.Student.getGender(), public int xuanzi.Student.getAge(), public void xuanzi.Student.setAge(int), private java.lang.String xuanzi.Student.show(java.lang.String,int,char), public void xuanzi.Student.setGender(char)]
// private char xuanzi.Student.getGender()
// ================================
// PUBLIC
// PUBLIC
// PUBLIC
// PRIVATE
// PUBLIC
// PUBLIC
// PRIVATE
// PUBLIC
// ================================
// PUBLIC - getName - String - [] - [] -
// PUBLIC - toString - String - [] - [class java.lang.IllegalArgumentException, class java.lang.ArithmeticException] -
// PUBLIC - setName - void - [class java.lang.String] - [] -
// PRIVATE - getGender - char - [] - [] -
// PUBLIC - getAge - int - [] - [] -
// PUBLIC - setAge - void - [int] - [] -
// PRIVATE - show - String - [class java.lang.String, int, char] - [] -
// PUBLIC - setGender - void - [char] - [] -
// ================================
1.5 反射实例化对象
1.5.1 通过反射实现类的实例化
- java.lang.Class
Student student = Student.class.newInstance();
// 通过 class 获取对象
- java.lang.reflect.Constructor
Student student = Student.class.getConstructor().newInstance();
// 通过构造获取对象
- 演示案例
public class Xz05 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Student student = new Student();
// 直接 new 对象
Student student1 = Student.class.newInstance();
// 通过 class 获取对象
Student student2 = Student.class.getConstructor().newInstance();
// 通过构造获取对象
Constructor<Student> studentConstructor = Student.class.getConstructor(String.class, char.class);
studentConstructor.setAccessible(true);
// 通过有参构造获取对象
Student student3 = studentConstructor.newInstance("XuanZi", '男');
Constructor<Student> studentConstructor1 = Student.class.getDeclaredConstructor(String.class, int.class);
studentConstructor1.setAccessible(true);
// 可设置私有构造函数
Student student4 = studentConstructor1.newInstance("XuanZi", 12);
System.out.println(student);
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
System.out.println(student4);
}
}
// xuanzi.Student{name='null', age=0, gender= }
// xuanzi.Student{name='null', age=0, gender= }
// xuanzi.Student{name='null', age=0, gender= }
// xuanzi.Student{name='XuanZi', age=0, gender=男}
// xuanzi.Student{name='XuanZi', age=12, gender= }
1.5.2 通过反射访问实例的字段
java.lang.reflect.Field
方法 | 说明 |
---|---|
xxx getXxx(Object obj) | xxx 表示8种基本数据类型之一,若 Field 实例表示的是一个静态属性,则 obj 可以设置为 null |
Object get(Object obj) | 以 Object 类型返回 obj 中相关属性的值 |
void setXxx(Object obj, xxx val) | 将 obj 中相关属性的值设置为 val。xxx 为8种基本数据类型之一 |
void set(Object obj, Object val) | 将 obj 中相关属性的值设置为 val |
void setAccessible(boolean flag) | 对相关属性设置访问权限。设置为 true 可以禁止 Java 语言访问检查 |
- 演示案例
public class Xz06 {
public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Field name = studentClass.getField("name");
System.out.println(name.get(student));
name.set(student, "玄子");
System.out.println(name.get(student));
}
}
// null
// 玄子
1.5.3 通过反射调用实例的方法
java.lang.reflect.Method
public Object invoke( Object obj, Object... args )
- Object:返回值
- Object obj:执行该方法的对象
- Object… args:执行该方法时传入的参数
- 演示案例
public class Xz07 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Method name = studentClass.getMethod("setName", String.class);
name.invoke(student, "张三");
System.out.println(student);
}
}
// xuanzi.Student{name='张三', age=0, gender= }
1.6 反射的优缺点
1.6.1 优点
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类
- 提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力
- 反射的应用领域:开源框架,如 MyBatis、Spring 等
1.6.2 缺点
- 性能问题:反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 代码维护问题:反射会模糊程序内部逻辑,可读性较差,且反射可能会破坏封装
二、XML
2.1 XML 简介
XML(Extensible Markup Language),可扩展标记语言
2.1.1 特点
- XML 与操作系统、编程语言的开发平台无关
- 规范统一,实现不同系统之间的数据交换
2.1.2 作用
- 数据存储
- 数据交换
- 数据配置
2.1.3 XML 文档结构
<?xml version="1.0" encoding="UTF-8"?>
<!-- 声明 -->
<books>
<!-- 根元素 -->
<!-- 图书信息 -->
<book id="bk101">
<!-- id="bk101" 属性 -->
<title>.NET高级编程</title>
<author>王姗</author>
<description>包含C#框架和网络编程等</description>
</book>
<book id="bk102">
<title>XML基础编程</title>
<author>李明明</author>
<description>包含XML基础概念和基本用法</description>
</book>
</books>
使用标签描述的文档元素信息,成对出现。
节点不包含子节点可自闭合
<title text="自闭合"/>
2.2 XML 标签
XML 文档的内容元素由一系列标签组成
<元素名 属性名="属性值">元素内容(子元素、字符等)</元素名>
<!-- 开始标签 --> <!-- 元素允许是空元素,语法:如<title></title>、<title/>--> <!-- 结束标签 -->
2.2.1 元素命名规则
- 名称中可以包含字母、数字或者其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字符
xml
(或者XML、Xml)开始 - 名称中不能含空格
2.2.2 属性语法规则
- 属性可以加在任何一个元素的起始标签上,但不能加在结束标签上
- 属性值用双引号包裹
- 一个元素可以有多个属性,多个属性之间用空格隔开
- 属性值中不能直接包含
<、"、&
2.2.3 XML 中的特殊字符的处理
XML 中的特殊字符包括 < 、> 、' 、" 、&
使用预定义实体对特殊字符进行转义
特殊字符 | 实体名称 |
---|---|
< | < |
> | > |
" | " |
’ | ' |
& | & |
在元素的文本中使用 CDATA 节处理
<description>
<![CDATA[讲解了元素<title>以及</title>的使用]]>
</description>
2.2.4 编写格式良好的 XML 文档
格式良好的 XML 文档需要遵循如下规则
- 有 XML 声明语句
- 有且仅有一个根元素
- 标签大小写敏感
- 属性值用双引号
- 标签成对/空标签关闭
- 元素正确嵌套
2.3 XML 解析技术
2.3.1 DOM
- 基于 XML 文档树结构的解析
- 适用于多次访问的 XML 文档
- 比较消耗资源
2.3.2 SAX
- 基于事件的解析
- 适用于大数据量的 XML 文档
- 占用资源少,内存消耗小
DOM 和 SAX 不针对特定语言,只是定义了一些接口,以及部分缺省实现(使用空方法实现接口)
2.3.3 JDOM
- 针对 Java 的特定文档模型
- 使用具体类而不使用接口
- API 大量使用了 Java 集合类型
2.3.4 DOM4J
- 非常优秀的开源 Java XML API
- 面向接口编程,比 JDOM 更加灵活
- 使用 Java 集合框架处理 XML
2.4 DOM 解析 XML 文档
2.4.1 DM 解析 XML 原理
文档对象模型(Document Object Model)DOM 把 XML 文档映射成一个倒挂的树
<book >
<title>三国演义</title>
<author>罗贯中</author>
<price>30元</price>
</book>
使用 Oracle 提供的 JAXP(Java API for XML Processing)
- org.w3c.dom:W3C 推荐的用于使用 DOM 解析 XML 文档的接
- org.xml.sax:用于使用 SAX 解析 XML 文档的接口
- javax.xml.parsers:解析器工厂工具,获得并配置特殊的分析器
2.4.2 解析 XML 文档实现步骤
- 创建解析器工厂 DocumentBuilderFactory 对象
- 由解析器工厂对象创建解析器 DocumentBuilder 对象
- 由解析器对象对 XML 文件进行解析、构建 DOM 树,创建相应的 Document 对象
- 以Document 对象为起点对 DOM 树的节点进行增加、删除、修改、查询等操作
2.4.3 解析 XML 部分常用 API
常用接口 | 说明 | 常用方法 |
---|---|---|
Node | 代表了文档树中的一个抽象节点,有 Document、Element、Text 等子对象 | NodeList getChildNodes() Node getFirstChild() Node getLastChild() Node getNextSibling() Node getPreviousSibling() Node appendChild(Node child) String getNodeName() String getNodeValue() short getNodeType() |
NodeList | 包含了一个或多个节点(Node)的列表 | int getLength() Node item(int index) |
Document | 表示整个 XML 文档 | NodeList getElementsByTagName(String tagname) Element getElementById(String id) Element getDocumentElement() Element createElement(String tagname) |
Element | XML 文档中的一个元素,是 Node 最主要的子对象 | String getAttribute(String name) NodeList getElementsByTagName(String tagname) String getTagName() |
2.4.4 访问 DOM 树节点属性
显示 XML 文件中收藏的手机品牌和型号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLL8bxCf-1691793219504)(./assets/xml%E7%BB%93%E6%9E%841.png)]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PhoneInfo>
<Brand name="华为">
<Type name="P90"/>
<Item>
<title>标题信息</title>
<link>链接</link>
<description>描述</description>
<pubDate>2023-02-01</pubDate>
</Item>
</Brand>
<Brand name="苹果">
<Type name="iPhone Z"/>
<Type name="iPhone ZL"/>
</Brand>
<Brand name="三星">
<Type name="NoteX"/>
</Brand>
</PhoneInfo>
- 演示案例一
public class Book {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList bookList = doc.getElementsByTagName("Brand");
for (int i = 0; i < bookList.getLength(); i++) {
Node brand = bookList.item(i);
Element element = (Element) brand;
String attrVal = element.getAttribute("name");
NodeList types = element.getChildNodes();
for (int j = 0; j < types.getLength(); j++) {
Node typeNode = types.item(j);
if (typeNode.getNodeType() == Node.ELEMENT_NODE) {
Element typeElement = (Element) typeNode;
String type = typeElement.getAttribute("name");
System.out.println("手机:" + attrVal + type);
}
}
}
}
}
- 演示案例二
public class Xz01 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
NodeList brandList = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse("src/main/resources/PhoneInfo.xml")
.getElementsByTagName("Brand");
for (int i = 0; i < brandList.getLength(); i++) {
Element brandElement = (Element) brandList.item(i);
System.out.println(brandElement.getTagName() + ":" + brandElement.getAttribute("name"));
NodeList typeList = brandElement.getElementsByTagName("Type");
for (int j = 0; j < typeList.getLength(); j++) {
Element typeElement = (Element) typeList.item(j);
System.out.println("\t" + typeElement.getTagName() + ":" + typeElement.getAttribute("name"));
NodeList itemList = brandElement.getElementsByTagName("Item");
for (int k = 0; k < itemList.getLength(); k++) {
Element itemElement = (Element) itemList.item(k);
NodeList pubDateList = itemElement.getElementsByTagName("pubDate");
System.out.print("\t" + itemElement.getTagName() + ":");
for (int l = 0; l < pubDateList.getLength(); l++) {
String textContent = pubDateList.item(l).getTextContent();
System.out.println(textContent);
}
}
}
}
}
}
// Brand:华为
// Type:P90
// Item:2023-02-01
// Brand:苹果
// Type:iPhone Z
// Type:iPhone ZL
// Brand:三星
// Type:NoteX
调用 Node 对象的 getChildNodes() 方法有时会包含 XML 文档中的空白符,导致调用 Node 对象方法出错,解决办法包括:
- 取得 Node 对象后判断
node.getNodeType() == Node.ELEMENT_NODE
,即判断是否是元素节- 点将 Node 对象转换为 Element 对象,使用 Element 的 getElementsByTagName(String name)
2.4.5 访问 DOM 树节点内容
显示XML文件中手机新闻的发布日期
- 演示案例
public class Book2 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList bookList = doc.getElementsByTagName("pubDate");
Element pubDateElement = (Element) bookList.item(0);
String nodeValue = pubDateElement.getFirstChild().getNodeValue();
System.out.println(nodeValue);
}
}
// 2023-02-01
2.4.6 添加 DOM 节点并保存
- 演示案例
public class Book3 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
Element brandElement = doc.createElement("Brand");
brandElement.setAttribute("name", "三星");
Element typeElement = doc.createElement("Type");
typeElement.setAttribute("name", "NoteX");
brandElement.appendChild(typeElement);
Element phoneInfoElement = (Element) doc.getElementsByTagName("PhoneInfo").item(0);
phoneInfoElement.appendChild(brandElement);
TransformerFactory tff = TransformerFactory.newInstance();
Transformer tf = tff.newTransformer();
DOMSource domSource = new DOMSource(doc);
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StreamResult streamResult = new StreamResult(Files.newOutputStream(Paths.get("src/main/java/CH15_XML/PhoneInfo.xml")));
tf.transform(domSource, streamResult);
}
}
<Brand name="三星">
<Type name="NoteX"/>
</Brand>
2.4.7 修改或删除 DOM 节点
- 演示案例
public class Book4 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList list = doc.getElementsByTagName("Brand");
for (int i = 0; i < list.getLength(); i++) {
Element brandItem = ((Element) list.item(i));
String brandName = brandItem.getAttribute("name");
if ("三星".equals(brandName)) {
// ElementBrand.setAttribute("name", "OPPO");
brandItem.getParentNode().removeChild(brandItem);
// 先获取父节点,再删除子节点
}
}
TransformerFactory tff = TransformerFactory.newInstance();
Transformer tf = tff.newTransformer();
DOMSource domSource = new DOMSource(doc);
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StreamResult streamResult = new StreamResult(Files.newOutputStream(Paths.get("src/CH15_XML/PhoneInfo.xml")));
tf.transform(domSource, streamResult);
}
}
2.5 DOM4J 解析 XML 文档
2.5.1 DOM4J 常用 API
DOM4J 的主要接口都定义在 org.dom4j 包里
接口说明 |
---|
Node:为所有的 DOM4J 中的 XML 节点定义了多态行为 |
Branch:为能够包含子节点的节点定义了公共行为 |
Element:定义 XML 元素 |
Document:定义 XML 文档 |
Entity:定义 XML 实体 |
Attribute:定义了 XML 的属性 |
DocumentType:定义 XML DOCTYPE 声明 |
ProcessingInstruction:定义 XML 处理指令 |
CharacterData:标识接口,标识基于字符的节点 |
Text:定义 XML 文本节点 |
CDATA:定义了 XML CDATA 区域 |
Comment:定义了 XML 注释的行为 |
常用接口 | 常用方法 |
---|---|
Branch | Element elementById(String id) Element addElement(String name) boolean remove(Node node) |
Document | Element getRootElement() |
Element | Element element(String name) List elements() List elements(String tagname) Iterator elementIterator() Iterator elementIterator(String name) Attribute attribute(String name) Element addAttribute(String name, String value) String getText() |
2.5.2 DOM4J 读取收藏信息
- 演示案例
package CH15_XML;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.Iterator;
public class Book5 {
public static void main(String[] args) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File("src/main/java/CH15_XML/PhoneInfo.xml"));
Element rootElement = document.getRootElement();
for (Iterator<Element> itBrand = rootElement.elementIterator(); itBrand.hasNext(); ) {
Element brand = itBrand.next();
System.out.println(brand.attributeValue("name"));
for (Iterator<Element> itType = brand.elementIterator(); itType.hasNext(); ) {
Element type = itType.next();
System.out.println("\t" + type.attributeValue("name"));
}
}
}
}
// 华为
// P90
// null
// 苹果
// iPhone Z
// iPhone ZL
// 三星
// NoteX
三、设计模式
3.1 设计模式简介
- 软件设计中的
三十六计
- 是人们在长期的软件开发中的经验总结
- 是对某些特定问题的经过实践检验的特定解决方法
- 被广泛运用在 Java 框架技术中
3.1.1 设计模式的优点
- 设计模式是可复用的面向对象软件的基础
- 可以更加简单方便地复用成功的设计和体系结构
- 帮助开发者做出有利于系统复用的选择,避免损害系统复用性的设计
- 使其他开发者更加容易理解其设计思路,便于团队交流
3.1.2 设计模式分类
GoF(Gang of Four,四人组)设计模式分为23种
范围/目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
3.1.3 面向对象设计原则
单一职责原则
- 一个类应该有且仅有一个引起它变化的原因
- 一个类应该只负责一个职责
开闭原则
- 对扩展开放,对修改关闭
里氏替换原则
- 引用基类的地方必须能透明地使用其子类的对象
- 可以用来判断继承关系是否合理
依赖倒置原则
- 依赖于抽象而不依赖于具体实现,针对接口编程
接口隔离原则
- 尽量将庞大臃肿的接口拆分成更小更具体的接口
- 接口中只包含客户感兴趣的方法
迪米特法则
- 又称最少知道原则
- 一个软件实体应当尽可能少地与其他实体发生相互作用
合成复用原则
- 尽量使用组合/聚合的方式而不是继承关系达到软件复用的目的
- 是 has-a 关系
3.2 简单工厂模式
如何解决类似“Service与某个具体Dao实现”耦合的问题?
将创建工作转移出来避免在Service中创建具体的Dao实现类,产生耦合
简单工厂模式,又叫做静态工厂方法模式,不属于 GoF 的23种设计模式之一,可以理解为工厂模式的一个特殊实现
3.2.1 简单工厂模式+依赖倒置原则
依据依赖倒置原则,使用setter方法传递依赖关系,减少Service对工厂类的依赖,降低耦合
public class NewsServiceImpl implements NewsService {
private NewsDao dao;
public void setDao(NewsDao dao) {
this.dao = dao;
}
… …
}
3.2.2 简单工厂+参数
简单工厂模式可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
// 创建NewsDao实例的工厂方法
public static NewsDao getInstance(String key) {
switch (key) {
case "mysql":
return new NewsDaoMySqlImpl();
case "oracle":
return new NewsDaoOracleImpl();
case "redis":
return new NewsDaoRedisImpl();
default:
throw new RuntimeException("无效的数据库类型:" + key + " ,DAO获取失败");
}
}
要创建的产品不多且逻辑不复杂的情况,可以考虑简单工厂模式
简单工厂模式包含如下角色
- 工厂(Factory)
- 抽象产品(Product)
- 具体产品(Concrete Product)
增加新的产品需要修改,工厂方法的判断逻辑,不符合开闭原则
3.3 工厂方法模式
3.3.1 实现方式
对简单工厂模式的进一步抽象,工厂方法模式的主要角色如下
- 抽象产品(Product)
- 抽象工厂(Abstract Factory)
- 具体产品(Concrete Product)
- 具体工厂(Concrete Factory)
3.3.2 代码案例
创建抽象工厂接口
public interface AbstractFactory {
public NewsDao getInstance();
}
为不同NewsDao实现创建相对应的具体工厂
// 以生产NewsDaoMySqlImpl实例的工厂为例
public class MySqlDaoFactory implements AbstractFactory {
@Override
public NewsDao getInstance() {
return new NewsDaoMySqlImpl();
}
}
在测试方法中通过特定工厂生产相关的NewsDao实例
AbstractFactory factory = new MySqlDaoFactory();
// 改变具体工厂可创建不同产品
NewsDao dao = factory.getInstance();
3.3.3 优缺点
优点
- 只需要知道具体工厂就可得到所要的产品,无须知道产品的具体创建过程
- 基于多态,便于对复杂逻辑进行封装管理
- 增加新的产品时无须对原工厂进行任何修改,满足开闭原则
缺点
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度
3.4 代理设计模式
单一职责原则的体现,包含如下角色
- 抽象主题(Subject)
- 真实主题(Real Subject)
- 代理(Proxy)
实现方式总体上分为静态代理和动态代理
- 静态代理由开发者针对抽象主题编写相关的代理类实现,编译之后生成代理类的class文件
- 动态代理是在运行时动态生成的,在运行时动态生成代理类字节码
3.4.1 基于接口的静态代理实现
// 抽象主题接口 - 图片
public interface Image {
void display();
}
// 真实主题类 - 真实图片
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image from disk: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理类 - 图片代理
public class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 调用代码
public class Client {
public static void main(String[] args) {
// 创建代理对象并显示图片
Image image = new ImageProxy("example.jpg");
image.display();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kXpBHobI-1691793219509)(./assets/image-20230804080107237.png)]
3.4.2 代理模式优点分析
- 代理模式将客户与目标对象分离,在一定程度上降低了系统的耦合度
- 代理对象可以对目标对象的功能进行扩展,目标对象和扩展功能职责清晰且不会产生耦合
3.4.3 动态代理
静态代理需要手工编写代理类,存在以下弊端
- 目标对象API发生变化,代理类也必须进行修改,增加工作量且不符合开闭原则
- 通过继承得到的代理类只能对一种类型进行代理,组件较多时,代理类的开发工作量巨大
- 动态代理提供了运行时动态扩展对象行为的能力
- 能够依据给定的业务规则,在运行时动态生成代理类
3.4.4 JDK 动态代理
从JDK 1.3版本开始引入
是面向接口的代理实现
- 要求被代理的目标对象必须通过抽象主题接口进行定义
核心API
- java.lang.reflect.InvocationHandler接口
- 代理方法的调用处理程序,负责为代理方法提供业务逻辑
- 包含方法:Object invoke(Object proxy, Method method, Object[] args)
- java.lang.reflect.Proxy类
- 负责动态创建代理类及其实例
- 主要方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
3.4.5 CGLIB 动态代理
如果被代理的目标对象不是通过接口进行定义的,JDK 动态代理将无法实施
- CGLIB(Code Generation Library)是一个功能强大,高性能的代码生成库
- 可以为没有实现接口的类提供代理,原理是为需要代理的类动态生成一个子类作为其代理类
需要使用继承和重写机制,CGLIB动态代理对于final类或final方法无能为力
从cglib https://github.com/cglib/cglib/releases下载所需的 jar 文件
- cglib-nodep-x.x.x.jar
主要 API
- net.sf.cglib.proxy.MethodInterceptor 接口
- 负责拦截父类的方法调用,以便加入代理的业务逻辑
- 包含方法
- Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
- net.sf.cglib.proxy.Enhancer 类
- 负责动态创建代理类及其实例
- 主要方法
- setSupperclass()
- setCallback()
- set…
- create()
3.4.6 JDK 和 CGLIB 动态代理的对比
- JDK 动态代理面向接口代理,只能对基于接口设计的目标对象进行代理
- CGLIB 动态代理可以通过继承方式实现,不依赖接口,但是不能代理 final 的类和方法
四、Redis
4.1 Redis 简介
Redis 是开源、高性能的key-value
数据库,属于 NoSQL 数据库
NoSQL 数据库与关系型数据库
- 关系型数据库:采用关系模型来组织数据,主要用于存储格式化的数据结构
- NoSQL 数据库:泛指非关系型数据库,向外提供特定的 API 而不是通用的 SQL 接口,数据访问更加高效
4.1.1 Redis 的特点
- 不仅支持简单的 string 类型数据,同时还提供 list、set、zset、hash 等数据结构的存储
- 支持数据持久化,可以将内存中的数据保存在磁盘中
- 支持 master-slave 模式的数据备份
4.2 Linux 安装 Redis
4.2.1 前置条件
Linux 本地体验需安装 Linux 虚拟机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8khrxLX-1691793219510)(./assets/image-20230726080419008.png)]
Linux 安装 Redis 需学到第 15 节
https://www.bilibili.com/video/BV1Sv411r7vd?p=15&vd_source=3b9216711765e1ac14ef21c8216ed8eb
4.2.2 安装包
redis https://download.redis.io/releases/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHir9fOg-1691793219511)(./assets/image-20230726121134846.png)]
选择需要的版本下载即可
4.2.3 安装教程
此处默认您已会使用 VM 虚拟机运行 Linux 系统,包括 Xshell 与 Xftp
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jPqLeuAT-1691793219511)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230726102746.png)]
虚拟机输入
ifconfig -a
获取 Linux IP 地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1rXfhV9e-1691793219512)(./assets/image-20230726103135318.png)]
使用 Xftp 将 Redis 安装包上传到 Linux
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3Xz9RkE-1691793219512)(./assets/Snipaste_2023-07-26_11-02-56.png)]
使用 Xshell 连接 Linux 输入
ll
查看上传状态然后输入
tar -zxvf Redis 文件名
解压 Redis 压缩包Redis 文件名输入两个字母按下 Tab 键可自动补全
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6DZhfz1Q-1691793219512)(./assets/Snipaste_2023-07-26_11-03-45.png)]
输入
cd
进入到解压后的 Redis 目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhfoROij-1691793219513)(./assets/Snipaste_2023-07-26_11-03-59.png)]
因为 Redis 使用 C 编写,所以需安装 GCC 依赖
输入
yum install -y gcc
这里的
-y
表示一键安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8yj9UIV-1691793219513)(./assets/Snipaste_2023-07-26_11-04-23.png)]
安装完成后输入
gcc -v
校验安装再输入
make install
安装 Redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcXvxShR-1691793219513)(./assets/Snipaste_2023-07-26_11-10-03.png)]
输入
cd utils
进入到utlis
目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hN1SwGOJ-1691793219513)(./assets/Snipaste_2023-07-26_11-11-01.png)]
输入
./install_server.sh
执行脚本
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQAtR5KR-1691793219514)(./assets/ee43889607d8180259a4987c166d15a.png)]
这里可能会出现报错,打开脚本注释以下内容后重新执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uj7sstd8-1691793219514)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230726111436.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f20mG8wx-1691793219514)(./assets/Snipaste_2023-07-26_11-12-51.png)]
执行脚本可对 Redis 进行设置,无特殊需求直接回车结束即可
4.2.4 校验
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tX73YeN-1691793219515)(./assets/image-20230726121548324.png)]
4.3 Windows 安装 Redis
4.3.1 安装包
Redis for Windows 5.0.14.1 https://github.com/tporadowski/redis/releases/tag/v5.0.14.1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MuTrWCy1-1691793219515)(./assets/image-20230726090924583.png)]
Redis 7.0.11 for Windows https://github.com/zkteco-home/redis-windows/releases/tag/7.0.11
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QQ17N5Cf-1691793219515)(./assets/image-20230726093618285.png)]
4.3.2 安装教程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SD4jZNIB-1691793219515)(./assets/redis-1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nTv4gIQf-1691793219515)(./assets/redis-2.png)]
选择安装路径,并勾选下方选项,将 Redis 添加到系统环境变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIUy9LBp-1691793219516)(./assets/redis-3.png)]
这个是 Redis 默认的端口号,无特殊需求不用改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xh7FUjED-1691793219516)(./assets/redis-4.png)]
设置最大内存限制,无特殊需求不用改
4.3.3 校验
键盘按下 Win + R
输入 cmd
打开命令提示符,输入以下指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qi4igT7P-1691793219516)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230726091513.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32gFADGi-1691793219516)(./assets/image-20230726091645826.png)]
4.3.4 RedisDesktopManager
RedisDesktopManager https://github.com/RedisInsight/RedisDesktopManager/releases/tag/2022.5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tgFCxfAF-1691793219516)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230726182227.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Be7xOgEW-1691793219517)(./assets/af0e26cb23427e843680837320f8e93.png)]
4.4 Linux 配置 Redis
4.4.1 环境配置
设置内存分配策略
echo 1 > /proc/sys/vm/overcommit_memory
开放Redis端口
- 配置防火墙开放6379端口
- 重启防火墙使规则生效
4.4.2 配置文件
设置Redis配置文件redis.conf
- 设置Redis服务可以监听的请求IP地址,多个IP地址使用空格分隔
bind 127.0.0.1 ::
- bind 0.0.0.0或使用 # 将bind指令注释掉,则可监听所有IP地址的请求
- 如果Redis服务运行于Internet上,建议仅绑定应用相关的客户端IP地址
- 设置Redis监听端口,默认是6379
port 6379
- 设置Redis访问密码,默认不需要密码
requirepass 123456
由于Redis的高性能特性,可以在很短的时间内并行尝试大量密码,因此应使用一个高强度的密码
- 设置Redis是否作为守护进程运行,默认no
daemonize yes
- 设置Redis的日志文件路径,默认"",不生成日志文件
logfile "/usr/local/redis-3.2.8/log/redis.log"
- 设置Redis数据库数量,默认是16
databases 16
- Redis中不同的可选数据库以命名空间的形式管理,数据文件是同一个2. Redis划分数据库主要用于在必要的情况下分隔同一应用中存储的key(不同数据库中可以使用相同名称的key),而不是为了区分多个应用
- 设置Redis的工作目录(即Redis的数据文件的存储目录),默认为 ./,即在Redis安装目录下
dir /data/redisdata/
4.4.3 启动Redis服务
- 执行Redis安装目录下bin目录中的redis-server命令
cd /usr/local/redis-3.2.8/
./bin/redis-server ./etc/redis.conf
启动Redis服务所依据的配置文件
注意daemonize指令设置为no或yes的区别
- 可以使用tail命令跟踪Redis日志信息
tail -F /usr/local/redis-3.2.8/log/redis.log
- daemonize为no时,可以另外开启一个会话连接至Redis服务器,并在此会话中跟踪Redis日志内容的变化2. 如果Redis的日志文件路径为默认的 logfile “”,则不会创建日志文件,daemonize为yes时,日志信息会直接输出在控制台中
4.4.4 启动Redis客户端
执行Redis安装目录下bin目录中的redis-cli命令
redis-cli [选项]
选项 | 说明 |
---|---|
-h <hostname> | 服务器主机地址,默认为127.0.0.1 |
-p <port> | 服务端口,默认为6379 |
-a <password> | Redis服务访问密码 |
-n <dbid> | 所要连接的数据库的id,默认为0 |
### 连接到本地Redis服务中dbid为0数据库
cd /usr/local/redis-3.2.8/
./bin/redis-cli
4.5 Windows 配置 Redis
Windows 版 Redis 不支持 daemonize 参数
- MSI 软件包可以直接将 Redis 安装成 Windows 服务,并通过 redis.windows-service.conf 文件配置其他参数
- 解压版 Redis 需通过命令将 redis-server.exe 注册为服务
// 进入到Redis的安装目录下执行
redis-server --service-install redis.windows.conf --loglevel verbose
其他配置参数、使用方式与 Linux 版相同
4.6 Redis常用命令
4.6.1 认证和退出操作
auth
- 请求进行身份验证
- 如果password与配置文件中的密码匹配,则返回OK状态代码并开始接受命令。否则返回错误quit要求服务器关闭连接
auth password
quit
- 要求服务器关闭连接
4.6.2 键值对操作
set
- 设置key以保存value(string类型)
- set正确执行,返回字符串OK。未执行set操作,则返回空回复nil
set key value [ex seconds | px milliseconds] [nx | xx]
- ex:设置指定的过期时间seconds,以秒为单位
- px:设置指定的过期时间milliseconds,以毫秒为单位
- nx:仅在key不存在时设置该key(只创建、不覆盖)
- xx:仅在key已存在时设置该key(只覆盖、不创建)
get
- 获取key对应value
- 如果key不存在,则返回特殊值nil。如果存储在key中的值不是字符串,则返回错误,因为get仅处理字符串值
get key
4.6.3 key 操作
exists
- 判断指定的key是否存在
- 从Redis 3.0.3开始,可以指定多个key
- 仅指定一个key时,key存在返回1,不存在返回0。
- 指定多个key时,返回存在的key的总数
exists key1 [key2 ...]
keys
- 返回和pattern(模式)匹配的所有key
keys pattern
支持的常用模式如下
h?llo
:匹配单个字符,如hello,hallo,hxllo等h*llo
:匹配任意字符,如hllo,heeeello等h[ae]llo
:包含一个指定字符,如hello或halloh[^ae]llo
:包含除指定字符外的一个字符,如hbllo,hcllo等,不包括hallo和helloh[a-c]llo
:匹配指定范围内的一个字符,如hallo,hbllo,hcllo- 如需匹配以上模式中特殊字符,需要使用
\
转义
del
- 删除指定的key
- 返回已删除的key的数量。如果key不存在则忽略该key
del key1 [key2 ...]
rename
- 将key重命名为newkey如果newkey已经存在则会被覆盖(此时rename执行隐式del操作)
- 若key不存在返回错误
rename key newkey
4.6.4 生命周期操作
expire
- 在key上设置超时时间timeout,时间以秒为单位
- 设置成功返回1,key不存在返回0
expire key timeout
ttl
- 以秒为单位返回key的剩余生存时间
- Redis 2.8及以上版本,key未设置超时时间返回-1,key不存在返回-2
- Redis 2.6及以下版本,未设置超时时间和key不存在均返回-1
ttl key
persist
- 删除key上现有的超时设置,使key变为永久有效
- 超时设置被删除,返回1
- key不存在或没有关联的超时设置,返回0
persist key
4.6.5 数据库操作
select
- 选择具有指定dbid的Redis逻辑数据库
select dbid
flushdb
- 删除当前所选数据库中的所有key
flushall
- 删除所有现有数据库中的所有key,而不仅仅是当前选定的数据库
4.7 Java 访问 Redis
Jedis 在 Java 应用中实现 Redis 客户端的功能
- 下载 Jedis 依赖
- 下载 Apache Commons Pool 2 组件
Jedis 的使用
- JedisPoolConfig:连接池配置对象
- maxTotal:最大活动连接数,默认为8。-1表示不限制
- maxIdle:最大空闲连接数,默认为8
- minIdle:最小空闲连接数,默认为0
- maxWaitMillis:从池中获取一个资源时的最大等待时间,单位是毫秒,-1表示永不超时
- testOnBorrow:在从池中获取一个资源时,是否提前进行验证操作
- JedisPool:连接池对象
- Jedis:封装Redis访问API的核心类
4.7.1 代码案例
- 普通连接
public class Xz01 {
public static void main(String[] args) {
Jedis redis = new Jedis("localhost", 6379);
System.out.println(redis.ping());
}
}
- 数据池连接
先打 jar 包
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0-beta2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
- 工具类
public class JedisAPI {
private static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(50);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMinIdle(7);
jedisPoolConfig.setMaxWaitMillis(10000);
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379, 10000);
// ,"0207",0
}
public boolean set(String key, String value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
public String get(String key) {
Jedis jedis = null;
String value = null;
try {
jedis = jedisPool.getResource();
value = jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return value;
}
public void destroy() {
if ((jedisPool != null) || jedisPool.isClosed()) {
jedisPool.close();
}
}
public String ping() {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.ping();
} catch (Exception e) {
e.printStackTrace();
return "false";
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
- 调用代码
public class Xz02 {
public static void main(String[] args) {
JedisAPI jedisAPI = new JedisAPI();
String set = jedisAPI.ping();
System.out.println(set);
jedisAPI.destroy();
}
}
玄子Share - Java 开发之框架基础技术 8.2