1 SRP
SRP(Single Responsible Principle), 單一職責原則,這是面對的最基本原則,也是實現彈性設計的最基本原則。
每個類或接口定義應該只包含一種明確的職責,同時僅有一種原因會導致這種定義的修改。一個復雜的類或接口的定義包含多個責任,很容易使你的設計失去彈性,很多因素都會導致這個類或接口的變更,由于它含有多種職責,這就意味著它是多種服務的提供者,會有多種依賴于它的客戶類,他的變更可能會導致大范圍的變更。
在作者看來,優先級最高的是你首先要保證接口的單一職責及方法的單一職責,接口通常意味可以更換不同的實現,為一個接口定義過多的職責意味著每個實現都會涉及多個職責,這將導致無法實現更小粒度的實現的復用。
2 面向抽象編成
如果你已經讀過GOF的《設計模式》,你便知道其中每一個模式都是基于此原則的,抽象(或接口)有效的解除了服務調用者和服務提供者間的耦合。
3 使用配置
通過修改配置文件便可以改變系統的某些特性,這種修改的區別于修改代碼,對于Java,C++而言這種修改是不需要編譯,有的修改甚至可以在運行時生效。DSL地運用可以使配置更加具有可讀性及更強的描述能力。在設計時將實現分為配置及框架部分是非常靈活的結構。
蔡超
HP 軟件架構師
軟件架構顧問
SCEA
IBM Certified Solution Designer for OOA&D vUML2
Chaocai2001@yahoo.com.cn
厭倦了那些厚書(特別是那些為了賺錢而特意寫厚的書),很多時候這些書讓我們找不到技術要點,甚至喪失了學習的興趣,而最終變成那些拒絕新技術的“頑固派”。
其實掌握技術的最佳方式是實踐,在實踐中不斷的深入學習。
本教程旨在幫助哪些已經掌握了OSGi和Spring技術基礎的開發人員,迅速將Spring DM應用于實際開發,這是一份入門教程,不求全面,但求簡單。
并請配合本教程的實例代碼一同學習。
下載教程和示例:
http://www.aygfsteel.com/Files/chaocai/spring-osgi.rar
實現外部DSL
與上一篇中所提及內部DSL不同,使用者不是通過API調用來使用DSL,而是通過我們定義的特定語法的領域語言來使用DSL。
1 XML形式的DSL
腳本文件
<process name="Auto-Door">
<state name="Open">
<transition event="time-out" next_state="Close"/>
</state>
<state name="Close">
<transition event="people-closer" next_state="Open"/>
</state>
</process>
實現
publicclass XmlConfigParser {
//followings are context variables
private Machine currentMachine;
private State currentState;
class ElementHandler extends DefaultHandler{
private String getAttributeValue(String elemName,String attributeName,Attributes attris){
String attrValue=attris.getValue(attributeName);
if (attrValue==null){
thrownew XmlConfigParseException("Element "+elemName+" shoudle have the attribute:"+attributeName);
}
return attrValue;
}
@Override
publicvoid endElement(String arg0, String arg1, String elemName)
throws SAXException {
if (elemName.equals("state")){
currentMachine.getStates().add(currentState);
}
}
@Override
publicvoid startElement(String arg0, String arg1, String elemName,
Attributes attris) throws SAXException{
if (elemName.equals("process")){
String processName=getAttributeValue(elemName,"name",attris);
currentMachine=new Machine(processName);
}
if (elemName.equals("state")){
String stateName=getAttributeValue(elemName,"name",attris);
currentState=new State(stateName);
}
if (elemName.equals("transition")){
String eventName=getAttributeValue(elemName,"event",attris);
String nextState=getAttributeValue(elemName,"next_state",attris);
Transition transition=new Transition();
transition.setEvent(new Event(eventName));
transition.setNextState(nextState);
currentState.getTransitions().add(transition);
}
}
}
public Machine parser(String fileName){
SAXParserFactory spfactory =
SAXParserFactory.newInstance();
try{
SAXParser saxParser =
spfactory.newSAXParser();
XMLReader reader=saxParser.getXMLReader();
reader.setContentHandler(new ElementHandler());
reader.parse(fileName);
returncurrentMachine;
}catch(Exception e){
thrownew XmlConfigParseException("parsing is failed",e);
}
}
}
實現要點
上述實現是通過SAX來進行XML解析的。
1 將領域模型結構直接映射為XML元素的結構
我們用這種方式來設計我們的DSL,這樣做的好處是DSL比較容易使用(更接近領域模型),同時解析程序也會相對簡單,比較容易生成相應的語義模型。
2 使用上下文變量
如上面程序中的:
private Machine currentMachine;
private State currentState;
他們就是上下文變量,由于SAX是順序解析的,所以必須保持正確的工作上下文,如把生產Transition對象加入到正確的State中。
2 自定義語言
腳本文件
Machine (Auto-Door){
State(Open){
Transition{
event : time-out ,
next-state : Close
}
}
State (Close){
Transition{
event : people-closer ,
next-state : Open
}
}
}
實現
自己設計語法并實現解析器,通常需要我們具備一定的編譯原理知識并且借用一定的解析器生成工具來幫助我們生產解析器代碼。
實現中本人使用了 Antlr
Antlr的語法描述文件:
grammar StateMachineG;
@header {
import org.ccsoft.statemachine.models.Machine;
import org.ccsoft.statemachine.models.State;
import org.ccsoft.statemachine.models.Transition;
import org.ccsoft.statemachine.models.Event;
}
@members {
public void emitErrorMessage(String msg) {
throw new RuntimeException(msg);
//super.emitErrorMessage(msg);
}
}
machine returns [Machine value] : 'Machine''('NAME')''{'{$value=new Machine($NAME.text);} (e=state{$value.getStates().add($e.value);})+'}';
state returns [State value] : 'State''('NAME')''{'{$value=new State($NAME.text);}(e=transition{$value.getTransitions().add($e.value);})+'}';
transition returns [Transition value]
: 'Transition''{'{$value=new Transition();}e=event{$value.setEvent($e.value);}','f=nextState{$value.setNextState($f.value);}'}';
event returns [Event value] : 'event'':'e=NAME{$value=new Event($NAME.text);};
nextState returns [String value]
: 'next-state'':'e=NAME{$value=$NAME.text;};
NAME : ('a'..'z' |'A'..'Z'|'0'..'9')+ ;
WS : (' ' |'"t' |'"n' |'"r' )+ {skip();} ;
實現要點
1 采用Antlr的內嵌Action
對于DSL的通常應用即通過外部腳本生產相關部分語義模型對象,使用Antlr的內嵌Action比采用語法樹方式簡單得多。
摘要:
引言
DSL(domain-specific language)并不是什么新的概念和技術,但是目前它已成為了一個技術熱點,近期各種類型的技術交流或研討會上你都可以看到關于DSL的主題。DSL似乎也在一夜間成為了大師們關注的焦點(Martin Fowler,Eric Evans等等)。
應用DSL可以有效的提高系統的可維護性(縮小了實現模型和領域模型的距離,提高了實現的可讀性)和...
閱讀全文
在很多大型應用中都會對數據進行切分,并且采用多個數據庫實例進行管理,這樣可以有效提高系統的水平伸縮性。而這樣的方案就會不同于常見的單一數據實例的方案,這就要程序在運行時根據當時的請求及系統狀態來動態的決定將數據存儲在哪個數據庫實例中,以及從哪個數據庫提取數據。
Figure 1 數據分割及多數據庫架構
通常這種多數據源的邏輯會滲透到業務邏輯中,同時也會給我們使用的數據訪問API諸如Hibernate和iBatis等帶來不便(需要指定多個SessionFactory或SqlMapClient實例來對應多個DataSource)。

