当前位置: 首页 > news >正文

JAVA设计模式之观察者模式

一、定义

观察者模式即发布-订阅模式(Publish/Subscribe):定义了一种一对多的依赖关系,让多个观察者对象同事监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

       观察者模式结构图,如下图1-1所示:

 

图 1-1

二、实例展示

      Subjcet 类,可翻译为主题或抽象通知者,一般由一个抽象类或一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

 1 abstract class Subject{
 2     private List<Observer> observers = new ArrayList<Observer>();
 3     
 4     //增加观察者
 5     public void Attach(){
 6         observers.Add(observer);
 7     }
 8     
 9     //移除观察者
10     public void Detach(){
11         
12     }
13 }

      Observer类,抽象观察者,为所有的具体观察者定义了一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。

1 abstract class Observer{
2     public abstract void Update();
3 }

      ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。 

 1 class ConcreteSubject extends Subject{
 2     private String subjectState;
 3     
 4     public String getSubjectState() {
 5         return subjectState;
 6     }
 7 
 8     public void setSubjectState(String subjectState) {
 9         this.subjectState = subjectState;
10     }
11 }

      ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

 1 class ConcreteObserver extends Observer{
 2     private String name;
 3     private String observerState;
 4     private ConcreteSubject subject;
 5     
 6     public ConcreteObserver(ConcreteSubject subject,String name){
 7         this.subject = subject;
 8         this.name = name;
 9     }
10     
11     @override
12     public void Update(){
13         observerState = subject.SubjectState;
14         System.out.println("观察者"+name+"的新状态是"+observerState);
15     }
16     
17     public String getSubjectState() {
18         return subjectState;
19     }
20 
21     public void setSubjectState(String subjectState) {
22         this.subjectState = subjectState;
23     }
24 }

      客户端代码:

 1 static void Main(String[] args){
 2     ConcreteSubject s = new ConcreteSubject();
 3     
 4     s.Attach(new ConcreteObserver(s,"X"));
 5     s.Attach(new ConcreteObserver(s,"Y"));
 6     s.Attach(new ConcreteObserver(s,"Z"));
 7     
 8     s.SubjectState = "ABC";
 9     s.Notify();
10 }

      结果显示:

1 观察者X的状态是ABC
2 观察者Y的状态是ABC
3 观察者Z的状态是ABC

三、使用场景

      1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们各自独立地改变和复用。

      2、当一个对象的改变需要同时改变其它对象的时候。

四、使用总结

       1、观察者模式的主要优点如下:

             1) 观察者模式可以实现表示层和逻辑层的分离,定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不用的表示层充当具体观察者角色。

             2) 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者集合,无需了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

             3) 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。        

             4) 观察者模式满足"开闭原则"的要求,增加新的具体观察者对象无须修改原有系统代码。在具体观察者和观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

       2、观察者模式的主要缺点如下:

             1) 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。

             2) 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

             3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 

五、观察者模式的典型应用

        1、JDK提供的观察者接口

              观察者模式在Java语言中的地位非常重要。在JDK的java.util包中,提供了Observer类及Observer接口,它们构成了JDK对观察者模式的支持。

              其中的Observer接口为观察者,只有一个update方法。当观察者目标发生变化时被调用,其代码如下:

