linugb118--java space

Java

#

Mule ESB 实战

ESB

1.下载
http://www.mulesoft.org/documentation/display/MULE2INTRO/Home

下载Mule ESB 2.2.1 Full Dist.

2.zip 文件

3.安装所需要的环境
http://www.mulesoft.org/documentation/display/MULE2INTRO/Installing+Mule
java1.5+

Mule Build Tool有三种方式
Ant/Maven/Mule IDE
下面以Maven 为例进行操作

4.设置环境变量
比如

Linux/UNIX

export JAVA_HOME=/opt/java/jdk
export MAVEN_HOME=/opt/apache/maven-2.0.9
export MAVEN_OPTS='-Xmx512m -XX:MaxPermSize=256m'
export MULE_HOME=/opt/mule
export PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin:$MULE_HOME/bin


Windows

set JAVA_HOME=C:\Program Files\Java\jdk
set MAVEN_HOME=C:\Apache\maven-2.0.9
set MAVEN_OPTS=-Xmx512m -XX:MaxPermSize=256m
set MULE_HOME=C:\Mule
set PATH=%PATH%;%JAVA_HOME%\bin;%MAVEN_HOME%\bin;%MULE_HOME%\bin
   
Note: If you will run Mule as a Windows service, you must create system environment variables instead of user environment variables.

创建一个Maven repository 目录,比如c:\.m2\repository 在windows系统上,如果windows 不让创建.m2文件夹,请用mkdir目录创建
打开Maven conf 目录,比如c:\apache-maven-2.0.9\conf;打开setting.xml 文件,查找localRepository
将注释去除或者添加一段
<localRepository>c:/.m2/repository</localRepository>

5.run Hello Word
进入windows cmd 命令行,分别查看ant/maven是否正常

在%MULE_HOME%\lib\user 中查看是否存在mule-example-hello.jar
如果不存在 那么我们需要ant 或者 mvn 来build jar
我对mvn不是很熟悉,就用ant,在MULE_HOME%\examples下可以看到hello的例子
下面有build.xml 和pom.xml
查看build.xml 会发现,执行ant setup就能build hello project
在下来就要运行mule,hello文件夹下面的readme有描述

------------------
   Linux / Unix
    ------------
    mule -config file:conf/hello-config.xml
    mule -config file:conf/hello-http-config.xml
     or
    export MULE_LIB=./conf
    mule -config hello-config.xml
    mule -config hello-http-config.xml

    Windows
    -------
    mule.bat -config file:conf/hello-config.xml
    mule.bat -config file:conf/hello-http-config.xml
     or
    SET MULE_LIB=.\conf
    mule.bat -config hello-config.xml
    mule.bat -config hello-http-config.xml
   如果运行 http config那么通过
    http://localhost:8888/?name=Ross 可访问
 --------------------------------------------
 
 更方便的是例子下面提供了hello.bat 直接运行该bat 并输入1或者2为参数就能启动hello mule
 (需要提醒的是,第一次启动mule会有一大堆许可文件要你看,你输入任何键回车后多次过后你就能发现mule启动成功了)
 
 在conf下有hello-config.xml/hello-http-config.xml
 hello-config.xml他们在console端,通过system.in输入message,然后在system.out输出
 hello-http-config.xml 表示在浏览器中输入http://localhost:8888/?name=Ross 那么web页面会现在动态的name
 
6最简单构建Mule project
你可以通过安装Eclipse Mule IDE
http://www.mulesoft.org/documentation/display/MULE2INTRO/Quick+Start
或者Setting Up Eclipse for Use with Maven
http://www.mulesoft.org/documentation/display/MULE2INTRO/Setting+Up+Eclipse+for+Use+with+Maven

 

 
   


 

posted @ 2010-08-11 13:55 linugb118 阅读(3811) | 评论 (0)编辑 收藏

关于 java.util.concurrent 您不知道的 5 件事,第 2 部分

并发 Collections 提供了线程安全、经过良好调优的数据结构,简化了并发编程。然而,在一些情形下,开发人员需要更进一步,思考如何调节和/或限制线程执行。由于 java.util.concurrent 的总体目标是简化多线程编程,您可能希望该包包含同步实用程序,而它确实包含。

本文是 第 1 部分 的延续,将介绍几个比核心语言原语(监视器)更高级的同步结构,但它们还未包含在 Collection 类中。一旦您了解了这些锁和门的用途,使用它们将非常直观。

关于本系列

您觉得自己懂 Java 编程?事实是,大多数开发人员都只领会到了 Java 平台的皮毛,所学也只够应付工作。在本系列 中,Ted Neward 深度挖掘 Java 平台的核心功能,揭示一些鲜为人知的事实,帮助您解决最棘手的编程困难。

1. Semaphore

在一些企业系统中,开发人员经常需要限制未处理的特定资源请求(线程/操作)数量,事实上,限制有时候能够提高系统的吞吐量,因为它们减少了对特定资源的争用。尽管完全可以手动编写限制代码,但使用 Semaphore 类可以更轻松地完成此任务,它将帮您执行限制,如清单 1 所示:


清单 1. 使用 Semaphore 执行限制
            import java.util.*;import java.util.concurrent.*;
            public class SemApp
            {
            public static void main(String[] args)
            {
            Runnable limitedCall = new Runnable() {
            final Random rand = new Random();
            final Semaphore available = new Semaphore(3);
            int count = 0;
            public void run()
            {
            int time = rand.nextInt(15);
            int num = count++;
            try
            {
            available.acquire();
            System.out.println("Executing " +
            "long-running action for " +
            time + " seconds... #" + num);
            Thread.sleep(time * 1000);
            System.out.println("Done with #" +
            num + "!");
            available.release();
            }
            catch (InterruptedException intEx)
            {
            intEx.printStackTrace();
            }
            }
            };
            for (int i=0; i<10; i++)
            new Thread(limitedCall).start();
            }
            }
            

即使本例中的 10 个线程都在运行(您可以对运行 SemApp 的 Java 进程执行 jstack 来验证),但只有 3 个线程是活跃的。在一个信号计数器释放之前,其他 7 个线程都处于空闲状态。(实际上,Semaphore 类支持一次获取和释放多个 permit,但这不适用于本场景。)

2. CountDownLatch

如果 Semaphore 是允许一次进入一个(这可能会勾起一些流行夜总会的保安的记忆)线程的并发性类,那么 CountDownLatch 就像是赛马场的起跑门栅。此类持有所有空闲线程,直到满足特定条件,这时它将会一次释放所有这些线程。


清单 2. CountDownLatch:让我们去赛马吧!
            import java.util.*;
            import java.util.concurrent.*;
            class Race
            {
            private Random rand = new Random();
            private int distance = rand.nextInt(250);
            private CountDownLatch start;
            private CountDownLatch finish;
            private List<String> horses = new ArrayList<String>();
            public Race(String... names)
            {
            this.horses.addAll(Arrays.asList(names));
            }
            public void run()
            throws InterruptedException
            {
            System.out.println("And the horses are stepping up to the gate...");
            final CountDownLatch start = new CountDownLatch(1);
            final CountDownLatch finish = new CountDownLatch(horses.size());
            final List<String> places =
            Collections.synchronizedList(new ArrayList<String>());
            for (final String h : horses)
            {
            new Thread(new Runnable() {
            public void run() {
            try
            {
            System.out.println(h +
            " stepping up to the gate...");
            start.await();
            int traveled = 0;
            while (traveled < distance)
            {
            // In a 0-2 second period of time....
            Thread.sleep(rand.nextInt(3) * 1000);
            // ... a horse travels 0-14 lengths
            traveled += rand.nextInt(15);
            System.out.println(h +
            " advanced to " + traveled + "!");
            }
            finish.countDown();
            System.out.println(h +
            " crossed the finish!");
            places.add(h);
            }
            catch (InterruptedException intEx)
            {
            System.out.println("ABORTING RACE!!!");
            intEx.printStackTrace();
            }
            }
            }).start();
            }
            System.out.println("And... they're off!");
            start.countDown();
            finish.await();
            System.out.println("And we have our winners!");
            System.out.println(places.get(0) + " took the gold...");
            System.out.println(places.get(1) + " got the silver...");
            System.out.println("and " + places.get(2) + " took home the bronze.");
            }
            }
            public class CDLApp
            {
            public static void main(String[] args)
            throws InterruptedException, java.io.IOException
            {
            System.out.println("Prepping...");
            Race r = new Race(
            "Beverly Takes a Bath",
            "RockerHorse",
            "Phineas",
            "Ferb",
            "Tin Cup",
            "I'm Faster Than a Monkey",
            "Glue Factory Reject"
            );
            System.out.println("It's a race of " + r.getDistance() + " lengths");
            System.out.println("Press Enter to run the race....");
            System.in.read();
            r.run();
            }
            }
            

注意,在 清单 2 中,CountDownLatch 有两个用途:首先,它同时释放所有线程,模拟马赛的起点,但随后会设置一个门闩模拟马赛的终点。这样,“主” 线程就可以输出结果。 为了让马赛有更多的输出注释,可以在赛场的 “转弯处” 和 “半程” 点,比如赛马跨过跑道的四分之一、二分之一和四分之三线时,添加 CountDownLatch

3. Executor

清单 1  清单 2 中的示例都存在一个重要的缺陷,它们要求您直接创建 Thread 对象。这可以解决一些问题,因为在一些 JVM 中,创建 Thread 是一项重量型的操作,重用现有 Thread 比创建新线程要容易得多。而在另一些 JVM 中,情况正好相反:Thread 是轻量型的,可以在需要时很容易地新建一个线程。当然,如果 Murphy 拥有自己的解决办法(他通常都会拥有),那么您无论使用哪种方法对于您最终将部署的平台都是不对的。

JSR-166 专家组(参见 参考资料)在一定程度上预测到了这一情形。Java 开发人员无需直接创建 Thread,他们引入了 Executor 接口,这是对创建新线程的一种抽象。如清单 3 所示,Executor 使您不必亲自对 Thread 对象执行 new 就能够创建新线程:


清单 3. Executor
            Executor exec = getAnExecutorFromSomeplace();
            exec.execute(new Runnable() { ... });
            

