中介者(Mediator)模式指定义了一个单独的中介对象,来封装一组对象之间的交互。即将这组对象之间的交互委派给中介对象,从而来避免对象之间的直接交互。比如我们各种设备之间的通信,就是通过服务器作为中介对象来进行交互:

一、中介者模式介绍

中介者又叫做调停模式,是一种对象行为型模式,它降低了对象之间的耦合性,让对象易于被独立地调用,是迪米特法则的典型应用,下面就来看看中介者模式的结构和实现:

1.1 中介者模式的结构

中介者模式主要通过引入用于协调其他对象或类之间相互调用的中介者类,为了让系统具有具有更好的灵活性和扩展性。其结构如下图所示:

上面的类图中主要包含以下角色:

  • Mediator:抽象中介者,是中介者的接口/抽象类
  • ConcreteMeditor:中介者的具体实现,实现中介者接口,定义一个List来管理Colleague对象
  • Colleague:抽象同事类,定义同事类的接口/抽象类,保存中介者对象,实现同事类的公共方法
  • ConcreteColleague1、ConcreteColleague2:具体同事类,实现抽象同事类。通过中介者间接完成具体同事类之间的通信交互

1.2 中介者模式的实现

根据上面的类图,可以实现如下代码:

  1. 抽象中介者及其实现
/*** @description: 中介者抽象类* @author: wjw* @date: 2022/4/7*/
public abstract class Mediator {/**注册同事类*/public abstract void register(Colleague colleague);/**处理接收逻辑*/public abstract void operation(Colleague colleague);
}/*** @description: 具体中介者类* @author: wjw* @date: 2022/4/7*/
public class ConcreteMediator extends Mediator{private List<Colleague> colleagues = new ArrayList<Colleague>();@Overridepublic void register(Colleague colleague) {if (!colleagues.contains(colleague)) {colleagues.add(colleague);colleague.setMediator(this);}}@Overridepublic void operation(Colleague colleague) {for (Colleague coll : colleagues) {if (!coll.equals(colleague)) {coll.receive();}}}
}
  1. 抽象同事类及其实现
/*** @description: 抽象同事类* @author: wjw* @date: 2022/4/7*/
public abstract class Colleague {protected Mediator mediator;public void setMediator(Mediator mediator) {this.mediator = mediator;}public abstract void receive();public abstract void send();
}/*** @description: 具体同事类1* @author: wjw* @date: 2022/4/7*/
public class ConcreteColleague1 extends Colleague{@Overridepublic void receive() {System.out.println("具体同事类 ConcreteColleague1 接收请求");}@Overridepublic void send() {System.out.println("具体同事类 ConcreteColleague1 发送请求");/*中介者进行转发*/mediator.operation(this);}
}/*** @description: 具体同事类2* @author: wjw* @date: 2022/4/7*/
public class ConcreteColleague2 extends Colleague{@Overridepublic void receive() {System.out.println("具体同事类 ConcreteColleague2 接收到请求");}@Overridepublic void send() {System.out.println("具体同事类 ConcreteColleague2 发送请求");mediator.operation(this);}
}
  1. 客户端测试类
/*** @description: 客户端* @author: wjw* @date: 2022/4/7*/
public class Client {public static void main(String[] args) {Mediator concreteMediator = new ConcreteMediator();Colleague concreteColleague1 = new ConcreteColleague1();Colleague concreteColleague2 = new ConcreteColleague2();concreteMediator.register(concreteColleague1);concreteMediator.register(concreteColleague2);concreteColleague1.send();concreteColleague2.send();}
}

测试结果为:

具体同事类 ConcreteColleague1 发送请求
具体同事类 ConcreteColleague2 接收到请求
具体同事类 ConcreteColleague2 发送请求
具体同事类 ConcreteColleague1 接收请求

二、中介者模式应用场景

2.1 中介者模式的适用情况

如果遇到以下情况可以考虑使用中介者模式:

  1. 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解
  2. 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
  3. 需要通过一个中间类来封装多个类中的行为,但又不想生成太多的子类

2.2 中介者模式在MVC模式中的应用