Figure 2 多數據源的選擇邏輯滲透至客戶端
解決方案

Figure 3 采用Proxy模式來封裝數據源選擇邏輯
通過采用Proxy模式我們在方案中實現一個虛擬的數據源,并且用它來封裝數據源選擇邏輯,這樣就可以有效地將數據源選擇邏輯從Client中分離出來。
Client提供選擇所需的上下文(因為這是Client所知道的),由虛擬的DataSource根據Client提供的上下文來實現數據源的選擇。
Spring2.x的版本中提供了實現這種方式的基本框架,虛擬的DataSource僅需繼承AbstractRoutingDataSource實現determineCurrentLookupKey()在其中封裝數據源的選擇邏輯。
實例:
publicclass DynamicDataSource extends AbstractRoutingDataSource {
static Logger log = Logger.getLogger("DynamicDataSource");
@Override
protected Object determineCurrentLookupKey() {
String userId=(String)DbContextHolder.getContext();
Integer dataSourceId=getDataSourceIdByUserId(userId);
return dataSourceId;
}
}
實例中通過UserId來決定數據存放在哪個數據庫中。
配置文件示例:
<bean id="dataSource" class="com.bitfone.smartdm.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
蔡超
HP 軟件架構師
軟件架構顧問
SCEA
IBM Certified Solution Designer for OOA&D vUML2
Chaocai2001@yahoo.com.cn
Mini-Container 在已發布在SourceForge上
相關鏈接:http://www.aygfsteel.com/chaocai/archive/2008/05/26/203020.html
項目地址:
http://sourceforge.net/projects/mini-container
在OO中可以使用抽象方法及接口來完成文中通過函數指針和結構體來實現的間接層。