使用 Executor 的主要缺陷与我们在所有工厂中遇到的一样:工厂必须来自某个位置。不幸的是,与 CLR 不同,JVM 没有附带一个标准的 VM 级线程池。

Executor 实际上 充当着一个提供 Executor 实现实例的共同位置,但它只有 new 方法(例如用于创建新线程池);它没有预先创建实例。所以您可以自行决定是否希望在代码中创建和使用 Executor 实例。(或者在某些情况下,您将能够使用所选的容器/平台提供的实例。)

ExecutorService 随时可以使用

尽管不必担心 Thread 来自何处,但 Executor 接口缺乏 Java 开发人员可能期望的某种功能,比如结束一个用于生成结果的线程并以非阻塞方式等待结果可用。(这是桌面应用程序的一个常见需求,用户将执行需要访问数据库的 UI 操作,然后如果该操作花费了很长时间,可能希望在它完成之前取消它。)

对于此问题,JSR-166 专家创建了一个更加有用的抽象(ExecutorService 接口),它将线程启动工厂建模为一个可集中控制的服务。例如,无需每执行一项任务就调用一次 execute()ExecutorService 可以接受一组任务并返回一个表示每项任务的未来结果的未来列表

4. ScheduledExecutorServices

尽管 ExecutorService 接口非常有用,但某些任务仍需要以计划方式执行,比如以确定的时间间隔或在特定时间执行给定的任务。这就是 ScheduledExecutorService 的应用范围,它扩展了 ExecutorService

如果您的目标是创建一个每隔 5 秒跳一次的 “心跳” 命令,使用 ScheduledExecutorService 可以轻松实现,如清单 4 所示:


清单 4. ScheduledExecutorService 模拟心跳
            import java.util.concurrent.*;
            public class Ping
            {
            public static void main(String[] args)
            {
            ScheduledExecutorService ses =
            Executors.newScheduledThreadPool(1);
            Runnable pinger = new Runnable() {
            public void run() {
            System.out.println("PING!");
            }
            };
            ses.scheduleAtFixedRate(pinger, 5, 5, TimeUnit.SECONDS);
            }
            }

这项功能怎么样?不用过于担心线程,不用过于担心用户希望取消心跳时会发生什么,也不用明确地将线程标记为前台或后台;只需将所有的计划细节留给 ScheduledExecutorService

顺便说一下,如果用户希望取消心跳,scheduleAtFixedRate 调用将返回一个 ScheduledFuture 实例,它不仅封装了结果(如果有),还拥有一个 cancel 方法来关闭计划的操作。

5. Timeout 方法

为阻塞操作设置一个具体的超时值(以避免死锁)的能力是 java.util.concurrent 库相比起早期并发特性的一大进步,比如监控锁定。

这些方法几乎总是包含一个 int/TimeUnit 对,指示这些方法应该等待多长时间才释放控制权并将其返回给程序。它需要开发人员执行更多工作 — 如果没有获取锁,您将如何重新获取? — 但结果几乎总是正确的:更少的死锁和更加适合生产的代码。(关于编写生产就绪代码的更多信息,请参见 参考资料 中 Michael Nygard 编写的 Release It!。)

posted @ 2010-07-23 15:25 linugb118 阅读(201) | 评论 (0)编辑 收藏

关于 java.util.concurrent 您不知道的 5 件事

Concurrent Collections 是 Java™ 5 的巨大附加产品,但是在关于注释和泛型的争执中很多 Java 开发人员忽视了它们。此外(或者更老实地说),许多开发人员避免使用这个数据包,因为他们认为它一定很复杂,就像它所要解决的问题一样。

事实上,java.util.concurrent 包含许多类,能够有效解决普通的并发问题,无需复杂工序。阅读本文,了解 java.util.concurrent 类,比如 CopyOnWriteArrayList  BlockingQueue 如何帮助您解决多线程编程的棘手问题。

1. TimeUnit

尽管本质上 不是 Collections 类,但 java.util.concurrent.TimeUnit 枚举让代码更易读懂。使用 TimeUnit 将使用您的方法或 API 的开发人员从毫秒的 “暴政” 中解放出来。

TimeUnit 包括所有时间单位,从 MILLISECONDS  MICROSECONDS  DAYS  HOURS,这就意味着它能够处理一个开发人员所需的几乎所有的时间范围类型。同时,因为在列举上声明了转换方法,在时间加快时,将 HOURS 转换回 MILLISECONDS 甚至变得更容易。

2. CopyOnWriteArrayList

创建数组的全新副本是过于昂贵的操作,无论是从时间上,还是从内存开销上,因此在通常使用中很少考虑;开发人员往往求助于使用同步的 ArrayList。然而,这也是一个成本较高的选择,因为每当您跨集合内容进行迭代时,您就不得不同步所有操作,包括读和写,以此保证一致性。

这又让成本结构回到这样一个场景:需多读者都在读取 ArrayList,但是几乎没人会去修改它。

CopyOnWriteArrayList 是个巧妙的小宝贝,能解决这一问题。它的 Javadoc 将 CopyOnWriteArrayList 定义为一个 “ArrayList 的线程安全变体,在这个变体中所有易变操作(添加,设置等)可以通过复制全新的数组来实现”。

集合从内部将它的内容复制到一个没有修改的新数组,这样读者访问数组内容时就不会产生同步成本(因为他们从来不是在易变数据上操作)。

本质上讲,CopyOnWriteArrayList 很适合处理 ArrayList 经常让我们失败的这种场景:读取频繁,但很少有写操作的集合,例如 JavaBean 事件的 Listeners。

3. BlockingQueue

BlockingQueue 接口表示它是一个 Queue,意思是它的项以先入先出(FIFO)顺序存储。在特定顺序插入的项以相同的顺序检索 — 但是需要附加保证,从空队列检索一个项的任何尝试都会阻塞调用线程,直到这个项准备好被检索。同理,想要将一个项插入到满队列的尝试也会导致阻塞调用线程,直到队列的存储空间可用。

BlockingQueue 干净利落地解决了如何将一个线程收集的项“传递”给另一线程用于处理的问题,无需考虑同步问题。Java Tutorial 的 Guarded Blocks 试用版就是一个很好的例子。它构建一个单插槽绑定的缓存,当新的项可用,而且插槽也准备好接受新的项时,使用手动同步和 wait()/notifyAll() 在线程之间发信。(详见 Guarded Blocks 实现。)

尽管 Guarded Blocks 教程中的代码有效,但是它耗时久,混乱,而且也并非完全直观。退回到 Java 平台较早的时候,没错,Java 开发人员不得不纠缠于这种代码;但现在是 2010 年 — 情况难道没有改善?

清单 1 显示了 Guarded Blocks 代码的重写版,其中我使用了一个 ArrayBlockingQueue,而不是手写的 Drop


清单 1. BlockingQueue
            import java.util.*;
            import java.util.concurrent.*;
            class Producer
            implements Runnable
            {
            private BlockingQueue<String> drop;
            List<String> messages = Arrays.asList(
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "Wouldn't you eat ivy too?");
            public Producer(BlockingQueue<String> d) { this.drop = d; }
            public void run()
            {
            try
            {
            for (String s : messages)
            drop.put(s);
            drop.put("DONE");
            }
            catch (InterruptedException intEx)
            {
            System.out.println("Interrupted! " +
            "Last one out, turn out the lights!");
            }
            }
            }
            class Consumer
            implements Runnable
            {
            private BlockingQueue<String> drop;
            public Consumer(BlockingQueue<String> d) { this.drop = d; }
            public void run()
            {
            try
            {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
            System.out.println(msg);
            }
            catch (InterruptedException intEx)
            {
            System.out.println("Interrupted! " +
            "Last one out, turn out the lights!");
            }
            }
            }
            public class ABQApp
            {
            public static void main(String[] args)
            {
            BlockingQueue<String> drop = new ArrayBlockingQueue(1, true);
            (new Thread(new Producer(drop))).start();
            (new Thread(new Consumer(drop))).start();
            }
            }

ArrayBlockingQueue 还体现了“公平” — 意思是它为读取器和编写器提供线程先入先出访问。这种替代方法是一个更有效,但又冒穷尽部分线程风险的政策。(即,允许一些读取器在其他读取器锁定时运行效率更高,但是您可能会有读取器线程的流持续不断的风险,导致编写器无法进行工作。)

注意 Bug!

顺便说一句,如果您注意到 Guarded Blocks 包含一个重大 bug,那么您是对的 — 如果开发人员在 main() 中的Drop 实例上同步,会出现什么情况呢?

BlockingQueue 还支持接收时间参数的方法,时间参数表明线程在返回信号故障以插入或者检索有关项之前需要阻塞的时间。这么做会避免非绑定的等待,这对一个生产系统是致命的,因为一个非绑定的等待会很容易导致需要重启的系统挂起。

4. ConcurrentMap

Map 有一个微妙的并发 bug,这个 bug 将许多不知情的 Java 开发人员引入歧途。ConcurrentMap 是最容易的解决方案。

当一个 Map 被从多个线程访问时,通常使用 containsKey() 或者 get() 来查看给定键是否在存储键/值对之前出现。但是即使有一个同步的 Map,线程还是可以在这个过程中潜入,然后夺取对 Map 的控制权。问题是,在对 put() 的调用中,锁在 get() 开始时获取,然后在可以再次获取锁之前释放。它的结果是个竞争条件:这是两个线程之间的竞争,结果也会因谁先运行而不同。

如果两个线程几乎同时调用一个方法,两者都会进行测试,调用 put,在处理中丢失第一线程的值。幸运的是,ConcurrentMap 接口支持许多附加方法,它们设计用于在一个锁下进行两个任务:putIfAbsent(),例如,首先进行测试,然后仅当键没有存储在 Map 中时进行 put。

5. SynchronousQueues

根据 Javadoc,SynchronousQueue 是个有趣的东西:

这是一个阻塞队列,其中,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。一个同步队列不具有任何内部容量,甚至不具有 1 的容量。

