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.blogjava.net/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.blogjava.net/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