1 public interface Observer{
2     void update(Observer o,Object arg);
3 }

               Observerable类则为目标类,相比示例中的Publisher类多了并发和NPE方面的考虑。

  1 public class Observable {
  2     private boolean changed = false;
  3     private Vector<Observer> obs;
  4 
  5     /** Construct an Observable with zero Observers. */
  6 
  7     public Observable() {
  8         obs = new Vector<>();
  9     }
 10 
 11     /**
 12      * Adds an observer to the set of observers for this object, provided
 13      * that it is not the same as some observer already in the set.
 14      * The order in which notifications will be delivered to multiple
 15      * observers is not specified. See the class comment.
 16      *
 17      * @param   o   an observer to be added.
 18      * @throws NullPointerException   if the parameter o is null.
 19      */
 20     public synchronized void addObserver(Observer o) {
 21         if (o == null)
 22             throw new NullPointerException();
 23         if (!obs.contains(o)) {
 24             obs.addElement(o);
 25         }
 26     }
 27 
 28     /**
 29      * Deletes an observer from the set of observers of this object.
 30      * Passing <CODE>null</CODE> to this method will have no effect.
 31      * @param   o   the observer to be deleted.
 32      */
 33     public synchronized void deleteObserver(Observer o) {
 34         obs.removeElement(o);
 35     }
 36 
 37     /**
 38      * If this object has changed, as indicated by the
 39      * <code>hasChanged</code> method, then notify all of its observers
 40      * and then call the <code>clearChanged</code> method to
 41      * indicate that this object has no longer changed.
 42      * <p>
 43      * Each observer has its <code>update</code> method called with two
 44      * arguments: this observable object and <code>null</code>. In other
 45      * words, this method is equivalent to:
 46      * <blockquote><tt>
 47      * notifyObservers(null)</tt></blockquote>
 48      *
 49      * @see     java.util.Observable#clearChanged()
 50      * @see     java.util.Observable#hasChanged()
 51      * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
 52      */
 53     public void notifyObservers() {
 54         notifyObservers(null);
 55     }
 56 
 57     /**
 58      * If this object has changed, as indicated by the
 59      * <code>hasChanged</code> method, then notify all of its observers
 60      * and then call the <code>clearChanged</code> method to indicate
 61      * that this object has no longer changed.
 62      * <p>
 63      * Each observer has its <code>update</code> method called with two
 64      * arguments: this observable object and the <code>arg</code> argument.
 65      *
 66      * @param   arg   any object.
 67      * @see     java.util.Observable#clearChanged()
 68      * @see     java.util.Observable#hasChanged()
 69      * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
 70      */
 71     public void notifyObservers(Object arg) {
 72         /*
 73          * a temporary array buffer, used as a snapshot of the state of
 74          * current Observers.
 75          */
 76         Object[] arrLocal;
 77 
 78         synchronized (this) {
 79             /* We don't want the Observer doing callbacks into
 80              * arbitrary code while holding its own Monitor.
 81              * The code where we extract each Observable from
 82              * the Vector and store the state of the Observer
 83              * needs synchronization, but notifying observers
 84              * does not (should not).  The worst result of any
 85              * potential race-condition here is that:
 86              * 1) a newly-added Observer will miss a
 87              *   notification in progress
 88              * 2) a recently unregistered Observer will be
 89              *   wrongly notified when it doesn't care
 90              */
 91             if (!changed)
 92                 return;
 93             arrLocal = obs.toArray();
 94             clearChanged();
 95         }
 96 
 97         for (int i = arrLocal.length-1; i>=0; i--)
 98             ((Observer)arrLocal[i]).update(this, arg);
 99     }
100 
101     /**
102      * Clears the observer list so that this object no longer has any observers.
103      */
104     public synchronized void deleteObservers() {
105         obs.removeAllElements();
106     }
107 
108     /**
109      * Marks this <tt>Observable</tt> object as having been changed; the
110      * <tt>hasChanged</tt> method will now return <tt>true</tt>.
111      */
112     protected synchronized void setChanged() {
113         changed = true;
114     }
115 
116     /**
117      * Indicates that this object has no longer changed, or that it has
118      * already notified all of its observers of its most recent change,
119      * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
120      * This method is called automatically by the
121      * <code>notifyObservers</code> methods.
122      *
123      * @see     java.util.Observable#notifyObservers()
124      * @see     java.util.Observable#notifyObservers(java.lang.Object)
125      */
126     protected synchronized void clearChanged() {
127         changed = false;
128     }
129 
130     /**
131      * Tests if this object has changed.
132      *
133      * @return  <code>true</code> if and only if the <code>setChanged</code>
134      *          method has been called more recently than the
135      *          <code>clearChanged</code> method on this object;
136      *          <code>false</code> otherwise.
137      * @see     java.util.Observable#clearChanged()
138      * @see     java.util.Observable#setChanged()
139      */
140     public synchronized boolean hasChanged() {
141         return changed;
142     }
143 
144     /**
145      * Returns the number of observers of this <tt>Observable</tt> object.
146      *
147      * @return  the number of observers of this object.
148      */
149     public synchronized int countObservers() {
150         return obs.size();
151     }
152 }

              可以使用Observable类以及Observer接口来实现一个微信公众号示例。

              增加一个通知类WechatNotice,用于推送通知的传递。

 1 public class WechatNotice {
 2     private String publisher;
 3     private String articleName;
 4 
 5     public WechatNotice(String publisher,String articleName){
 6         this.publisher = publisher;
 7         this.articleName = articleName;
 8     }
 9 
10     public String getPublisher() {
11         return publisher;
12     }
13 
14     public void setPublisher(String publisher) {
15         this.publisher = publisher;
16     }
17 
18     public String getArticleName() {
19         return articleName;
20     }
21 
22     public void setArticleName(String articleName) {
23         this.articleName = articleName;
24     }
25 }

              然后改写 WeChatClient 和 WeChatAccounts,分别实现JDK的 Observer 接口和继承 Observable 类。

 1 import java.util.Observable;
 2 import java.util.Observer;
 3 
 4 public class WeChatClient implements Observer {
 5     private String username;
 6     public WeChatClient(String username) {
 7         this.username = username;
 8     }
 9 
10     public void update(Observable o, Object arg) {
11         //WeChatAccounts weChatAccounts = (WeChatAccounts) o;
12         WechatNotice notice = (WechatNotice) arg;
13         System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
14 
15     }
16 }
 1 import java.util.Observable;
 2 
 3 public class WeChatAccounts extends Observable {
 4     private String name;
 5 
 6     public WeChatAccounts(String name) {
 7         this.name = name;
 8     }
 9 
10     public void publishArticles(String articleName, String content) {
11         System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
12         setChanged();
13         notifyObservers(new WechatNotice(this.getName(), articleName));
14     }
15 
16     public String getName() {
17         return name;
18     }
19 
20     public void setName(String name) {
21         this.name = name;
22     }
23 }

              测试:

 1 public class TestMain {
 2     public static void main(String[] args){
 3         WeChatAccounts accounts = new WeChatAccounts("飞鹰");
 4 
 5         WeChatClient user1 = new WeChatClient("张三");
 6         WeChatClient user2 = new WeChatClient("李四");
 7         WeChatClient user3 = new WeChatClient("王五");
 8 
 9         accounts.addObserver(user1);
10         accounts.addObserver(user2);
11         accounts.addObserver(user3);
12 
13         accounts.publishArticles("设计模式 | Java设计模式之观察者模式及其应用场景", "观察者模式的内容...");
14 
15         accounts.deleteObserver(user1);
16         accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "建筑者模式的内容....");
17     }
18 }

              测试结果如下,可以发现结果如示例一致。