本质上讲,SynchronousQueue 是之前提过的 BlockingQueue 的又一实现。它给我们提供了在线程之间交换单一元素的极轻量级方法,使用 ArrayBlockingQueue 使用的阻塞语义。在清单 2 中,我重写了 清单 1 的代码,使用 SynchronousQueue 替代ArrayBlockingQueue


清单 2. SynchronousQueue
            import java.util.*;
            import java.util.concurrent.*;
            class Producer
            implements Runnable
            {
            private BlockingQueue<String> drop;
            List<String> messages = Arrays.asList(
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "Wouldn't you eat ivy too?");
            public Producer(BlockingQueue<String> d) { this.drop = d; }
            public void run()
            {
            try
            {
            for (String s : messages)
            drop.put(s);
            drop.put("DONE");
            }
            catch (InterruptedException intEx)
            {
            System.out.println("Interrupted! " +
            "Last one out, turn out the lights!");
            }
            }
            }
            class Consumer
            implements Runnable
            {
            private BlockingQueue<String> drop;
            public Consumer(BlockingQueue<String> d) { this.drop = d; }
            public void run()
            {
            try
            {
            String msg = null;
            while (!((msg = drop.take()).equals("DONE")))
            System.out.println(msg);
            }
            catch (InterruptedException intEx)
            {
            System.out.println("Interrupted! " +
            "Last one out, turn out the lights!");
            }
            }
            }
            public class SynQApp
            {
            public static void main(String[] args)
            {
            BlockingQueue<String> drop = new SynchronousQueue<String>();
            (new Thread(new Producer(drop))).start();
            (new Thread(new Consumer(drop))).start();
            }
            }

实现代码看起来几乎相同,但是应用程序有额外获益:SynchronousQueue 允许在队列进行一个插入,只要有一个线程等着使用它。

在实践中,SynchronousQueue 类似于 Ada 和 CSP 等语言中可用的 “会合通道”。这些通道有时在其他环境中也称为 “连接”,这样的环境包括 .NET (见 参考资料)。

结束语

当 Java 运行时知识库提供便利、预置的并发性时,为什么还要苦苦挣扎,试图将并发性导入到您的 Collections 类?本系列的下一篇文章将会进一步探讨 java.util.concurrent 名称空间的内容。

posted @ 2010-07-23 15:23 linugb118 阅读(230) | 评论 (0)编辑 收藏

Java中finalize

Java中finalize()
java这个finalize内置方法,估计很多人不会去用途,如果理解这个方法的用法和含义就能做一些可能我们一起认为不能做的事情。
在JAVA中有一种垃圾收集器的机制,当它运行时(通常在系统内存低到一定限度时自动运行),会回收不再使用的对象所占用的内存,所以,在JAVA程序中,我们通常只考虑创建对象,而从不关心对象的清除。Finalize()是JAVA为类提供的一种特殊方法。垃圾收集器的工作过程大致是这样的:一旦垃圾收集器准备好释放无用对象占用的存储空间,它首先调用那些对象的finalize()方法,然后才真正回收对象的内存。通过使用finalize(),就可以在垃圾收集器运行期间进行一些特殊的工作。

你们也就是说,当gc事件启动时候,他是调用对象的finalize(),来实现真正的回收,那么首先这些对象是没有用的,最简单的使用,我可以在finalize()中添加
system.out.print 来跟踪系统的回收了那些对象,可以深层次的了解系统对象的使用情况,比如那些对象回收最频繁等等。

另外我在网上也看到有人这么用finalize(). 主要是统计在线人。这个网上也有很多,本人自己也做过,但是在logout的时候,有三种情况
1.点击程序的logout,这个我们可以监听到。2.去别的网站 3.关闭浏览器。(当然现在第二,第三有些网站也能通过script捕获到,这里我们不谈)
我们假定传统的,session在服务端还是存在的,一般是经过服务器端timeout,自动将这个session的对象失效,那么我们在这些对象调用finalize()做一些
统计就能知道那些人已经离线。
具体这个例子
http://www.qqread.com/java/w712250600.html

这只是一个例子

我想说的是,我们如果理解了finalize的含义和用途,就能在很多地方用好它,未尝不是一种新方式。

posted @ 2010-07-16 17:18 linugb118 阅读(1825) | 评论 (1)编辑 收藏

log4j java版如何将日志写入数据库

 

其实如果是直接通过jdbc去连接数据库,那么下面的链接的

http://www.dankomannhaupt.de/projects/index.html
的jdbcappender.zip 已经能很方便的实现这个功能,

但是在现实情况,特别是大型应用,基本都是通过datasource来获取
connection,而这个zip中已经明确说了不支持 DataSource,那么我们怎么办呢?

我是这样解决的,对于j2ee的应用本身不管你用spring+hibernate还是c3p0 来获取
Datasource,最终他还是得到jdbc中的connection来进行数据库操作,而jdbcappender
也是通过connection来操作数据库,那么思路就是如果通过在你框架中获取jdbc的connection,
然后将他set到jdbcapplender中就可以了。

确定这种思路后,我就需要了解jdbcappender.zip中的代码,看如果将connection 放入jdbcappender中

首先 我们看一下如果正常通过jdbc配置,这个jdbcAppender是怎么集成到log4j中的
下面是jdbcAppender的用法

public class Log4JTest {
 // Create a category instance for this class
 static Logger logger = Logger.getLogger(Log4JTest.class);

 public static void main(String[] args) {
 
  
  JDBCAppender ja = new JDBCAppender();

  // Set options with method setOption()
  ja.setConnector("org.apache.log4j.jdbcplus.examples.OracleConnectionHandler");
  ja.setUrl("jdbc:oracle:thin:@..");
  ja.setUsername("mex_pr_dev65");
  ja.setPassword("mex_pr_dev65");

  ja.setSqlhandler("org.apache.log4j.jdbcplus.examples.SqlHandler");

  // Add the appender to a category
  
  logger.addAppender(ja);
  logger.debug("debug");
 
  
        }
 }
}

上面的类基本分为这么几个部分
1.和一般log4j用法一样, 获取logger instance
2.new 一个JDBCAppender
3.为jdbc设置两部分参数,一个是jdbc 建立连接的参数,如url等 另一部分是具体insert sql的handler(你可以用handler来体现insert 语句也可以将sql写入log4j的配置文件,我这里就用handler处理insert语句)
4.将logger instance中添加刚才设置的JDBCAppender
5.完成

从上面的例子来看好像不能set connection,那么后来查看JDBCAppender的源码发现
JDBCAppender有setConnectionHandler(interface JDBCConnectionHandler())方法;
可以通过这个方法能将connection放入jdbcappender中,但必须实现接口DBCConnectionHandler

接口JDBCConnectionHandler 如下
public interface JDBCConnectionHandler {

    /**
     * Get a connection
     *
     * @return The Connection value
     * @exception Exception
     *                Description of Exception
     */
    Connection getConnection() throws Exception;

    /**
     * Get a defined connection
     *
     * @param _url
     *            Description of Parameter
     * @param _username
     *            Description of Parameter
     * @param _password
     *            Description of Parameter
     * @return The Connection value
     * @exception Exception
     *                Description of Exception
     */
    Connection getConnection(String _url, String _username, String _password) throws Exception;
}

这个时候你发现 我们找了半天Connection的地方原来在这里

那么我只要实现这个接口的getConnection() 方法就能将connection放入JDBCAppender中,

现在我们构建一个Handler,我这里用的框架只是Hibernate3

我需要从hibernate3中获取connecion就可以了

public class Cas2HibernateConnectionHandler implements JDBCConnectionHandler {

   //hibernate 的session Factory
   SessionFactory sf = HibernateConnection.getInstance();
 
 public Connection getConnection(){
  Connection con =null;
  try {
   con = sf.openSession().connection();
  
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  return con;
 }

 public Connection getConnection(String arg0, String arg1, String arg2)
   throws Exception {
  // TODO Auto-generated method stub
  return null;
 }

}

这就是我的handler,为了让代码更清楚,我把我的HibernateConnection也贴出来,这是常见的Sington模式获取hibernate的SessionFactory
public class HibernateConnection {

 private static SessionFactory sessionFactoryInstance = null;

 private HibernateConnection() {
 }
 
 //单元测试用的
 synchronized public static SessionFactory getInstance() {
  
   if (sessionFactoryInstance == null) {
    Configuration config;
    try {    
     config = new Configuration().configure(new File("E:\\cas2\\config\\hibernate\\hibernate.cfg.xml"));
     sessionFactoryInstance = config.buildSessionFactory();
    } catch (HibernateException e) {
     e.printStackTrace();
    }
   }

  return sessionFactoryInstance;
 }
}

说到这里我顺便说一下,我这边框架用的是hibernate3,还有很多数据库相关的框架比如hibernate+spring或者c3p0等等,这些都无所谓
只要找到相应如果获得jdbc connection的方法,同时构建自己的ConnectionHandler并实现里面的getConnection()就可以了。

到这个时候数据库连接已经能获取了,接下来就需要写具体的insert语句,这里有两种方法,一种就是直接在log4j的配置文件中写
这个配置文件可以xml,也可以properties,这个网站也都有具体描述怎么做,这么不说了;
另外一种就是ja.setSqlhandler("org.apache.log4j.jdbcplus.examples.SqlHandler");
自己实现一个sqlHandler 后set到JDBCAppender中就可以了

新构建的sqlHander需要实现接口
public interface JDBCSqlHandler {

 /**
  * Get a sql-statement.
  * Return null or empty string if nothing should be logged.
  *
  * @return The SQL statement. Null if there is nothing to log.
  * @exception Exception
  *                Any Exception
  */
 String getStatement(LoggingEvent event) throws Exception;
}

他里面只有一个方法getStatement,里面写入具体insert 语句就可以了

public class CasLoginHandler implements JDBCSqlHandler {

 private final int MAX_LENGTH_MESSAGE = 3000;