比如说,在MVC框架中,控制器(Controller)就是模型(Model)和视图(View)之间的中介者:

  • Model(模型):代表一个存取对象的数据,有Dao、Bean等等
  • View(视图):表示所看到的东西,比如网页、JSP等用于展示模型中的数据
  • Controller(控制器):作用于模型和视图中间,控制数据流向模型对象,在数据变化时更新视图

三、中介者模式实战

3.1 ORM框架

我们知道在Java与数据库交互中JDBC可以完成对多种数据库操作,举一个利用JDBC查询的例子:

//1.加载MySQL驱动注入到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
//2.提供JDBC连接的URL、用户名和密码
String url = "jdbc:mysql://localhost:3306/test_db?";
String username = "root";
String password = "root";
//3.创建数据库的连接
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建statement实例
Statement statement = connection.createStatement();
//5.1执行SQL语句,得到ResultSet对象,
String query = "select * from test";  //查询语句,也可以换成CRUD的其他语句
ResultSet resultSet = statement.executeQuery(query);
while(resultSet.next()){//5.2通过ResultSet读取数据后,将数据转换成JavaBean对象
}
//6.关闭连接对象
connection.close();

在上面的步骤中,步骤1~4和6都可以封装重复执行,但是在第5步中,需要完成关系模型ResultSet到对象模型JavaBean的转换,而这一部分使用通用的方式封装这种复杂的转换是比较困难的,因此有ORM(Object Relational Mapping, 对象-关系映射)框架来解决对象转换关系模型的映射问题。同时也屏蔽了之前JDBC连接中的重复代码,只提供简单的API供开发人员进行使用。

3.2 利用中介者模式模仿MyBatis核心功能

在本案例中我们通过模仿MyBatis 中核心ORM框架功能,来使用中介者模式。首先来看看ORM框架在数据库和应用交互中的位置:

从图中可以看出,ORM框架位于数据库层和应用层中间,相当于两者之间的中介。在实际MyBatis 实现过程中,不仅用到了中介者模式,还有工厂模式和建造者模式。

在ORM框架实现的核心类中,包括加载配置文件、对XML进行解析、获取数据库session、操作数据库以及返回结果等步骤。在ORM内部的结构如下图所示(来自《重学Java设计模式》):

  • 左上框内是对数据库的定义和处理,包括<T> T selectOne<T> List<T> selectList等等
  • 右上是对数据库配置的开启session的工厂处理类,工厂会操作DefaultSqlSession
  • 最后是核心类SqlSessionFactoryBuilder,它可以实现处理工厂、解析文件、拿session等操作

下面就来看看具体代码

实战代码

  1. 创建对应数据库、JavaBean和Dao接口

创建数据库design-mediatro,数据表userschool


创建与数据库相对应的JavaBean、Dao接口

/*** @description: 学校类* @author: wjw* @date: 2022/4/7*/public class School {private Long id;private String name;private String address;private Date createTime;private Date updateTime;//get set...
}
/*** @description: 用户类* @author: wjw* @date: 2022/4/7*/public class User {private Long id;private String name;private Integer age;private Date createTime;private Date updateTime;//get set...
}
/*** @description: SchoolDao接口* @author: wjw* @date: 2022/4/7*/
public interface ISchoolDao {User querySchoolInfoById(Long treeId);
}
/*** @description: 用户Dao接口* @author: wjw* @date: 2022/4/7*/
public interface IUserDao {User queryUserInfoById(Long id);
}
  1. mapper相关配置文件

mybatis-config-datasource

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/design_mediator?useUnicode=true"/><property name="username" value="root"/><property name="password" value="root123"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/><mapper resource="mapper/School_Mapper.xml"/></mappers>
</configuration>

School_Mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.ISchoolDao"><select id="querySchoolInfoById" resultType="po.School">SELECT id, name, address, createTime, updateTimeFROM schoolWHERE id = #{id}</select>
</mapper>

User_Mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="po.User">SELECT id, name, age, createTime, updateTimeFROM userwhere id = #{id}</select><select id="queryUserList" parameterType="po.User" resultType="po.User">SELECT id, name, age, createTime, updateTimeFROM userWHERE age = #{age}</select></mapper>
  1. mediator中介者部分

SqlSession ,定义对数据库操作的查询接口,包括一个结果和多个查询结果,同时包括有参和无参的方法