1 <飞鹰>微信公众号 发布了一篇推送,文章名称为 <设计模式 | Java设计模式之观察者模式及其应用场景>,内容为 <观察者模式的内容...> 
2 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景>
3 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景>
4 用户<张三> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之观察者模式及其应用场景>
5 
6 <飞鹰>微信公众号 发布了一篇推送,文章名称为 <设计模式 | Java设计模式之建筑者模式及其应用场景>,内容为 <建筑者模式的内容....> 
7 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>
8 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>

        2、Spring ApplicationContext 事件机制中的观察者模式。

             spring的事件机制是从java的事件机制拓展而来,ApplicationContext 中事件处理是由 ApplicationEvent 类和 ApplicationListener 接口来提供的。如果一个Bean实现了 ApplicationListener 接口,并且已经发布到容器中去,每次 ApplicationContext 发布一个 ApplicationEvent 事件,这个Bean就会接到通知

             1) ApplicationContext:事件源,其中的 publishEvent()方法用于触发容器事件

             2) ApplicationEvent:事件本身,自定义事件需要继承该类,可以用来传递数据

             3) ApplicationListener:事件监听器接口,事件的业务逻辑封装在监听器里面

             使用 spring 事件机制重新实现示例

 1 import org.springframework.context.ApplicationEvent;
 2 
 3 public class WechatNotice extends ApplicationEvent {
 4 
 5     private String publisher;
 6     private String articleName;
 7 
 8     public WechatNotice(Object source, String publisher, String articleName) {
 9         super(source);
10         this.publisher = publisher;
11         this.articleName = articleName;
12     }
13 
14     public String getPublisher() {
15         return publisher;
16     }
17 
18     public void setPublisher(String publisher) {
19         this.publisher = publisher;
20     }
21 
22     public String getArticleName() {
23         return articleName;
24     }
25 
26     public void setArticleName(String articleName) {
27         this.articleName = articleName;
28     }
29 }

 

 1 import org.springframework.context.ApplicationEvent;
 2 import org.springframework.context.ApplicationListener;
 3 
 4 public class WeChatClient  implements ApplicationListener {
 5 
 6     private String username;
 7 
 8     public WeChatClient(String username) {
 9         this.username = username;
10     }
11 
12 
13     public void onApplicationEvent(ApplicationEvent event) {
14         if (event instanceof WechatNotice) {
15             WechatNotice notice = (WechatNotice) event;
16             System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
17         }
18     }
19 
20     public void setUsername(String username) {
21         this.username = username;
22     }
23 
24 }
 1 import org.springframework.beans.BeansException;
 2 import org.springframework.context.ApplicationContext;
 3 import org.springframework.context.ApplicationContextAware;
 4 
 5 public class WeChatAccounts  implements ApplicationContextAware {
 6 
 7     private ApplicationContext ctx;
 8     private String name;
 9 
10     public WeChatAccounts(String name) {
11         this.name = name;
12     }
13 
14     public ApplicationContext getCtx() {
15         return ctx;
16     }
17 
18     public void setCtx(ApplicationContext ctx) {
19         this.ctx = ctx;
20     }
21 
22     public String getName() {
23         return name;
24     }
25 
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
31         this.ctx = applicationContext;
32     }
33 
34     public void publishArticles(String articleName, String content) {
35         System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
36         ctx.publishEvent(new WechatNotice(this.name, this.name, articleName));
37     }
38 }

             在 resources 目录下创建 spring.xml 文件,填入下面的内容

 1 <beans xmlns="http://www.springframework.org/schema/beans"
 2                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 4     <bean id="WeChatAccounts" class="com.jdk8.webchatListener.WeChatAccounts" scope="prototype">
 5          <constructor-arg name="name" value=""></constructor-arg>
 6     </bean>
 7     <bean id="WeChatClient1" class="com.jdk8.webchatListener.WeChatClient">
 8          <constructor-arg name="username" value="张三"></constructor-arg>
 9     </bean>
