#
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
并发 Collections 提供了线程安全、经过良好调优的数据结构,简化了并发编程。然而,在一些情形下,开发人员需要更进一步,思考如何调节和/或限制线程执行。由于 java.util.concurrent
的总体目标是简化多线程编程,您可能希望该包包含同步实用程序,而它确实包含。
本文是 第 1 部分 的延续,将介绍几个比核心语言原语(监视器)更高级的同步结构,但它们还未包含在 Collection 类中。一旦您了解了这些锁和门的用途,使用它们将非常直观。
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!。)
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 事件的 Listener
s。
回页首
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
还体现了“公平” — 意思是它为读取器和编写器提供线程先入先出访问。这种替代方法是一个更有效,但又冒穷尽部分线程风险的政策。(即,允许一些读取器在其他读取器锁定时运行效率更高,但是您可能会有读取器线程的流持续不断的风险,导致编写器无法进行工作。)
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
名称空间的内容。
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的含义和用途,就能在很多地方用好它,未尝不是一种新方式。
其实如果是直接通过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到不同字段,这个有很多方式,可以思考一下哪种更方便,更简洁。
我最近研究这个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!,那就表示成功了。
演化架构和紧急设计: 演化架构
敏捷架构的考虑和技术
Neal Ford 是一家全球性 IT 咨询公司
ThoughtWorks 的软件架构师和 Meme
Wrangler。他的工作还包括设计和开发应用程序、教材、杂志文章、课件和视频/DVD 演示,而且他是各种技术书籍的作者或编辑,包括最近的新书
The Productive
Programmer 。他主要的工作重心是设计和构建大型企业应用程序。他还是全球开发人员会议上的国际知名演说家。请访问他的
Web 站点。
简介: 这一期的 演化架构和紧急设计
将会解决演化架构相关的各种主题,包括设计和架构之间的重要区别(以及如何区分两者),您在创建企业级架构时遇到的某些问题,以及面向服务的架构中静态类型和动态类型的区别。
查看本系列更多内容
发布日期: 2010 年 3 月 01 日
级别: 中级
其他语言版本: 英文
在 本系列的第一期
中,我推荐了软件世界中的一些架构定义。无论如何,如果您已经阅读过本系列,您会注意到我花费了大部分时间在设计上。我之所以这么做是基于以下几个原因:首先,在当前紧急设计尚未被广泛关注时,软件世界里存在很多架构定义(良莠不齐);其次,在设计方面很多问题都有具体的、不受环境限制的解决方案。架构往往还涉及到很多组织内的物理和逻辑基础设施,使其难以独立谈起。
这一期填补了敏捷构架材料缺失的空白。在此我讨论的是如何分辨架构和设计,涵盖了一些广泛的架构考虑,然后通过讨论版本控制端点,浅谈敏捷的面向服务架构(SOA)。
分辨架构和设计
Martin Fowler 对架构的定义(来自和他的对话中)是我认为最好的:
架构就是完成之后很难更改的东西。所以这种东西应该尽可能越少越好。
您可以想象一下架构和设计之间的交互,如图 1 中所示的关系:
图 1.
架构和设计的关系
一个软件系统的架构形成是所有其他部分存在的基础,如 图 1
中的灰盒所示。设计部分存在于架构之上,如红盒所示。处于更基础的地位,架构部分难以移动和替换是因为您不得不移动所有以架构为基础的部分来适应改变。这一定义使识别设计和架构更为简单。例如,您所使用的
Web 框架就是一个架构,因为它难以替换。尽管,在那个 Web
框架中您使用不同的设计模式来表述特定的目标,这就表示大部分的正式设计模式是设计,而不是架构的一部分。
Fowler 所定义的架构的推论是:您应该灵活地构造架构部分,以便能够更轻松地替换它们(如果真的需要的话)。但是如何才能确保这点呢?这里有个例子。
许多框架都会试图诱导您使用其自身的类,而不是 JDK 或者一个开放标准机构(例如 OASIS)提供的更普遍的类。这就是耦合的
“毒贩模式”:如果您服从这些诱导,您就只能永远受制于框架。这些框架采取的普遍方法就是,如果您使用了它们的类,某方面就会变得异常简单。这方面的完美例子就来自于
Apache Struts Web 框架(见 参考资料)。
在您的应用程序中包含业务规则和其他非基础设施代码的类是域类:它们包含着您的问题领域相关的有趣信息。Sturts 中的一个好助手类就是
ActionForm
类。如果您从 ActionForm
继承了您的域对象,
您的应用程序就会变得更方便。您可以从参数完成自动表格填充、自动验证(Web 和服务器层),以及其他便利。您所要做的就只是把 Struts
ActionForm
类作为子集,如图 2 所示:
图 2. 使用 Struts
ActionForm
类
在 图 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 式集成使用 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 站点。
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()
读完上面的四剑客,估计你应该对面向对象一切皆为对象的理念有了更深刻的理解的。^_^
上次有幸参加了高焕堂老师的软件架构的讲座:
总结了下面几点:
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 做一个系统一定要只要你的控制中心和整合中心,而且他们只有一个并且只有一个连接。控制中心好比大脑
而整合中心好比骨骼。在控制中心可以增加状态机来增加控制力和安全性。