    public String getStatement(LoggingEvent event) throws Exception {
        // try { throw new Throwable(); } catch (Throwable th) {
        // th.printStackTrace(); }
        LocationInfo locinfo = event.getLocationInformation();
        ThrowableInformation throwableinfo = event.getThrowableInformation();
        StringBuffer throwableStringBuffer = new StringBuffer();
        String locinfoString = "'', '', '', ''";

        if (locinfo != null) {
            locinfoString = "'" + locinfo.getClassName() + "', '" + locinfo.getMethodName()
                    + "', '" + locinfo.getFileName() + "', '" + locinfo.getLineNumber() + "'";
        }
        if (throwableinfo != null) {
            String[] lines = throwableinfo.getThrowableStrRep();
            for (int index = 0; index < lines.length; index++) {
                throwableStringBuffer = (StringBuffer) throwableStringBuffer.append(lines[index]
                        + "\r\n");
            }
        }

        StringBuffer sb = new StringBuffer();
        sb.append("Insert into UM_SYS_LOG (ID, LOG_DATE, LOG_LEVEL, LOGGER, OPERATOR, APPLICATION_NAME, MESSAGE_KEY, LOG_MESSAGE)Values(");
        sb.append("SEQ_UM_SYS_LOG.nextval,");
        sb.append("TO_DATE('");
        sb.append(DateFormatUtil.formDateToyyyyMMdd24(new Date(event.timeStamp)));
        sb.append("','YYYY-DD-MM HH24:mi:SS')");
        sb.append(",'");
        sb.append(event.getLevel().toString());
        sb.append("','");
        sb.append(event.getLoggerName());
        sb.append("','bgao','CAS2','login sucess','user bgao loginSuccess')");

        return sb.toString();
    }

}

这是我构建的CasLoginHandler。
然后将该Handler 设置到JDBCAppender中就行了
ja.setSqlhandler("com.bgao.log.CasLogHandler");


这样整个将log日志写入数据库就完成了。当然下面还有问题可以供思考,一般log.debug("msg");这种写法所有的日志信息都是以一条msg输出
,比如"张三在127.0.0.1机器查询表×××" ;如何简单得将一句话insert到不同字段,这个有很多方式,可以思考一下哪种更方便,更简洁。

 

 

 

 

posted @ 2010-07-09 15:25 linugb118 阅读(2863) | 评论 (1)编辑 收藏

memcached window版服务器端安装并测试

我最近研究这个memcache 发现这个东东,版本以及名称很多,有点混乱,这两天研究下来我是这么梳理的,不知道对不对,和大家一起分享
Memcached 是分布式cache,他有服务端和client端,核心版本是在Linux上运行
官方网站为 http://memcached.org/ 对应的wiki在google上
http://code.google.com/p/memcached/ 其实现在memcached的相关文档和代码都在google.code上了
对应Linux 上版本的维护的挺好,但是对于windows版本的就很糟糕了,可能因为大型应用大多是Unix或其变种

我在google上搜索了半天找到下面基本版本
windows的就有
http://jehiah.cz/projects/memcached-win32/
http://www.splinedancer.com/memcached-win32/ 基于上面的win32
win-1.2.6版本可以在
http://code.jellycan.com/memcached/ 找到

虽然这三个地方都是memcached for windows 而且还是不同人写的,但是他们感觉好像都有版本约定,比较有序,
比如http://jehiah.cz/projects/memcached-win32/ 好像是1.1--1.2.1
http://www.splinedancer.com/memcached-win32/ 本身网站上就写了
This is a port of memcached to the win32 architecture by Kenneth Dalgleish, based on Kronuz's 1.2.1 port
是基于上面的1.2.1写的 他有的版本是1.2.4
而http://code.jellycan.com/memcached/ 版本是1.2.5 1.2.6
这种有序是我猜想的,也有可能是他们都是根据核心 memcached 进行编译为win32版本,而win32的版本根据核心memcached版本来定义的。
这些只是猜想,反正结论是windows 的memcached server版本比较分散没有主要维护,但是他们也有各种的版本历史。如有知道这个历史内幕的
请反馈,谢谢。

我刚刚在google wiki上发现了windows 一起其他os的memcached链接
http://code.google.com/p/memcached/wiki/Start 【Server ports to other platforms--->windows】
现在是1.4版本了,其实也是链接到其他website
http://labs.northscale.com/memcached-packages/ (又多了一个出处)

好了,到现在我们不再猜测他的历史问题,虽然windows 版本很多但是他们的安装步骤都是一样的
我现在从http://labs.northscale.com/memcached-packages/ 下载的是最新的版本memcached-win32-1.4.4-54-g136cb6e.zip

一,安装memcached for windows
1.解压memcached-win32-1.4.4-54-g136cb6e.zip
2.将里面的文件放入 E:\memcached\memcached_win32
3.直接到目录E:\memcached\memcached_win32 下执行命令 memcached.exe -d install 安装服务
4.这个时候去控制面板--》管理工具--》服务 中就能看到一个memcached 的服务
5.如果卸载服务 那就memcached.exe -d uninstall,除此 还有 start restart命令,具体你可以通过memcached.exe -h 查看帮助

目前只是可以看到是否安装了服务,接下来我们应该去测试这个memcached server是否成功
测试这个memcached 有很多方式,
本身memcahed 有很多client端http://code.google.com/p/memcached/wiki/Clients
有C++,java,.net,php 等等
他们的任何一个client都可以用来测试,我们这里就用php
首先我们要搭建php 环境
二,安装apache和php(这些网上都有介绍)
1.下载apache2 http://httpd.apache.org/download.cgi
2.安装 apache2
3.下载php5
http://windows.php.net/download/ 注意要下VC6,VC6支持apache
而VC9不支持apache 是支持IIS
4.我们下载zip的这个包(比较绿色)
5.解压zip包 主目录为E:\php5,将“php.ini-recommended”文件备份并更名为“php.ini”。
6.查看php.ini
  查找“extension_dir”字段,赋值为php解压路径中的ext目录下,如"E:\php5\ext"
  查找 cgi.force_redirect 字串.默认值为1.将其修改为0.并取消前面的;号
7.分别查找扩展,将其之前的;去掉。
     extension=php_mbstring.dll(宽字符,用于支持PhpMyAdmin,避免出现字符显示问题)
     extension=php_mcrypt.dll(用于支持PhpMyAdmin)
     这些dll在E:\php5\ext可以找到,这里我们为了连接memcached server 我们需要
     加入一行 ‘extension=php_memcache.dll’一般ext没有该dll
   请在http://downloads.php.net/pierre/ 中下载相应的memcache.dll 放入到ext文件夹中
   我用的是 php_memcache-5.2-Win32-vc6-x86-20090408.zip
   反正就这几个memcache dll你都试试(php的这种方式真是不好,应该还有其他方式,这里没有研究)

8.配置Apache以支持php5:
     打开apache安装目录下的“conf”文件夹,apache的配置主要依靠httpd.conf,用编译工具打开该文件,修改其中的某些字段:
     (1)Listen 字段, 其后默认值为80,你可以修改该端口值以改变apache服务的端口(不至于和tomcat等工具的端口发生冲突)
     (2)DocumentRoot 这是你自己网页文件的放置目录,默认为apache安装目录下的“htdoc”文件夹,也可以改为本机上的其他目录,采用绝对路径。
           我使用的是:DocumentRoot "D:/phpwork/"(新建的工作目录)   
9.设置起始页:
     这个地方可以照抄我的配置,也可以自己增加需要的起始页文件名。注意文件名之间用空格隔开,而不是用逗号
     <IfModule dir_module>
      DirectoryIndex index.php index.html default.php default.html index.htm
     </IfModule>
9.配置php模块:在#LoadModule(有一排的代码) 后加上两句话(此处为我的安装目录,可根据自己的安装情况进行适当的**)
     PHPIniDir "E:/php5/"
     LoadModule php5_module "E:/php5/php5apache2_2.dll"  [这个php5apache2_2.dll 有下载的php5.3版本中就没有,后来下载了5.2,第一次配php就遇到这种事情,这种模式真的很不好]
10. 保存httpd.conf文件,重启Apache 如果成功启动,在phpwork下新建一个HelloWorld.php文件
         
          <?php
               echo "HelloWorld!<br>";
               phpinfo();
          ?>
  在浏览器中输入http://localhost/:你自己设置的端口号/HelloWorld.php.哈哈~~~至此将输出HelloWorld以及php配置环境变量信息,这就成功了。
 11.测试memcached,在phpwork下新建一个memcacheTest.php文件
 <?php
$mem = new Memcache;
$mem->connect("127.0.0.1", 11211);
$mem->set("key", 'This is a test!', 0, 60);
$val = $mem->get('key');
echo $val;
?>
在浏览器中输入http://localhost/:你自己设置的端口号/memcacheTest.php 如果看见This is a test!,那就表示成功了。

 

 

posted @ 2010-05-13 16:22 linugb118 阅读(5771) | 评论 (0)编辑 收藏

【转】豆瓣架构