10     <bean id="WeChatClient2" class="com.jdk8.webchatListener.WeChatClient">
11          <constructor-arg name="username" value="李四"></constructor-arg>
12     </bean>
13     <bean id="WeChatClient3" class="com.jdk8.webchatListener.WeChatClient">
14          <constructor-arg name="username" value="王五"></constructor-arg>
15     </bean>
16 </beans>

              测试:

 1 import org.springframework.context.ApplicationContext;
 2 import org.springframework.context.support.ClassPathXmlApplicationContext;
 3 
 4 public class TestMain {
 5      public static void main(String[] args) {
 6         ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
 7 
 8         WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts");
 9         accounts.setName("飞鹰");
10         accounts.setApplicationContext(context);
11 
12         accounts.publishArticles("设计模式 | Java设计模式之建筑者模式及其应用场景", "观察者模式的内容...");
13       }
14 }

             输出如下:

1 用户<张三> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>
2 用户<李四> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>
3 用户<王五> 接收到 <飞鹰>微信公众号 的推送,文章标题为 <设计模式 | Java设计模式之建筑者模式及其应用场景>

             在此示例中 ApplicationContext 对象的实际类型为 ClassPathXmlApplicationContext,其中的与 publishEvent 方法相关的主要代码如下:

 1 private ApplicationEventMulticaster applicationEventMulticaster;
 2 
 3 public void publishEvent(ApplicationEvent event) {
 4     this.getApplicationEventMulticaster().multicastEvent(event);
 5     if (this.parent != null) {
 6         this.parent.publishEvent(event);
 7     }
 8 }
 9 