/*** @description: 各种查询接口* @author: wjw* @date: 2022/4/7*/
public interface SqlSession {<T> T selectOne(String statement);<T> T selectOne(String statement, Object parameter);<T> List<T> selectList(String statement);<T> List<T> selectList(String statement, Object parameter);void close();
}

SqlSession具体实现类DefaultSqlSession,包装jdbc层

/*** @description: SqlSession具体实现类* @author: wjw* @date: 2022/4/7*/
public class DefaultSqlSession implements SqlSession{private Connection connection;private Map<String, XNode> mapperElement;public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {this.connection = connection;this.mapperElement = mapperElement;}@Overridepublic <T> T selectOne(String statement) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {ArrayList<T> list = new ArrayList<>();try {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();while (resultSet.next()) {T obj = (T) clazz.newInstance();for (int i = 1; i <= columnCount; i++) {Object value = resultSet.getObject(i);String columnName = metaData.getColumnName(i);String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);Method method;if (value instanceof Timestamp) {method = clazz.getMethod(setMethod, java.util.Date.class);} else {method = clazz.getMethod(setMethod, value.getClass());}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}@Overridepublic <T> T selectOne(String statement, Object parameter) {XNode xNode = mapperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {int size = parameterMap.size();if (parameter instanceof Long) {for (int i = 1; i <= size; i++) {preparedStatement.setLong(i, Long.parseLong(parameter.toString()));}return;}if (parameter instanceof Integer) {for (int i = 1; i <= size; i++) {preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));}return;}if (parameter instanceof String) {for (int i = 1; i <= size; i++) {preparedStatement.setString(i, parameter.toString());}return;}Map<String, Object> fieldMap = new HashMap<>();Field[] declaredFields = parameter.getClass().getDeclaredFields();for (Field field : declaredFields) {String name = field.getName();field.setAccessible(true);Object obj = field.get(parameter);field.setAccessible(false);fieldMap.put(name, obj);}for (int i = 1; i <= size; i++) {String parameterDefine = parameterMap.get(i);Object obj = fieldMap.get(parameterDefine);if (obj instanceof Short) {preparedStatement.setShort(i, Short.parseShort(obj.toString()));continue;}if (obj instanceof Integer) {preparedStatement.setInt(i, Integer.parseInt(obj.toString()));continue;}if (obj instanceof Long) {preparedStatement.setLong(i, Long.parseLong(obj.toString()));continue;}if (obj instanceof String) {preparedStatement.setString(i, obj.toString());continue;}if (obj instanceof Date) {preparedStatement.setDate(i, (java.sql.Date) obj);}}}@Overridepublic <T> List<T> selectList(String statement) {XNode xNode = mapperElement.get(statement);try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement, Object parameter) {XNode xNode = mapperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic void close() {if (null == connection) {return;}try {connection.close();} catch (SQLException e) {e.printStackTrace();}}
}

SqlSessionFactory接口,利用工厂模式在每次数据库操作时都会获取每一次执行的SqlSession

/*** @description: SqlSession工厂类* @author: wjw* @date: 2022/4/7*/
public interface SqlSessionFactory {SqlSession openSession();
}

DefaultSqlSessionFactory具体实现类

/*** @description: SqlSessionFactory具体实现类* @author: wjw* @date: 2022/4/7*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {//向下传递configuration配置文件this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration.connection, configuration.mapperElement);}
}

SqlSessionFactoryBuilder具体实现,包括build(构建实例化元素)parseConfiguration(解析配置)dataSource(获取数据库配置)connection(Map<String, String> dataSource)(链接数据库)mapperElement(解析sql语句)

/*** @description: 创建解析xml文件的类,初始化SqlSession工厂类* @author: wjw* @date: 2022/4/7*/
public class SqlSessionFactoryBuilder {/*** 构建实例化元素* @param reader 读入* @return SqlSession*/public DefaultSqlSessionFactory build(Reader reader) {SAXReader saxReader = new SAXReader();try {/**保证在不联网时一样可以解析xml,否则会从互联网中获取dtd文件*/saxReader.setEntityResolver(new XMLMapperEntityResolver());Document document = saxReader.read(new InputSource(reader));Configuration configuration = parseConfiguration(document.getRootElement());return new DefaultSqlSessionFactory(configuration);} catch (Exception e) {e.printStackTrace();}return null;}/*** 解析配置* @param root* @return*/private Configuration parseConfiguration(Element root) {Configuration configuration = new Configuration();configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));configuration.setConnection(connection(configuration.dataSource));configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));return configuration;}private Map<String, String> dataSource(List<Element> list) {Map<String, String> dataSource = new HashMap<>(4);Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String name = e.attributeValue("name");String value = e.attributeValue("value");dataSource.put(name, value);}return dataSource;}/*** 连接数据库* @param dataSource* @return*/private Connection connection(Map<String, String> dataSource) {try {Class.forName(dataSource.get("driver"));return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));} catch (Exception e) {e.printStackTrace();}return null;}//解析sql语句private Map<String, XNode> mapperElement(List<Element> list) {Map<String, XNode> map = new HashMap<>();Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String resource = e.attributeValue("resource");try {Reader reader = Resources.getResourceAsReader(resource);SAXReader saxReader = new SAXReader();Document document = saxReader.read(new InputSource(reader));Element root = document.getRootElement();String namespace = root.attributeValue("namespace");// SELECTList<Element> selectNodes = root.selectNodes("select");for (Element node : selectNodes) {String id = node.attributeValue("id");String parameterType = node.attributeValue("parameterType");String resultType = node.attributeValue("resultType");String sql = node.getText();// ? 匹配Map<Integer, String> parameter = new HashMap<>();Pattern pattern = Pattern.compile("(#\\{(.*?)})");Matcher matcher = pattern.matcher(sql);for (int i = 1; matcher.find(); i++) {String g1 = matcher.group(1);String g2 = matcher.group(2);parameter.put(i, g2);sql = sql.replace(g1, "?");}XNode xNode = new XNode();xNode.setNamespace(namespace);xNode.setId(id);xNode.setParameterType(parameterType);xNode.setResultType(resultType);xNode.setSql(sql);xNode.setParameter(parameter);map.put(namespace + "." + id, xNode);}} catch (Exception ex) {ex.printStackTrace();}}return map;}
}
  1. 测试验证