 各位观众朋友大家好,这里是InfoQ中文站的赖翥翔,现在在首届QCon北京大会的现场,坐在我旁边的是来自豆瓣网的洪强宁。强宁你好,向大家介绍一下自己以及自己和豆瓣的联系。 
我是大概在06年的3月份加入豆瓣的。当时应该是豆瓣的02号程序员。01号是阿北。现在是任豆瓣的首席架构师。负责豆瓣技术开发的相关工作。
 我记得在之前社区中有对豆瓣高并发能力的讨论,豆瓣现在的用户数量以及访问量如何?用了多长时间达到了现在的水平? 
现在的话,我刚才没有上网,不知道现在是不是已经达到了300万用户,如果还没有达到的话,马上就会到了,可能是今天,可能是明天。300万是指我们的注册用户,另外还有千万级的非注册用户。访问量的话,现在应该是两千万每天。
 如果能达到这样的访问量,确实说明豆瓣高并发的能力是相当强,我想请您从技术这个角度介绍一下豆瓣网的架构。 
这个话题比较大一点,我刚才在演讲的时候,已经表述这方面的问题了。可以这么说,最简单的方法来说,豆瓣网可分割成两大块:一块是前端的Web,也就是用户在浏览器访问的时候会触发一系列的操作,从数据库拿出数据,渲染成HTML页面反馈给用户,这是前端;另外一块是后端,在豆瓣有一个很强的数据挖掘团队,每天把用户产生的数据进行分析,进行组合,然后产生出用户推荐,然后放在数据库里面,前端会实时的抓取这些数据显示给用户。
 如果是这样子,要是让你重新设计的话,你会觉得有必要改进里面哪些部分吗? 
豆瓣(架构)设计现在在WEB这一端主要是用这么几种技术:前端是nginx和lighttpd,中间是Quixote的Web框架,后面是MySQL以及我们自己开发的DoubanDB。这些除了Quixote都是一些比较流行的、尖端的技术。Quixote稍微老一点,如果要重新设计的话,可能会在这方面做一些考虑。比如Python社区中的Django、Pylons等等都是可以考虑的,那么在豆瓣的内部的话,我们一般是用web.py,很轻量的一个Web框架来做,也是非常不错的选择,它可能需要自己做的事情多一点。但是,也不太可能完全重新设计了。
 那如果要缓解高并发所带来的压力,Cache的利用肯定是一个非常有效的途径。那么豆瓣的缓存命中率一般是多大?这方面的策略是怎样? 
Memcache命中率一般都在97%左右,应该还算是比较高的。策略其实是比较简单的,如果每次要去执行一个比较耗时耗资源的操作,比如说去数据库查询的话,就会以Python的Object形式存放在Memcache里面,下次再拿这个数据的时候就直接从Cache中拿就行了。这边选择什么样的东西,尽量有一个Guideline,必须是要耗时的,耗资源的,而且是重复使用的。比如它是耗资源的,但是只用一次,Cache也没有意义。差不多用这种方法保证Cache的东西都是真正有效的,也提高了命中率。
 要提高承受高压力的流量,另外一个有效的措施是对数据库来进行分区分片,在这方面豆瓣是怎么做的? 
豆瓣现在还没有达到数据库分片的程度。我们现在最常见的手段是,按照功能分区。我们会把数据表分成几个独立的库,现在是一共有4个库。每个表都是库的一个部分,每个库会有主副两个。通过这种方式来减轻数据库的压力,当然这个是现在的方案,再往后的话,表的行数会增长,到达一定的程度后,还要进行水平分割,这是肯定的。然后我们现在的技术方面,在操作数据库之前,首先获取数据库的游标,有一个方法,这个方法会干所有的事情,我们以后做的时候会从这个方法中进行判断该从哪取东西。这个架构已经在了,只是现在还没有做这一步而已。
 数据库这边主要采用什么解决方案呢? 
在数据库这边,我们主要用的是MySQL。MySQL有一个问题,大文本字段会影响它的性能。如果数据量过大的话,它会挤占索引的内存。那么现在一个行之有效的方法是,我们另外建立一套可伸缩的Key-Value数据库,叫做DoubanDB。我们把不需要索引的大文本字段,放到DoubanDB里面去。MySQL只保存需要索引的Relationship这方面的信息。这样给MySQL数据库降低了压力,也就可以保证它的性能。
 比如说像保证数据的安全性,以及数据库的吞吐量,豆瓣是怎样的策略呢? 
首先DoubanDB会把每个数据在三个节点进行备份,任何一个出现故障都不会影响索取数据。MySQL是通过双Master方案,同时还会带1到2个slave,所以说在MySQL中我们会有三到四个的备份。这点是可以放心的。
 你刚才说到MySQL的双Master方案,这方面会不会存在什么问题?比如说同步的问题,等等? 
在MySQL里面,双Master方案是一个比较经典的方案,我们现在用它很大一部分是为了解决我们同步延迟的问题。在做切换的时候,会出现同步延迟的问题,但其实MySQL的同步速度还是可以的,在切换的时候,我们会忍受几秒钟等待同步的时间。在做脚本的切换的时候,我们会稍微等一下。
 豆瓣的数据表一般是怎么样的规模? 
数据表,这个不好说了,因为不同的表都是不一样的。我们最大的表是“九点”的Entry表,“九点”的爬虫爬过来的所有的文章,现在应该有四千万左右的行数。然后其他的上百万的表也有很多。还有包括收藏表也有千万级的行数。
 在这种海量数据的情况下,对数据表的就结构变更,一定是一个比较麻烦的问题。常见的情况,比如增加一个新的索引,会导致索引好几个小时。像豆瓣之前会存在这样的问题,是怎么解决的呢? 
这个问题曾经让我们吃过苦头,在忽视它的状况下就去改表,然后就锁了很长时间。后来我们意识到这个问题,如果有表的改动的话,我们会先在一个测试的库上试验一下它的时间长短,是不是在可接受的范围,如果是可接受的范围,比如说几分钟,就做一个定时任务,在深夜里面去执行。如果耗时是不可忍受的,就必须通过其他技术手段,我们现在的手段一般是建一个新表,这个新表从旧表同步数据,然后再写数据的时候,也会同步,往两边写,一直到两边完全一样了,再把旧表删掉,大概是这样一个方式。
 刚才您好像提过你们设计了自己的DoubanDB,还有一个是DoubanFS,这两者关系是怎么样的? 
首先是先出来的DoubanFS,我们刚开始的时候用MogileFS来解决我们可扩展图片存储的问题,由于MogileFS有一个重型数据库,这成为了它的性能瓶颈。我们为了解决这个问题,开发了DoubanFS,基于哈希来寻找节点。之后,我们又发现了新的问题,数据库中的大文本字段也会影响性能。所以,我们在DoubanFS的基础上,换了一个底层,做了一些调整,参照Amazon的dynamo思想,搭建了DoubanDB,把文本字段放在DoubanDB里面。做完之后,又反过来用DoubanDB来实现FS,大致是这么一个过程。
 DoubanFS跟DoubanDB的实现,他们在对于内容的安全性,或者内容的冗余性… 
都是(备份)三份。这都是可以配置的,现在的配置是3份。
 DoubanDB就是用什么机制实现的? 
DoubanDB简单来说是这样子:你来一个Key,它是Key-Value数据库,你要写或读的时候,通过这个Key来寻找这个值。拿一个Key对它做哈希,通过Consistent哈希方法去查找它在哪个节点上,然后往这个节点上去写或读。在这个节点上,顺着哈希的wheel顺次的找到第二、三个节点,写的时候会保证这三个节点都写,读的时候是任意一个,如果其中一个读失败了,会自动切换到下一个。
 您刚才提DoubanDB的话,是采用的技术是? 
DoubanDB的底层存储用的是TokyoCabinet,是一个很轻量级、高效的Key-Value数据库。我们在它的基础之上,做了分布式,用这种方式来实现的。
 实际上有一些其他的方案可以解决,比如说像Berkeley DB(简称BDB)、CouchDB等等,你们为什么要选择TokyoCabinet? 
最简单的原因是由于它足够快,实际上BDB跟它比较类似,BDB更加强大一些。对我们而言,我们在这边就是需要一个可靠、高效的Key-Value存储,这两个其实是我们都可以替换的,只要统一下接口就可以。CouchDB的话就是另外一个东西了,它是一个文档型数据库,它不仅仅做了一个Key-Value的工作,它还在这上面做了很多其他的事情,比如它有View的概念,可以进行query。这些TokyoCabinet是没有的,而我们暂时也不需要这些功能。CouchDB是一个很有意思的数据库,我们可能会在其他方面(应用),我们也在研究它。
 从我们刚才的讨论中,Web前端你用了nginx又用了lighttpd。它们都是非常流行的前端,这两种方案经常打架,豆瓣为什么把它们融合在一块? 
这是历史原因。我们其实没有刻意地去倾向某一个。这两个都是非常优秀的Web Server,都很轻量,都很高效。最开始的时候我们用的是lighttpd,然后是因为出现过一些问题,其实不是lighttpd的问题,但当时我们怀疑可能是lighttpd有问题,就尝试了一下nginx,觉得这个也不错,然后这个结构就保留下来了。nginx对开发者和用户的友好性都更好一些。我举个例子,比如说重启,其实在豆瓣的Web Server是经常要重启的,我们会有一个健康检查的脚本,定时的检查网站是不是正常,如果觉得不正常的话,就会做一些保护措施,其中就包括重启。lighttpd的重启,是一个很粗暴的Kill。Nginx是一个reload的方案,会先把手头的事情做完了再重启。这样会好很多,而且它会在重启之前会帮你做一些好的事情。所以,现在我们用Nginx越来越多。Nginx的配置文件也比lighttpd写起来更舒服一些。
 豆瓣现在有一个庞大的用户群体,针对这样一些海量数据做好数据挖掘,肯定不是一件容易的事情,能从技术这个角度讲讲挖掘的实现吗? 
在豆瓣专门有一个算法团队,他们的主要工作就是数据挖掘。这边讲技术实现的话,可能就讲不完了。只能讲一些大概,数据挖掘是怎么和前端结合起来的,让用户看见的。每天用户在豆瓣上的操作都会产生很多数据,在豆瓣上面看到的东西,收藏的东西,都会存在数据库或是访问日志。每天这些信息都会传到算法团队的机器上,然后会从这个数据中建立一个稀疏矩阵,你看过什么,干过什么。他们维护了一个很高效的稀疏矩阵运算库,然后用它来做各种各样的尝试,去看是否能得到好的结果,一旦发现这个结果很好,就会把它写到数据库里面。然后用户在访问的时候,前端从数据库中取出推荐给你的数据,然后把这些数据做一些过滤(比如你读过的东西就不再给你展现了)、调整,最后展现给用户。基本上是这么一个逻辑。
 从刚才你所描述的内容,可以发现豆瓣其实是一个应用非常多的,几乎用的都是开源框架吧? 
全部是开源的。
 我相信你们从社区的智慧以及各方面都会获取很多东西,我不知道豆瓣对开源社区是不是也做了一些回馈? 
是有的,我们最大的回馈形式是patch。我们用很多的开源软件,这当中就不可避免的有各种各样的问题,我们会尝试通过自己的努力解决这些问题,把我们的解决方案反馈给开发者。比较典型的像libmemcached,是一个C的memcached客户端。现在也是非常火的,基本是一个官方的C的客户端。它其实有很多bug,我们在使用的时候发现,去修正它。现在我们的团队成员里面有直接就是它的开发成员。比如说像Python的Mako模板,也是用的人非常多的模板。我们也在使用,使用起来发现它的性能稍微弱一些,我们也花了精力对它进行了优化,这个优化现在也是被接受了,在Mako的后来版本发布出来了。然后豆瓣自己也有一些开源的项目,最主要的开源的项目是豆瓣API的访问客户端,这个是在google code上面,也有很多志愿者参与进来,帮我们一起修改。然后从另外一个方面来说,豆瓣和国内的开源社区也有紧密的联系。豆瓣的上线通知就是发在开源组织CPUG的邮件列表里面的,豆瓣的很多成员也是CPUG的成员,会在邮件列表里面去帮助回答问题,讨论问题,这也是一种回馈的方式。
 豆瓣的开发团队是怎么样的? 
我们现在开发团队这边是11个人,有全职有兼职,还是比较放松。我们采用的是敏捷的方法,但是也不是完全的一模一样的方式。在豆瓣内部,我们尽可能地去发挥每个人的创造力。比如,在豆瓣作息是自由的,你可以自己决定什么时候来,什么时候走。比如你想在家里面静下心来写code,你可以往邮件列表里面发条消息说,我今天不过来了,就可以在家里面。每天会有很多的讨论,我们在豆瓣的办公室是一个独立的区域。在这个区域里面有白板,大家可以随时讨论。然后每周我们会有一个技术交流会议,大家轮流来发表一下自己最近在看一些什么东西,有什么心得,跟大家分享一下,这些都促进团队的沟通与发展的,很有用的东西。
 看来豆瓣是一个相当开放、技术和兴趣驱动的团队。 
我们希望一直保持这样的样子。
 那现场的观众有没有什么问题?其他记者:我是接着社区那个话题问一下,豆瓣现在有了很多的积累,有很多东西都已经成形了,有没有考虑说开放一些项目?
我们是有这个计划的。比如说DoubanDB,实际上我们在创立这个项目的时候,就是说这个项目我们做出来后是要开源的,到现在还没开源,是因为这个项目还在变化之中。由于开发的时间上的限制,所以现在还和豆瓣本身的数据绑得太紧,我们而且也是在不断地调整,现在还在调整的过程当中。找一个合适时机,我们会把它跟的豆瓣的数据剥离出来,成为一个可以独立地去安装、运行的应用的时候,就会把它拿出来,我想应该很快就能够做到这点。
 非常感谢强宁接受我们的采访,也恭喜今天在大会的演讲上面取得了非常圆满的成功。 
谢谢。

posted @ 2010-03-15 13:48 linugb118 阅读(199) | 评论 (0)编辑 收藏

架构话题

演化架构和紧急设计: 演化架构

敏捷架构的考虑和技术

Neal Ford, 软件架构师, ThoughtWorks Inc.

 

简介: 这一期的 演化架构和紧急设计 将会解决演化架构相关的各种主题,包括设计和架构之间的重要区别(以及如何区分两者),您在创建企业级架构时遇到的某些问题,以及面向服务的架构中静态类型和动态类型的区别。

查看本系列更多内容

发布日期: 2010 年 3 月 01 日
级别: 中级 其他语言版本: 英文

1 star2 stars3 stars4 stars5 stars 平均分 (共 5 个评分 )

 

本系列的第一期 中,我推荐了软件世界中的一些架构定义。无论如何,如果您已经阅读过本系列,您会注意到我花费了大部分时间在设计上。我之所以这么做是基于以下几个原因:首先,在当前紧急设计尚未被广泛关注时,软件世界里存在很多架构定义(良莠不齐);其次,在设计方面很多问题都有具体的、不受环境限制的解决方案。架构往往还涉及到很多组织内的物理和逻辑基础设施,使其难以独立谈起。

关于本系列

系列 旨在从全新的视角来介绍经常讨论但是又难以理解的软件架构和设计概念。通过具体示例,Neal Ford 将帮助您在演化架构紧急设计 的灵活实践中打下坚实的基础。通过将重要的架构和设计决定推迟到最后责任时刻,您可以防止不必要的复杂度降低软件项目的质量。

这一期填补了敏捷构架材料缺失的空白。在此我讨论的是如何分辨架构和设计,涵盖了一些广泛的架构考虑,然后通过讨论版本控制端点,浅谈敏捷的面向服务架构(SOA)。

分辨架构和设计

Martin Fowler 对架构的定义(来自和他的对话中)是我认为最好的:

架构就是完成之后很难更改的东西。所以这种东西应该尽可能越少越好。

您可以想象一下架构和设计之间的交互,如图 1 中所示的关系:


图 1. 架构和设计的关系
叠盒图

一个软件系统的架构形成是所有其他部分存在的基础,如 图 1 中的灰盒所示。设计部分存在于架构之上,如红盒所示。处于更基础的地位,架构部分难以移动和替换是因为您不得不移动所有以架构为基础的部分来适应改变。这一定义使识别设计和架构更为简单。例如,您所使用的 Web 框架就是一个架构,因为它难以替换。尽管,在那个 Web 框架中您使用不同的设计模式来表述特定的目标,这就表示大部分的正式设计模式是设计,而不是架构的一部分。

Fowler 所定义的架构的推论是:您应该灵活地构造架构部分,以便能够更轻松地替换它们(如果真的需要的话)。但是如何才能确保这点呢?这里有个例子。

许多框架都会试图诱导您使用其自身的类,而不是 JDK 或者一个开放标准机构(例如 OASIS)提供的更普遍的类。这就是耦合的 “毒贩模式”:如果您服从这些诱导,您就只能永远受制于框架。这些框架采取的普遍方法就是,如果您使用了它们的类,某方面就会变得异常简单。这方面的完美例子就来自于 Apache Struts Web 框架(见 参考资料)。

在您的应用程序中包含业务规则和其他非基础设施代码的类是域类:它们包含着您的问题领域相关的有趣信息。Sturts 中的一个好助手类就是 ActionForm 类。如果您从 ActionForm 继承了您的域对象, 您的应用程序就会变得更方便。您可以从参数完成自动表格填充、自动验证(Web 和服务器层),以及其他便利。您所要做的就只是把 Struts ActionForm 类作为子集,如图 2 所示:


图 2. 使用 Struts ActionForm
将域类耦合到 Struts 的图示

图 2 中,标签为 Model 的盒子包含了您的域对象。它扩展了 Struts 的 ActionForm,使得这一结构此后难以改变。如果以后您决定 ScheduleItem 也需要在一个 Swing 应用程序中运行,那就很难办了。您只剩下两个难以接受的解决方案:将所有的 Struts 拖拽到 Swing 应用程序中(且不使用它)或者摆脱对 Struts 的依赖。

较好的替代方案就是采用组合而不是继承,如图 3 所示:


图 3. 通过组合来对您的域类解耦合
进行组合而不是继承的图示

在此版本中,域类(黄色部分)包含了一个定义日程项目语义的界面。原始的 ScheduleItem 将实现这个界面,它还可以由 ScheduleItemForm 来实现,使得这两个类的语义总是保持一致。反过来,ScheduleItemForm 拥有 ScheduleItem 域对象的一个实例,ScheduleItemForm 的所有读值器和写值器传递到封装的 ScheduleItem 的底层读值器和写值器。这就允许您利用 Struts 的良好特性,同时摆脱该框架的束缚。

经验法则是:可以使框架对您有所了解,而您不可以对框架有所了解。只要您可以维持那种关系,您就能避免把自己的代码耦合到基础设施中去,这使您能够更轻易地改变架构和设计。有时可能要多花点功夫来完成这个任务,但是您可以拥有更好的灵活性。Struts 并不是唯一向您提供这种诱惑的框架。几乎所有的框架都包含将您限制在框架中的帮助工具。如果您在域类中导入来自某个框架或者厂商的数据包,那您以后就有得头疼了。


关于架构的考虑

除了架构的定义,典型的企业设置中还出现了各种广泛的问题。我将在这里介绍针对其中一些问题的敏捷架构解决方法。

架构的政治

当您被提升到架构师职位时,公司政治将是您所要遇到的众多难题之一。因为架构师 基本上是公司中最高的技术职位,您会成为 IT 部门内发生的所有决策的发言人(和辩护人),无论好坏。事实上,您还常常要因为失败受到责备,却不会因为成功而赢得信任。一些新上任的架构师试图对这些置之不理(当您在技术职位时这也许非常有效),但是在您的新职位这明显行不通。

请您记住在许多软件项目中,沟通比技术更为重要。如果您曾经在某个软件项目上失败过,那么请您思考一下失败的原因:是出于某个技术 原因,还是某些沟通 问题?大部分时间,失败是因为沟通而不是技术。技术问题有其解决方案。(有时它们很难解决,但总归有解决方案。)但社会问题就更加复杂和棘手了。Peopleware(见 参考资料)这本书中有这样一句名言:

总是存在人的问题。

即使是您认为应该按部就班,直截了当的技术决策,也会有政治参杂其中,特别是您处于决定是否批准购买某企业工具的职位。(从乐观的角度看,您可能有机会由某个工具厂家掏腰包打次异国情调的高尔夫。)请记得,作为一名架构师,您不仅需要做出重要的决策,您还必须为这些决策辩护。有时和您交谈的人有他们自己的议事日程,这些内容或许在逻辑上行不通,但是在企业政治的考验面前却行得通。不要气馁,您要记清楚最初之所以作出这个决策的原因。

构建与购买

大公司中常出现的普遍问题之一就是决定是构建还是购买:针对现在的需求,我们是应该购买 COTS(Commercial Off-the-Shelf Software)还是自己构建?要做出此决策的动机是可以理解的 — 如果公司可以找到一些完全符合自身需要的现成软件,这样就节约了时间和金钱。不幸的是,许多软件厂商理解这一需求,所以编写可以定制的打包软件,如果软件不能完全符合客户的需要的话。他们意在尽力构建最通用的软件,因为这样能适用更多的生态系统。但是越是通用,就越需要定制。所以有时即使很多顾问在,也需要花费很多年才能完成所有的定制代码。

是否应该购买 COTS 的问题实际上归结为另一个问题:业务流程是由软件在战略上 还是经费上 支持?如果业务流程仅仅是经费问题,购买 COTS 就合情合理。这类软件例子包括人力资源、财务、以及其他普通的业务流程。战略 软件在您的业务领域给您竞争优势,这个竞争优势不能轻易放弃。

避免陷阱

请记住:并不是所有的业务流程都是可定制的,它们根据业务不同而千差万别。不要轻信那些声称已经编写了您要的业务流程的厂商。如果他们真的拥有这样的流程,他们肯定也在把这些流程卖给您的竞争对手。

图 4 所示的流程图用于帮助您决定是构建还是购买:


图 4. 决策是构建还是购买的流程图
构建还是购买决策流程图

在这个流程图中,您要做出的第一个决策就是战略和经费的重要区别。如果需求是战略性的,您往往需要自己构建解决方案。如果不这么做,您就会将自己置于一个和对手公平竞争的环境中,而不是构建完全符合您现在和将来需求的软件。打包软件吹嘘其可定制性,但还是有对定制程度的限制。如果您自己编写,会花费较长的时间,但是您有了一个平台,在这个平台上您可以构建将您和对手区分开的软件。

流程图中的第二个决策就是询问数据包软件是否能立刻起作用。在购买数据包软件时常见的一个陷阱就是错误估计其适应您的业务流程所需的准确时间;大部分公司都把这个时间错估了一个数量级。您所需的定制越多,所耗费的时间就越长。更糟糕的是,一些公司还允许改变他们的业务流程来适应软件。这是一个错误,因为无论好坏,您的业务流程都应和对手的有所区别。

这个决策树中的第三步就是询问数据包是否可扩展,这和定制性 刚好相反。可扩展的系统由经过良好定义的方法来扩展功能,而无需一切事先就绪。这些扩展点包括经过良好定义的 APIs、SOAP 调用等等。定制意味着您要通过 “欺骗” 来让数据包完成您的工作。例如,如果您试图打开一个 WAR 文件,那么您可以用一个不同的图像(必须用 index.gif 来命名)来替换用 index.gif 命名的文件,您是进行定制而不是扩展。最终检验标准是您的更改是否能够通过升级。如果是,您就扩展了数据包;如果不是,您就定制了数据包。定制不鼓励您不断升级数据包,因为您会意识到对新版本做出相同的改变需要付出多少努力。那么,趋势就是不进行更新,落后于最新版四、五个版本,这将使您面临失去对现在正在使用的老版本的支持的危险。

是经费问题还是战略问题因公司而异。例如,我曾为一家财务服务公司做过顾问,它的招聘过程被认为是其关键战略优势之一。他们雇佣最好、最聪明的人,花费大量的时间和精力来寻找适合的人。他们曾就购买 COTS 人力资源系统咨询过我的意见,我建议他们不要那样做:为什么要让自己置身于一个和对手公平竞争的环境呢?最后,他们采纳了我的建议,编写自己的 HR 系统。编写花费了较长的时间,但一旦完成,他们就有了一个平台,能够完成对其对手来说更劳动密集型的任务。招聘对许多组织来说是简单的经费问题,但对这家公司来说却是战略问题。


架构中的类型控制

SOA 计划中经常出现的一个更技术化(更不面向流程)的主题往往和分布式系统中的类型控制和版本控制有关。这就是这类项目中常见的陷阱之一。它之所以常见,不仅因为人们很容易遵循工具厂商铺好的路,还因为问题需要一段时间才能凸显出来 — 最严重的问题产生于您不了解在项目早期应该知道的东西。

关于能否用动态类型语言构建 “企业” 系统的争论已经有了定论,这个结论现在也不能给予什么启示。然而,这一争论意味着就端点的类型控制而言,对分布式系统有了重要的考虑。所谓端点,指的是两个完全不同的系统之间的通信门户。两个相互竞争的类型控制样式是 SOAP 和 Representational State Transfer (REST),前者通常采用诸如 Web Services Description Language (WSDL)这样的标准来创建一个强类型,而后者适用于类型更宽松的、以文档为中心的方法(见 参考资料)。SOAP 与 REST 的详细优缺点不在本文的讨论范围之内;在此我主要想说的是端点层面上宽松类型的好处,这些好处可以使用任一样式实现。

更动态的类型控制在端点处是很重要的,因为这些端点会在以不同速度演变的系统之间形成一个已发布的集成 API。您想在那些系统之间避免严格耦合的特定签名(类型和参数名),因为那样会使通信的双方都很脆弱、容易崩溃,削弱了您分别对两个应用程序进行版本升级的能力。

这里有个例子。在传统的 SOAP 式集成中,使用的协议类型是 Remote Procedure Call (RPC),并用 WSDL 来定义两个应用程序间通话的详细信息,如图 5 所示:


图 5. 在应用程序间使用 RPC 式调用
RPC 式端点

RPC 式集成使用 WSDL 来进行一个 “常规” 方法调用,并将其抽象出来发送到 SOAP。这样,每个类都映射到 WSDL 中的一个类型,包括其所有参数的类型。这种方法将通信双方强烈耦合到一起,因为它们都依赖 WSDL 来定义发送的内容和预期接收的内容。问题源于这种严格的定义。如果您需要修改其中一个应用程序来采用不同的参数或者改变现有的类型,且不能同时更改这两个应用程序,那又该怎么办呢?该如何对端点进行版本控制?有几个方法是可行的,但所有这些方法都有严重的妥协之处。例如,您可以用新的 WSDL 定义创建另外一个端点。如果原始端点命名为 addOrder,那么您可以创建另一个端点,命名为 addOrder2。您会看到这种方法前景不妙。不久,您就会有数十个稍有不同、到处包含重复代码的端点来处理一次性情况,因为一旦发布,就很难预测人们会怎么应用这些集成点。您也可以使用 Universal Description, Discovery, and Integration (UDDI)(或者仅仅是哈希图)这样的工具来欺骗端点,但那并不会有很好的效果。根本问题就是端点间的严格耦合,那会阻止其按自然、独立的速度发展演变。

一种替代方法就是把集成端点当做宽松类型对待,如图 6 所示:


图 6. 在集成端点采用宽松类型控制
以文档为中心的集成

通过将有趣的端点信息传送到一个文档内,您可以在通信双方任意一方主要升级和次要升级过程中保持端点定义不变。您可以灵活选择,而不是依赖 WSDL 来严格定义预期的内容。现在,端点总是接收一个封装该端点所需类型的文档。

要解决端点的版本控制问题,端点要做的第一步就是把文档解开,确定已经传输的内容,并用预期的内容与之协调。我通常联合使用 Factory 和 Strategy 设计模式(见 参考资料)来确定是否正在获得预期的内容,如图 7 所示:


图 7. 在端点内解开内容来确定类型
确定类型的策略

端点的首要工作就是查看文档的清单,确定它包含的内容。然后,它用一个库来实例化适当的策略,将那些信息从文档中抽出。一旦所有部分都通过验证(必要时可用 WSDL),反序列化的对象就被传递,用于业务处理。

这种方法有几个好处。首先,拥有一个带有两个正交作业的机制是个坏主意,然而那正是传统 RPC 所假设的:端点既要负责提供已发布的集成 API,又要负责验证类型。因为其有两个行为,所以您可能会弄混代码,使其更难理解和维护。其次,现在这个端点可以有多个用户,每个用户使用稍有差异的版本。只要您有一个策略,您就能够用相同的端点支持任何版本(包括那些更新缓慢的应用程序的老版本)。这允许您根据需要进行改变,不用担心这会迫使企业内应用程序的其他部分和您的改变保持一致 —— 它们可以根据自己的进度改变并使用新的文档版本。

目前没有任何工具或者框架允许您轻松地实现这种方法,但是一些额外的前期工作提供了之前提到过的好处。您可以使用 SOAP 或 REST 来实现这个样式(不过在 REST 中会更容易,因为它本身就是以文档为中心的)。通过创建一个宽松类型的生态系统,您可以使不同的开发小组按自己的节奏开展工作,从而使整个企业的应用程序使用以最小的摩擦前进。这就是演化架构的精髓:奠定一个基础来支持尽快实施无摩擦的、不损害功能的变革。


结束语

架构是个庞大且复杂的软件主题;在这部分中,我试图涉及许多不同的方面,从政治到 SOA 中的端点版本控制的实现细节。在以后的部分中,我将不断充实这些关于一般架构和新架构方法的想法,帮助您构建一个可发展的 SOA,以免向软件商支付数百万美元的高额费用。


参考资料

学习

讨论

关于作者

Neal Ford 是一家全球性 IT 咨询公司 ThoughtWorks 的软件架构师和 Meme Wrangler。他的工作还包括设计和开发应用程序、教材、杂志文章、课件和视频/DVD 演示,而且他是各种技术书籍的作者或编辑,包括最近的新书 The Productive Programmer 。他主要的工作重心是设计和构建大型企业应用程序。他还是全球开发人员会议上的国际知名演说家。请访问他的 Web 站点

posted @ 2010-03-15 10:46 linugb118 阅读(200) | 评论 (0)编辑 收藏

OO四剑客