10 ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
11     return this.applicationEventMulticaster;
12 }
13 
14 protected void initApplicationEventMulticaster() {
15         ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
16         if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
17             this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
18         } else {
19             this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
20             beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
21         }
22 
23     }

             其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通过遍历 ApplicationListener(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor 来并发执行 ApplicationListener 的 onApplicationEvent 方法,与示例本质上是一致的

 1 public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
 2     private Executor taskExecutor;
 3 
 4     public void multicastEvent(final ApplicationEvent event) {
 5         Iterator var2 = this.getApplicationListeners(event).iterator();
 6 
 7         while(var2.hasNext()) {
 8             final ApplicationListener listener = (ApplicationListener)var2.next();
 9             Executor executor = this.getTaskExecutor();
10             if (executor != null) {
11                 executor.execute(new Runnable() {
12                     public void run() {
13                         listener.onApplicationEvent(event);
14                     }
15                 });
16             } else {
17                 listener.onApplicationEvent(event);
18             }
19         }
20 
21     }
22 }

五、观察者模式不足

             尽管已经用了依赖倒转的原则,但是"抽象通知者"还是依赖"抽象观察者",也就是说,万一没有了抽象观察者这样的接口,该通知的功能就完不成了。另外就是每个具体观察者,不一定都是"更新"的方法要调用的,需要实现的功能根本风马牛不相及的。要实现该功能,请关注下一节的"java委托功能的实现"。

 参考:

   1、http://laijianfeng.org/2018/10/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E5%8F%8A%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8/

   2、大话设计模式。

转载于:https://www.cnblogs.com/ITBlock/p/10134545.html

相关文章:

  • 安装mongo,新建数据库,添加普通用户
  • 【EOS】Cleos基础
  • 视频课左右滑动后应该定位
  • 2019智能合约开发新趋势
  • Tmux教程
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • echarts的各种常用效果展示
  • 查询数据核心语法
  • 亚洲诚信联合又拍云升级云端SSL证书服务
  • Android性能UI卡顿
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【记一次pull request的惨痛教训】不可见的分隔符之Zero-with-space
  • 在SQL 2005中用T-SQL插入中文数据时出现的问号或乱码的解决方案[转]
  • (轉貼)《OOD启思录》:61条面向对象设计的经验原则 (OO)
  • 电子书下载:Programming Entity Framework DbContext
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • CentOS7 安装JDK
  • django开发-定时任务的使用
  • javascript 哈希表
  • Kibana配置logstash,报表一体化
  • MySQL数据库运维之数据恢复
  • OSS Web直传 (文件图片)
  • PAT A1017 优先队列
  • PAT A1050
  • Python学习之路13-记分
  • spring cloud gateway 源码解析(4)跨域问题处理
  • tab.js分享及浏览器兼容性问题汇总
  • tensorflow学习笔记3——MNIST应用篇
  • ucore操作系统实验笔记 - 重新理解中断
  • Webpack 4x 之路 ( 四 )
  • Yii源码解读-服务定位器(Service Locator)
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 对象引论
  • 记录一下第一次使用npm
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 微信开放平台全网发布【失败】的几点排查方法
  • 学习使用ExpressJS 4.0中的新Router
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • puppet连载22:define用法
  • # Java NIO(一)FileChannel
  • (16)Reactor的测试——响应式Spring的道法术器
  • (阿里巴巴 dubbo,有数据库,可执行 )dubbo zookeeper spring demo
  • (分布式缓存)Redis分片集群
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (三)mysql_MYSQL(三)
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)关于多人操作数据的处理策略
  • (转载)深入super,看Python如何解决钻石继承难题
  • .net CHARTING图表控件下载地址
  • .net 中viewstate的原理和使用
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .NET业务框架的构建