/*** @description: 测试* @author: wjw* @date: 2022/4/7*/
public class ApiTest1 {private Logger logger = LoggerFactory.getLogger(ApiTest1.class);@Testpublic void test_querySchoolInfoById() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {School school = session.selectOne("dao.ISchoolDao.querySchoolInfoById", 1L);logger.info("测试结果:{}", JSON.toJSONString(school));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}@Testpublic void test_queryUserInfoById() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User user = session.selectOne("dao.IUserDao.queryUserInfoById", 1L);logger.info("测试结果:{}", JSON.toJSONString(user));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}@Testpublic void test_queryUserList() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User req = new User();req.setAge(18);List<User> userList = session.selectList("dao.IUserDao.queryUserList", req);logger.info("测试结果:{}", JSON.toJSONString(userList));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}}

测试结果:

10:54:56.778 [main] INFO  ApiTest1 - 测试结果:{"address":"北京市海淀区颐和园路5号","createTime":1571376957000,"id":1,"name":"北京大学","updateTime":1571376957000}
---------------------------------------------------------------------------------
10:56:17.654 [main] INFO  ApiTest1 - 测试结果:{"age":18,"createTime":1576944000000,"id":1,"name":"⽔⽔","updateTime":1576944000000}
---------------------------------------------------------------------------------
10:56:42.983 [main] INFO  ApiTest1 - 测试结果:[{"age":18,"createTime":1576944000000,"id":1,"name":"⽔⽔","updateTime":1576944000000},{"age":18,"createTime":1576944000000,"id":2,"name":"⾖⾖","updateTime":1576944000000}]

参考资料

《重学Java设计模式》

《Java设计模式》

《MyBatis技术内幕》

http://c.biancheng.net/view/1393.html

