-
Notifications
You must be signed in to change notification settings - Fork 49
Home
软件架构的本质在于模块拆分,这些经过拆分的模块经过某种契约协同满足应用软件的对外需求。在当今,软件的需求都是迭代产生的,易变的。所以,对软件本身来说,如何最大限度的应对变化,“预见未来”,甚至“随需应变”是对架构设计的极致目标。
那么,什么样子的架构才能应对未来尽可能多的可能性呢?在客户端开发工具层面,Eclipse应该是业界最成功的案例。Eclipse最早提供的功能就是一个Java的IDE,但是基于Eclipse,可以扩展出几乎任何桌面式GUI应用;并且既有的Java开发环境也可以进行无限想象的功能扩展。Eclipse之所以能够支持如此“多样性的未来”,是因为Eclipse采用了插件式框架,以及OSGI类加载机制。
JPlugin借鉴Eclipse设计思想,引入到服务端(Server Side),希望能够做到服务端的Eclipse。JPlugin引入插件的思想,插件、扩展点、扩展等基本概念都有实现,插件的生命周期有完整的实现。插件式架构的核心就是,在可能变化的地方定义扩展点,让所有产生变化的地方都扩展来实现;同时,用插件在组织应用软件的模块,最大限度实现松耦合和依赖倒置;还有,由于可以在扩展点方便地管理所有扩展,这对系统的监控以及开发高性能的应用都提供了很多方便。
使用JPlugin,对于应对复杂的应用以及应用未来的变化应该说是非常好的选择;同时,对于一次性的或者较简单的应用来说,JPlugin也提供了现成的核心插件可供使用。这些基本插件提供了诸如MVC、交易、日志、数据持久化(提供了Mybatis和Hiberinate的集成器)、缓存、调度等功能,可以方便地快速开发应用。
在Maven项目中增加如下依赖:
<dependency>
<groupId>net.jplugin.mvn</groupId>
<artifactId>jplugin-core</artifactId>
<version>1.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.jplugin.mvn</groupId>
<artifactId>jplugin-web</artifactId>
<version>1.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
#3.相关配置文件 ##数据库配置 数据库配置文件部署以后发布在在WEB-INF/classes/config目录下面。
#jdbc driver class name
driverClassName=com.mysql.jdbc.Driver
#the database url
url=jdbc:mysql://localhost:3306/weapp?useUnicode=true&characterEncoding=utf8
dbuser=root
dbpassword=toor
maxActive=100
#max idle connections
maxIdle=50
#max wait seconds till time out
maxWait=2000
##日志配置 日志配置文件在 WEB-INF/classes/config/log4j.properties,默认配置如下,一般无需修改。另外,Jplugin体系支持专用的单独日志文件,请在后面“日志”章节查看。
log4j.rootLogger=ERROR, root,stdout
log4j.appender.root=org.apache.log4j.DailyRollingFileAppender
log4j.appender.root.file=${work-dir}/logs/root
log4j.appender.root.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.root.layout=org.apache.log4j.PatternLayout
log4j.appender.root.layout.ConversionPattern=%d %-5p %F %L - %m%n
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c] %m%n
##Plugin注册配置 WEB-INF/classes/config/plugin.cfg文件注册一个或者多个Plugin类。在“Plugin与Plugin注册”章节有详细介绍。
#4.Plugin与Plugin的注册 ##Plugin类及其作用
在JPlugin项目中,理论上所有代码都被分到不同的Plugin当中,在实际使用中,一个Plugin一般为Eclipse中一个Java项目。
每一个Plugin必须包含一个Plugin主类,该类继承自AbstractPlugin父类。Plugin主类为该Plugin在软件体系中的实际标识,在主类注册该Plugin的扩展点、扩展,提供该Plugin的优先级。
##系统级Plugin和用户级Plugin Plugin分两类:系统级Plugin、用户级Plugin。系统级Plugin为框架本身提供的Plugin。用户级Plugin为在应用中开发的Plugin。
##系统级别Plugin的注册和加载 系统级Plugin无需注册,系统启动时自动加载。系统级Plugin的优先级都小于0(负数),所以一般会优先加载。
##用户级Plugin的注册和加载
用户级Plugin需要在WEB-INF/classes/config/plugin.cfg文件中进行注册,这个文件中可以注册一个或者多个类,每一行代表一个类名称。比如:
com.hello.HelloPlugin
#5.Web控制器
Web控制器提供一个类作为Web请求的源。JPlugin提供两种格式的Web控制器,基础Web控制器和扩展Web控制器。
##基础Web控制器
基础Web控制器不需要继承任何父类,但是每个方法都需要传入HttpServletRequest和HttpServletResponse对象作为参数。下例支持 http://xxxxx/custbasic.do 访问:
Plugin.java这样写:
public class Plugin extends AbstractPlugin{
public Plugin(){
ExtensionWebHelper.addWebControllerExtension(this, "/custbasic", CustomerControllerBasic.class);
}
public void init() {
}
@Override
public int getPrivority() {
return 0;
}
}
CustomerControllerBasic.java:
public class CustomerControllerBasic {
public void list(HttpServletRequest req,HttpServletResponse res) {
。。。。。。
}
}
##扩展Web控制器
扩展Web控制器需要继承AbstractExController父类,每个方法都不需要参数。下例支持 http://xxxxx/list.do 访问:
Plugin.java这样写:
public class Plugin extends AbstractPlugin{
public Plugin(){
ExtensionWebHelper.addWebExControllerExtension(this, "/cust", CustomerController.class);
}
public void init() {
}
@Override
public int getPrivority() {
return 0;
}
}
CustomerControllerBasic.java:
public class CustomerControllerBasic extends AbstractExController{
public void list() {
//可以调用getParam, getAttr,setAttr等父类方法
//可以调用renderJsp、renderJson等方法
}
}
#6.开发和使用服务
如果想发布一些Singleton的服务,并通过ServiceFacotory.getService()方法获取到,可以先在Plugin类中把服务扩展出去,然后所有的代码中都可以使用了:比如在Plugin类构造函数中加入如下代码片段: ExtensionServiceHelper.addServiceExtension(this,”s1” , MyService.class); 然后在需要的地方,就可以用ServiceFactory.getService("s1",MyService.class);
##注意: ServiceFactory.getService(cls)的会最终实现为下面的代码:ServiceFactory.getService(cls.getName(),cls); 所以,鼓励把服务的实现和接口分离。然后,把注册的服务名字定位接口类的全名。如此就可以用 ServiceFactory.getService(接口类) 来获取服务了。
#7.开发和使用业务规则服务 ##关于业务规则服务: 业务规则服务运行过程中被系统拦截,并增加事务和日志等必要内容,日志通过Annotation来标记。 业务规则服务必须有一个接口和实现类,接口中需要对每一个方法用@Rule的标记进行标记。一般有两种用法; 不进行事务控制: @Rule 进行事务控制: @Rule(methodType=TxType.REQUIRED)
##业务规则服务注册: 在Plugin类构造函数中加入代码片段: ExtensionCtxHelper.addRuleExtension(this, ICustomerService.class.getName(), ICustomerService.class, CustomerServiceImpl.class);
##业务规则服务使用: ICustomerService svc = RuleServiceFactory.getRuleService(ICustomerService.class);
##业务规则服务的日志记录: 业务规则服务运行过程中,默认记录在日志:${tomcatdir}/nswork/logs/rulelog.log当中。记录了规则的执行、消耗的事件、事务有无完成等。
#8.使用日志 ##日志文件说明 文件名 日志内容 Root 一般日志 Rulelog.log 记录业务规则日志 Request.log 记录请求日志
##获取Root日志服务 ServiceFactory.getService(ILogService.class).getLogger("com.abc.xxx") #9.使用独立日志文件
如果在应用中由于需要把一些信息记录到单独的日志文件当中,可以通过插件动态配置: ServiceFactory.getService(ILogService.class).getSpecicalLogger ("mylog.log") #10.使用Mybatis ##服务的配置和获取 首先要在Plugin类的构造函数中注册Mybatis的Mapper扩展: ExtensionMybatisDasHelper.addMappingExtension(this,ICustomerMapper.class); 然后,可以在需要的地方获取到Mybatis服务。 IMybatisService s = ServiceFactory.getService(IMybatisService.class); ##服务的使用 有两种方法使用Mybatis服务,我们更推荐使用方法2. ###方法1: 可以通过openSession方法获取到Mybatis的SqlSession对象。使用这种方法,切记需要在用完以后关闭SqlSession。 IMybatisService s = ServiceFactory.getService(IMybatisService.class); SqlSession sess = s.openSession();
###方法2: 使用操作Mapper对象的模板方法,这样就不需要关闭SqlSession了。 IMybatisService s = ServiceFactory.getService(IMybatisService.class); s.runWithMapper(ICustomerMapper.class,new IMapperHandler() { public void run(ICustomerMapper mapper) { long custId = mapper.add(name, address); } }); 如果需要得到mapper中方法的返回值,可以这样写: IMybatisService s=ServiceFactory.getService(IMybatisService.class);
List list = s.returnWithMapper(ICustomerMapper.class, new IMapperHandlerForReturn<ICustomerMapper, List>() {
public List fetchResult(ICustomerMapper mapper) {
return mapper.listAll();
}
});
#11.使用Hiberinate 略,目前不要使用Hiberinate #12.发布和使用Restful服务 Resutful服务说明 Restful服务发布机制可以把一个Java类或方法发布为服务,进行跨平台的调用。 Restful服务的发布 加入有一个加法计算的类如下,注意需要增加@Para的标记,否则默认为arg0,arg1,…… public class AddService { public String add(@Para(name="a") int a, @Para(name="b") int b){ return a-b +""; } }
在Plugin类的构造函数中加入下面代码,将把整个类的所有方法都发布为Restful服务
ExtensionWebHelper.addRestMethodExtension(this, "/addRest", AddService.class);
在Plugin类的构造函数中加入下面代码,将只把add方法发布为Restful服务,其他方法不发布
ExtensionWebHelper.addRestMethodExtension(this, "/addRest", AddService.class,”add”);
Restful服务调用地址和参数 调用的URL地址,上面例子中发布的Add服务调用的URL地址为 http://..../addRest/add.do 传入参数规则:每一个参数对应一个http的参数名称,如果是简单类型,直接传值。如果是复杂类型,请把参数值序列化为JSON字符串。 比如上面的服务可以使用 http://xxx/addRest/add.do?a=1&b=2 调用。 返回值结构 返回值为json结构如下:
{success:true,"code":0,"message":null,"content":{"return":"1"}}
其中success=true表示成功,否则表示失败。 档success=false时,code表示错误代码。 如果不成功message域保存错误描述。 如果成功content/return域保存返回结果。 在Rest方法实现中如何控制各个返回的JSON属性 可以在Restfule方法的实现中用RestMethodState类来控制rest返回的固定属性(success、code、msg): 设置是否成功:RestMethodState. setSuccess() 设置返回编码(错误码):RestMethodState. setCode() 设置错误信息:RestMethodState. setMessage()
复杂类型处理规则 对于复杂类型的参数或者返回值,都会序列化为标准的Json进行传递。比如 {name:”zhangsan”,age:1} #13.发布和使用远程服务 远程服务说明 相对于Restfule的服务,远程服务可以通过JpluginClient进行更直接的调用,其体验就像调用本地方法,并且有更高的性能。 远程服务的发布 假如有一个减法计算类如下 public class SubService { public int sub (int a,int b){ return a-b; } } 发布远程服务,在Plugin类的构造函数中增加如下代码: ExtensionWebHelper.addRemoteCallExtension(this, "/subRemote", SubService.class);
远程服务的调用 调用远程服务需要依赖 jplugin-client 或者 jplugin-core. 并需要开发一个本地接口 public interface ISubService { public int sub (int a,int b); } 调用代码如下: public class SubRemoteClient { public static void main(String[] args) { Client client = ClientFactory.getThreadLocalClient(ISubService.class); client.setServiceBaseUrl("http://localhost:8080/jplugin-study/subRemote/"); ISubService obj = client.getObject(); int ret = obj.sub(5, 3); System.out.println("resuult = "+ret); } }
复杂类型处理规则 对于参数或者返回值是复杂类型的情况,采用Java的序列化处理。 #14.使用HTTP请求过滤器