Client.java
AppInterface app=new AppImpl();
AppInterface app1=new AppProxy(app);
AppProxy.java
public class AppProxy implements AppInterface{
private AppInterface appRef;
public AppProxy(AppInterface appRef){
this.appRef=appRef
}
public void doSomething(){
/*some codes*/
}
}
通過Proxy來實現間接層,相互嵌套可以實現多個間接層,并且可以通過一個AppBuilder來創建這個對象,組合多個間接層。間接層中可以實現文中提及的對參數的預處理。
同時,我也認為文中提及的間接層也可以是Adapter。
本文介紹了常見面向對象語言(Java,C#等)OverLoad對于運行時執行的方法邦定的局限,并且如何通過Double Dispatch來實現運行時行為邦定。
1 根據對象來選擇行為問題
public interface Event {
}
public class BlueEvent implements Event {
}
public class RedEvent implements Event {
}
public class Handler {
public void handle(Event event){
System.out.println("It is event");
}
public void handle(RedEvent event){
System.out.println("It is RedEvent");
}
public void handle(BlueEvent event){
System.out.println("It is BlueEvent");
}
}
public class Main {
public static void main(String[] args) {
Event evt=new BlueEvent();
new Handler().handle(evt);
}
}
你認為運行結果是什么呢?
結果:It is event
是不是有點出乎意料,不是It is BlueEvent,這是應為Overload并不支持在運行時根據參數的運行時類型來幫定方法,所以要執行哪個方法是在編譯時就選定了的。
2 Double Dispatch Pattern
由于Java,C++及C#都具有上述局限,通常我們只能通過Switch或if結構來實現,當然這種實現方式既不優雅而且影響代碼的可維護性。
通過以下的Double Dispatch Pattern便可以優雅的實現。
public interface Event {
public void injectHandler(EventHandler v);
}
public class BlueEvent implements Event {
public void injectHandler(EventHandler v) {
v.handle(this);
}
}
public class RedEvent implements Event {
public void injectHandler(EventHandler v) {
v.handle(this);
}
}
public class EventHandler {
public void handle(BlueEvent e){
System.out.println("It is BlueEvent");
}
public void handle(RedEvent e){
System.out.println("It is RedEvent");
}
}
public class Main {
public static void main(String[] args) {
Event evt=new BlueEvent();
evt.injectHandler(new EventHandler());
}
}
其實設計模式(GoF)中的Visitor模式就是Double Dispatch的一種應用。
蔡超
HP
軟件架構師
軟件架構顧問
SCEA,SCBCD,MCSD
IBM Certified Solution Designer for
OOA&D vUML2
Chaocai2001@yahoo.com.cn,chao.cai@hp.com
Spring DM 1.1.x的最大特性便是它可以支持在其中部署WEB應用,我使用后感覺這是個很酷的特性,我甚至覺得用這種方式開發基于OSGi WEB應用比使用Spring DM Server更好,至少目前你可以獲得更好的便攜性(可以在多個Spring DM支持的OSGi平臺上運行),并且Spring DM Server并沒有提供更多的企業應用支持。
不過對于剛使用Spring DM進行WEB應用開發的人來說,成功地配置卻不是一件容易的事。以下詳細的講解一下相關配置。
1 運行環境所需的Bundles:
0 ACTIVE system.bundle_3.2.2.R32x_v20070118
1 ACTIVE com.springsource.slf4j.api_1.5.0
2 RESOLVED org.springframework.osgi.jetty.web.extender.fragment.osgi_1.0.0
Master=46
3 ACTIVE org.springframework.bundle.osgi.extender_1.0.1.v200803070100
4 ACTIVE org.springframework.bundle.spring.core_2.5.5
5 ACTIVE org.springframework.bundle.spring.web_2.5.5
6 ACTIVE com.springsource.org.objectweb.asm_2.2.3
7 RESOLVED osgi_log_config_1.0.0
Master=36
8 ACTIVE org.springframework.bundle.osgi.core_1.0.1.v200803070100
9 ACTIVE com.springsource.slf4j.log4j_1.5.0
10 ACTIVE org.springframework.bundle.spring_2.5.2.v200803070100
11 ACTIVE org.springframework.bundle.spring.context_2.5.5
12 ACTIVE javax.servlet_2.4.0.v200706111738
13 ACTIVE org.springframework.osgi.servlet-api.osgi_2.5.0.SNAPSHOT
14 ACTIVE com.springsource.net.sf.cglib_2.1.3
15 ACTIVE org.springframework.bundle.spring.beans_2.5.5
16 ACTIVE javax.servlet.jsp_2.0.0.v200706191603
18 ACTIVE org.springframework.osgi.jetty.start.osgi_1.0.0
19 ACTIVE org.springframework.bundle.osgi.io_1.0.1.v200803070100
20 ACTIVE org.aopalliance_1.0.0
21 ACTIVE org.springframework.bundle.spring.context.support_2.5.5
23 ACTIVE com.springsource.org.aopalliance_1.0.0
24 ACTIVE org.springframework.bundle.spring.aop_2.5.5
25 ACTIVE com.springsource.slf4j.org.apache.commons.logging_1.5.0
30 ACTIVE org.objectweb.asm_2.2.3
33 ACTIVE org.mortbay.jetty.server_6.1.9
35 ACTIVE org.mortbay.jetty.util_6.1.9
36 ACTIVE org.springframework.osgi.log4j.osgi_1.2.15.SNAPSHOT
Fragments=7
37 ACTIVE org.mortbay.jetty_5.1.11.v200706111724
43 ACTIVE org.springframework.bundle.osgi.extender_1.1.2
44 ACTIVE org.springframework.bundle.osgi.io_1.1.2
45 ACTIVE org.springframework.bundle.osgi.web_1.1.2
46 ACTIVE org.springframework.bundle.osgi.web.extender_1.1.2
Fragments=2
47 ACTIVE org.springframework.bundle.osgi.core_1.1.2
以上這些Bundles可以在spring dm 1.1.2的發布包中找到,以上Bundles的start level設置為2。
2 加入Log4j日志配置Bundles
這個Bundles的目的在于提供log4j.properties,詳細做法可以參考本人的”spring osgi快速入門”
3 開發WEB應用
WEB應用的開發方式和普通的WEB基本上一樣,只是加入一些OSGi的配置。
大致結構如下:
META-INF
MANIFEST.MF
WEB-INF
Classes
Lib
Web.xml
applicationContext.xml
1 MANIFEST.MF配置參考:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Osgi_web_app Plug-in
Bundle-SymbolicName: osgi_web_app
Bundle-Version: 1.0.0
Bundle-Vendor: ccsoft
Import-Package: javax.servlet,
javax.servlet.http,
javax.servlet.resources;version="2.4.0",
org.ccsoft.service,
org.springframework.osgi.web.context.support;version="1.1.2",
org.springframework.web.context,
org.springframework.web.context.support
Bundle-ClassPath: WEB-INF/classes/,
.
Require-Bundle: org.springframework.bundle.osgi.core,
org.springframework.bundle.osgi.io,
org.springframework.bundle.spring.beans,
org.springframework.bundle.spring.context,
org.springframework.bundle.spring.core
2 為了在web應用中使用spring dm的IoC功能,web.xml中需要加入一些特定配置,類似于使用Spring時的配置,web.xml配置參考如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Simple Osgi WebApp Bundle</display-name>
<description>Simple OSGi War</description>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>org.ccsoft.web.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
</web-app>
至于applicationContext.xml則是標準的spring dm配置文件形式,只是沒有放在我們所熟悉的位置(META-INF/spring)
配置示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<osgi:reference id="HelloServiceOsgi" interface="org.ccsoft.service.SpeakService"/>
</beans>
在你的WEB應用中可以使用如下代碼來訪問別的Bundle提供的服務:
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(req.getSession().getServletContext());
SpeakService ss=(SpeakService)ctx.getBean("HelloServiceOsgi");
與你使用Spring開發WEB應用的寫法是完全一致的。
好了現在你可以利用spring dm開發你的web應用了。更多相關問題還會在后續文章中逐一討論。
蔡超
軟件架構師
Chao.cai@hp.com
Chaocai2001@yahoo.com.cn
致力于OSGi在中國的推廣
OSGi平臺為我們提供了強大的動態特性,通過分析我們可以發現這些動態特性的實現與很多常用的設計模式相關,了解其中原理直接將這些模式用于我們的應用開發,也可以有效地實現動態特性。
1 Broker模式:實現服務提供者與服務使用者的分離及解耦。Bundle通過所能提供的服務將自己注冊至Framework,調用者通過Framework查找所需的服務。Bundle的服務注冊是實現服務自動發現的基礎。

2 監聽者模式:這是實現動態特性的關鍵,通過監聽者模式服務的使用者(實現監聽接口)可以獲得所依賴的服務提供者(Bundle)的狀態變化的通知,從而動態處理與服務提供者間的關系以實現動態特性,不僅如此OSGi Framework自身同樣有效的融合了這種通知機制,使得實現監聽者接口的Bundle可以了解Framework的狀態變化。

在我們的應用系統中借鑒OSGi的原理,同樣可以有效地實現動態特性。
蔡超
軟件架構師
軟件架構顧問
SCEA,SCBCD
IBM Certified Solution Designer for OOA&D vUML2
Chaocai2001@yahoo.com.cn