开发创建XMPP“发布订阅”扩展(xmpp pubsub extend)
发布订阅(PubSub)是一个功能强大的XMPP协议扩展。用户订阅一个项目(在xmpp中叫做node),得到通知时,也即当事项节点更新时。xmpp服务器通知用户(通过message格式)。
节点类型:
- Leaf node: 叶子节点,包含了发布项.
- Collection node: 可以看做集合节点,它下面包含叶子.
注意:不能订阅整个Collection node,只能订阅Leaf node
访问和发布模式 Access and Publisher Models
- Open: 任何人都能订阅
- Authorize: 订阅请求必须由所有者批准,只有认证的用户可以订阅项目。
- Whitelist: 白名单里的用户可以订阅.
- Presence: 只有能收到发布者也即Owner的即席状态的用户才能收到订阅.
- Roster: 只有在用户花名册或花名册组内的用户可以收到订阅事项提醒
在openfire里,Whitelist的配置如下:
发布者模式:
- Open: anyone may publish items to the node.(权限最大)
- Publishers: owners and publishers are allowed to publish items to the node.
- Subscribers: owners, publishers and subscribers are allowed to publish items to the node.
发布订阅的过程,发布者发布到叶子节点,订阅者收到消息提醒
XMPP中的订阅流程
1、首先,需要确认你的服务器支持pubsub特性
1.1 查询XMPP服务的所有服务
<iq type='get'
from='wangxin@im/CVTalk'
to='im'
id='11'>
<query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
返回:
<iq id="11" to="wangxin@im/PC" from="im" type="result">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="server" name="Openfire Server" type="im"/>
<identity category="pubsub" name="null" type="pep"/>
<feature var="http://jabber.org/protocol/pubsub#manage-subscriptions"/>
<feature var="http://jabber.org/protocol/pubsub#modify-affiliations"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-default"/>
<feature var="http://jabber.org/protocol/pubsub#collections"/>
<feature var="jabber:iq:private"/>
<feature var="http://jabber.org/protocol/disco#items"/>
<feature var="vcard-temp"/>
<feature var="http://jabber.org/protocol/pubsub#publish"/>
<feature var="urn:xmpp:archive:auto"/>
<feature var="http://jabber.org/protocol/pubsub#subscribe"/>
<feature var="http://jabber.org/protocol/pubsub#retract-items"/>
<feature var="http://jabber.org/protocol/offline"/>
<feature var="http://jabber.org/protocol/pubsub#meta-data"/>
<feature var="jabber:iq:register"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-subscriptions"/>
<feature var="http://jabber.org/protocol/pubsub#default_access_model_open"/>
<feature var="jabber:iq:roster"/>
<feature var="http://jabber.org/protocol/pubsub#config-node"/>
<feature var="http://jabber.org/protocol/address"/>
<feature var="http://jabber.org/protocol/pubsub#publisher-affiliation"/>
<feature var="http://jabber.org/protocol/pubsub#item-ids"/>
<feature var="http://jabber.org/protocol/pubsub#instant-nodes"/>
<feature var="http://jabber.org/protocol/commands"/>
<feature var="http://jabber.org/protocol/pubsub#multi-subscribe"/>
<feature var="http://jabber.org/protocol/pubsub#outcast-affiliation"/>
<feature var="http://jabber.org/protocol/pubsub#get-pending"/>
<feature var="google:jingleinfo"/>
<feature var="jabber:iq:privacy"/>
<feature var="urn:xmpp:archive:manage"/>
<feature var="http://jabber.org/protocol/pubsub#subscription-options"/>
<feature var="jabber:iq:last"/>
<feature var="http://jabber.org/protocol/pubsub#create-and-configure"/>
<feature var="urn:xmpp:ping"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-items"/>
<feature var="jabber:iq:time"/>
<feature var="http://jabber.org/protocol/pubsub#create-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#persistent-items"/>
<feature var="jabber:iq:version"/>
<feature var="http://jabber.org/protocol/pubsub#presence-notifications"/>
<feature var="http://jabber.org/protocol/pubsub"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-affiliations"/>
<feature var="http://jabber.org/protocol/pubsub#delete-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#purge-nodes"/>
<feature var="http://jabber.org/protocol/disco#info"/>
<feature var="http://jabber.org/protocol/rsm"/>
</query>
</iq>
1.2 查询某一项XMPP子域,如pubsub
<iq type='get'
from='wangxin@im/PC'
to='pubsub.im'
id='11'>
<query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
返回:
<iq id="11" to="wangxin@im/PC" from="pubsub.im" type="result">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="pubsub" name="Publish-Subscribe service" type="service"/>
<feature var="http://jabber.org/protocol/pubsub"/>
<feature var="http://jabber.org/protocol/pubsub#collections"/>
<feature var="http://jabber.org/protocol/pubsub#config-node"/>
<feature var="http://jabber.org/protocol/pubsub#create-and-configure"/>
<feature var="http://jabber.org/protocol/pubsub#create-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#delete-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#get-pending"/>
<feature var="http://jabber.org/protocol/pubsub#instant-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#item-ids"/>
<feature var="http://jabber.org/protocol/pubsub#meta-data"/>
<feature var="http://jabber.org/protocol/pubsub#modify-affiliations"/>
<feature var="http://jabber.org/protocol/pubsub#manage-subscriptions"/>
<feature var="http://jabber.org/protocol/pubsub#multi-subscribe"/>
<feature var="http://jabber.org/protocol/pubsub#outcast-affiliation"/>
<feature var="http://jabber.org/protocol/pubsub#persistent-items"/>
<feature var="http://jabber.org/protocol/pubsub#presence-notifications"/>
<feature var="http://jabber.org/protocol/pubsub#publish"/>
<feature var="http://jabber.org/protocol/pubsub#publisher-affiliation"/>
<feature var="http://jabber.org/protocol/pubsub#purge-nodes"/>
<feature var="http://jabber.org/protocol/pubsub#retract-items"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-affiliations"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-default"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-items"/>
<feature var="http://jabber.org/protocol/pubsub#retrieve-subscriptions"/>
<feature var="http://jabber.org/protocol/pubsub#subscribe"/>
<feature var="http://jabber.org/protocol/pubsub#subscription-options"/>
<feature var="http://jabber.org/protocol/pubsub#default_access_model_open"/>
<feature var="http://jabber.org/protocol/disco#info"/>
</query>
</iq>
1.3 查询发布订阅中的某一个持久化的叶子节点
<iq type='get'
from='wangxin@im/PC'
to='pubsub.im'
id='info1'>
<query xmlns='http://jabber.org/protocol/disco#info'
node='NodeID_003'/>
</iq>
返回
<iq id="info1" to="wangxin@im/PC" from="pubsub.im" type="result">
<query xmlns="http://jabber.org/protocol/disco#info" node="NodeID_003">
<identity category="pubsub" name="null" type="leaf"/>
<feature var="http://jabber.org/protocol/pubsub"/>
<feature var="http://jabber.org/protocol/disco#info"/>
<x xmlns="jabber:x:data" type="result">
<field var="FORM_TYPE" type="hidden">
<value>http://jabber.org/protocol/pubsub#meta-data</value>
</field>
<field label="节点的简化名" var="pubsub#title" type="text-single">
<value/>
</field>
<field label="节点的描述" var="pubsub#description" type="text-single">
<value/>
</field>
<field label="Whether the node is a leaf (default) or a collection" var="pubsub#node_type" type="text-single">
<value>leaf</value>
</field>
<field label="The collection with which a node is affiliated." var="pubsub#collection" type="text-single"/>
<field label="是否允许订阅" var="pubsub#subscribe" type="boolean">
<value>1</value>
</field>
<field label="强制设置新的订阅" var="pubsub#subscription_required" type="boolean">
<value>0</value>
</field>
<field label="用事件通知投送有效载荷" var="pubsub#deliver_payloads" type="boolean">
<value>1</value>
</field>
<field label="当节点配置改变时通知订阅者" var="pubsub#notify_config" type="boolean">
<value>1</value>
</field>
<field label="当节点被删除时通知订阅者" var="pubsub#notify_delete" type="boolean">
<value>1</value>
</field>
<field label="当节点的项目被删除时通知订阅者" var="pubsub#notify_retract" type="boolean">
<value>1</value>
</field>
<field label="仅投送通知给有效的用户" var="pubsub#presence_based_delivery" type="boolean">
<value>0</value>
</field>
<field label="指定有效的数据类型给此节点" var="pubsub#type" type="text-single">
<value/>
</field>
<field label="XSLT信息体" var="pubsub#body_xslt" type="text-single">
<value/>
</field>
<field label="XSLT有效载荷" var="pubsub#dataform_xslt" type="text-single">
<value/>
</field>
<field label="指定谁可以订阅和查看项目" var="pubsub#access_model" type="list-single">
<value>open</value>
<option>
<value>authorize</value>
</option>
<option>
<value>open</value>
</option>
<option>
<value>presence</value>
</option>
<option>
<value>roster</value>
</option>
<option>
<value>whitelist</value>
</option>
</field>
<field label="指定发布者模型" var="pubsub#publish_model" type="list-single">
<value>publishers</value>
<option>
<value>publishers</value>
</option>
<option>
<value>subscribers</value>
</option>
<option>
<value>open</value>
</option>
</field>
<field label="好友列表允许订阅" var="pubsub#roster_groups_allowed" type="list-multi"/>
<field label="有问题时联系相关人员" var="pubsub#contact" type="jid-multi"/>
<field label="默认的语言" var="pubsub#language" type="text-single">
<value>English</value>
</field>
<field label="节点的主人" var="pubsub#owner" type="jid-multi">
<value>test17@im</value>
</field>
<field label="节点的发布者" var="pubsub#publisher" type="jid-multi"/>
<field label="选择实体将收到的信息回复给项目" var="pubsub#itemreply" type="list-single">
<value>owner</value>
</field>
<field label="多用户对话房间的回复将被传递" var="pubsub#replyroom" type="jid-multi"/>
<field label="给用户的回复将被传递" var="pubsub#replyto" type="jid-multi"/>
<field label="发送项目给新的订阅者" var="pubsub#send_item_subscribe" type="boolean">
<value>1</value>
</field>
<field label="持续的项目被存储" var="pubsub#persist_items" type="boolean">
<value>0</value>
</field>
<field label="项目的最大数字被持续化" var="pubsub#max_items" type="text-single">
<value>-1</value>
</field>
<field label="最大的有效载荷字节大小" var="pubsub#max_payload_size" type="text-single">
<value>5120</value>
</field>
</x>
</query>
</iq>
测试:通过smackx 和spark im客户端实现发布订阅
发布者:
import java.util.Date; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.pubsub.AccessModel;
import org.jivesoftware.smackx.pubsub.ConfigureForm;
import org.jivesoftware.smackx.pubsub.FormType;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.PublishModel;
import org.jivesoftware.smackx.pubsub.SimplePayload; public class Publisher {
private static XMPPConnection connection = new XMPPConnection("im.cvte.cn");
private static String USRE_NAME = "test17";
private static String PASSWORD = "password";
private static String nodeId = "NodeID_003"; static{
try {
connection.connect();
connection.login(USRE_NAME,PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args)throws Exception{ try{
PubSubManager manager = new PubSubManager(connection,"pubsub.im"); LeafNode myNode = null;
try {
myNode = manager.getNode(nodeId); //创建叶子节点
} catch (Exception e) {
e.printStackTrace();
}
if(myNode == null){
myNode = manager.createNode(nodeId);
}
String id1 = "1001"; SimplePayload payload1 = new SimplePayload("message","pubsub:cvtalk","<message xmlns='pubsub:cvtalk'><body>"+ id1+":消息发布:"+ new Date().toString()+"</body></message>" ); //设置叶子节点参数,目前失灵
ConfigureForm f = new ConfigureForm(FormType.submit);
//配置参数
f.setPersistentItems(true); //是否持久化
f.setDeliverPayloads(true);
f.setAccessModel(AccessModel.open);
f.setPublishModel(PublishModel.publishers);
//f.setSubscribe(true);
//通过设置创建叶子
//myNode =(LeafNode)manager.createNode(nodeId, f); PayloadItem<SimplePayload> item1 = new PayloadItem<SimplePayload>(id1, payload1);
//不带itemID的SimplePayload,同样是OK的
//PayloadItem<SimplePayload> item1 = new PayloadItem<SimplePayload>(payload1);
myNode.publish(item1);
System.out.println("-----publish item1-----------"); }
catch(Exception E)
{E.printStackTrace();} }
}
订阅者,这里的代码请写到spark的LoginDialog的login()方法 :
private boolean login() {
.......
connection.login(.......
.......
PubSubManager manager = new PubSubManager(connection,"pubsub.im");
Node eventNode = manager.getNode("NodeID_003");
eventNode.addItemEventListener(new ItemEventListener<PayloadItem>() {
public void handlePublishedItems(ItemPublishEvent evt) {
System.out.println("收到订阅的载荷数量=" + evt.getItems().size());
for (Object obj : evt.getItems()) {
PayloadItem<SimplePayload> item = (PayloadItem<SimplePayload>) obj;
System.out.println("订阅项目=" + item.getPayload().toString());
}
}
});
eventNode.subscribe(connection.getUser());
......
订阅到达的消息
<message id="NodeID_003__wangxin@im__0K463" to="wangxin@im/PC" from="pubsub.im">
<thread>n4Ch63</thread>
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="NodeID_003">
<item id="1001">
<message xmlns="pubsub:cvtalk">
<body>1001:消息发布:Tue Dec 08 15:36:59 CST 2015</body>
</message>
</item>
</items>
</event>
<headers xmlns="http://jabber.org/protocol/shim">
<header name="pubsub#subid">GP00jOONb9Lg2PRr0K0T01xunpquPmVC2q7QhjYg</header>
</headers>
</message>
smack中的pubsub的其他操作
获取节点配置
public ConfigureForm getDefaultConfiguration()
throws XMPPException
{
// Errors will cause exceptions in getReply, so it only returns
// on success.
PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace());
return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT);
}
删除节点
public void deleteNode(String nodeId)
throws XMPPException
{
sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace());
nodeMap.remove(nodeId);
}
监听器
一共有3个监听:
- ItemDeleteListener
- ItemEventListener
- NodeConfigListener
其中 ItemEventListener使用的是泛型参数,类型是 org.jivesoftware.smackx.pubsub.Item
public interface ItemEventListener <T extends Item>
{
/**
* Called whenever an item is published to the node the listener
* is registered with.
*
* @param items The publishing details.
*/
void handlePublishedItems(ItemPublishEvent<T> items);
}
另外,Personal Event Publishing (XEP-163) 也是基于发布订阅,xmpp包体结构很类似,发布的代码:
PEPManager pepManager = new PEPManager(smackConnection);
pepManager.addPEPListener(new PEPListener() {
public void eventReceived(String inFrom, PEPEvent inEvent) {
LOGGER.debug("Event received: " + inEvent);
}
}); PEPProvider pepProvider = new PEPProvider();
pepProvider.registerPEPParserExtension("http://jabber.org/protocol/tune", new TuneProvider());
ProviderManager.getInstance().addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", pepProvider); Tune tune = new Tune("jeff", "1", "CD", "My Title", "My Track");
pepManager.publish(tune);
接收的监听:
public interface PEPListener { /**
* Called when PEP events are received as part of a presence subscribe or message filter.
*
* @param from the user that sent the entries.
* @param event the event contained in the message.
*/
public void eventReceived(String from, PEPEvent event); }
最后一个问题,在openfire中叶子节点上的新项目持久化到哪里了?
PubSubPersistenceManager类中writePendingItems负责持久化到数据库
private static void writePendingItems(Connection con, LinkedListNode<RetryWrapper> addItem, boolean batch) throws SQLException
但每次发布却看不到数据库中的记录,可以在下面代码找到答案,原来都提交内存了
writePendingItems(Connection con, LinkedList<RetryWrapper> addList, LinkedList<PublishedItem> delList) 将数据库中的记录删除了
/**
* Flush the cache(s) of items to be persisted (itemsToAdd) and deleted (itemsToDelete).
* @param sendToCluster If true, delegate to cluster members, otherwise local only
*/
public static void flushPendingItems(boolean sendToCluster)
{
// forward to other cluster members and wait for response
if (sendToCluster) {
CacheFactory.doSynchronousClusterTask(new FlushTask(), false);
} if (itemsToAdd.getFirst() == null && itemsToDelete.getFirst() == null) {
return; // nothing to do for this cluster member
} Connection con = null;
boolean rollback = false;
LinkedList<RetryWrapper> addList = null;
LinkedList<PublishedItem> delList = null; // Swap pending items so we can parse and save the contents from this point in time
// while not blocking new entries from being cached.
synchronized(itemsPending)
{
addList = itemsToAdd;
delList = itemsToDelete; itemsToAdd = new LinkedList<RetryWrapper>();
itemsToDelete = new LinkedList<PublishedItem>(); // Ensure pending items are available via the item read cache;
// this allows the item(s) to be fetched by other request threads
// while being written to the DB from this thread
int copied = ;
for (String key : itemsPending.keySet()) {
if (!itemCache.containsKey(key)) {
itemCache.put(key, (((RetryWrapper)itemsPending.get(key).object)).get());
copied++;
}
}
if (log.isDebugEnabled() && copied > ) {
log.debug("Added " + copied + " pending items to published item cache");
}
itemsPending.clear();
} // Note that we now make multiple attempts to write cached items to the DB:
// 1) insert all pending items in a single batch
// 2) if the batch insert fails, retry by inserting each item separately
// 3) if a given item cannot be written, return it to the pending write cache
// By default step 3 will be tried once per item, but this can be configured
// (or disabled) using the "xmpp.pubsub.item.retry" property. In the event of
// a transaction rollback, items that could not be written to the database
// will be returned to the pending item write cache.
try {
con = DbConnectionManager.getTransactionConnection();
writePendingItems(con, addList, delList);
} catch (SQLException se) {
log.error("Failed to flush pending items; initiating rollback", se);
// return new items to the write cache
LinkedListNode<RetryWrapper> node = addList.getLast();
while (node != null) {
savePublishedItem(node.object);
node.remove();
node = addList.getLast();
}
rollback = true;
} finally {
DbConnectionManager.closeTransactionConnection(con, rollback);
}
}
参考网页:
http://xmpp.org/extensions/xep-0060.html
http://xmpp.org/extensions/xep-0163.html
https://community.igniterealtime.org/thread/38433
http://www.igniterealtime.org/support/articles/pubsub.jsp
http://blog.csdn.net/u011163195/article/details/17683741
开发创建XMPP“发布订阅”扩展(xmpp pubsub extend)的更多相关文章
- 17-EasyNetQ:非泛型的发布&订阅扩展方法
自从EasyNetQ第一个版本开始,它就可以发布/订阅特定类型的消息. bus.Subscribe<MyMessage>("subscriptionId", x =&g ...
- 《JavaScript设计模式与开发实践》-- 发布-订阅模式
详情个人博客:https://shengchangwei.github.io/js-shejimoshi-fabudingyue/ 发布-订阅模式 1.定义 发布-订阅模式:发布-订阅模式又叫观察者模 ...
- EasyNetQ使用(九)【非泛型的发布&订阅扩展方法,发生错误的情况 】
自从EasyNetQ第一个版本开始,它就可以发布/订阅特定类型的消息. bus.Subscribe<MyMessage>("subscriptionId", x =&g ...
- 《 javascript 设计模式与开发实践 》 ---发布-订阅模式 代码小问题
定义公共事件: 删除事件优化:
- AKKA集群中的分布式发布订阅
集群中的分布式发布订阅 如何向一个不知道在哪个节点上运行的actor发送消息呢? 如何向集群中的所有actor发送感兴趣的主题的消息? 这种模式提供了一个中介actor,akka.cluster.pu ...
- javascript中的发布订阅模式与观察者模式
这里了解一下JavaScript中的发布订阅模式和观察者模式,观察者模式是24种基础设计模式之一. 设计模式的背景 设计模式并非是软件开发的专业术语,实际上设计模式最早诞生于建筑学. 设计模式的定义是 ...
- 《JavaScript设计模式与开发实践》笔记第八章 发布-订阅模式
第八章 发布-订阅模式 发布-订阅模式描述 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 发布-订阅模式可以广泛应用于 ...
- 如何开发、本地测试、发布 Laravel 扩展包?
如何开发.本地测试.发布 Laravel 扩展包? Laravel/ 1年前/ 4022 / 11 现在已经有了很多,关于如何开发 Laravel 扩展包的文章.但是大多文章写的太过片面,不 ...
- python开发-实现redis中的发布订阅功能
Python3学习(二十七):python实现Redis的订阅与发布(sub-pub机制) 先介绍一下redis的pub/sub功能: Pub/Sub功能(means Publish, Subscri ...
随机推荐
- mac挂载ntfs文件系统方法
1.插入磁盘,并查看 zz@pzdeMacBook-Pro:~/Volumes/ntfs16g$ df Filesystem 512-blocks Used Available Capacity iu ...
- mysql 数据插入优化方法(concurrent_insert=2)
当一个线程对一个表执行一个DELAYED语句时,如果不存在这样的处理程序,一个处理器线程被创建以处理对于该表的所有DELAYED语句.通常来说,在MyISAM里读写操作是串行的,但当对同一个表进行查询 ...
- 2019.02.09 bzoj2560: 串珠子(状压dp+简单容斥)
传送门 题意简述:nnn个点的带边权无向图,定义一个图的权值是所有边的积,问所有nnn个点都连通的子图的权值之和. 思路: fif_ifi表示保证集合iii中所有点都连通其余点随意的方案数. gig ...
- 2019.01.02 NOIP训练 三七二十一(生成函数)
传送门 生成函数基础题. 题意简述:求由1,3,5,7,9这5个数字组成的n位数个数,要求其中3和7出现的次数都要是偶数. 考虑对于每个数字构造生成函数. 对于1,5,9:∑nxnn!=ex\sum_ ...
- java常用设计模式二:工厂模式
1.简单工厂模式(静态工厂方法模式) 抽象实例: public interface People { void talk(); } 具体实例: public class Doctor implemen ...
- hadoop学习笔记(三):hdfs体系结构和读写流程(转)
原文:https://www.cnblogs.com/codeOfLife/p/5375120.html 目录 HDFS 是做什么的 HDFS 从何而来 为什么选择 HDFS 存储数据 HDFS 如何 ...
- gj12-2 协程和异步io
12.3 epoll+回调+事件循环方式url import socket from urllib.parse import urlparse # 使用非阻塞io完成http请求 def get_ur ...
- django调用py报错 django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured.
完整报错信息如下 django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, bu ...
- Silverlight子窗口(ChildWindow)传递参数到父窗口演示
在企业级项目中,子窗口(ChildWindow)是一个常用控件,其展示方式是以弹出窗口来显示信息. 这里我将演示,子窗口传递参数到父窗口的方法.由于我的开发环境都是英文环境,所以部分中文可能显示不正常 ...
- Eclipse sysout 和 fore 不起作用
Content Assist ↑ 这是主角,可以快速生成语句. sysout 快捷键之后生成了 System.out.println(); fore 快捷键之后生成了 for(String arg : ...