                                      OO四剑客
   现在面向对象的开发已经基本成为程序员认定的真理,围绕这个思想很多人做了将原来不是面向对象的东西转换
为面向对象的工作,比如面向对象数据库
(参考 『http://linugb118.blog.ccidnet.com/blog-htm-do-showone-uid-39808-type-blog-itemid-102097.html』db4o 参考我以前写的)
,还比如or-mapping的出现,等等。
   本人以前也对面向对象的工具以及一些面向对象的产品进行过源码的研究,后来在看了《Jakarta Commons Cookbook》
后,我觉得具体可以这么归纳面向对象开发的四个要素:Predicate(断言),Closure(终结者),Comparator(比较器),Transformer(转换),
 我们先不解释这四个要素的含义和用意,我们先来看看非面向对象语言的描述,如果在学校学习过面向过程的
编程语言如PASCAL的话,大家其实知道,如果需要实现某个功能,那么在一个函数中从头写到尾,其实对于计算机
本身而言,他也是从头到尾执行的,他本身不会跳转,只有你给if或者else 这样的词语告诉它,那么机器才会根据你的
意思知道是跳转到别的地方还是继续执行。目前跳转的常用模式就是if(A){do}else{} 以及循环while(A) do{},
  这里开始引入第一个要素Predicate(断言),什么是Predicate,其实上面的A就是Predicate。在面向过程语言中。
往往是等于,AND,OR,不等于等一些判断,而等式链接的直接是变量,链接符号那么是预先定义的符号,比如==,&&,||, ! 等等
那么面向对象的话,一切都是对象,首先等式链接的两边是对象,虽然对象语言中仍然有预先定义的符号,但是我们
觉得这个不符合面向对象的原则【一切皆是对象】,于是我们要把等式符号也要转换为对象,我们就引入接口Predicate
在接口Predicate中
public interface Predicate
{
  public boolean evaluate(Object object);
}

只有一个方法evaluate,他返回true或者false;
在common collections中 与等式符号对于的有
EqualPredicate   ==
NotPredicate     !=
TruePredicate    true
FalsePredicate    false
OrPredicate      ||
AndPredicate     &&
等等
而本身因为链接的是对象,因此和对象相关的特定Predicate有
IdentityPredicate
InstanceOfPredicate
NullPredicateNullIsTruePredicate
NotNullPredicateNullIsFalsePredicate
UniquePredicate
除了上面已经实现的Predicate,而Predicate本身因为是接口,所有用户也可以自己去实现自己的Predicate
比如
public class LaunchPredicate implements Predicate {
public LaunchPredicate( ) {}
public boolean evaluate(Object object) {
      if(...)
       {return true}
        return false;
}}
另外我们说过Predicate本身是对象,而等式链接的对象当然也可以是Predicate对象本身,因此就有了无穷无尽的
组合,也就是Composite Predicates   
上面的AndPredicate和OrPredicate就是能链接Predicate对象的Predicate,除此之外还有
AllPredicate, OnePredicate, AnyPredicate, NonePredicate 当然从原则上说其他Predicate的实现也能
链接Predicate对象,但是含义不够通用。
   前面对照面向过程语言我讲的是if/while后面的断言部分,那么我们现在看看if/while后面do的部分
在面向过程语言中,do就是一段代码。那么如果利用面向对象的思想来思考,那么do应该是一个对象,那这个对象
就是Closure(终结者)

public interface Closure
{
  public void execute(java.lang.Object input);
}
在接口中,我们可以看出里面只有一个execute接口,用户可以实现自己的Closure来实现代码片段想做的事情。
接下来,有个问题,多个代码片段可能合成一个片段,那么怎么处理,Closure 同样可以,只要引入ChainedClosure 
就可以了,ChainedClosure的用法
Closure[] cArray = new Closure[] { repairShielding, fuel };
Closure preLaunch = new ChainedClosure( cArray );
我们刚才看的只是片段里面没有if/while 断言,如果片段里面有if/while 断言怎么办,那么这个时候我们
根据上面讲的断言引入三个特殊的Closure:IfClosure 和WhileClosure/ForClosure
他们的用法如下
Predicate isWinning = new Predicate( ){...}
Closure sell = new Closure( ){...}
Closure buy = new Closure( ){...}
Closure stockAction = new IfClosure( isWinning, buy, sell );
这是一个买卖股票的例子,如果isWinning为true 那么执行买入buy,否则执行卖出;

Closure drive = new Closure( ) {...}
Predicate hasFuel = new Predicate( ) {...}
Closure useAllFuel = new WhileFuel( hasFuel, drive );
执行循环数字那么就用ForClosure
Closure driveSome = new ForClosure( 5, drive );
   到这里,我们需要回过来看看对象的断言,对象怎么能够相互比较的,其实很简单,有两种方法,一种
方法:继承Comparable,实现CompareTo方法 另外一种是:用外部的Comparator来比较。
参考【我写过一篇文件
http://linugb118.blog.ccidnet.com/blog-htm-do-showone-uid-39808-type-blog-itemid-102094.html】
我们这里讲的是Comparator,Comparator比较灵活,独立于对象。
public interface Comparator
{
  public int compare(Object o1, Object o2); 
  public boolean equals(Object obj);
}
Comparator接口中主要需要实现的是compare方法。你可以自己写自己的Comparator,当然collections中
已经有了些常用的Comparator
如ReverseComparator 反向比较
如果要给book反向排序,那么就这样使用Collections.sort( books, reverseComparator );
多个比较器同时使用就用ComparatorChain;
如下面例子:
ComparatorChain comparatorChain = new ComparatorChain( );
comparatorChain.addComparator( new BeanComparator( "lastName" ) );
comparatorChain.addComparator( new BeanComparator( "firstName" ) );
comparatorChain.addComparator( new BeanComparator( "age" ), true );

除此之外 还有特定的比较器NullComparator和FixedOrderComparator
他们的用法:
Comparator nullComparator = new NullComparator( BookComparator );
String[] medalOrder = {"tin", "bronze", "silver", "gold", "platinum"};
Comparator medalComparator = new FixedOrderComparator( medalOrder );----按medalOrder指定的顺序排
   到现在为止,面向对象的四剑客我们已经讲过三个,剩下最后一个是Transformer(转换)
public interface Transformer
{
  public java.lang.Object transform(java.lang.Object input) 
}
其实从程序的角度,我觉得Transformer和Closure都是将input 的东西进行处理然后输出,好像一样的,
没错,从程序角度是一样的,但是如果从对象角度来看,他们还是有一些区别,Transformer是将一个对象
转换为其他对象或者一个新的对象,一般不太会改变对象只是做一些转换,而Closure就是处理对象,会在
上面做很多改变。
Transformer同样可以用ChainedTransformer来将多个Transformer链起来使用
Transformer[] chainElements = new Transformer[] { multiply, increment };
Transformer chain = new ChainedTransformer( chainElements );
另外Transformer可以和Predicate一起形成条件开关转换器SwitchTransform
Predicate[] pArray = new Predicate[] { isOdd, isEven };
Transformer[] tArray = new Transformer[] { oddTransform, evenTransform };
Transform predicateTransform = 
new SwitchTransform( pArray, tArray, new NOPTransformer( ) );
这里如果isOdd就执行oddTransform,如果isEven就执行evenTransform,默认执行new NOPTransformer() 

   读完上面的四剑客,估计你应该对面向对象一切皆为对象的理念有了更深刻的理解的。^_^
 










posted @ 2010-02-02 10:59 linugb118 阅读(2100) | 评论 (0)编辑 收藏

高焕堂老师的软件架构的讲座

上次有幸参加了高焕堂老师的软件架构的讲座:
总结了下面几点:
1.做框架的思想很简单,就是所谓的雕刻之道,软件就如一块大理石,把多余的部分去掉,那就可以了
再比如如何做汽车的框架,为了满足汽车能在沙滩上,地面上,山坡上跑,我们只要把轮胎去掉,那么
剩下的就是框架,做软件完整的API不要写,留给空位就行了。
2.麦肯锡的思路(反向思维):当需要想完成某个目标的时候,往往一般人会想我现在应该先去做什么,然后
再做什么,这样的思路往往出来的step by step 只有一种,如果反向思维,从现在目标开始反向推理出前一
阶段的几种可能性,然后从分别对这几种可能性再向前推,以此类推可以形成一个树状,然后根据先有情况
去除不能满足的链路,这样同样的问题 你的思路和方法以及可选择的路线就多很多,往往不是一条。这就是
反向思维
3.如果把软件生产比作工厂,请问软件工厂的原料是什么? 是需求? 如果回答是需求,那就错了。
需求和架构没有关系。需求是桌面,架构是桌脚,桌脚的要几个,什么形状和桌面没有关系,只要桌脚能支持
桌面就行了。
4.架构就像万里长城,他是保护自己人的,是自己人能安居乐业,外面的多变都被万里长城挡在外面,
框架下面的可以多变,没钱就改版,改版就有钱。
5.写框架的是强龙,写AP的是地头蛇。买主出现才有地头蛇。也就是需求出现的时候才有地头蛇。
6.软件哲学,如何让先写的call后写的?引入接口和基类就能完成这个问题
7.子接口因为都是基础基类,那么他们之间怎么new,如果他们要new 也就向框架要,这样才能不违背框架的用意。
8.框架先不要考虑太多效率的问题,效率的问题在后面慢慢修改,这样可以减少考虑的因素,更容易理清。
9.强龙要有主控权,那么框架所做的事情就是能让强龙能包容改变
10.如果不想子系统继承那么就用final 关键字
11.如果两个类 不要相互继承,但是要他们相互call,那么就在他们里面分别定义一个方法,相互call
12.基类告诉子类,让子类call她,那么子类才能call基类,没有call子类,那么子类不能先去call她。
13一般进程process 是不共享的,他们在不同的位置区间,如果要跨进程的call,那么用IPC。而Process
一般分Main thread;Message Quene;Main Looper。其中Main thread是主线程,他通过Looper 一直查看他的
MQ,MQ记录要求做的事情,如果MQ里面有什么事情,那么Main thread 就拿到他把他做掉。
14.Main thread 主要是处理UI相关的用户事件,而且一般有时间设置比如每个function不能超过5s。
15Andriod中是通过IBinder 来实现跨进程的通信。
16.主线程一定有一个MQ 一个Looper。而小线程没有,所有小线程从一开始到执行完就结束了,但是小线程
不能touch UI,只有Main thread 可以touch UI相关的用户事件。
17 架构师是在暗室里面抓黑猫,在没有路的情况下找出一条可行之路,所有没有步骤可言。
18 做框架 尽量把人家会抓住你的地方分开,如果实在分不开,可以当壁虎,把壁虎的尾巴给人家抓。
19 框架的东西尽量要用c++写,因为c++比起java 安全,快,无反编译。
20 做一个系统一定要只要你的控制中心和整合中心,而且他们只有一个并且只有一个连接。控制中心好比大脑
而整合中心好比骨骼。在控制中心可以增加状态机来增加控制力和安全性。

posted @ 2010-01-28 14:29 linugb118 阅读(3017) | 评论 (4)编辑 收藏

仅列出标题
共5页: 上一页 1 2 3 4 5 下一页 

My Links

Blog Stats

常用链接

留言簿(1)

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