中介者模式及其应用场景相关推荐

  1. 【设计模式】中介者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

    文章目录 一.中介者模式简介 二.中介者模式适用场景 三.中介者模式优缺点 四.中介者模式 与 观察者模式 五.中介者模式 代码示例 1.聊天室 2.用户 3.运行实例 一.中介者模式简介 中介者模式 ...

  2. 中介者模式 调停者 Mediator 行为型 设计模式(二十一)

    中介者模式(Mediator) 调度.调停 意图 用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散 而且可以独立地改变它们之间的交互. 中介者模式又 ...

  3. 《研磨设计模式》chap10 中介者模式Mediator(1)简介

    1. 使用场景 如果电脑没有主板,各个配件的关系为 有主板后: 2. 中介者模式简介:各对象通过中介者来交互 //Mediator.java public interface Mediator { p ...

  4. 23种设计模式之中介者模式

    中介者模式的定义 中介者模式, 当多个类彼此关联, 会增大耦合性, 这时各个模块通过中介者进行交流, 每个模块只负责自己的业务逻辑, 不属于自己的就丢给中介者, 降低耦合 定义: 用一个中介对象封装一 ...

  5. 设计原则与模式009 中介者模式

    中介者模式:名如其义:其核心设计原则是迪米勒法则;通过中介者降低彼此模型之间的沟通 1产生场景 中介者模式场景介绍:假设有一个进销存模型,进货是根据销售情况决定,同时进货会影响库存; 最开始的流程伪代 ...

  6. java 中介模式_中介者模式

    什么是中介者模式? 在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室.QQ群.短信平台和房产中介.不论是QQ游戏还是QQ群,它们都是充当一个中间平台,QQ用户可以登录这个中间平台与其他Q ...

  7. 中介者模式(Java)

    中介者模式(Java) 下面是关于我所写的所有设计模式代码(还是建议自己手打或者想一个别的例子练习一次) (https://github.com/lihang212010/DesignPatterns ...

  8. 详解设计模式:中介者模式

    中介者模式(Mediator Pattern)也被称为调停者模式,是在 GoF 23 种设计模式中定义了的行为型模式. 中介者模式 是用来降低多个对象和类之间的通信复杂性.这种模式提供了一个中介类,该 ...

  9. 设计模式之中介者模式(Mediator Pattern)

    中介者模式定义 Define an object that encapsulates how a set of objects interact. Mediator promotes loose co ...

最新文章

  1. 智领新时代 慧享新生活 —— CITE2018新闻发布会在北京召开
  2. Luogu 2827 [NOIP2016] 蚯蚓
  3. 外挂学习之路(14)--- 游戏中的二叉树
  4. abap 函数远程启用的模块参数_SAP AMDP介绍 - ABAP托管的HANA数据库过程
  5. linux执行脚本n,Linux执行sh脚本空白
  6. python写入excel特定区域_Python读取Excel中符合特定条件的数据,并写入新的表格中...
  7. Python3 —— 逗号分隔值CSV
  8. Android中LayoutInflater()方法
  9. 【JVM】javap命令行分析揭示boolean的本质的虚拟机指令
  10. python多继承_Python多继承,__init__
  11. 超越存储,历久弥新!新华三发布入门级存储产品
  12. Linux下的段错误产生的原因及调试方法-转
  13. 计算机安全知识策划书,安全知识策划书
  14. flink cdc 2.0.0 sql 开发模板,及踩坑记录
  15. 【Unity3D】GUI控件
  16. linux查看u盘的分区格式化,linux U盘格式化ext3
  17. NeoKylin-Server使用docker部署跨主机redis集群
  18. 如何在HTML中添加背景音乐?
  19. 奶瓶(beini) 又一蹭网神器 1.2.2增强版带600万密码字典
  20. 各个国家 不同字符集的unicode 编码范围

热门文章

  1. 《Mysql数据库》
  2. CAS和自旋到底是一个概念吗?
  3. mac 打字卡顿的解决方法
  4. [附源码]计算机毕业设计JAVAjsp高校奖学金评定管理系统
  5. 研究生如何做好科研和发表文章
  6. 《数据之美》读书笔记
  7. 计算机怎么升级64位操作系统,如何将计算机的32位更改为64位
  8. 亿图图示----科学与软件展示
  9. oracle select ora-16000,ORA-00604, ORA-16000: 打开数据库以进行只读访问
  10. 美国国立卫生研究院(NIH)江晓芳组诚聘生物信息学博士后