Java客户端API
添加依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
创建会话
- 构造器方法
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd)
ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
- 参数详解
| 参数名 | 描述 |
|---|---|
| connectString | 形如ip:port,ip:port/path1/path2,表示zk服务器列表,带path路径的表示基于此path操作 |
| sessionTimeout | 客户端会话超时时间,毫秒值,在sessionTimeout时间内没有进行有效的心跳检测,则认为会话超时 |
| watcher | 默认监听器,可以不设置,传null即可 |
| canBeReadOnly | boolean值,true表示只读,默认为false |
| sessionId和sessionPasswd | 会话id和会话的秘钥,当一个会话创建后,会自动生成对应的id和秘钥,主要用来恢复会话 |
- 例子
package com.banary.base;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.util.concurrent.CountDownLatch;
public class ZookeeperFactory {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
createZk2();
}
public static void createZk1() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
System.out.println(zooKeeper.getState());
countDownLatch.await();
System.out.println("zk实例创建成功");
}
public static void createZk2() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
System.out.println(zooKeeper.getState());
countDownLatch.await();
long sessionId = zooKeeper.getSessionId();
byte[] sessionPasswd = zooKeeper.getSessionPasswd();
//使用错的sessionId和sessionPasswd
zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher(), 1l, "test".getBytes());
//使用对的sessionId和sessionPasswd
zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher(), sessionId, sessionPasswd);
Thread.sleep(Integer.MAX_VALUE);
}
private static class DemoWatcher implements Watcher{
public void process(WatchedEvent watchedEvent) {
System.out.println("收到zk event:" + watchedEvent);
countDownLatch.countDown();
}
}
}
- 注意
创建zk对象的方法是异步的,此处采用CountDownLatch实现同步
创建节点
- 方法(不支持递归创建,如果该节点已存在,会抛出异常NodeExistsException)
#同步方法
public String create(String path, byte[] data, List<ACL> acl, CreateMode createMode) throws KeeperException, InterruptedException
#异步方法
public void create(String path, byte[] data, List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx)
- 参数详解
| 参数名 | 描述 |
|---|---|
| path | 要创建的数据节点的路径 |
| data | 字节数组,该节点的初始内容,需要自己序列化成字节数组 |
| acl | 节点的安全策略,详见ZooDefs.Ids |
| createMode | 枚举类型,相见CreateMode |
| cb | 异步创建时的回调函数,需要开发者自己实现对应的AsyncCallback子接口,如StringCallback,重写void processResult(int var1, String var2, Object var3, String var4)方法,当zk服务器创建完节点,客户端自动调用该方法 |
| ctx | 回调函数上下文,和回调函数一起使用 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import java.util.concurrent.CountDownLatch;
public class CreateDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
asyncCreate();
}
/**
* 同步创建
* @throws Exception
*/
public static void syncCreate() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
//阻塞,直到zk链接成功
countDownLatch.await();
//创建临时节点,没有权限限制
String path1 = zooKeeper.create("/zk-test-ephemeral-", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("临时节点1创建成功:"+ path1);
//创建临时顺序节点,没有权限限制
String path2 = zooKeeper.create("/zk-test-ephemeral-", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("临时节点2创建成功:" + path2);
}
/**
* 异步创建
* @throws Exception
*/
public static void asyncCreate() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
//阻塞,直到zk链接成功
countDownLatch.await();
//创建临时节点,没有权限限制
zooKeeper.create("/zk-test-ephemeral-", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL,
new DemoCallback(), "path1");
//创建临时顺序节点,没有权限限制
zooKeeper.create("/zk-test-ephemeral-", "1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
new DemoCallback(), "path2" );
Thread.sleep(Integer.MAX_VALUE);
}
private static class DemoWatcher implements Watcher{
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
countDownLatch.countDown();
}
}
}
private static class DemoCallback implements AsyncCallback.StringCallback {
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println("创建节点:[" + rc + ", " + path + ", " + ctx.toString() +
", 真实path:" + name + "]");
}
}
}
- 异步方法回调接口参数说明
| 参数名 | 描述 |
|---|---|
| rc | 服务端响应码:0 创建成功;-4 客户端和服务端的链接断开;-110 节点已存在;-112 会话过期 |
| path | 对应create方法中节点路径参数值 |
| ctx | 接口调用时传如API的ctx,即对应异步create方法中的ctx参数 |
| name | 创建成功后,对应节点的真是路径 |
- 同步方法和异步方法比较
- 同步方法会阻塞线程,异步方法不会
- 同步会抛出异常,异步方法不会,异常信息是通过状态码的方式传递到回调函数中
删除
- 方法(删除时不支持递归删除,也就是说某个节点如果存在子节点,则不能删除)
#同步删除
public void delete(String path, int version) throws InterruptedException, KeeperException
#异步删除
public void delete(String path, int version, VoidCallback cb, Object ctx)
- 参数说明
| 参数名 | 描述 |
|---|---|
| path | 要删除的节点对应的path |
| version | 数据节点的版本号,乐观锁 |
| cb | 异步删除时的回调函数 |
| ctx | 回调函数的参数 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import java.util.concurrent.CountDownLatch;
public class DeleteDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
asyncDelete();
}
public static void syncDelete() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/deleteDemo", "deleteDemo".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
Thread.sleep(10000);
zooKeeper.delete(path, 0);
}
public static void asyncDelete() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/deleteDemo", "deleteDemo".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
Thread.sleep(5000);
zooKeeper.delete(path, 0, new DemoCallback(), "asyncDelete");
Thread.sleep(5000);
}
private static class DemoWatcher implements Watcher{
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("创建zk会话:" + watchedEvent.getState());
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
System.out.println("zk会话创建成功");
countDownLatch.countDown();
}
}
}
public static class DemoCallback implements AsyncCallback.VoidCallback{
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("删除节点成功:[" + rc + ", " + path + ", " + ctx.toString() + "]");
}
}
}
查询节点数据内容getData
- 方法
public byte[] getData(String path, Watcher watcher, Stat stat) throws KeeperException, InterruptedException
public byte[] getData(String path, boolean watch, Stat stat) throws KeeperException, InterruptedException
public void getData(String path, Watcher watcher, DataCallback cb, Object ctx)
public void getData(String path, boolean watch, DataCallback cb, Object ctx)
- 参数
| 参数名 | 描述 |
|---|---|
| path | 要获取数据的节点的路径 |
| watcher | 监听器 |
| watch | true表示使用默认的监听器,即创建zk对象时注册的监听器 |
| stat | 数据节点的状态信息,传入一个旧的stat变量,服务器响应后会用的新的stat变量替换 |
| cb | 异步方法的回调函数 |
| ctx | 回调函数上下文参数 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class GetDataDemo {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
asyncGetData();
}
/**
* 同步获取数据
* @throws Exception
*/
public static void syncGetData() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/syncGetData", "syncGetData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(new String(zooKeeper.getData(path,true, new Stat())));
}
/**
* 异步获取数据
*/
public static void asyncGetData() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/asyncGetData", "asyncGetData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(new String(zooKeeper.getData(path,true, new Stat())));
Thread.sleep(Integer.MAX_VALUE);
}
private static class DemoWatcher implements Watcher{
@Override
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
countDownLatch.countDown();
}
}
}
private static class DemoCallback implements AsyncCallback.DataCallback{
@Override
public void processResult(int rc, String path, Object ctx, byte[] bytes, Stat stat) {
System.out.println("异步获取的数据内容为:" + new String(bytes));
}
}
}
查询子节点getChildren
- 方法
public List<String> getChildren(String path, Watcher watcher) throws KeeperException, InterruptedException
public List<String> getChildren(String path, boolean watch) throws KeeperException, InterruptedException
public void getChildren(String path, Watcher watcher, ChildrenCallback cb, Object ctx)
public void getChildren(String path, boolean watch, ChildrenCallback cb, Object ctx)
public List<String> getChildren(String path, Watcher watcher, Stat stat) throws KeeperException, InterruptedException
public List<String> getChildren(String path, boolean watch, Stat stat) throws KeeperException, InterruptedException
public void getChildren(String path, Watcher watcher, Children2Callback cb, Object ctx)
public void getChildren(String path, boolean watch, Children2Callback cb, Object ctx)
- 参数
| 参数名 | 描述 |
|---|---|
| path | 要查询的节点 |
| watcher | 注册一个监听器 |
| watch | 使用默认的监听器 |
| cb | 异步方法的回调函数 |
| ctx | 回调函数上下文参数 |
| stat | 闯入一个旧的Stat对象,查询后,会被服务端响应的新Stat对象替换 |
修改
- 方法
#同步方法
public Stat setData(String path, byte[] data, int version) throws KeeperException, InterruptedException
#异步方法
public void setData(String path, byte[] data, int version, StatCallback cb, Object ctx)
- 参数
| 参数名 | 描述 |
|---|---|
| path | 要修改的数据节点的路径 |
| data | 修改后的数据 |
| version | 数据修改时基于的版本号,即乐观锁 |
| cb | 异步方法的回调函数 |
| ctx | 回调函数上下文参数 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class UpdateDemo {
public static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
asyncUpdate();
}
/**
* 同步更新
*/
public static void syncUpdate() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/syncUpdate", "syncUpdate".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
Stat stat = new Stat();
System.out.println(new String(zooKeeper.getData(path, true, stat)));
System.out.println(stat.getCzxid() + "," + stat.getMzxid() + "," + stat.getVersion());
//-1 表示不加乐观锁
zooKeeper.setData(path, "32123".getBytes(), -1);
System.out.println(new String(zooKeeper.getData(path, true, stat)));
System.out.println(stat.getCzxid() + "," + stat.getMzxid() + "," + stat.getVersion());
}
/**
* 异步更新
* @throws Exception
*/
public static void asyncUpdate() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
String path = zooKeeper.create("/asyncUpdate", "asyncUpdate".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
Stat stat = new Stat();
System.out.println(new String(zooKeeper.getData(path, true, stat)));
System.out.println(stat.getCzxid() + "," + stat.getMzxid() + "," + stat.getVersion());
zooKeeper.setData(path, "dsadsa".getBytes(), -1, new DemoCallback(), "回调");
Thread.sleep(Integer.MAX_VALUE);
}
private static class DemoWatcher implements Watcher{
@Override
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
countDownLatch.countDown();
}else if(watchedEvent.getType() == Event.EventType.NodeDataChanged){
System.out.println("节点数据内容发生了变化");
}
}
}
private static class DemoCallback implements AsyncCallback.StatCallback{
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("修改节点:[" + rc + ", " + path + ", " + ctx.toString() +
", stat:" + stat.toString() + "]");
}
}
}
判断节点是否存在
- 方法
public Stat exists(String path, Watcher watcher) throws KeeperException, InterruptedException
public Stat exists(String path, boolean watch) throws KeeperException, InterruptedException
public void exists(String path, Watcher watcher, StatCallback cb, Object ctx)
public void exists(String path, boolean watch, StatCallback cb, Object ctx)
- 参数
| 参数名 | 描述 |
|---|---|
| path | 要判断的节点路径 |
| watcher | 注册一个监听器,用来监听节点被创建,删除,更新 |
| watch | 是否使用默认的监听器 |
| cb | 异步方法的回调函数 |
| ctx | 回调函数上下文参数 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class ExistsNodeDemo {
public static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
asyncExists();
}
/**
* 同步
*/
public static void syncExists() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
//创建
String path = zooKeeper.create("/syncExists", "syncExists".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
Stat stat = zooKeeper.exists(path, true);
System.out.println(stat.toString());
//更新
stat = zooKeeper.setData(path, "3213".getBytes(), -1);
System.out.println(stat.toString());
//删除
zooKeeper.delete(path, -1);
Thread.sleep(Integer.MAX_VALUE);
}
/**
* 异步
*/
public static void asyncExists() throws Exception{
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
//创建
String path = zooKeeper.create("/asyncExists", "asyncExists".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
zooKeeper.exists(path, true, new DemoCallback(), "asyncExists");
Thread.sleep(Integer.MAX_VALUE);
}
private static class DemoWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
countDownLatch.countDown();
}else if(Event.EventType.NodeCreated == watchedEvent.getType()){
System.out.println("创建节点");
}else if(Event.EventType.NodeDataChanged == watchedEvent.getType()){
System.out.println("修改节点");
}else if(Event.EventType.NodeDeleted == watchedEvent.getType()){
System.out.println("删除节点");
}
}
}
public static class DemoCallback implements AsyncCallback.StatCallback{
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("[" + rc + ", " + path + ", " + ctx.toString() +
", stat:" + stat.toString() + "]");
}
}
}
权限控制
- 方法
#zk会话对象的方法
public void addAuthInfo(String scheme, byte[] auth)
- 参数
| 参数名 | 描述 |
|---|---|
| scheme | 权限控制模式,枚举值:world,auth,digest,ip和super |
| auth | 权限信息 |
- 例子
package com.banary.base;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class AuthDemo {
private static CountDownLatch countDownLatch = null;
public static void main(String[] args) throws Exception{
authCreate();
}
public static void authCreate() throws Exception{
countDownLatch = new CountDownLatch(1);
ZooKeeper zooKeeper = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
zooKeeper.addAuthInfo("digest", "auth".getBytes());
String path = zooKeeper.create("/auth", "auth".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
countDownLatch = new CountDownLatch(1);
ZooKeeper zooKeeper2 = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
zooKeeper2.addAuthInfo("digest", "auth".getBytes());
System.out.println(new String(zooKeeper2.getData(path, true, new Stat())));
countDownLatch = new CountDownLatch(1);
ZooKeeper zooKeeper1 = new ZooKeeper("localhost:2181", 5000, new DemoWatcher());
countDownLatch.await();
System.out.println(new String(zooKeeper1.getData(path, true, new Stat())));
}
private static class DemoWatcher implements Watcher {
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
countDownLatch.countDown();
}
}
}
}
总结
- 除了创建会话是异步的,其他操作都存在同步和异步方法,同步会抛出异常
- 创建会话、查询(包括查询该节点的数据和子节点和判断节点存在)都可以注册监听器,也可以使用会话的默认监听器
Java客户端API的更多相关文章
- JAVA客户端API调用memcached两种方式
1. memcached client for java客户端API:memcached client for java 引入jar包:java-memcached-2.6.2.jar package ...
- zookeeper的Java客户端API
zookeeper作为一个分布式服务框架,主要用来解决分布式数据一致性问题,对多种语言提供了API.这里主要记录下JAVA客户端API的使用. 1.创建会话 客户端可以通过创建一个ZooKeeper实 ...
- Zookeeper的java客户端API使用方法(五)
前面几篇博文,我们简单的介绍了一下zookeeper,如何安装zookeeper集群,以及如何使用命令行等.这篇博文我们重点来看下Zookeeper的java客户端API使用方式. 创建会话 客户端可 ...
- hadoop系列二:HDFS文件系统的命令及JAVA客户端API
转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...
- 读《分布式一致性原理》JAVA客户端API操作2
创建节点 通过客户端API来创建一个数据节点,有一下两个接口: public String create(final String path, byte data[], List<ACL> ...
- [转载] ZooKeeper的Java客户端API
转载自 http://www.cnblogs.com/ggjucheng/p/3370359.html http://zookeeper.apache.org/doc/trunk/javaExampl ...
- 读《分布式一致性原理》JAVA客户端API操作3
更新数据 客户端可以通过zookeeper的API来更新一个节点的数据内容,有如下两个接口: public Stat setData(final String path, byte data[], i ...
- Zookeeper Java客户端API的使用
1. 原生api 具体查看下面github代码 2. ZkClient ZkClient是Github上一个开源的ZooKeeper客户端.ZkClient在ZooKeeper原生 A ...
- 读《分布式一致性原理》JAVA客户端API操作
创建会话 客户端可以通过创建一个Zookeeper实例来连接服务器.4种构造方法如下 ZooKeeper(connectString, sessionTimeout, watcher): ZooKee ...
随机推荐
- linux防火墙之 ufw
Usage: ufw COMMAND Commands: enable enables the firewall 开启ufw防火墙 disable disables the firewall 禁用防火 ...
- js获取字符串最后一位方法
方法一:运用String对象下的charAt方法 charAt() 方法可返回指定位置的字符. str.charAt(str.length – 1) 请注意,JavaScript 并没有一种有别于字符 ...
- C# .net中json字符串和对象之间的转化方法
http://blog.csdn.net/xuexiaodong009/article/details/46998069 json作为作为一种最常用的数据,应用很广泛,在.net中如何把一个对象转化为 ...
- Layout 不可思议(二)—— 两侧定宽的三列布局
三列布局作为网页设计中最常见的布局,其实现方法早已被诸位前端大神摸透 网上相关的文章很多,原本已无必要再做赘述 不过既然开了 Layout 系列,三列布局就是必修课 本文整理了一些常用的实现方法,然后 ...
- js 对象的值传递
一.变量赋值的不同 1.原始值 在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的. 2.引用值: 在将一个保存着对象内存地址的变量复制给另一个变量 ...
- hadoop的安装和配置(三)完全分布式模式
博主会用三篇文章为大家详细说明hadoop的三种模式: 本地模式 伪分布模式 完全分布模式 完全分布式模式: 前面已经说了本地模式和伪分布模式,这两种在hadoop的应用中并不用于实际,因为几乎没人会 ...
- winform展示Unity3D文件(支持动态改变文件路径)
winform下展示Unity3D文件可以支持对Unity3D实现的模块进行包装,以及在其他的项目中需要展示Unity3D的界面时候,恰到适宜地进行打开展示,这里我展示如何使用winform打开Uni ...
- MAC OS 如何安装命令行工具:Command Line Tools
打开终端输入:xcode-select --install 回车 安装好了测试结果:gcc -v 显示如下: xcode-select: note: install requested for com ...
- 6、ABPZero系列教程之拼多多卖家工具 框架后台的设置
接着上篇文章,现在去修改注册登录逻辑代码还为时过早,我们还需要到后台去设置一些配置. 管理---设置 先配置好这2项设置,邮箱配置是为了验证注册时功能是否正常,下一篇文章需要用到. 注:邮箱配置中的密 ...
- WPF 依赖属性源码 洞察微软如何实现DependencyProperty
依赖属性DependencyProperty是wpf最重要的一个类,理解该类如何实现对学习wpf帮助很大! 终于找到了该类的源码!仔细阅读源码,看看微软如何玩的花招! File: Base\Syste ...