# 构件上下文

构件运行环境为EOS逻辑流引擎、数据服务提供了一个运行环境。该运行环境提供了资源管理服务、应用上下文信息服务、应用生命周期事件管理等。

构件运行环境提供了构件包资源加载以及构件包资源热部署框架,同时提供对应用上下文以及构件包上下文的管理,对资源采用统一的接口进行访问。

# 相关概念

img

构件运行环境包括四方面的内容:应用上下文、扩展点、资源加载和资源的热部署。

  • 应用上下文提供应用的元数据,扩展点提供一种事件监听框架,在应用启动、停止以及构件包的加载、卸载事件发生时执行用户的相应动作;
  • 资源加载对构件包中的所有资源提供管理,提供统一的框架对资源进行加载,并对资源是否需要热部署进行控制;
  • 扩展点提供了一种回调机制,在应用启动、停止,构件包加载、卸载时回调用户的动作;
  • 资源热部署提供了资源的动态更新机制,资源如果进行了修改则会立即生效。

# 应用上下文

在运行期,每个EOS应用有一个构件运行环境来管理这个应用相关的资源。

针对每个EOS应用,都有一组针对此应用的相关数据值,我们称之为应用的上下文。 应用上下文包括的数据有:应用名称、应用工作路径、应用Home路径、应用配置文件夹路径(系统、用户)、应用系统相关构件存储库路径(working/system目录),应用用户相关构件存储库路径(working/user目录)。

构件运行环境提供一单实例类ApplicationContext取得应用的上下文信息,其使用方式如下表所示:

作用 方法
取应用上下文单例 ApplicationContext context = ApplicationContext.getInstance();
获取应用名称 context.getAppName();
获取应用工作路径 context.getApplicationWorkPath();
获取Home路径 context.getEOS_HOME();
获取应用配置文件夹路径(系统、用户) context.getApplicationConfigPath();
获取应用系统相关构件存储库路径 context.getApplicationSystemWorkingPath();
获取应用用户相关构件存储库路径 context.getApplicationUserWorkingPath();

# 资源加载

构件运行环境提供了资源的加载机制,针对特定的类型文件进行加载,如.xsd、.biz文件等,为了提高资源加载速度,资源加载支持懒加载模式,只有用到的资源才进行加载。

# 资源扫描类型

在EOS Server启动时,构件运行环境会扫描应用对应的系统构件存储库和用户构件存储库目录,并记录目录下的所有资源信息。为提高扫描的效率,用户可以配置不需要扫描的类型和路径,配置文件在sys-config.xml文件中,配置项为:

<module name="Runtime">
        <group name="monitor">
            <configValue key="exclude">
                [目录名]|[*.扩展名]
            </configValue>
        </group>
</module>

不扫描内容的配置,采用"|"分割,可以同时排除多个目录,或者多种类型的扩展名;对于扩展名的配置,前面必须设置为"*",否则按目录进行过滤。

注意

目录的设置只支持构件包下的一层目录,如com、classes等,不支持多层目录(如com/eos)。

# 加载资源

构件运行环境提供了统一的资源加载模型,需要加载的每种文件类型都有一个加载器(Loader)与之对应,加载器负责把物理模型解析为逻辑模型,供运行期调用。 EOS中用到的类型加载器有:数据实体、逻辑流、页面流等。

为了提高系统的启动速度,在资源加载时支持懒加载,只有在使用到资源时,才装载资源,提高运行的效率。 懒加载配置在sys-config.xml文件中,配置项为:

<module name="Runtime">
        <group name="loading">
            <configValue key="lazy">
        <!-- true:懒加载 false:一次性加载-->
                false
            </configValue>
        </group>

# 热更新

在EOS的开发中,开发人员会频繁修改逻辑流、数据模型等。为了减少应用重启次数,构件运行环境提供了资源热更新功能。

资源热更新采用守护线程的方式,在系统启动时会启动守护线程监听资源的变化,如果资源变化包括新增、修改和删除的文件,守护线程会重新调用加载器(Loader)对变更资源进行加载。

根据资源量的大小,可以设置守护线程的扫描间隔,资源多时可以设置较长时间。 扫描间隔的时间配置在sys-config.xml文件中,其设置如下:

<module name="Runtime">
        <group name="monitor">
            <configValue key="interval">
                扫描时间配置(单位秒)
            </configValue>
        </group>
</module>

如果设置的扫描间隔小于等于零,则守护线程不会启动。

注意

  • 如果配置热部署,则资源只有在配置的扫描间隔时间后才起作用;
  • 如果扫描时间小于等于零,则资源不会热部署。

# 扩展点

在应用启动、停止,构件包加载、卸载时,构件运行环境提供了一种回调机制,当这些事件发生时,可以回调用户定义的操作,完成其特定的功能。

# 应用启动、停止扩展点

当构件运行环境启动或者停止时,触发事件(RuntimeEvent),调用用户的回调接口。 事件RuntimeEvent中包含的对象有:

  • 系统配置文件对象Configuration(对应配置文件sys-config.xml)
  • 用户配置文件对象Configuration(对应配置文件user-config.xml)
  • 用户自定义配置文件对象Configuration(对应配置文件user-local-config.xml)
  • 应用上下文对象ApplicationContext

扩展方式如下:

  1. 实现接口
public interface IRuntimeListener {
    /**
     * 启动应用
     * @param envent 事件源
     */
    public void start(RuntimeEvent envent);
 
    /**
     * 结束应用
     * @param envent事件源
     */
    public void stop(RuntimeEvent envent);
}
  1. 配置扩展点 应用的回调接口配置在handler-startup.xml中,存放目录为$EOS_HOME/working/$AppName/config/下,格式为:
<handlers>
    <handler handler-class="xxxx"/>
    ...
</handlers>

其中xxxx位置为实现IRuntimeListener接口的用户回调接口全路径。

说明

扩展点执行的顺序为配置文件中定义的顺序。

# 构件包加载、卸载扩展点

在构件包发生加载、卸载事件时回调用户的接口,监听构件包的变化,此监听事件是针对所对应的构件包,可以为不同构件包配置不同的监听接口。

  • 监听器配置 构件包变化的监听器在配置文件handler-contribution.xml中,文件存放到构件包所在路径的META-INF/目录下,属性handle-class的值为监听接口的全路径。 示例图中描述了构件包com.primeton.eos的目录结构。 img handler-contribution.xml的内容示例如下:
<handlers>
    <handler handle-class="com.primeton.ContributionDemoListener"/>
</handlers>

说明

监听器执行的顺序为文件中配置的顺序。

  • 监听接口定义 用户监听接口必须实现接口IContributionListener,监听的事件类型有构件包加载事件、卸载事件和构件包加载完成事件,接口的定义如下:
public interface IContributionListener extends EventListener{
 
    /**
     * Contribution加载时执行方法.
     *
     * @param event the event
     */
    public void load (IContributionEvent event);
 
    /**
     * Contribution加载完成时执行的任务
     * @param event
     */
    public void loadFinished(IContributionEvent event);
    /**
     * Contribution卸载时执行方法.
     *
     * @param event the event
     */
    public void unLoad(IContributionEvent event);
 
}
  • 监听事件源 事件发生时,提供的事件源对象为IContributionEvent,其中包含了构件包的元数据(对应的数据文件为$构件包目录/META-INF/MANIFEST.MF)和构件包的配置信息(对应的数据文件为$构件包目录/META-INF/contribution.eosinf),其定义如下:
public interface IContributionEvent {
 
    /**
     * 取得Contribution 元数据.
     *
     * @return the contribution meta data
     */
    public ContributionMetaData getContributionMetaData ();
 
    /**
     * 取得Contribution 配置,contribution.eosinf文件内容.
     *
     * @return the contribution configuration
     */
    public Configuration getContributionConfiguration();
}
  • 构件包元数据 构件包元数据文件Manifest.MF格式,遵从OSGI的Bundle规范,有如下属性:
#Manifest 默认版本
Manifest-Version:1.0
# Bundle-Name 构件包显示名称
Bundle-Name: Primeton Test Composite
#构件包的唯一标识,也就是 Bundle 的名称
Bundle-SymbolicName: com.eos.data.simple
# Bundle-Version构件包主版本号
Bundle-Version: 6.0
#构件包提供者
Bundle-Vendor: %Primeton
#依赖的其他构件包,可以是多个,以逗号分割
Require-Bundle:
#允许构件包中存在多个 jar,也可以使用相对路径作为资源加载的位置
Bundle-ClassPath: .
#本构件包所属的模块,
eos-module: module1/sub_module1
#构件包默认的名称空间
eos-basePath: com.eos.bigbank.module1.sub_module1
#构件包中包含所有 web 资源,相对于 web 应用根的相对目录
eos-webCtxPath: bigbank

构件包对应的元数据对象为ContributionMetaData,其定义的方法如下:

public class ContributionMetaData{
    获取构件包的显示名.
public String getLabel()
    获取构件包标识.
public String getName()
    获取构件包版本.
public String getVersion()
    获取构件包提供商.
public String getVendor()
    获取依赖的构件包标识列表.
public String[] getRequired()
    获取构件包的类路径.
public String[] getClassPathes()
    获取构件包所属的Module.
public String getContainingModule()
    获取构件包的base path.
public String getBasePath()
    获取构件包对应的Web Context path.
public String getWebCtxPath()
    获取构件包的location(绝对路径).
public String getContributionLocation()
    获取构件包的location(绝对路径).
public File getContributionDirectory()
}

# 上下文接口及方法说明

# 包含的类

类名 描述
com.eos.runtime.core.ApplicationContext 应用上下文
com.eos.runtime.core.IRuntimeListener 应用启动停止监听接口
com.eos.runtime.core.RuntimeEvent 应用监听事件
com.eos.runtime.resource.ContributionMetaData 构件包元数据
com.eos.runtime.resource.IContributionListener 构件包加载、卸载监听接口
com.eos.runtime.resource.IContributionEvent 构件包加载、卸载监听事件

# 类的方法

# com.eos.runtime.core.ApplicationContext

  • 类的说明 应用上下文元数据。

  • 类的方法

方法 说明
getInstance() 获取上下文实例,为静态方法
getAppName() 获取应用名称
getApplicationWorkPath() 获取应用工作路径
getEOS_HOME() 获取应用Home路径
getApplicationConfigPath() 获取应用配置文件夹路径(系统、用户)
getApplicationSystemWorkingPath() 获取应用系统相关构件存储库路径
getApplicationUserWorkingPath() 获取应用用户相关构件存储库路径
getEarRealPath() 获取应用EAR所在物理目录
getWarRealPath() 获取WAR所在目录

# com.eos.runtime.core.IRuntimeListener

  • 类的说明 构件运行环境启动或者停止时,用户的回调接口。

  • 类的方法

方法 说明
start(RuntimeEvent envent); 启动应用时触发事件
stop(RuntimeEvent envent); 结束应用时触发事件

# com.eos.runtime.core.RuntimeEvent

  • 类的说明 构件运行环境启动或者停止时的事件源。

  • 类的方法

方法 说明
getApplicationContext() 应用上下文
getSystemConfiguration() 系统配置文件对象,对应配置文件$EOS_HOME/working/$AppName/config/ sys-config.xml
getUserConfiguration() 用户配置文件对象Configuration,对应配置文件$EOS_HOME/ working/$AppName/config/ user-config.xml
getUserLocalConfiguration() 用户自定义配置文件对象Configuration,对应配置文件$EOS_HOME/working/$AppName/config/user-local-config.xml

# com.eos.runtime.resource.ContributionMetaData

  • 类的说明 构件包元数据。

  • 类的方法

方法 说明
getLabel() 获取构件包的显示名
getName() 获取构件包标识
getVersion() 获取构件包版本
getVendor() 获取构件包提供商
getRequired() 获取依赖的构件包标识列表
getClassPathes() 获取构件包的类路径
getContainingModule() 获取构件包所属的Module
getBasePath() 获取构件包的base path
getWebCtxPath() 获取构件包对应的Web Context path
getContributionLocation() 获取构件包的location(绝对路径),返回类型为String
getContributionDirectory() 获取构件包的location(绝对路径),返回类型为File

# com.eos.runtime.resource.IContributionListener

  • 类的说明 在构件包发生加载、卸载事件时回调用户的接口,监听构件包的变化。

  • 类的方法

方法 说明
load (IContributionEvent event) Contribution加载时执行方法
loadFinished(IContributionEvent event) Contribution加载完成时执行的任务
unLoad(IContributionEvent event) Contribution卸载时执行方法

# com.eos.runtime.resource.IContributionEvent

  • 类的说明 构件包加载、卸载事件发生时,提供的事件源对象,包含了构件包的元数据和和构件包的配置信息。

  • 类的方法

方法 说明
getContributionMetaData () 取得Contribution元数据
getContributionConfiguration() 取得Contribution配置,contribution.eosinf文件内容

# 日志服务

日志模块提供两方面的功能:

  • 供用户出现问题时查看诊断用;
  • 供用户记录用。

用户可以查看的日志有三个层次:

  • 服务器日志:服务器记录的日志;
  • 应用层记录的日志:应用处理并记录;
  • 构件包层记录的日志:用户自己记录。

应用层日志有三种:

  • 系统日志:大颗粒的整个系统基本运行状况,比如逻辑流、服务、定时任务、SQL、运算逻辑调用的入口/出口、异常进行记录,用于分析调用栈的关系、调用执行时间等;
  • 引擎日志:细颗粒的引擎的运行状况,并可打印出上下文,便于管理员、开发人员和业务人员分析某一个图元、sql语句等执行的问题,也可用于性能分析;
  • 跟踪日志:各模块的细粒度运行轨迹,对平台级的程序,如流程引擎等程序执行过程中打印trace、debug、info、warn、error等不同级别的调试日志,便于开发人员从程序实现的层次查找、定位问题和bug。

构件包层日志,用户通过使用系统提供日志功能自己记录。系统通过封装slf4j来提供记录日志的功能,用户可以配置、扩展、重新封装日志。

# 服务器日志

  • 记录时机:域管理和应用部署;
  • 记录级别:trace、debug、info、warn、error;
  • 配置文件:$BOOT/src/main/resources/logback-spring.xml;
  • 日志文件:配置文件中指定;
  • 记录格式:没有固定格式。

例如:

2008-05-20 15:37:58 DEBUG [com.primeton.system.impl.helper.ContributionBuilder:392]  Deploy contributions to admin server's repository

# 应用层的系统日志

  • 记录时机:逻辑流(B)执行、EOS服务(E)执行、定时任务(D)执行、SQL(Q)执行(超过时限者记录)、运算逻辑(X)执行(超过时限者记录);
  • 记录行为:开始(Begin)、结束(End)、异常(Exception)、运行(Run);
  • 配置文件:${BOOT}/src/main/resources/logback-spring.xml;
  • 日志文件:配置文件中指定的格式;
  • 记录格式:有固定格式 [记录时间][请求编号][B|E|D|Q|X(类型)][Begin|End|Exception|Run][全名][执行时长][父全名][IP地址][登录用户ID][登录用户名][当前内存总量][当前空闲内存量][自定义信息]。

# 记录格式的具体说明

  1. [记录时间]是代表日志记录时间,格式:yyyy-MM-dd HH:mm:ss.SSS。
  2. [请求编号]为每次请求的唯一编号。
  3. [B|E|D|Q|X]是各个层次的编号(类型简称),分别说明如下:
    • [B]:逻辑流请求;
    • [E]:EOS服务请求;
    • [D]:定时任务请求;
    • [Q]:SQL请求;
    • [X]:运算逻辑。
  4. [Begin/End/Exception/Run]是记录行为。
    • Begin:程序入口,方法开始;
    • End:程序出口,方法结束;
    • Exception:程序中断,抛出异常;
    • Run:程序运行,时间较长时使用。
  5. [全名]是请求的全名称。
    • [B/E]:当前线程的运行时上下文堆栈的栈顶元素的全名;
    • [D]:定时任务名称;
    • [Q]:SQL语句;
    • [X]:调用运算逻辑类名 + 方法名。
  6. [执行时长]是请求的执行时长。
    • [B/E]:目前为空;
    • [D]:定时任务执行时长;
    • [J]:目前为空;
    • [Q]:SQL执行时长;
    • [X]:运算逻辑执行时长。
  7. [父全名]是请求的父全名称。
    • [B/E]:当前线程的运行时上下文堆栈的栈顶的前一个元素的全名;
    • [D]:当前线程的运行时上下文堆栈的栈顶元素的全名,由于定时任务肯定是异步执行,所以此项意义不大;
    • [J]:当前线程的运行时上下文堆栈的栈顶元素的全名;
    • [Q]:当前线程的运行时上下文堆栈的栈顶元素的全名;
    • [X]:当前线程的运行时上下文堆栈的栈顶元素的全名。
  8. [IP地址]是当前用户的客户端IP地址。
  9. [登录用户ID]是当前用户ID。
  10. [登录用户名]是当前用户名称。
  11. [当前内存总量]以KByte为单位的数字。
  12. [当前空闲内存量]以KByte为单位的数字。
  13. [自定义信息]用户信息。

# 举例

  • 逻辑流记录的日志格式如下:
[2008-11-28 10:32:56.562][402880c51de0e655011de0f10ea70061][B][End][com.primeton.governor.ConfigComponent.getDatasourceNames.biz][][com.primeton.governor.config.ContributionDatasourceConfig.flow?_eosFlowAction=action0][192.168.2.133][null][sysadmin][130240K][82699K][]
  • 定时任务记录的日志格式如下:
[2008-11-28 10:31:09.828][402880c51de0e5c0011de0ef7887009c][D][End][methodTask][16][][null][null][null][130240K][89163K][]
  • SQL记录的日志格式如下:
[2008-11-28 10:35:57.562][402880c51de0e5c0011de0ef0f2e0029][Q][Run][SELECT MIN(NEXT_FIRE_TIME) AS ALIAS_NXT_FR_TM FROM EOS_QRTZ_TRIGGERS WHERE TRIGGER_STATE = ? AND NEXT_FIRE_TIME >= 0][16][][null][null][null][130240K][89544K][]
  • 运算逻辑记录的日志格式如下:
[2008-11-28 10:36:43.625][402880c51de0e655011de0f4902d0075][X][Run][com.primeton.governor.mbean.ConfigMBeanAdaptor.createAppEngineLogConfigMBeanAdaptor][47][com.primeton.governor.config.LogComponent.getContribution.biz][192.168.2.133][null][sysadmin][130240K][86979K][]

# 应用层的引擎日志

  • 记录时机:主要是页面流、逻辑流的图元执行轨迹日志,以及DAS的Sql执行、文件的上传、引擎handler的执行、服务的执行、定时任务的执行情况;

  • 配置文件:${BOOT}/src/main/resources/logback-spring.xml;

  • 日志文件:配置文件中指定的位置;

  • 记录格式:有固定格式 [开始标志][记录时间][请求编号][请求类型][操作类型][可选项][IP地址][登录用户ID][登录用户名][异常堆栈信息][结束标志]。

    特别说明:

    每个日志项都有标识,如[请求编号]:[@reqID][402880c51de0e5c0011de0efc7b000f1]

# 记录格式的具体说明

  1. [开始标志]为[@@]。

  2. [记录时间]代表日志记录时间,格式:yyyy-MM-dd HH:mm:ss.SSS。标识为:[@time]。

  3. [请求编号]为每次请求的唯一编号。标识为:[@reqID]。

  4. [请求类型]是各种请求的类型,标识为:[@type]。分别说明如下:

    • [_bizlogic] :逻辑流请求;
    • [_eosservice] :EOS服务请求;
    • [_schedule] :定时任务请求;
    • [_sql] :数据库请求;
    • [_handler] :引擎handler请求;
    • [_uploadFile] :文件上传请求。
  5. [操作类型]是每种请求的具体操作类型。标识为:[@operType]

    • [_bizlogic] :逻辑流请求;

      • 共通 开始:start 结束:end 异常:exception

        可选项:

        • 全名称:标识为[@qName],逻辑流全名称
        • 上下文:标识为[@context],上下文信息
      • 图元:开始、结束、空操作、异常、循环开始、循环结束、调用服务、调用逻辑流图元不记录日志 赋值:assign 调用运算逻辑:invoke_bizlet 调用逻辑流:subprocess 事务开始:transactionbegin 事务提交:transactioncommit 事务回滚:transactionrollback

        可选项:

        • 全名称:标识为[@qName],图元值
        • 描述:标识为[@desc],图元注释
        • 上下文:标识为[@context],上下文信息
    • [_eosservice] :EOS服务请求;

      • 共通 开始:start 结束:end 异常:exception

        可选项:

        全名称:标识为[@qName],服务全名称

    • [_schedule] :定时任务请求;

      • 共通 开始:start 结束:end 异常:exception

        可选项:

        • 全名称:标识为[@qName],定时任务名称
        • 执行时长:标识为[@runningTime],定时任务执行时长
    • [_sql] :数据库请求;

      • 连接 数据库连接创建:connection_create 数据库连接关闭:connection_close 数据库连接异常:connection_exception

        可选项:

        • 数据库类型:标识为[@dbType],数据库类型
        • 数据库版本:标识为[@dbVersion],数据库版本
        • 数据源名称:标识为[@dsName],数据源名称
        • 连接url:标识为[@dbUrl],连接URL
        • 用户名:标识为[@dbUserName],数据库用户名
        • 数据库驱动:标识为[@dbDriverName],数据库驱动
      • sql执行 执行:sql_execute

        可选项:

        • sql语句:标识为[@sql],执行的sql语句
        • 条数:标识为[@count],sql执行结果返回的记录数
        • 执行时长:标识为[@runningTime],sql执行时长
      • sql参数 参数序列:sql_param

        可选项:

        • hib参数序列:标识为[@hibSqlParam],参数序号
        • 命名Sql参数序列:标识为[@namingSqlParam],参数序号
        • 参数属性:标识为[@propertyName],参数所对应的列名称,hib参数没有这一项
        • 参数类型:标识为[@paramType],参数所对应的数据库的列定义的类型
        • 实际参数类型:标识为[@argsType],实际所传的数据的类型
        • 实际参数值:标识为[@argsValue],实际所传的数据值
      • sql结果集组装 结果集组装成SDO:res2sdo

        可选项:

        • 条数:标识为[@count],sql执行结果集长度
        • 执行时长:标识为[@runningTime],结果集组装时长
    • [_handler] :引擎handler请求;

      • 共通 开始:start 结束:end

        可选项:

        全名称:标识为[@qName],handler类名 + 方法名

    • [_uploadFile] :文件上传请求;

      • 共通 上传:upload

        可选项:

        • 上传文件名称:标识为[@uploadFileName]
        • 上传文件大小:标识为[@uploadFileSize]
        • 上传文件保存路径:标识为[@uploadFileSavePath]
        • 执行时长:标识为[@runningTime],上传文件上传时长
  6. [可选项],请参照每一个操作类型。

  7. [IP地址]是当前用户的远程IP地址,标识为:[@IP]。

  8. [登录用户ID]是当前用户ID。标识为:[@userID]。

  9. [登录用户名]是当前用户名称。标识为:[@userName]。

  10. [异常堆栈信息],标识为[@exception]。内容为: [{ ...... }]

  11. [结束标志]:[$$]。

# 举例

  • 逻辑流记录的日志格式如下:
[@@][@time][2008-11-28 10:36:43.562][@reqID][402880c51de0e655011de0f4902d0075][@type][_bizlogic][@operType][start][@qName][com.primeton.governor.config.LogComponent.getContribution.biz][@IP][127.0.0.1][@userID][0][@userName][sysadmin][$$]
  • 定时任务记录的日志格式如下:
[@@][@time][2008-11-28 10:17:24.953][@reqID][402880c51de0d1b4011de0e2e2d900f5][@type][_schedule][@operType][start][@qName][methodTask][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • sql连接记录的日志格式如下:
[@@][@time][2008-11-28 10:17:24.968][@reqID][402880c51de0d1b4011de0e2e2d900fa][@type][_sql][@operType][connection_create][@dbType][MySQL][@dbVersion][5.1.28-rc-community][@dsName][default][@dbUrl][jdbc:mysql://127.0.0.1:3306/eos][@dbUserName][root@localhost][@dbDriverName][com.mysql.jdbc.Driver][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • sql执行记录的日志格式如下:
[@@][@time][2008-11-27 18:39:35.947][@reqID][402880c51ddd85a8011ddd883fab0002][@type][_sql][@operType][sql_execute][@sql][select this_.GLOBAL_ID as GLOBAL1_6_0_, this_.SERVICE_NAME as SERVICE2_6_0_, this_.HOST as HOST6_0_, this_.PORT as PORT6_0_, this_.PROTROL as PROTROL6_0_, this_.CONTEXT as CONTEXT6_0_, this_.APP_NAME as APP7_6_0_, this_.PRIORITY as PRIORITY6_0_, this_.TYPE as TYPE6_0_, this_.EXTENSION1 as EXTENSION10_6_0_, this_.EXTENSION2 as EXTENSION11_6_0_, this_.EXTENSION3 as EXTENSION12_6_0_, this_.EXTENSION4 as EXTENSION13_6_0_, this_.EXTENSION5 as EXTENSION14_6_0_, this_.EXTENSION6 as EXTENSION15_6_0_ from EOS_SERVICE_ENDPOINT this_ order by this_.PRIORITY asc][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • sql参数记录的日志格式如下:
[@@][@time][2008-11-28 10:12:26.937][@reqID][402880c51de0d1b4011de0ddda070011][@type][_sql][@operType][sql_param][@hibSqlParam][1][@paramType][string][@argsType][java.lang.String][@argsValue][1][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • sql组装记录的日志格式如下:
[@@][@time][2008-11-28 10:11:55.468][@reqID][402880c51de0d1b4011de0ddda070011][@type][_sql][@operType][res2sdo][@count][7][@runningTime][0ms][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • engineHandler记录的日志格式如下:
[@@][@time][2008-11-28 10:14:45.265][@reqID][402880c51de0d1b4011de0e015030021][@type][_handler][@operType][start][@qName][com.primeton.access.authorization.impl.AccessedBizHandler.doAfter][@IP][127.0.0.1][@userID][0][@userName][eos-default][$$]
  • 上传文件记录的日志格式如下:
[@@][@time][2008-11-28 12:00:21.515][@reqID][402880c51de0e655011de141120d009d][@type][_uploadFile][@operType][upload][@uploadFileName][ETestServer.ecd][@uploadFileSize][88738][@uploadFileSavePath][D:\primeton\tomcat\de3065\eosserver\working\eos-governor\upload\402880c51de0e655011de141220b00a1.ecd][@runningTime][0ms][@IP][127.0.0.1][@userID][0][@userName][eos-governor][$$]

# 应用层的跟踪日志

  • 记录时机:EOS各模块;
  • 记录级别:trace、debug、info、warn、error;
  • 配置文件:${BOOT}/src/main/resources/logback-spring.xml;
  • 日志文件:配置文件中指定的位置;
  • 记录格式:没有固定格式。

例如:

[2008-05-20 16:02:48,968][INFO][com.eos.common.config.mbean.AbstractConfigurationHandler][Line:230] AbstractConfigurationHandler doLoad end.(15)

# 构件包层的跟踪日志

  • 使用:构件包;
  • 记录级别:trace、debug、info、warn、error;
  • 配置文件:${BOOT}/src/main/resources/logback-spring.xml;
  • 记录格式:没有固定格式。

例如:

[2008-05-20 16:02:48,968][INFO][com.eos.common.config.mbean.AbstractConfigurationHandler]
[Line:230] AbstractConfigurationHandler doLoad end.(15)

# 构件包跟踪日志取得

类com.eos.runtime.core.TraceLoggerFactory实现。

# 获取某一Contribution的跟踪日志记录器示例

Logger getContributionTraceLogger(String contributionName, String loggerName)

参数说明:

  • contributionName:构件包名称
  • loggerName:日志名称,即一个类似类名的名称(不包括路径)

返回值说明:

  • 跟踪日志记录器实例。

# 构件包日志取得记录示例

Logger log = TraceLoggerFactory.getContributionTraceLogger("contribuitonName", "loggerName");
log.info("start.");

# 日志接口及方法说明

# 包含的类

类名 描述
com.eos.system.logging.Logger 日志记录器
com.eos.runtime.core.TraceLoggerFactory 跟踪日志工厂

# 类的方法

# com.eos.system.logging.Logger

  • 类的说明 日志通用记录器,通过日志记录器可以记录多种级别的日志,他们分别是:TRACE | INFO | WARN | DEBUG | ERROR 。每个级别有相应的几个方法对应,例如:记录INFO级别日志有info(String)。另外,日志记录器不能够直接实例化必须通过{@link TraceLoggerFactory}的getLogger(String)或者getLogger(Class)获取。

  • 类的方法

方法 说明
String getName() 获取logger名称
boolean isTraceEnabled() 检查Level.TRACE级别是否使能
void trace(String message) 记录Level.TRACE级别的日志信息
void trace(String message, Object[] params) 记录Level.TRACE级别的日志信息
void trace(Throwable t) 记录Level.TRACE级别的日志信息
void trace(String message, Throwable t) 记录Level.TRACE级别的日志信息
void trace(String message, Object[] params, Throwable t) 记录Level.TRACE级别的日志信息
void log(Level level, String callerFQCN, String message, Throwable t) 记录指定级别的日志信息
void log(Level level, String callerFQCN, String message, Object[] params, Throwable t) 记录指定级别的日志信息

# com.eos.runtime.core.TraceLoggerFactory

  • 类的说明 跟踪日志工厂。

  • 类的方法

方法 说明
Logger getContributionTraceLogger(String contributionName, String loggerName) 获取某一Contribution的跟踪Logger实例
Logger getContributionTraceLogger(String contributionName, Class<?> clazz) 获取某一Contribution的跟踪Logger实例
Logger getLogger(String loggerName) 获取跟踪Logger实例
Logger getLogger(Class<?> clazz) 获取跟踪Logger实例

# 异常服务

在EOS Platform 开发工具包中,我们有提供专门的全局统一的异常处理机制,默认处理Throwable异常,并根据用户自定义的error code message封装成统一格式返回给调用端,如果用户在应用中需要自己处理异常,我们也支持,具体在日常后端Java开发中如何使用该功能,请参考如下步骤:

pom依赖

添加eos-sdk依赖包

<dependency>
    <groupId>com.primeton.eos</groupId>
    <artifactId>eos-server-starter</artifactId>
</dependency>

自定义runtimeException继承EOSRuntimeException

以下代码仅作参考:

public class AFCRuntimeException extends EOSRuntimeException {
    private static final long serialVersionUID = 828959048127312471L;

    /**
     *
     * @param code 异常编码
     */
    public AFCRuntimeException(String code) {
        super(code);
    }

    /**
     *
     * @param code 异常编码
     * @param params 异常描述时用到的格式化参数
     */
    public AFCRuntimeException(String code, Object... params) {
        super(code, params);
    }

    /**
     *
     * @param code 异常编码
     * @param cause 上层异常
     * @param params 异常描述时用到的格式化参数
     */
    public AFCRuntimeException(String code, Throwable cause, Object... params) {
        super(code, params, cause);
    }

    /**
     *
     * @param code 异常编码
     * @param cause 上层异常
     */
    public AFCRuntimeException(String code, Throwable cause) {
        super(code, cause);
    }

}

自定义异常编码和异常编码配置文件

异常编码定义:

public enum AFCExceptionCode {

    UNKONWN_ERROR("999999"),

    // 100001-100100 系统异常编码

    ARGS_VALIDATION_ERROR("100001"),
    SERIALIZE_OBJECT_ERROR("100002"),
    NOT_LOGIN("100003"),

    // 100101-100200 auth模块异常编码
    PARAMETERS_ARE_MISSING("100101"),


    // 100201-100300 dict模块异常编码
    DICT_ENTRY_CODE_EXISTED("100201"),
    DICT_TYPE_CODE_EXISTED("100202"),

    // 100301-100400 oauth模块异常编码
    USER_INFO_ERROR("100301"),
    USER_HAS_DISABLE("100302"),
    USER_IS_NOT_EXIST("100303"),
    
    //认证中心异常码定义
    GLBOAL_SESSION_TOKEN_IS_EMPTY("100323"),
    USER_IS_NOT_EXIST_IN_APP("100324"),

    // 100401-100500 org模块异常编码
    WORKGROUP_CODE_EXISTED("100401"),
    WORKGROUP_EMPLOYEE_EXISTED("100402"),

    // 100501-100600 portal模块异常编码
    OPERATION_LOG_NULL_ERROR("100501"),

    // 100601-100700 resource模块异常编码
    RESOURCE_MENU_EXISTED("100601"),
    RESGROUP_RES_EXISTED("100602"),

    // 100701-100800 tenant模块异常编码
    ATTRIBUTE_TRANSFORMATION_ERROR("100701"),

    // 100801-100900 user模块异常编码
    USER_CODE_EXISTED("100801"),

    // 100901-101000 公共资源管理模块异常编码
    CONNECTION_FAILD("100901"),

    ;

    private String code;
    private AFCExceptionCode(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    /**
     * 创建运行时异常.<br>
     * @return
     */
    public AFCRuntimeException runtimeException() {
        return new AFCRuntimeException(this.getCode());
    }

    /**
     *
     * @param params
     * @return
     */
    public AFCRuntimeException runtimeException(Object... params) {
        return new AFCRuntimeException(this.getCode(), params);
    }

    /**
     * 创建运行时异常.<br>
     * @param cause
     * @return
     */
    public AFCRuntimeException runtimeException(Throwable cause) {
        return new AFCRuntimeException(this.getCode(), cause);
    }

    /**
     * 创建运行时异常.<br>
     * @param cause
     * @param params
     * @return
     */
    public AFCRuntimeException runtimeException(Throwable cause, Object... params) {
        return new AFCRuntimeException(this.getCode(), cause, params);
    }

}

对应异常编码配置文件:

ExceptionResource.properties:

999999 = Server internal error, please contact the administrator
100001 = System error :: Parameter verification error
100002 = System error :: Serialization object error, object ''{0}''
100003 = System login error :: Current status Not logged in
100004 = Error importing file content''{0}''
100005 = Error importing location connect''{0}''
100006 = Error exporting''{0}''
...........

ExceptionResource_zh_CN.properties:

999999 = \u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458!
100001 = \u7CFB\u7EDF\u9519\u8BEF::\u53C2\u6570\u6821\u9A8C\u9519\u8BEF
100002 = \u7CFB\u7EDF\u9519\u8BEF::\u5E8F\u5217\u5316\u5BF9\u8C61\u9519\u8BEF, object ''{0}''
100003 = \u7CFB\u7EDF\u767B\u5F55\u5F02\u5E38::\u5F53\u524D\u72B6\u6001\u672A\u767B\u5F55
100004 = \u5BFC\u5165\u6587\u4EF6\u5185\u5BB9\u9519\u8BEF''{0}''
100005 = \u5BFC\u5165\u5730\u5740\u4FE1\u606F\u9519\u8BEF''{0}''
100006 = \u5BFC\u51FA\u9519\u8BEF''{0}''
............

注意:如果自定义的runtimeException是继承自EOSRuntimeException,那么异常编码文件的目录一定是固定在 META-INF/resources下面,名称叫 ExceptionResource.properties 中文:ExceptionResource_zh_CN.properties

开发中使用

if (!checkPassword(user.getPassword(), loginUser.getPassword())) {
    session.setAttribute("currentSessionError","error");
    throw AFCExceptionCode.USER_INFO_ERROR.runtimeException();
}

用户自定义异常处理(不使用eos统一异常处理规范)

自定义异常Response响应结构,代码示例如下:

public class ResponseErrorModel {
	
	private int status;
    private String code;
    private String error;
    private String message;
	
	public ResponseErrorModel() {
		this.status = status;
        this.code = code;
        this.error = error;
        this.message = message;
	}

	/**
     * Http Response的状态码
     * 
     * @return Http Response的状态码
     */
    public int getStatus() {
        return status;
    }

    /**
     * 错误码
     * 
     * @return 错误码
     */
    public String getCode() {
        return code;
    }

    /**
     * 错误简要信息
     * 
     * @return 错误简要信息
     */
    public String getError() {
        return error;
    }

    /**
     * 错误详细信息
     * 
     * @return 错误详细信息
     */
    public String getMessage() {
        return message;
    }
}

自定义异常拦截器,代码示例如下:

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE - 10)
public class ControllerExceptionHandler {

    private static Logger logger = TraceLoggerFactory.getLogger(ControllerExceptionHandler.class);
    
    @ExceptionHandler(value = Throwable.class)
    protected ResponseEntity<ResponseErrorModel> handleThrowable(Throwable e, WebRequest request) {
		logger.error(ex.toString(), ex);

        ResponseErrorModel errorObject = null;
        if (ex instanceof XXXAppException) {
            errorObject = ((SDKAppException) ex).getErrorObject();
        } else if (ex instanceof XXXAppRuntimeException) {
            errorObject = ((SDKAppRuntimeException) ex).getErrorObject();
        }
        if (errorObject == null) {
            
            int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
            String code = ex.getClass().getSimpleName();
            String error = ex.getClass().getName();
            String message = ex.getMessage();
            errorObject = new ResponseErrorModel(status, code, error, message);
        }
		
		return new ResponseEntity(errorObject, HttpStatus.resolve(errorObject.getStatus()));
    }
}

接下来就是自定义用户应用自己的runtimeException、异常编码、配置文件,具体步骤请参考上面即可。

# MUO

# MUO

MUO(Managed User Object)是受管用户对象,用于防止用户在运算逻辑、逻辑流、服务中直接访问和Http请求相关的数据上下文 (opens new window)

EOS引擎会在调用运算逻辑、逻辑流、服务时自动创建一个MUO,并根据user-config.xml的配置将HttpSession中的部分数据放入MUO,并把MUO传入给运算逻辑、逻辑流、服务使用,在运算逻辑、逻辑流、服务执行完毕后,EOS引擎会把在运算逻辑、逻辑流、服务执行中对MUO的修改值回写到HttpSession中。

使用MUO的优点:

  • 可以做到更清楚的分层,不允许用户在逻辑层直接操作HttpSession中的数据;

  • 用户在逻辑层对MUO的修改会自动回写HttpSession,这样使得在集群环境下不至于出现逻辑层直接操作HttpSession的attribute后忘了做setAttribute,造成集群间的HttpSession不同步。

    说明

    如果用户可以在逻辑层直接访问HttpSession,并写了如下代码,就会造成集群间的session不同步。

//session is a instance of HttpSession
    User user = session.getAttribute("user");
    user.setOrderId(123);
    //如果这时候没有调用session.setAttribute("user",user);
    //虽然本JVM的HttpSession中的user是改变了,但是集群中却没有改变,
    //因为没有监听到HttpSession的属性改变的事件,就没有通知集群做更新。

# MUO数据上下文

MUO在EOS中是通过IMUODataContext表现出来的。MUO上下文是根据在user-config.xml中Session-Manage/Managed-User-Object节点下的配置,在调用运算逻辑、逻辑流和服务时,由逻辑流引擎自动创建的。MUO中的数据根据Session-Manage/Managed-User-Object节点下配置的configValue,把HttpSession中对应的attribute拷贝到IMUODataContext中。 如下面user-config.xml中MUO的配置示例,就是指允许在MUO中设置一个customer的属性,对应的值是一个com.test.Customer类的实例。

<!--user-config.xml的配置-->
<module name="Session-Manage">
        <group name="Managed-User-Object">
            <configValue key="customer">java:com.test.Customer</configValue>
        </group>
</module>

在下面几种不同的情况下,使用上面的配置文件创建MUO的过程会有不同:

  • 如果是在页面流中同步/异步调用运算逻辑、逻辑流或本地服务,引擎就会自动创建一个MUO并把HttpSession中属性名称为"customer"的属性值放到IMUODataContext中,然后把该MUO和当前线程绑定;
  • 如果是定时器或者是工作流的自动活动执行时,因为没有HttpSession,server和workflow会使用一个Map代替HttpSession去创建MUO,因此customer的属性对应的值一定为null,并且会给MUO设置相应的虚拟用户,然后会把该MUO和当前线程绑定;
  • 如果是使用Studio开发调试逻辑流,则需要在调式数据中的节点下设置节点,这样调试时MUO里才会有customer对象。

在Studio调试数据中设置customer:

<MUOContext>
  <customer>
    <customerId>123</customerId>
        <customerName>Jack</customerName>
  </customer>
</MUOContext>

说明

  • configValue中的key就是HttpSession中attribute的name;
  • configValue的节点值指定HttpSession中属性值的类型,分为"java:"和"sdo:"2种,主要用于Studio的代码提示,server在运行时不做类型校验和自动创建;
  • MUO支持DataObject和JavaBean以及数组,List和Map;
  • 在逻辑流开发中如果要访问MUO中的数据,需要以"m:"开头,如"m: customer/customerId"。

MUO和HttpSession

  • 虽然创建MUO时是根据配置从HttpSession取出属性值放入MUODataContext,但是在放入时并不会进行类型校验,即如果MUO配置一个类型为java:com.test.Customer的customer属性,但是HttpSession里的customer的属性存放的是java:com.test.User类型的属性值,在创建MUO以后,MUO里面的customer属性值就是java:com.test.User的;
  • 在页面流中通过赋值图元对SessionContext赋值,如"customer/name"="primeton",此时,在HttpSession中会创建一个属性名称为"customer"的java.util.HashMap对象,HashMap里会有一个key="name",其value="primeton"。这说明对SessionContext的赋值操作,并不会根据MUOContext的配置来动态创建节点对象;
  • 在逻辑流中通过赋值图元对MUOContext赋值,如"customer/name"="primeton",此时,在MUOContext中就会创建一个属性名称为"customer"的com.test.Customer对象,并给其name属性赋值为"primeton"。这说明对MUOContext的赋值操作会根据MUOContext的配置来动态创建节点对象。

MUO中还有个系统默认的隐藏属性是userObject,对应的类型是java:com.eos.data.datacontext.IUserObject,用于存放用户的身份信息。关于IUserObject的具体说明请参见用户对象 (opens new window)

注意

用户在配置configValue时要注意,不要把key写成"userObject",因为会被系统默认的属性覆盖。 如果configValue的节点值是以"java:"开头,那么所指定的java class必须是一个符合javabean规范的class。

虽然MUO也是一种数据上下文,并且数据上下文允许创建(create)、删除(delete)、增加(add)数据对象的操作,但MUO并不允许create、delete、add的操作,否则会抛出异常。因为MUO是个受管对象,只能访问在user-config.xml中配置过的属性,所以不允许动态的创建和删除属性。 MUO只允许在某个属性上设置数据对象(set)和根据属性名称取用户对象(get)操作,而且set/get操作必须以configValue中配置过的key开头,否则MUO不会有任何改变。

例如在Session-Manage/Managed-User-Object中配置了java:com.test.Customer,可使用以下代码操作MUO:

//获取当前线程中的IMUODataContext
IMUODataContext muo = DataContextManager.current().getMUODataContext();
//获取客户的id,注意用customer开头,因为configValue的key是customer
muo.get("customer/custId");
//设置客户的消费日期
muo.set("customer/custDate",new java.util.Date());
//获取不存在的属性,返回null
muo.get("customer/ noexistdkey");
//设置不存在的属性,抛异常
muo.set("customer/ noexistdkey","value");
//获取不存在的key,返回null。
muo.get("noexistdkey");
//设置不存在的key,没有任何效果
muo.set("noexistdkey","value");
//获取用户信息(userObject是系统默认的隐藏属性)
muo.get("userObject/userId");
 
//create,delete,add抛异常
muo.createObject("customer", com.test.Customer.class);
muo.add("customer","value");
muo.deleteObject("customer");

说明

  • 在同步调用运算逻辑、逻辑流、本地服务时,会使用当前线程的MUO并且对于MUO的修改会回写到HttpSession中;
  • 异步调用运算逻辑、逻辑流、服务时,会复制当前线程的MUO并且对于MUO的修改不会回写HttpSession;
  • 定时器在执行时,会使用server的虚拟用户创建一个新的MUO,对于MUO的修改不会回写;
  • 工作流的自动活动在执行时,会使用workflow的虚拟用户创建一个新的MUO,对于MUO的修改不会回写;
  • 调用远程服务时,复制当前线程的MUO并且对于MUO的修改也不会回写。

MUO的配置方式

MUO可以通过2种方式配置:

  • 手工修改$EOS_HOME/working/$APP_NAME/config/user-config.xml中的Session-Manage/Managed-User-Object节点的配置。 重启server后,这些修改的配置是生效的,但是在Governor中看到的配置信息可能与修改后的配置不一致,因为系统不会自动根据working下的配置文件同步仓库中的配置文件。Governor管理时,显示的是domain下的仓库中的配置文件。(不推荐使用)
  • 通过Governor修改MUO的配置,但要求添加的MUO属性的数据类型必须事先部署。因为Governor会做校验,判断类型是否存在。(推荐使用)

# 用户对象

用户对象是存放用户信息的对象。它主要用于在日志记录中确定一个用户做了哪些操作,以及在做资源访问权限校验时根据这个对象来判断是否有权限访问资源(如:页面流、逻辑流等)。

IUserObject是一个用户对象的接口,继承了一个IOpenable的接口,可以动态地增加或删除属性,类似于一个Map。

com.eos.data.datacontext.UserObject 是IUserObject的实现类,用户登录之后需要由应用开发人员创建UserObject实例并调用基础构件库方法com.eos.foundation.eoscommon.OnlineUserManagerUtil.login(IUserObject userObject),EOS会把UserObject放入到HttpSession中,对应的attribute的名字是"userObject"。

提示

  • 当引擎处理EOS客户端请求(.biz.ext,.flow等等)的时候,会根据HttpSession创建MUO,自动把HttpSession里的userObject放入MUO。

  • 如果要在非EOS客户端请求(例如Servlet调用)中,通过Java代码直接访问逻辑流,就需要在com.eos.foundation.eoscommon.OnlineUserManagerUtil.login(IUserObject userObject)方法后,使用MUOTemplate.execute(HttpSession,MUOCallBack)进行逻辑流调用,保证逻辑流中能获取到MUO,详情请参考10 在java代码中直接访问逻辑流 (opens new window)

    {
        "userRemoteIP":null,  //用户的真实ip
        "userRealName":null,  
        "userMail":null,
        "userOrgName":null,
        "attributes":{
            "userRoles":"", //角色集合
            "positionList":"", //岗位集合
            "isOrgManager":false, //是否为机构管理员
            "userDataPrivilegeRes":[], //user-data privilege 资源
            "orgList":"", //组织机构id集合
            "TENANT_ID":"sys_tenant",  //租户id,来自afc_user或者afc_employee表的tenantId字段,来源取决于登录方式不同
            "employeeId":"441",  //员工id,来自afc_employee表的id字段
            "userLoginName":"租户管理员", //员工名称,来自afc_employee表的name字段
            "childOrgIds":[],  //员工所在机构的子机构id集合
            "userFunctionCodes":[], //功能码编码集合
            "employeeCode":"admin" //员工编码,来自afc_employee表的code字段
        },
        "sessionId":null,
        "userName":"admin", //用户的登录账号,来自afc_user表的code字段
        "userId":"441", //用户的登录账号id来自afc_user表的id字段
        "userOrgId":null,
        "uniqueId":"26cdb8f5-bf8a-4272-9536-afe48b57ba84" //登录后的访问凭证,也是获取userObject的唯一标识
    }
    

# 校验用户是否登录

EOS通过在user-config.xml中配置Access-Http/Accessed-Mode/Portal="true|false"来指定访问页面/页面流时,是否需要校验用户已登录。

  • 当Portal="true"时,不校验用户是否登录,但是如果HttpSession中没有userObject的属性,那么引擎会创建一个guest的虚拟用户放入HttpSession和OnlineUserManager中;
  • 当Portal="false"时,校验用户是否登录。

注意

判断用户是否登录的标准是HttpSession和OnlineUserManager中都有UserObject对象,并且uniqueId相同

说明

关于OnlineUserManager和OnlineUserManagerUtil:

  • OnlineUserManager是EOS提供的在线用户列表管理接口;
  • OnlineUserManagerUtil是在OnlineUserManager基础上封装的接口,作为基础构件库提供给用户使用。

# 注销用户

注销用户时可以调用OnlineUserManagerUtil.logoutByUniqueId (String uniqueId)或者logoutByUserId(String userId)。因为EOS支持同一用户多次登录,所以每个UserObject是通过uniqueId作为唯一标识的。

说明

uniqueId是在创建UserObject时自动生成的唯一的标识。

调用logoutByUniqueId就是注销一个UserObject,如果调用logoutByUserId则会把userId相同的多个UserObject注销。

# 一个简单用户登录的例子

  1. UserLogin.jsp开发
<form action="com.primeton.samples.base.muo.userLogin.flow" method="post">
  用户id: <input type="text" name="user/userId" /> <br>
  用户密码: <input type="password" name="user/attributes/pwd" /> <br>
  <input type="submit" value="提交" />
</form>

说明

user在页面流中定义成一个"com.eos.data.datacontext.UserObject"类型的变量。 因为UserObject并没有定义pwd这个属性,所以要通过动态的属性"attributes"下设置一个pwd的String属性。

  1. 页面流开发 img img 首先在action0的连线上定义一个com.eos.data.datacontext.UserObject的对象,UserObject对象的属性值是通过上面的UserLogin.jsp页面提交过来的,然后调用一个逻辑流去检查用户是否合法(如通过数据库校验,或者是其他已有的用户认证系统),如果检查通过就调用一个运算逻辑OnlineUserManagerUtil.login;如果检查失败抛出了异常就跳转到登录错误页面。这样一个用户登录流程就算结束了。

# 一个简单用户登出的例子

  1. UserLogout.jsp开发
<form action="com.primeton.samples.base.muo.userLogout.flow" method="post">
  <input type="submit" value="登出" />
</form>
  1. 页面流开发 img

    说明

    在页面流中调用基础构件库的com.eos.foundation.eoscommon.OnlineUserManagerUtil.logoutByUniqueId()无参方法,该方法会从当前HttpSession中remove属性名称为"userObject"的属性,并从OnlineUserManager的在线用户列表中删除该用户。

# 用户登录回调接口

用户登录回调接口的使用可以使应用开发人员不需要在页面流中进行用户有效性校验以及登录,而只要通过设置一个回调接口的实现类就能完成用户登录。这样可以做到通过不同方式访问应用时,只需使用同一套登录时的身份验证机制,如通过http访问和远程服务调用。 用户登录回调接口定义如下:

public interface IUserLoginCallback
{ public UserObject login(UserCredential credential) throws IllegalUserException; }

该接口的主要作用就是根据传入的一个UserCredential参数来和已经存在的合法的用户(如数据库中的记录)进行比较,判断是否是合法用户,如果是合法用户那么就要根据这个UserCredential创建一个UserObject,并返回,否则抛出IllegalUserException异常。 UserCredential是一个实现了Map接口的用户身份认证的对象,用于收集用户登录时所填的用户名、密码等。

一个简单实现回调接口的例子

场景描述:只有当UserCredential中的属性"loginUser"的值等于"admin"时,才认为是有效用户。

  1. 代码
package com.primeton.samples.base.muo;
public class TestCallback implements IUserLoginCallback {
public UserObject login(UserCredential credential)
throws IllegalUserException {
if ("admin".equals((String) credential.get("loginUser")))
{
   UserObject userObject = new UserObject();
   userObject.setUserId("0001");
   userObject.setUserName("admin");
   userObject.setUserOrgId("org1");
   return userObject; }
else
{
   throw new IllegalUserException("only admin is legal user"); }
}
}
  1. 配置文件:
<!--user-config.xml的配置-->
<module name="Session-Manage">
<group name="UserLoginCallback">
<configValue key="Impl-Class">com.primeton.samples.base.muo.TestCallback
</configValue>
</group>
</module>
  1. 页面流开发 创建页面流"com.primeton.samples.base.muo.userLoginWithCallback.flow",内容如下: img 首先在action0的连线上定义一个UserCredential的对象,UserCredential对象的属性值是通过页面上提交过来的,然后自行开发一个运算逻辑UserLogin,如果UserLogin抛出了IllegalUserException就跳转到登录错误页面;否则就是登录成功,结束流程。

  2. 运算逻辑UserLogin

    代码如下:

@Bizlet("用户登录")
public static void login(UserCredential credential)
        throws IllegalArgumentException {
    if (credential == null) {
        throw new IllegalArgumentException("the 'UserCredential' is null.");
    }
    OnlineUserManager.login(credential);
}

可以看到这个运算逻辑还是很简单的,真正起作用的只有OnlineUserManager.login(credential)这行代码,在OnlineUserManager.login(credential)方法里会调用通过user-config.xml配置的用户登录回调接口"com.test.TestCallback"创建出一个UserObject,然后再调用OnlineUserManager.login(IUserObject userObject)完成用户登录。

说明

如果想使用回调接口进行用户登录,就必须调用OnlineUserManager.login(UserCredential credential)方法,该方法会调用回调接口创建出一个UserObject并接着调用OnlineUserManager.login(IUserObject userObject)完成用户登录。

  1. 登录页面

    代码如下:

<%@page pageEncoding="UTF-8"%>
<%@include file="/common/common.jsp"%>
<%@include file="/common/skins/skin0/component.jsp" %>
<html>
<head>
<title>UserLoginWithCallback</title>
</head>
<body>
    <form action="com.primeton.samples.base.muo.userLoginWithCallback.flow" method="post">
        用户名称: <input type="text" name="user/loginUser" /> <br>
        <input type="submit" value="提交" />
    </form>
</body>
</html>

# 虚拟用户

虚拟用户用于EOS支持定时器、工作流的自动活动,以及portal模式登录,方便跟踪这些活动的发起者是谁。

虚拟用户的配置方式

虚拟用户可以通过2种方式配置:

  • 手工修改$EOS_HOME/working/$APP_NAME/config/user-config.xml中的Session-Manage/Virtual-UserObject节点的配置。 重启server后,这些修改的配置是生效的,但是在Governor中看到的配置信息可能与修改后的配置不一致,因为系统不会自动根据working下的配置文件同步仓库中的配置文件。Governor管理时,显示的是domain下的仓库中的配置文件。(不推荐使用)
  • 通过Governor修改虚拟用户的配置。配置路径Governor->配置->MUO->虚拟用户信息。(推荐使用)
<!--user-config.xml的配置!>
<module name="Virtual-UserObject">
        <group name="server">
            <!--config的key是固定的,不能动态增删,value是对应的值-->
             <configValue key="User-Id">0</configValue>
             <configValue key="User-Name">server</configValue>
                     <configValue key="User-Email"></configValue>
                     <configValue key="User-Org-Id"></configValue>
                     <configValue key="User-Org-Name"></configValue>
                     <configValue key="User-Real-Name"></configValue>
                     <configValue key="User-Remote-Ip">127.0.0.1</configValue>
        </group>
        <group name="workflow">
            <!--config的key是固定的,不能动态增删,value是对应的值-->
             <configValue key="User-Id">0</configValue>
             <configValue key="User-Name">workflow</configValue>
                     <configValue key="User-Email"></configValue>
                     <configValue key="User-Org-Id"></configValue>
                     <configValue key="User-Org-Name"></configValue>
                     <configValue key="User-Real-Name"></configValue>
                     <configValue key="User-Remote-Ip">127.0.0.1</configValue>
        </group>
        <group name="portal">
            <!--config的key是固定的,不能动态增删,value是对应的值-->
             <configValue key="User-Id">guest</configValue>
             <configValue key="User-Name">guest</configValue>
                     <configValue key="User-Email"></configValue>
                     <configValue key="User-Org-Id"></configValue>
                     <configValue key="User-Org-Name"></configValue>
                     <configValue key="User-Real-Name"></configValue>
                     <configValue key="User-Remote-Ip"></configValue>
        </group>
</module>

EOS默认提供了3个虚拟用户,分别是server、workflow、portal。

  • server的虚拟用户用在定时器触发调用任务时;
  • workflow的虚拟用户用在工作流执行自动活动时;
  • portal的虚拟用户用在当http方法模式为门户模式时,用户访问页面时不需要进行登录认证,系统自动创建一个portal虚拟用户。

# MUO接口及方法说明

# 包含的类

类名 描述
com.eos.data.datacontext.IMUODataContext 一个受管控的用户对象
com.eos.data.datacontext.IUserObject 用户信息对象
com.eos.common.muo.IUserLoginCallback 用户登录回调接口
com.eos.common.muo.UserCredential 用户身份认证的对象
com.eos.access.http.MUOCommonUtil MUO的通用工具类

# 类的方法

# com.eos.data.datacontext.IMUODataContext

  • 类的说明 一个受管的用户对象,继承IDataContext。

  • 类的方法

    方法 说明
    String[] getManagedKeys() 获得受管理的keys,对应于Session-Manage/Managed-User-Object下的configValue的key
    boolean[] keysIsChanged() 获得MUO里被改变的key的索引,和managedKeys对应
    IUserObject getUserObject() 获得UserObject

# com.eos.data.datacontext.IUserObject

  • 类的说明 用户信息对象,用于存放用户登录后的一些用户个人信息。

  • 类的方法

方法 说明
方法 说明
String getUniqueId() 获取UserObject的唯一标识
String getUserId() 获取用户id
String getUserMail() 获取用户Mail
String getUserName() 获取用户姓名
getUserOrgId() 获取用户组织id
String getUserOrgName() 获取用户组织名称
String getUserRealName() 获取用户真正的名字
String getUserRemoteIP() 获取用户的IP地址
void setAttributes(Map<String, Object> attributes) 设置动态属性
Map<String, Object> getAttributes() 获得动态属性
void put(String key, Object value) 增加一个动态属性
Object get(String key) 获得一个动态属性
boolean contains(String key) 是否包含动态属性
Object remove(String key) 删除一个动态属性
void clear() 清空所有动态属性

# com.eos.common.muo.IUserLoginCallback

  • 类的说明 用户登录回调接口。

  • 类的方法

方法 说明
UserObject login(UserCredential credential) throws IllegalUserException 根据UserCredential做用户登录校验并创建UserObject返回

# com.eos.common.muo.UserCredential

  • 类的说明 用户身份认证的对象。实现了java.util.Map的接口。

  • 类的方法

方法 说明
void setUserName(String userName) 设置用户姓名
String getUserName() 获取用户姓名
void setPassword(String pwd) 设置用户密码
String getPassword() 获取用户密码

# com.eos.access.http.MUOCommonUtil

  • 类的说明 MUO的通用工具类,用于在有HttpSession的情况下,可以方便的调用逻辑流和服务。

  • 类的方法

方法 说明
Object[] invokeBizWithMUO(final String componentName, final String operationName, final Object[] params, HttpSession session) throws Throwable 根据传入的session创建MUO并调用逻辑流,逻辑流对MUO的修改会反映到session中。
Object[] invokeServiceWithMUO(final String serviceName, final String operationName, final Object[] params, HttpSession session) throws Throwable 根据传入的session创建MUO并调用服务,服务对MUO的修改会反映到session中。

# SDO

Service Data Objects(SDO)是一种数据编程架构和API。它统一了不同数据源类型的数据编程,提供了对通用应用程序模式的健壮支持,并使应用程序、工具和框架更容易查询、读取、更新和检查数据。

SDO中关键的概念包括Data Object(数据对象),data graph(数据图)。

  • 一个DataObject包含一组有名字的属性,这些属性可以是一个值类型(data type)或是指向另一个DataObject的引用。DataObject的API提供了一组动态的数据API来操作这些属性。 作为对比,开发人员以前通常会使用Java Bean来作为数据对象用于数据传递或者数据持久化。我们称Java Bean类型的API为静态的,因为它已预先定义好一系列的属性以及其getter/setter方法。然而,静态数据API并不总能执行。因为有时Java类甚至还并不存在。 举例来说,在许多动态查询中,返回数据的形式并不是已知的预先类型,这样我们就不能将数据填写到已经存在的Java类中。 另一个例子是,数据结构是可扩展的;例如,对于XML数据,在用户剖析它之前,通常不知道它的精确类型(假定它的XML模式结构是可扩展的)。 而DataObject接口的便利之处在于:它提供了动态的数据API。当用户需要产生一个能支持包括动态查询、未知数据类型和可扩展模式等情况的通用框架时,有一个动态的数据API会更加有用。
  • data graph是一个相关数据对象(DataObject)的集合。data graph能够跟踪图中DataObject的变化。这些变化包括新增DataObject,删除DataObject以及修改DataObject中的属性。

# 数据对象(DataObject)

数据对象用于描述业务数据,数据对象利用属性保存数据。 数据对象接口的设计原则是使编程更为简单。因为它提供了对所有普通类型的业务数据的访问以及各种访问模式的支持,如基于名字、索引和路径进行访问。

img

DataObject order = DataFactroy. INSTANCE.create("com.eos","Order");
//基于名字访问
order.getString("orderId");
//基于索引访问,索引从0开始,所以1就相当于属性orderDate
order.getDate(1);
//基于路径访问, 路径是一个基于Xpath子集的表达式,具体见参考手册中的Xpath的部分
Order.getString("orderLines[1]/orderLineId");

数据对象接口包含以下方法(具体API参见接口及方法说明 (opens new window)):

  • 获取和设置数据对象的属性;
  • 查询一个属性是否被设置;
  • 创建一个新的被包含的数据对象实例;
  • 从数据对象的包含者中删除该数据对象;
  • 从数据对象的包含者脱离(Detach)该数据对象;
  • 获取数据对象的包含者及该数据对象在包含者中所对应的属性;
  • 获取根数据对象;
  • 获取数据对象所属的数据图;
  • 获取数据对象的类型;
  • 获取数据对象的顺序(未实现);
  • 获取数据对象的额外属性。

# 数据对象的种类

数据对象可以分为以下几类:

  • Basic:该类型的数据对象和JavaBean类似。所有允许的属性集合可以通过getType().getProperties()方法获得。 通过get(property)方法可以访问属性的值。属性内部的顺序是可以维护的,但是跨越多个属性(见下面的说明)之间的顺序是不可维护的。
  • Open:该类型的数据对象和一个使用Map来保存额外属性的JavaBean类似。额外的属性不是由getType().getProperties()方法返回属性的一部分。 通过使用getInstanceProperties()可以获得一个指定数据对象的实际拥有的属性。属性值可以使用get(property)方法访问。属性内部的顺序是可以维护的,但是跨越多个属性之间的顺序是不可维护的。

说明

多个属性之间的顺序不可维护是指:如果有一个数据对象A,其中有Many-Valued的属性bs、cs。如果用户的操作顺序是A.set("bs[1]","b_value1")、A.set("cs[1]","c_value1")、A.set("bs[2]","b_value2"),这样的操作顺序就是跨越多个属性之间的顺序。现在EOS6的实现中无法维护这种顺序。即bs数据里的元素的顺序是可以保证的,但对bs和cs的之间的顺序是未实现的。SDO2.1规范里有sequenced类型的DataObject可以维护这种关系,但是EOS6暂时没有实现。

# 数据对象的属性值

数据对象拥有给其属性所赋的数据值。例如,一个采购订单数据对象order的orderDate属性有一个值为:2005-6-30。通过使用order.get("orderDate") 和order.set("orderDate")方法,可以获取或修改其orderDate属性的值。

在数据对象接口上,可以基于属性的名字通过使用get(String path)方法访问属性值,也可以利用属性的索引或者直接利用属性对象(get(property))访问属性值。数据对象的get(String path)方法可以使用路径。在这里,路径可能仅仅是一个属性的名字,也有可能是一个基于Xpath子集的路径表达。关于EOS支持的Xpath子集参见Xpath (opens new window)

注意

直接调用set(path,value)接口时:

  • 如果path是个属性名称,对于value值既不会按照属性的类型做类型校验,也不会做类型转换;
  • 如果path是个Xpath,那么只会做data type属性的类型转换。

# 多值(Many-Valued)数据对象属性

属性可能有一个或多个值。如果一个属性是多值的,那么property.isMany()返回true,get(property)方法将返回一个列表(List)。

在数据接口上,当属性没有值时,返回值为List的数据对象方法将返回一个空List而不是返回一个null。返回的List可以描述数据对象的值的任何变化。

对于访问多值属性来说,使用getList(property)方法是极其方便的。如果property.isMany()是true,那么set(property, value)和setList(property, value) 方法需要"value"分别为java.util.Collection和List对象。这些方法和在使用完getList(property).clear()之后紧接着使用getList(property).addAll(value)方法效果一样。

对于多值属性,get()和getList()方法将返回一个包含当前值的列表List。通过List接口立即可以对数据对象的当前值进行更新操作。每次使用get()或getList()方法访问时,都将返回同一个List对象。

# 判断一个属性是否被设置

对于一个多值属性,isSet(property)方法将返回:

  • True:如果List不为空
  • False:如果List为空

对于一个单值属性,isSet(property)方法将返回:

  • True:如果属性被set(),并且没有unset();
  • False:如果属性还没有被set()或者已经被unset()了。

当调用了set()而没有调用unset(),则isSet()返回true,而不管属性值有没有发生修改。 例如,当调用了set(property,property.getDefault()),则isSet(property)返回true。

unset(property)方法可以用来清空一个简单属性,因此在unset(property)之后,isSet(property)返回false,get(property)返回缺省值。 delete()方法将取消数据对象的所有非只读属性的值。在unset()之后,get(property)将返回缺省值,对于多值属性,则返回一个空列表。

# 包含(Containment)

数据图内的数据对象被组织成一个树型结构。一个数据对象作为树的根节点,而其它数据对象则组成这个树的其它叶子节点。

由根数据对象开始的包含引用(containment references)可以组成一个树型结构。根数据对象引用其它数据对象,而这些数据对象则可以进一步引用更深一层的数据对象。除了根数据对象,数据图内的每一个数据对象必定有一个从树内其它节点而来的包含引用。图内的每一个数据对象可以跟踪它的包含内容引用的位置。

对于一个数据图来说,拥有一个非包含引用是可能的。这些引用所指向的数据对象是同一个数据图的一部分(引用的数据对象必定是同一个树的一部分),但是这些引用不会影响数据图的树型结构。

包含引用和非包含引用都是数据对象的属性。该属性的类型可以是任何一种数据对象类型。

一个特殊的数据对象引用属性是否是一个包含引用或非包含引用由该数据图的数据模型定义,例如定义一个XML文档数据类型的XSD。一旦数据模型被定义好后,将不能被修改。可以通过访问property.isContainment()来查询一个特殊的引用是否是一个包含引用。

容器型数据对象(container DataObject)含其它数据对象。一个数据对象最多只能被一个容器性数据对象包含。如果一个数据对象没有容器,那么它就是一个根数据对象。

getContainer()和getContainmentProperty()方法提供了自上而下的简单浏览数据对象的途径。getContainer()方法返回一个父数据对象,getContainmentProperty()方法返回包含该对象的父对象中的对应的属性。使用detach()方法,在不做任何改变的情况下,可以将一个数据对象能够从它的父对象中移出。即把父对象中的对应的property unset。

# 创建和删除数据对象

创建方法可以创建一个该属性所属类型的数据对象,或者创建一个在参数中说明的类型的数据对象,并且能够将一个创建好的数据对象添加到指定的属性。如果属性是单值的,属性的值将被设置为该新创建的对象。如果属性是多值的,新创建的对象将被作为最后一个对象而添加到该属性中。只有包含属性能够被用来创建对象。一个新创建的对象的所有属性都是未被设过值的(isSet==false)。

delete()方法取消(unset)了数据对象的所有非只读属性。如果包含属性不是只读的,delete()方法也将把该数据对象从包含其的数据对象中移出。包含属性所包含的递归的子数据对象也将被删除。如果包含属性是只读的,将会抛出异常。

如果其它数据对象有一个非包含属性指向已删除的数据对象,那么这些引用将不会被修改。然而为了满足数据图的封闭特性,这些引用指向的对象的属性值会被修改。一个被删除的数据对象能够被再次使用,并且能够被再次添加到数据图中。

# 开放内容的数据对象属性

开放内容的数据对象就是数据对象的种类 (opens new window)中介绍的为Open的数据对象。

数据对象有两种类型的属性:

  • 指定类型(Type)说明的属性;
  • 未指定类型(Type)说明的属性,这样的属性叫做开放内容(open content)。

指定类型(Type)的属性可以通过getType().getProperties()方法获得,该方法将返回一个列表。除了拥有有类型描述的属性之外,数据对象还能够拥有开放属性。

当Type.isOpen()为true时,允许拥有开放内容属性。一些类型将开放(open)设置为false,因为它们不能接受附加的属性。

一个属性如果出现在getInstanceProperties()方法返回值中而不是出现在getType().getProperties()方法的返回值中,那么该属性来源于开放内容。如果一个属性来源于开放内容,那么isSet(property)方法必定返回true。

当你使用getInstanceProperties()方法时,将返回一个只读的列表,里面包含一个数据对象当前所使用的所有属性。这包括开放内容的属性。属性的顺序由所有getType().getProperties()方法返回值开始;其它属性排在后面。每一次对getInstanceProperties()方法的调用将返回同一个列表对象,除非该数据对象已经更新,从而导致列表内容改变。

根据一个属性名称,通过调用DataObject的getInstanceProperty(propertyName)可以获取相应的property。

# 属性索引

当一个数据对象拥有多个属性时,每一个属性都能够被一个数字索引所引用,其中第一个属性的数字索引从0开始。

get(int property)方法中使用的属性索引是该属性在getInstanceProperties()方法返回的列表中的位置。

如果数据正在修改之中,不推荐对开放内容使用按属性索引这种方式访问;除非该索引被用在getInstanceProperties()方法获得的属性中,因为在getInstanceProperties()方法中,开放内容的属性索引能够改变,如果有几个开放内容属性的值被重复的设置和取消设置。

下面的例子是正确的,因为索引和getInstanceProperties()方法一起使用。

注意

由于数据对象不是同步的,因此用户不应该在读的同时对其进行更新操作。

该实例显示了一个普通的模式,在所有实例属性中循环并且打印属性名和值:

for (int i=0; i<myDo.getInstanceProperties().size(); i++){
    Property p = (Property) myDo.getInstanceProperties().get(i);
    System.out.println(p.getName()+"="+myDo.getString(i));
}

# 类型(Type)

Type分为2种。如果Type是由一组属性(Property)构成,那么可以称这种Type为复杂类型,复杂类型用于表示一个DataObject定义;相对应的如果Type不是由一组属性构成的,那么可以称这种Type是值类型(data type)。

一个Type总是有:

  • Name – 必须在同一个URI下的所有Types中唯一。
  • URI – URI是个逻辑概念,对应为xsd的target namespace。
  • boolean的字段(field)来表明该type是否是开放的,抽象的,或者是一个值类型(data type)。

一个Type还可以有:

  • Properties – Type中定义的属性。如果是data type则没有属性。
  • Instance Class – 被用于实现Type的java.lang.Class。
    • 如果DataType==true,那么将肯定有一个Instance Class,例如:java.lang.Integer或java.lang.String;
    • 如果DataType==false并且是使用代码生成,那么Instance Class是个可选项。可能会是:com.eos.User。
  • Aliases – Type的别名,和Name一样,必须在同一个URI下的所有Types中唯一。所有根据Name操作Type的方法,都可以使用aliases name。
  • Instance properties – 对一个Type实例的增加的动态属性。

# 属性(Property)

一个数据对象是由属性值建立起来的。

一个属性包括了:

  • Name – 属性的名称。在属性所属的Type中唯一。
  • Type – 属性的类型。那些是DataObject类型的属性被称作引用(reference),其余的属性被称做属性(attribute)。
  • Containment – 表示一个属性是否是包含属性。
  • Many – 表示一个属性是单值(single-valued)还是多值(many-valued)。
  • ReadOnly – 表示属性是否是只读。
  • Alias names – 属性的别名。在属性所属的Type中唯一。
  • Default Value – 属性的默认值。
  • Nullable – 表示属性是否可以设置null值。
  • Instance properties – 对一个Property实例的增加的动态属性。

# SDO接口及方法说明

由于EOS暂时没有完全实现SDO规范,所以下面所列的API就是EOS所实现的全部功能。

# 包含的类

类名 描述
commonj.sdo.DataObject 一个数据对象表现为有结构的数据
commonj.sdo.Type 数据对象或属性的类型
commonj.sdo.Property 数据对象所拥有的属性
commonj.sdo.helper.DataFactory 用于数据对象的创建
commonj.sdo.helper.XSDHelper 加载用于Type定义的XML Schema (XSD)
commonj.sdo.helper.TypeHelper 查找已经定义加载过的Type
com.eos.data.sdo.DynamicXSDLoader 动态的加载xsd的inputstream

# 类的方法

# commonj.sdo.DataObject

  • 类的说明 数据对象是SDO中的核心构件。数据对象支持反射(reflection),基于路径的访问,以及方便的创建和删除等方法。 每一个数据对象都持有数据作为属性的值,属性可以按照名字或索引访问。 数据对象还提供一系列方便的访问属性的方法。 例如: 通过studio定义数据集"com.primeton.eos.example.newdataset1",数据集里有实体"Company"和"Department"。 img img

//创建Company的sdo对象。 //create()的第一个参数是URI(就是xsd的targetNamespace),在EOS中就是数据集的名称。 //第二个参数是实体名称。 DataObject company = DataFactory.INSTANCE.create("com.primeton.eos.example.newdataset1","Company"); company.get("name"); 相当于 company.get(company.getType().getProperty("name")) company.set("name", "acme"); company.get("department.0/name") 相当于 ((DataObject)((List)company.get("department")).get(0)).get("name") .n 下标从0开始 company.get("department[1]/name") 相当于 []下标从1开始 company.get("department[number=123]") 返回第一个number=123的department

  • 类的方法
方法 说明
Object get(String path) 返回该对象或者能够通过该对象达到的另一个对象的属性值。path可以是一个属性名称,或者是一个Xpath子集,以下方法的参数path都是这样。
void set(String path, Object value) 设置path对应属性值。
boolean isSet(String path) 判断path对应的属性是否被设置过。
void unset(String path) 取消对path对应的属性的设置。
boolean getBoolean(String path) 获取path对应的属性值,并会把该属性值转换为boolean类型。
byte getByte(String path) 获取path对应的属性值,并会把该属性值转换为byte类型。
char getChar(String path) 获取path对应的属性值,并会把该属性值转换为char类型。
double getDouble(String path) 获取path对应的属性值,并会把该属性值转换为double类型。
float getFloat(String path) 获取path对应的属性值,并会把该属性值转换为float类型。
int getInt(String path) 获取path对应的属性值,并会把该属性值转换为int类型。
long getLong(String path) 获取path对应的属性值,并会把该属性值转换为long类型。
short getShort(String path) 获取path对应的属性值,并会把该属性值转换为short类型。
byte[] getBytes(String path) 获取path对应的属性值,并会把该属性值转换为byte[]类型。
BigDecimal getBigDecimal(String path) 获取path对应的属性值,并会把该属性值转换为BigDecimal类型。
BigInteger getBigInteger(String path) 获取path对应的属性值,并会把该属性值转换为BigInteger类型。
DataObject getDataObject(String path) 获取path对应的属性值,并会把该属性值转换为DataObject类型。
Date getDate(String path) 获取path对应的属性值,并会把该属性值转换为Date类型。
String getString(String path) 获取path对应的属性值,并会把该属性值转换为String类型。
List getList(String path) 获取path对应的属性值,该属性是个多值属性。
void setBoolean(String path, boolean value) 给path对应的属性设置一个boolean值。
void setByte(String path, byte value) 给path对应的属性设置一个byte值。
void setChar(String path, char value) 给path对应的属性设置一个char值。
void setDouble(String path, double value) 给path对应的属性设置一个double值。
void setFloat(String path, float value) 给path对应的属性设置一个float值。
void setInt(String path, int value) 给path对应的属性设置一个int值。
void setLong(String path, long value) 给path对应的属性设置一个long值。
void setShort(String path, short value) 给path对应的属性设置一个short值。
void setBytes(String path, byte[] value) 给path对应的属性设置一个byte[]值。
void setBigDecimal(String path, BigDecimal value) 给path对应的属性设置一个BigDecimal值。
void setBigInteger(String path, BigInteger value) 给path对应的属性设置一个BigInteger值。
void setDataObject(String path, DataObject value) 给path对应的属性设置一个DataObject值。
void setDate(String path, Date value) 给path对应的属性设置一个Date值。
void setString(String path, String value) 给path对应的属性设置一个String值。
void setList(String path, List value) 给path对应的属性设置一个List值。
Object get(int propertyIndex) 获取propertyIndex对应的属性值。propertyIndex是指属性在定义时的顺序。
void set(int propertyIndex, Object value) 给propertyIndex对应的属性设置值。
boolean isSet(int propertyIndex) 判断propertyIndex对应的属性是否被设置过。
void unset(int propertyIndex) 取消对propertyIndex对应的属性的设置。
boolean getBoolean(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为boolean类型。
byte getByte(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为byte类型。
char getChar(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为char类型。
double getDouble(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为double类型。
float getFloat(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为float类型。
int getInt(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为int类型。
long getLong(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为long类型。
short getShort(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为short类型。
byte[] getBytes(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为byte[]类型。
BigDecimal getBigDecimal(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为BigDecimal类型。
BigInteger getBigInteger(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为BigInteger类型。
DataObject getDataObject(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为DataObject类型。
Date getDate(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为Date类型。
String getString(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为String类型。
List getList(int propertyIndex) 获取propertyIndex对应的属性值,并会把该属性值转换为List类型。
void setBoolean(int propertyIndex, boolean value) 给propertyIndex对应的属性设置一个boolean值。
void setByte(int propertyIndex, byte value) 给propertyIndex对应的属性设置一个byte值。
void setChar(int propertyIndex, char value) 给propertyIndex对应的属性设置一个char值。
void setDouble(int propertyIndex, double value) 给propertyIndex对应的属性设置一个double值。
void setFloat(int propertyIndex, float value) 给propertyIndex对应的属性设置一个float值。
void setInt(int propertyIndex, int value) 给propertyIndex对应的属性设置一个int值。
void setLong(int propertyIndex, long value) 给propertyIndex对应的属性设置一个long值。
void setShort(int propertyIndex, short value) 给propertyIndex对应的属性设置一个short值。
void setBytes(int propertyIndex, byte[] value) 给propertyIndex对应的属性设置一个byte[]值。
void setBigDecimal(int propertyIndex, BigDecimal value) 给propertyIndex对应的属性设置一个BigDecimal值。
void setBigInteger(int propertyIndex, BigInteger value) 给propertyIndex对应的属性设置一个BigInteger值。
void setDataObject(int propertyIndex, DataObject value) 给propertyIndex对应的属性设置一个DataObject值。
void setDate(int propertyIndex, Date value) 给propertyIndex对应的属性设置一个Date值。
void setString(int propertyIndex, String value) 给propertyIndex对应的属性设置一个String值。
void setList(int propertyIndex, List value) 给propertyIndex对应的属性设置一个List值。
Object get(Property property) 获取property对应的属性值。
void set(Property property, Object value) 给property对应的属性设置值。
boolean isSet(Property property) 判断property对应的属性是否被设置过
void unset(Property property) 取消对property对应的属性的设置。
boolean getBoolean(Property property) 获取property对应的属性值,并会把该属性值转换为boolean类型。
byte getByte(Property property) 获取property对应的属性值,并会把该属性值转换为byte类型。
char getChar(Property property) 获取property对应的属性值,并会把该属性值转换为char类型。
double getDouble(Property property) 获取property对应的属性值,并会把该属性值转换为double类型。
float getFloat(Property property) 获取property对应的属性值,并会把该属性值转换为float类型。
int getInt(Property property) 获取property对应的属性值,并会把该属性值转换为int类型。
long getLong(Property property) 获取property对应的属性值,并会把该属性值转换为long类型。
short getShort(Property property) 获取property对应的属性值,并会把该属性值转换为short类型。
byte[] getBytes(Property property) 获取property对应的属性值,并会把该属性值转换为byte[]类型。
BigDecimal getBigDecimal(Property property) 获取property对应的属性值,并会把该属性值转换为BigDecimal类型。
BigInteger getBigInteger(Property property) 获取property对应的属性值,并会把该属性值转换为BigInteger类型。
DataObject getDataObject(Property property) 获取property对应的属性值,并会把该属性值转换为DataObject类型。
Date getDate(Property property) 获取property对应的属性值,并会把该属性值转换为Date类型。
String getString(Property property) 获取property对应的属性值,并会把该属性值转换为String类型。
List getList(Property property) 获取property对应的属性值,并会把该属性值转换为List类型。
void setBoolean(Property property, boolean value) 给property对应的属性设置一个boolean值。
void setByte(Property property, byte value) 给property对应的属性设置一个byte值。
void setChar(Property property, char value) 给property对应的属性设置一个char值。
void setDouble(Property property, double value) 给property对应的属性设置一个double值。
void setFloat(Property property, float value) 给property对应的属性设置一个float值。
void setInt(Property property, int value) 给property对应的属性设置一个int值。
void setLong(Property property, long value) 给property对应的属性设置一个long值。
void setShort(Property property, short value) 给property对应的属性设置一个short值。
void setBytes(Property property, byte[] value) 给property对应的属性设置一个byte[]值。
void setBigDecimal(Property property, BigDecimal value) 给property对应的属性设置一个BigDecimal值。
void setBigInteger(Property property, BigInteger value) 给property对应的属性设置一个BigInteger值。
void setDataObject(Property property, DataObject value) 给property对应的属性设置一个DataObject值。
void setDate(Property property, Date value) 给property对应的属性设置一个Date值。
void setString(Property property, String value) 给property对应的属性设置一个String值。
void setList(Property property, List value) 给property对应的属性设置一个List值。
DataObject createDataObject(String propertyName) 根据propertyName对应的属性的类型创建一个数据对象,并设置到对应的属性上。
DataObject createDataObject(int propertyIndex) 根据propertyIndex对应的属性的类型创建一个数据对象,并设置到对应的属性上。
DataObject createDataObject(Property property) 根据property对应的属性的类型创建一个数据对象,并设置到对应的属性上。
void delete() 从包含该数据对象的数据对象中删除该数据对象。
DataObject getContainer() 得到包含该数据对象的数据对象。
Property getContainmentProperty() 得到该数据对象在Container中对应的属性。
Type getType() 得到该数据对象对应的type。
List /* Property */ getInstanceProperties() 得到该数据对象的所有属性,包括动态属性。
Property getInstanceProperty(String propertyName) 根据propertyName获得对应的属性。
void detach() 从包含该数据对象的数据对象中脱离该数据对象。说明 detach和delete的区别是:detach只是把当前数据对象从它的包含对象中unset,当前数据对象的属性值不做任何改动;delete则是不但把当前数据对象从它的包含对象中unset,而且还把当前数据对象的属性值都unset,并且当前数据对象中包含的其它数据对象会递归的调用delete。

# commonj.sdo.Type

  • 类的说明 数据对象或属性的类型。

  • 类的方法

方法 说明
String getName() 得到type的名称。
String getURI() 获取type所属的URI(即targetNamespace)。
Class getInstanceClass() 得到type实现对应的java.lang.Class(暂时只对data type有效)。
boolean isInstance(Object object) 判断对象是否是该type的实例(暂时只对data type有效)。
List /Property/ getProperties() 获取type定义的属性。 如果是data type返回空list。 和规范不同的是,由于EOS的实现暂时不支持type的继承,所以该方法的返回值只是该type所包含的属性。
Property getProperty(String propertyName) 根据propertyName在getProperties()获取属性。
boolean isDataType() 是不是基本的数据类型。
boolean isOpen() 是否可以动态的增加属性。
List /Property/ getDeclaredProperties() 获取type定义的属性(不包括继承的type)。 如果是data type返回空list。

# commonj.sdo.Property

  • 类的说明 表现为一个在Type或DataObject里的Property。

  • 类的方法

方法 说明
String getName() 获取属性的名字。
Type getType() 获取属性对应的Type。
boolean isMany() 是否是多值属性。
boolean isContainment() 是否是个包含属性。
Type getContainingType() 获取包含该属性的Type
Object getDefault() 得到默认值。
boolean isOpenContent() 是否是动态属性。

# commonj.sdo.helper.DataFactory

  • 类的说明 DataObject的创建工厂。

  • 类的方法

方法 说明
DataObject create(String uri, String typeName) 根据Type的uri和name创建一个DataObject。
DataObject create(Type type) 根据Type创建一个DataObject。

# commonj.sdo.helper.XSDHelper

  • 类的说明 加载用于Type定义的XML Schema(XSD)。

  • 类的方法

方法 说明
List /Type/ define(String xsd) 根据一个xsd的字符串生成Type。
List /Type/ define(Reader xsdReader, String schemaLocation) 根据一个xsd文件的Reader生成Type。 第2个参数暂时无用。
List /Type/ define(InputStream xsdInputStream, String schemaLocation) 根据一个xsd文件的InputStream生成Type。 第2个参数暂时无用。

# commonj.sdo.helper.TypeHelper

  • 类的说明 Type的查询接口。

  • 类的方法

方法 说明
Type getType(String uri, String typeName) 根据uri和typeName查找Type。 如果没有找到,返回null。

# com.eos.data.sdo.DynamicXSDLoader

  • 类的说明 动态的加载xsd的InputStream。

  • 类的方法

方法 说明
void load(InputStream xsdInputStream, String schemaLocation, boolean isAdded) 动态的加载xsd的inputstream。 参数: Unknown macro: {span}xsdInputStream:xsd的inputstream。 schemaLocation:xsd对应的location,可以为null。 isAdded:true(新增)、false(更新)。
InputStream input = ......;
   try{
      DynamicXSDLoader.load(input,null,true);
   } finally {
       if (input!=null)
           input.close();
   }

# EOS支持的Data Type

data type和java class的对应关系如下:

sdo type name URI = commonj.sdo java class
Boolean boolean
Byte byte
Bytes byte[]
Char char
Date java.util.Date
Time java.lang.String
Decimal java.math.BigDecimal
Double double
Float float
Int int
Integer java.math.BigInteger
Long long
Short short
String java.lang.String

data type之间是否允许类型转换如下表所示(x表示允许):

From To Boolean Byte Char Double Float Int Long Short String Bytes Decimal Integer Date Time
Boolean x x
Byte x x x x x x x
Char x x
Double x x x x x x x x x
Float x x x x x x x x x
Int x x x x x x x x x
Long x x x x x x x x x x
Short x x x x x x x
String x x x x x x x x x x x x x x
Bytes x x x
Decimal x x x x x x x
Integer x x x x x x x x
Date x x x
Time x

# SDO对象图和对象树的说明

每个圆圈代表一个DataObject。对象树如下图所示。

img

对象图如下图所示:

img

对象树是一种自顶向下单方向,并且除了根对象外每个对象有且只有一个引用的特殊的对象图。

对比上面2张图可以发现,在对象图中A_2_1的父对象变成了A_1,并且A_2和A_2_1之间的引用变成了红色。

在对象树中的所有的引用都是SDO所指的包含引用,而在对象图中除了A_2和A_2_1之间的引用是SDO所指的非包含引用外,其余的引用也都是包含引用。

包含引用和非包含引用的差别显示在delete的时候。

如在对象树中删除A_2,那么A_2的所有非只读属性会被unset,并且A_2的所有包含引用指向的对象A_2_1和A_2_2的所有非只读属性也会被unset。

如在对象图中删除A_2,那么A_2的所有非只读属性会被unset,并且A_2的包含引用指向的对象A_2_2的所有非只读属性也会被unset。和在对象树中不同的是,因为A_2对A_2_1是非包含引用,所以在删除A_2的时候不会对A_2_1有操作。

# Xpath

Xpath提供了访问对象的内容的一组语法,通过该语法,可以遍历访问对象;这些对象可以以JavaBean、SDO、Map等各种形式存在,也可以是它们的混合形式。利用Xpath语法,屏蔽和统一了这些对象的访问方式。

# Xpath语法

EOS支持的Xpath语法如下:

path ::= '/'? (step '/')* step
step ::= '@'? property
    | property '[' index_from_1 ']'
        | #                                                                 ;;如果是DOM节点,取节点的值
    | property '.' index_from_0
    | reference '['@? attribute '=' value ']'
    | ".."
property ::= NCName ;; may be simple or complex type
attribute ::= NCName ;; must be simple type
reference :: NCName ;;
index_from_0 ::= Digits
index_from_1 ::= NotZero (Digits)?
value ::= Literal
Literal ::= '"' [01 Xpath语法^"]* '"'
    | "'" [01 Xpath语法^']* "'"
NotZero ::= [1-9]
Digits ::= [0-9]+

EOS Xpath与SDO Xpath的语法有以下差异:

  • Step不支持".."回溯;
  • 不支持scheme;
  • Value只支持字符串,不支持数字和布尔值;
  • 支持/#,获取DOM节点的值。

举一个对DOM节点值的访问的例子,假设在一个混合对象JavaBean中,有一个Element类型的名称为elem的DOM对象。该DOM对象如下:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<element>zhangsan</element>

那么就可以通过Xpath语法"elem/#"获取DOM节点的值"zhangsan"了。

根据上面列出的Xpath语法,我们可以看到,除了标准Xpath语法外,EOS的Xpath语法额外添加了一种下标取值的语法,即".(以0开始的下标)"。"department.0/name"相当于"department[1]/name"。

# Xpath所支持访问的对象

Xpath支持的访问的对象有以下几种:

  • Javabean:普通的java Bean对象;
  • DataObject:SDO的DataObject对象;
  • Collection:Collection数据对象,包括数组、List;
  • Map:Map数据对象;
  • Document:Document文档对象;
  • PageContext:Http请求的PageContext对象;
  • ServletContext:Http请求的ServletContext对象;
  • HttpServletRequest:Http请求的ServletRequest对象;
  • HttpSession:Http请求的HttpSession对象。

# 类型映射(type mapping)

在根据Xpath创建对象,对对象属性进行赋值时,如果不能从对象的数据元信息(比如从DataObject的Type中获取的属性,从JavaBean、Map中获取属性的类型)中得到当前要创建的节点信息,那么就需要根据类型映射获取该节点的类型。

在根据Xpath对对象进行赋值操作时,如果要动态创建对象或属性,则类型映射的规则如下:

Key(前缀是xpath) Value 备注
xpath:Xpath表达式 java:实现类的名称 如果实现类的名称是以'[ ]'为后缀的,表示要创建一个数组
xpath:Xpath表达式 sdo:DataObject类型名称 如果类型名称是以'[ ]'为后缀的,表示要创建一个DataObject数组

# 示例

  • 对于Xpath和java实现类型的映射
com.primeton.java包下有类AddressType
class AddressType{
private String type;
public void setType(String type){
this.type=type;
}
}
XPathLocator locator=XPathLocator.getInstance();
Map typeMapping=new HashMap();
typeMapping.put("xpath:/property","java:com.primeton.java.AddressType");
locator.setTypeMappings(typeMapping);
locator.setValue(new HashMap(),"/property/type","test");
XPathLocator locator2=XPathLocator.getInstance();
Map typeMapping2=new HashMap();
typeMapping2.put("xpath:/property","java:com.primeton.java.AddressType");
locator2.setTypeMapping(typeMapping2);
locator2.setValue(new HashMap(),"property[1]/type","test");
  • 对于Xpath和DataObject实现类型的映射(定义了类型为com.primeton.sdo.AddressType的SDO数据对象,它包含有名称type的属性)
XPathLocator locator=XPathLocator.getInstance();
Map typeMapping=new HashMap();
typeMapping.put("xpath:/property","sdo:com.primeton.java.AddressType");
locator.setTypeMappings(typeMapping);
locator.setValue(new HashMap(),"/property/type","test");
XPathLocator locator2=XPathLocator.getInstance();
Map typeMapping2=new HashMap();
typeMapping2.put("xpath:/property","sdo:com.primeton.java.AddressType");
locator2.setTypeMapping(typeMapping2);
locator2.setValue(new HashMap(),"property[1]/type","test");

# 规则和约束

设置类型映射时,对于集合类型(List),不支持设置集合的元素类型。

比如类似下面的类型映射是不支持的。

Map typeMapping=new HashMap();
typeMapping.put("xpath:/property","java:java.util.ArrayList< com.primeton.java.AddressType >")

# 动态的创建节点

# 节点的父对象为Map结构

当该节点的父对象为Map,且该节点没有指定类型映射,则会为该节点生成HashMap或者Object数组,具体生成哪种类型,根据Xpath表达式语法而定。当Xpath的节点Xpath为property[index](其中index从1开始)时,生成Object数组;否则生成HashMap对象。

  • 示例1
XPathLocator locator=XPathLocator.getInstance();
locator.setValue(new HashMap(),"property[1]","test");

等同于

Map root=new HashMap();
Object[] data=new Object[1];
data[0]="test";
root.put("property",data);
  • 示例2
XPathLocator locator2=XPathLocator.getInstance();
locator2.setValue(new HashMap(),"property/subProperty","test");

等同于

Map root=new HashMap();
Map subMap=new HashMap();
subMap.put("subProperty","test");
root.put("property",subMap);

# 节点的父对象为非Map的结构

当需要动态创建的节点的父对象为其他类型,则会根据父对象的元信息,取得该节点所对应的类型。如果该类型是一个可实例化的对象,就创建对象。否则就根据类型映射查找该节点对应的类型,创建对象,如果没有查找到,就抛异常。

定义了这样一个类CompoundClass:

class CompoundClass{
    private List datas;
    public List getDatas(){
        return this.datas;
    }
    public void setDatas(List datas){
        this.datas=datas;
    }
}
CompoundClass compound =new CompoundClass();
XPathLocator locator=XPathLocator.getInstance();
locator.setValue(compound,"datas[1]","test");

等同于

CompoundClass compound =new CompoundClass();
 
List list = compound.getDatas();
if (list == null) {
  list = new ArrayList();
  compound.setDatas(list);
}
list.add("test");

# 对DOM进行访问

# 对Node的Xpath数据读取约束

对Node进行Xpath访问时,Document或Element根的名称被忽略。

比如:JavaBean中有一个名称为dom的Document对象,名称为elem的Element对象。

class JavaBean{
private Document dom;
private Element elem;
publc Document getDom(){
return dom;
}
public Element getElem(){
return elem;
}
}

其中dom和elem属性都是下面的结构

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<root name="rootName">
  <order price="100">
   <item/>
</order>
</root>

那么访问Document的order节点的Xpath为"/dom/order",访问Element的order节点的Xpath为"/elem/order"。

# 对Node的Xpath操作

对Node进行操作时,XPathLocator中的addValue、setValue、getValues都有特定的语义。相关的语义结合下面的例子说明:

  • 数据:

    <Account>
              <Address id="1">xxxx</Address>
              <Address id="2">yyyyy</Address>
              <Order/>
           </Account>
    
  • 场景一: addValue("/Account/Address[id='1']", "jjjj");

    结果:

    <Account>
              <--!把Address[id='1']这个节点看作一个List节点,
                    为这个节点增加一个子节点TextNode="jjjj"-->
              <Address id="1">xxxxjjjj</Address>
              <Address id="2">yyyyy</Address>
             <Order/>
          </Account>
    
  • 场景二: addValue("/Account/Order, customerNode);

    结果:

    <Account>
              <Address id="1">xxxx</Address>
              <Address id="2">yyyyy</Address>
              <!--把 Order节点作为一个List,为他增加一个子节点-->
              <Order>
                  <Customer/>
              </Order>
           </Account>
    
  • 场景三:setValue("/Account/Order, newOrderNode)

    结果:

    <Account>
              <Address id="1">xxxx</Address>
              <Address id="2">yyyyy</Address>
              <!--取代了原来的Order节点,将原有Order的字节点全部删除,
                    将newOrderNode的子节点全部添加到原有Order节点中  -->
              <Order/>
          </Account>
    
  • 场景四:List<Node> nodes=getValues("Account/Address") 结果:获取到了Account/Address的节点列表

# Xpath接口及方法说明

# 包含的类

类名 描述
com.eos.data.xpath.XPathLocator 使用Xpath语法对各种混合对象进行取值和设置的类

# 类的方法

# com.eos.data.xpath.XPathLocator

  • 类的说明 使用Xpath语法对各种混合对象进行取值和设置的类。

  • 类的方法

    方法 说明
    void setTypeMappings(Map<String, String> mappings) 设置创建对象的实现类型映射,用于动态的创建对象时,指定对象的实现类型
    static XPathLocator getInstance() 创建一个XpathLocator对象实例
    java.lang.Object getValue(Object rootValue, java.lang.String xpath) 获取指定Xpath路径上的值
    List getValues(Object rootValue, java.lang.String xpath) 获取指定Xpath路径上的值,以List形式返回
    void setValue(Object rootValue, java.lang.String xpath, java.lang.Object value) 设置对象指定Xpath路径上的属性的值,并动态创建不存在的Xpath
    void setValue(Object rootValue, java.lang.String xpath, java.lang.Object value, boolean createPath) 设置对象指定Xpath路径上的属性的值,根据要求确定是否动态创建不存在的Xpath
    void addValue(Object rootValue, java.lang.String xpath, java.lang.Object value) 设置对象指定Xpath路径上的属性的值,并动态创建不存在的Xpath
    void addValue(Object rootValue, java.lang.String xpath, java.lang.Object value, boolean createPath) 设置对象指定Xpath路径上的属性的值,根据要求确定是否动态创建不存在的Xpath
    void deleteValue(Object rootValue, java.lang.String xpath) 删除指定Xpath路径上的对象

# 数据库连接

数据库连接管理提供了对数据源的管理功能,允许用户配置多个数据源,并提供接口,用于获取应用级别和构件包级别的数据源连接。它还提供了一组扩展接口,在JDBC操作过程中,对数据库连接connection、执行语句statement、结果集Resultset进行监控。

# 多数据源配置

EOS6中允许用户配置多个数据源,每个数据源都有引用名称,系统管理着数据源名称到数据源的映射,用户可以根据数据源名称得到数据源。

系统默认提供了一个名称为default的数据源,用户不指定数据源引用名称时,就是取默认的数据源。

//获取系统默认的数据源
DataSource ds=DataSourceHelper.getDataSource();
//获取contribution级别的数据源
getContributionDataSource(String dataSourceAlias)

如何获取数据源详见接口及方法说明中的类com.eos.common.connection.DataSourceHelper (opens new window)

应用级别的数据源有两种配置,一种是c3p0数据源,一种是jndi的数据源。相关的配置在用户配置文件user-config.xml中。详细说明见配置文件中的注释。

<module name="DataSource">
<group name="default">
<!—JNDI类型数据源的名称>
<configValue key="Jndi-Name">EOSDefaultDataSource</configValue>
<!—数据源连接的隔离级别,用于外部没有启动事务的环境下,数据库连接的隔离级别>
<configValue key="Transaction-Isolation">ISOLATION_READ_COMMITTED</configValue>
<!—数据库的类型,目前支持有Oracle,DB2,SQLServer>
<configValue key="Database-Type">DB2</configValue>
<!—测试数据库连接用,用于获取连接是否有效时,执行的测试sql>
<configValue key="Test-Connect-Sql">SELECT * from UNIQUETABLE</configValue>
<!—测试数据库连接用,用于获取连接是否有效时,最多可以重试多少次 >
<configValue key="Retry-Connect-Count">-1</configValue>
</group-->
 
<!—以下是c3p0的数据源配置>
<group name="other">
<configValue key="C3p0-DriverClass">com.ibm.db2.jcc.DB2Driver</configValue>
<configValue key="C3p0-Url">jdbc:db2://192.168.1.251:50000/eos</configValue>
<configValue key="C3p0-UserName">eos6si</configValue>
<configValue key="C3p0-Password">eos6si</configValue>
<configValue key="C3p0-PoolSize">10</configValue>
<configValue key="C3p0-MaxPoolSize">50</configValue>
<configValue key="C3p0-MinPoolSize">10</configValue>
<configValue key="Transaction-Isolation">ISOLATION_READ_COMMITTED</configValue>
<configValue key="Database-Type">DB2</configValue>
<configValue key="Jdbc-Type">IBM DB2 Driver(Type4)</configValue>
<configValue key="Test-Connect-Sql">SELECT * from UNIQUETABLE</configValue>
<configValue key="Retry-Connect-Count">-1</configValue>
</group>
</module>

系统除了在应用级别配置了数据源,还在构件包级别配置了数据源的引用。 相关的配置在构件包级别的配置文件contribution.eosinf中。

<!-- datasource config -->
<module name="DataSource">
<group name="Reference">
      <!—default表示构件包对数据源的引用名称,other表示在应用级别配置的数据源的引用名称,
            在构件包中根据构件包级别数据源获取的接口获得的数据源。
            实际上就是对应到应用级别的数据源,
            这样可以不用修改构件包代码,就可以在不同数据源之间切换。-->
 <configValue key="default">other</configValue>
</group>
</module>

警告

对于使用构件包的数据源引用名称default去查找数据源时,如果对应的应用数据源名称不存在,会使用"default"的应用数据源代替。而如果构件包的数据源引用名称不是default且对应的应用数据源名称不存在时,会抛异常。 如上面示例的配置,如果在user-config.xml中没有配置名称为other的应用数据源,当在构件包中获取构件包级别的default数据源时,实际使用的是default的应用数据源(因为没有找到other的应用数据源)。

# JDBC操作扩展

用户在JDBC操作过程中,系统提供扩展点,供用户扩展;同时提供给用户注入扩展点实现的接口。

比如用户在数据库的JDBC操作过程中,调用数据库连接,connection.close()方法时,用户可能在这个过程中加入自己的行为或功能。

系统提供给用户的扩展点如下:

  • 数据库连接connection提供的扩展点
    • 创建数据库连接时
    • 关闭数据库连接时
    • 当调用数据库连接方法发生异常时
  • Statement提供的扩展点
    • 创建statement时
    • 关闭statement时
    • 当调用Statement方法发生异常时
    • 在sql语句执行之前
    • 在sql语句执行之后
    • 在sql语句执行时发生异常
  • ResultSet提供的扩展点
    • 当结果集被创建时
    • 当结果集被关闭时
    • 当调用结果集方法发生异常时

用户可以通过JDBC操作扩展的注册接口注册自己的扩展实现。

//获取回调接口的管理者
ISynchronizationManager manager=SynchronizationManagers.getSynchronizationManager();
 
//获取数据库连接的同步扩展实现列表
List<IConnectionSynchronization> connSyns= Manager.getConnectionSynchronizations();
 
//用户自己实现的连接同步扩展。
IConnectionSynchronization connSyn=new UserDefineConnSyn();
 
//增加该扩展实现到同步列表中,以供系统回调
connSyns.add(connSyn);

# 数据库JDBC操作监控

根据JDBC操作扩展功能,EOS6自定义了一些扩展,用于对数据库操作的监控,获取相关的统计数据,进而进行性能方面的优化。

监控的方面包含:

  • 统计监控未关闭的数据源连接
  • 监控当前连接和最大连接数量
  • 监控连接调用栈
  • 统计未关闭的statement,统计statement调用栈
  • 统计未关闭的resultset,统计resultset调用栈
  • 记录sql的调用时间和频率
  • 关闭未关闭的连接
  • sql输出到控制台控制

用户可以通过配置对这些监控进行打开和关闭操作。具体的配置信息在用户配置文件user-config.xml中。

<!--数据连结管理监控配置-->
    <!--
        UnclosedConn[true|false,默认值false]:
        是否统计未关闭的连接;
        StackConn[true|false,默认值false]:
        是否记录连接的调用栈,主要用于分析调用的路径,例如,可以观察那个调用导致长时间的连接未关闭;
        UnclosedStatement[true|false,默认值false]:
        是否统计未关闭的Statement;
        StackStatement[true|false,默认值false]:
        是否记录Statement的调用栈,主要用于分析调用的路径,例如,可以观察那个调用导致长时间的Statement未关闭;
        UnclosedResultSet[true|false,默认值false]:
        是否统计未关闭的ResultSet;
        StackResultSet[true|false,默认值false]:
        是否记录ResultSet的调用栈,主要用于分析调用的路径,例如,可以观察那个调用没有关闭ResultSet;
        IsLogSqlExcTimes[true|false,默认值false]:是否统计SQL的执行次数
 
        LogSqlWhenTimeout(单位毫秒):
        SQL记录的执行时间阈值,如果超过(>=阈值)阈值则记录SQL语句(记录到系统日志中),否则不记录;
        IsLogActiveConnNum[true|false,默认值false]:
        是否统计当前活动的数据库连接数;
    -->
    <module name="Connection">
        <group name="Monitor">
            <configValue key="UnclosedConn">true</configValue>
            <configValue key="StackConn">true</configValue>
            <configValue key="UnclosedStatement">true</configValue>
            <configValue key="StackStatement">true</configValue>
            <configValue key="UnclosedResultSet">true</configValue>
            <configValue key="StackResultSet">true</configValue>
            <configValue key="IsLogSqlExcTimes">true</configValue>
 
            <configValue key="LogSqlWhenTimeout">-1</configValue>
            <configValue key="IsLogActiveConnNum">true</configValue>
        </group>
    </module>

# 数据源和事务的关系

数据源可以是一个c3p0数据源,也可以是一个jndi数据源。

EOS6提供了对单个数据源的事务管理的实现(本地事务),即数据源事务管理器,也提供了利用全局事务(JTA)进行管理的方式。

  • 本地事务用于tomat环境下;
  • 全局事务用于其他的J2EE服务器(jboss/weblogic/websphere)中。

利用本地事务进行管理时,数据库连接的autoCommit属性会自动的设为false,这样就可以利用事务管理器对数据进行提交或者回滚。

在事务中通过对连接进行关闭,实际上并不能把数据库连接关掉,只有当事务进行提交或者回滚时,数据库连接才会真正的释放。

见下面的编程模型:

ITransactionManager manager = TransactionManagerFactory.getTransactionManager();
try{
    manager.begin();
    conn = ConnectionHelper.getConnection();
    //在事务环境下,获取的是当前事务中跟当前线程绑定的数据源连接,
    //如果没有找到数据源连接,则创建一个新的连接,绑定到事务中。
    //在非事务环境下,直接从数据源取得连接。
    conn.prepareStatement("select * from SERVICE_ENDPOINT");
    conn.close();//这里的连接并不是真正的释放掉
    conn.prepareStatement("select * from SERVICE_ENDPOINT");
    manager.commit();//事务提交的同时,连接会自动关闭
}catch(Exception e){
    e.printStackTrace();
    manager.rollback();
}

# 数据库连接接口及方法说明

# 包含的类

类名 描述
com.eos.infra.connection.ConnectionFactory 创建数据库连接的工厂类
com.eos.infra.connection.IConnectionSynchronization 数据库连接的回调接口。 用于在连接创建、关闭以及发生异常时执行回调。
com.eos.infra.connection.IResultSetSynchronization 结果集的回调接口。 用于在结果集创建、关闭以及发生异常时执行回调。
com.eos.infra.connection.IStatementSynchronization Statement、PrearedStatement、CallableStatement的回调接口。
com.eos.infra.connection.ISynchronizationManager 回调接口的管理者。
com.eos.common.connection.ConnectionHelper 数据库连接工具类,通过该工具类可以从指定或者默认的数据源中获取数据库连接。通过该工具类获取的数据库连接均为代理实例,该代理默认提供了:SQL的频次、执行时长统计、并且能够监控未关闭的连接。
com.eos.common.connection.DataSourceHelper 数据源助手类。 通过该助手类,可以获取指定业务构件配置的指定数据源、当前业务构件的指定数据源、默认数据源。

# 类的方法

# com.eos.infra.connecion.ConnectionFactory

  • 类的说明 创建数据库连接的工厂。

  • 类的方法

方法 说明
static Connection createConnection(DataSource dataSource) 创建一个没有同步回调的数据库连接
static Connection createConnection(DataSource dataSource, ISynchronizationManager synchronizationManager) 创建一个有回调同步的数据库连接,具体回调实现从ISynchronizationManager取

# com.eos.common.connection.ConnectionHelper

  • 类的说明 数据库连接工具类,通过该工具类可以从指定或者默认的数据源中获取数据库连接。通过该工具类获取的数据库连接均为代理实例,该代理默认提供了:SQL的频次、执行时长统计、并且能够监控未关闭的连接。

  • 类的方法

方法 说明
static Connection getConnection(String dataSourceName) 获取一个指定数据源名称的数据库连接代理
static Connection getConnection() 获取一个默认数据源的数据库连接代理
static Connection getContributionConnection(String contributionName, String dataSourceAlias) 获取一个Contribution中指定的数据源的数据库连接代理
static Connection getCurrentContributionConnection(String dataSourceAlias) 获取当前Contribution中指定的数据源的数据库连接代理

# com.eos.common.connection.DataSourceHelper

  • 类的说明 数据源助手类。通过该助手类,可以获取指定业务构件配置的指定数据源、当前业务构件的指定数据源、默认数据源。

  • 类的方法

方法 说明
static DataSource getDataSource(String dataSourceName) 根据数据源引用名称,获取应用级数据源。
static DataSource getDataSource() 获取应用级默认数据源。
static DataSource getContributionDataSource(String dataSourceAlias) 根据数据源的别名,获取当前Contribution中的指定数据源。
static DataSource getContributionDataSource(String contributionName, String dataSourceAlias) 根据contribution的名称和数据源的别名,获取数据源。
static DataSource getContributionDataSource() 获取当前Contribution中的默认数据源。
static String getReferencedDataSourceName(String contributionName, String dataSourceAlias) 根据Contribution名字和数据源别名取得对应的应用级数据源的名字。
static String getReferencedDataSourceName(String dataSourceAlias) 在当前Contribution中,根据数据源别名取得对应的应用数据源的名字。
static String[] dataSourceNames() 取得所有应用数据源的名字。

# 事务管理

传统上,J2EE开发者有两个事务管理的选择:全局或本地事务。

  • 全局事务由应用服务器管理,使用JTA;
  • 本地事务是和资源相关的,比如一个和JDBC连接关联的事务。

这个选择有深刻的含义。例如,全局事务可以用于多个事务性的资源(典型例子是关系数据库和消息队列)。使用本地事务,应用服务器不需要参与事务管理,并且不能帮助确保跨越多个资源(需要指出的是多数应用使用单一事务性的资源)的事务的正确性。

EOS6提供了一致的事务管理抽象模型;它能使开发者在任何环境下使用统一的编程模型,而不需要关心使用的是全局事务,还是本地事务。

EOS6目前只支持编程式的事务,不支持声明式的事务。

# 事务传播方式

通常在一个事务中执行的所有代码都会在这个事务中运行。但是,如果一个事务上下文已经存在,有几个选项可以指定一个事务性方法的执行行为。例如,简单地在现有的事务中继续运行(大多数情况);或者挂起现有事务,创建一个新的事务。

EOS6支持以下几种事务传播方式:

传播方式 数值 说明
PROPAGATION_REQUIRED 0 外部没有事务,启动一个新的事务;否则加入外部的事务
PROPAGATION_SUPPORTS 1 外部没有事务,则运行在非事务上下文中;否则,加入外部事务
PROPAGATION_MANDATORY 2 外部没有事务,抛出运行期异常;否则,加入外部事务
PROPAGATION_REQUIRES_NEW 3 无论外部是否存在事务,均启动一个新事务;如果存在外部事务,则先挂起外部事务,然后启动新事务
PROPAGATION_NOT_SUPPORTED 4 无论外部是否存在事务,都运行在非事务上下文中,如果外部有事务,则挂起外部事务
PROPAGATION_NEVER 5 如果外部存在事务,则抛出运行期异常,否则,运行在非事务上下文中

以上这些传播方式常量都定义在对外接口com.eos.common.transaction.ITransactionDefinition中。

在事务环境下,如果在事务的配置文件(参见事务管理器的相关配置 (opens new window))中不指定事务的传播方式,默认用PROPAGATION_REQUIRED。 同时允许在事务开始接口中设置事务的传播方式。

说明

事务的传播也支持跨应用服务器的ejb调用。有一点需要说明的是在weblogic环境下,对于跨domain之间的ejb调用的事务的传播,需要两个domain的名称不能相同,两个server的名称也不能相同。

# 事务隔离级别

在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同:

  • 未授权读取(Read Uncommited):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据操作,则另外一个事务不允许同时进行写数据操作,但允许其他事务读此行数据。该隔离级别可以通过"排他写锁"实现。
  • 授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过"瞬间共享读锁"和"排他写锁"实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
  • 可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过"共享读锁"和"排他写锁"实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
  • 序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过"行级锁"是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

EOS6支持以下几种隔离级别:

隔离级别 常量定义 数值
默认的隔离级别 ISOLATION_DEFAULT -1
未授权读 ISOLATION_READ_UNCOMMITTED 1
授权读取 ISOLATION_READ_COMMITTED 2
可重复读取 ISOLATION_REPEATABLE_READ 4
序列化 ISOLATION_SERIALIZABLE 8

如果在事务开始接口中不指定事务的隔离级别的话,那么就用用户在事务配置中指定的隔离级别。默认情况下,事务配置中的事务隔离级别为数据库默认的隔离级别。

相关数据库的默认的隔离级别如下:

  • oracle=2(ISOLATION_READ_COMMITTED)
  • db2=2(ISOLATION_READ_COMMITTED)
  • SQLServer=2(ISOLATION_READ_COMMITTED)
  • informix=0(TRANSACTION_NONE)

在事务环境下,如果在事务的配置文件(参见事务管理器的相关配置 (opens new window))中不指定事务的隔离级别,默认用ISOLATION_DEFAULT,即用相关数据库默认的隔离级别。

同时允许通过API动态的修改在事务中的隔离级别(参见事务管理器 (opens new window))。

# 事务管理器

事务管理器是操作事务的接口。可以通过它执行事务的开始、提交、回滚,以及查看事务状态等操作。

事务的编程模型如下:

ITransactionManager txManager=TransactionManagerFactory.getTransactionManager();
txManager.begin();//默认的事务传播方式是required
try{
txManager.begin();//默认的事务传播方式是required
try{
//do something......
txManager.commit();
}catch(Exception e){
txManager.rollback();
//throw e;
}
//do something......
txManager.commit();
}catch(Exception e){
txManager.rollback();
throw e;
}

下面对事务管理器的接口进行说明:

  • 事务开始
void begin(int... attrs)

其中参数attrs依次定义了事务的属性包括传播方式和隔离级别。

默认情况下不填写的话,事务传播用PROPAGATION_REQUIRED,隔离级别用数据库默认的隔离级别。

  • 事务提交
void commit()

提交事务,实际动作依赖于当前事务的启动方式和状态。

  • 如果当前的事务是新启动(比如用required new 传播属性开启的)的事务,并且该事务状态不为rollbackonly,则提交当前的事务;否则回滚当前的事务。若当前事务启动时,有外部事务被挂起,则还要恢复外部事务。

  • 如果当前的事务不是新启动的事务,不作任何操作。

  • 如果发现事务状态为rollbackonly,则对外抛出IllegalStateException。

  • 事务回滚

void rollback()

回滚事务,实际动作依赖于当前事务的启动方式和状态。

如果当前事务不是新启动的事务,则设置事务状态为rollbackonly;否则对事务做回滚操作,并且若当前事务启动时,有外部事务被挂起,则完成回滚操作后还要恢复外部事务。

注意

WebLogic环境下,在跨weblogic domain之间的带JTA全局事务传播的ejb调用过程中,有两个限制条件:

  • 两个domain之间必须互相信任。
  • 两个domain之间不允许domain的名称,server的名称相同。

EOS安装后,domain的名称都是eosdomain,并且所有的应用都是部署在AdminServer上的,因此在跨domain之间调用JTA事务时,需要手工创建一个domain,名称不同于eosdomian,在其上创建一个server,名称不同于AdminServer,然后把eos的应用部署在这个server上,并且建立该domain和eosdomain之间的信任。

# 事务管理器的相关配置

事务管理的配置文件在系统配置sys-config.xml中,列出了针对不同服务器的相关配置,系统会根据不同的服务器自动选取对应的事务管理策略。

<module name="TxManager">
<group name="Default">//用于tomcat环境
    <configValue key="Provider">
    com.primeton.common.transaction.impl.datasource.DataSourceTransactionManagerSetProvider
    </configValue>
    <configValue key="Propagation">
        PROPAGATION_REQUIRED
    </configValue>
    <configValue key="Isolation">ISOLATION_DEFAULT</configValue>
</group>
<group name="Jboss">//用于Jboss环境
    <configValue key="Provider">
    com.primeton.common.transaction.impl.jta.JbossJtaTransactionManagerProvider
    </configValue>
    <configValue key="TransactionManager">
        java:/TransactionManager
    </configValue>
    <!-- configValue key="UserTransaction">
        java:comp/UserTransaction
    </configValue -->
    <configValue key="Propagation">
        PROPAGATION_REQUIRED
    </configValue>
    <configValue key="Isolation">ISOLATION_DEFAULT</configValue>
</group>
<group name="Weblogic">//用于Weblogic环境
    <configValue key="Provider">
        com.primeton.common.transaction.impl.jta.WebLogicJtaTransactionManagerProvider
    </configValue>
    <configValue key="Propagation">
        PROPAGATION_REQUIRED
    </configValue>
    <configValue key="Isolation">ISOLATION_DEFAULT</configValue>
</group>
<group name="Websphere">//用于Websphere环境
    <configValue key="Provider">
        com.primeton.common.transaction.impl.jta.WebSphereJtaTransactionManagerProvider
    </configValue>
    <configValue key="Propagation">
        PROPAGATION_REQUIRED
    </configValue>
    <configValue key="Isolation">ISOLATION_DEFAULT</configValue>
</group>
</module>

# 事务同步

EOS6在事务操作过程中,提供了事务同步机制;可以在事务挂起、事务恢复、事务提交之前、事务完成之前,以及事务完成之后执行其他用户自定义的扩展。

可以通过事务同步注册接口把事务同步注册进来。

关于事务同步和同步注册接口参见接口及方法说明 (opens new window)

# 事务状态

EOS6中事务有以下几种状态:

事务状态 说明
Status.STATUS_NO_TRANSACTION 当前线程中没有事务存在
Status.STATUS_MARKED_ROLLBACK 当前事务已经被设置为setRollback标记
Status.STATUS_ACTIVE 当前事务处于运行中
Status.STATUS_COMPLETED 当前事务已经回滚或提交

# 事务在定时器中的应用

在定时器中,对定时器的管理可以参与到外部的事务中,也可以不参与外部的事务。

用户可以用两种编程方式,对定时器进行管理。

  • 非参与外部事务的方式

    直接调用定时器的管理接口。

//创建一个任务和触发器
ScheduleManager.addTask(task,trigger);
  • 参与外部事务的方式
ITransactionManager manager = TransactionManagerFactory.getTransactionManager();
manager.begin();
//创建一个任务和触发器
ScheduleManager.addTask(task,trigger);
    manager.commit();

# 事务在逻辑流中的应用

在逻辑流中调用子逻辑流、运算逻辑和服务时,有两种事务传播方式:join和suspend。

对于是否接受外部事务传递的定义,运算逻辑和服务总是接受外部事务传递的;逻辑流可以接受,也可以不接受外部事务传递。

  • 在调用逻辑流、运算逻辑和服务时,如果以join的方式调用,若设置了接受外部事务传递,那么不做任何处理直接调用;若指定了不接受外部事务传递,此时如果外面没有事务,则不做任何处理直接调用,如果外面有事务,那么就抛出异常。
  • 在调用逻辑流、运算逻辑和服务时,如果以suspend方式调用,那么就以PROPAGATION_REQUIRES_NEW方式开启事务。

在逻辑流中如果嵌套多个事务开始图元,那么实际上只有第一个事务开始图元起作用,内部嵌套的事务开始图元被忽略。

对于跨多个应用之间的服务的调用,只有用ejb绑定的方式(即在服务存储库中,该服务被声明是用ejb方式调用的)调用服务,才支持事务的传播。

# 事务接口及方法说明

# 包含的类

类名 描述
com.eos.common.transaction.ITransactionManager 事务管理接口,通过该接口可以启动、提交、回滚事务
com.eos.common.transaction.TransactionManagerFactory 事务管理工厂类,通过它可以创建事务管理接口
com.eos.common.transaction.ITransactionSynchronization 事务同步接口,需要用户扩展
com.eos.common.transaction.TransactionSynchronizationManager 事务同步接口注册管理器

# 类的方法

# com.eos.common.transaction.ITransactionManager

  • 类的说明 事务管理接口,通过该接口可以启动、提交、回滚事务。

  • 类的方法

方法 说明
方法 说明
void begin(int... transactionAttributes) 启动事务,事务的启动具体行为依赖于参数指定的事务传播方式; 只能输入两个事务属性:1、事务传播方式;2、隔离级别
void commit(); 提交事务,实际动作依赖于当前事务的启动方式和状态
void rollback(); 回滚事务,回滚的实际动作依赖事务状态。
void setRollbackOnly(); 将当前事务标识为rollback-only
Status getStatus(); 获取当前事务状态,可能的状态有四种:Unknown macro: {span}STATUS_COMPLETED 已完成STATUS_ACTIVE 活动STATUS_MARKED_ROLLBACK 标记为回滚STATUS_NO_TRANSACTION 不在事务中

# com.eos.common.transaction.TransactionManagerFactory

  • 类的说明 事务管理工厂类,通过它可以创建事务管理接口。

  • 类的方法

方法 说明
static ITransactionManager getTransactionManager() 获取默认的事务管理器

# com.eos.common.transaction.ITransactionSynchronization

  • 类的说明 事务同步接口继承了spring中的TransactionSynchronization接口,需要用户扩展。

  • 类的方法

方法 说明
void suspend() 挂起事务时执行的操作
void resume() 恢复事务时执行的操作
void beforeCommit(boolean readOnly) 在事务提交之前进行
void beforeCompletion() 在调用事务commit/rollback之前执行
void afterCompletion(int status) 在调用事务commit/rollback之后执行

# com.eos.common.transaction.TransactionSynchronizationManager

  • 类的说明 事务同步接口注册管理器。

  • 类的方法

方法 说明
static void registerSynchronization(ITransactionSynchronization synchronization) 注册一个事务同步回调
static boolean isExistSynchronization(ITransactionSynchronization synchronization) 判断是否注册了该事务同步回调

# EOS元数据

EOS元数据描述了EOS Server、EOS应用、构件包、逻辑流、数据实体这些对象在运行时的基本信息。

描述对象 JavaClass
EOS Server com.eos.runtime.metadata.IServerMetaData
EOS 应用 com.eos.runtime.metadata.IApplicationMetaData
构件包 com.eos.runtime.metadata.IContributionMetaData
数据实体 com.eos.runtime.metadata.ISDOTypeMetaData
逻辑流 com.eos.runtime.metadata.IBizMetaData

以上对象有一个树状层次关系:

IServerMetaData
       |
       |---IApplicationMetaData
               |
               |---ISDOTypeMetaData
               |
               |---IContributionMetaData
                       |
                       |---IBizMetaData

由上图我们可以看出,通过元数据可以找出一个EOS Server中有多少EOS应用,每个应用中定义了多少数据实体和构件包,每个构件包中包含了多少逻辑流。

说明

因为数据实体解析完成后,只有targetNamespace的信息,并没有保存它被定义时所在的构件包,所以通过元数据只能查找在每个应用下所有的数据实体。

通过下面的示例代码,可以获取eos-default的应用中有多少数据实体。

//获取EOS server的所有应用时,如果传入的是null,则获取的是当前server的所有应用.
IApplicationMetaData[] applications = MetaDataHelper.getApplicationMetaDatas(null);
for (IApplicationMetaData application : applications) {
     if ("eos-default".equals(application.getName())) {
      ISDOTypeMetaData[] sdoTypeMetadatas = application.getSDOTypeMetaDatas();
      for (ISDOTypeMetaData metadata : sdoTypeMetadatas)
        System.out.println(metadata.getName());
     }//end if
}// end for
 
//下面的代码可以实现和上面一样的功能
//获取应用的数据实体的时候,如果传入的是null,则获取的是当前应用的所有数据实体.
ISDOTypeMetaData[] sdoTypeMetadatas = MetaDataHelper.getSDOTypeMetaDatas(null);
for (ISDOTypeMetaData metadata : sdoTypeMetadatas) {
     System.out.println(metadata.getName());
}

说明

上面2段代码,第一段是通过获取应用,然后再获取应用所包含的数据实体;第二段是直接获取当前应用所包含的数据实体。 通过MetaDataHelper的接口获取元数据是每次都会实时的去获取最新的元数据,这在应用被频繁动态更新时有效。但是每次都调用MetaDataHelper的接口会影响效率,为了提高效率,可以通过MetaDataHelper获取一个EOS Server或一个EOS应用的元数据(IServerMetaData或IApplicationMetaData),然后通过IServerMetaData或IApplicationMetaData获取其所包含的页面流、逻辑流、数据实体,这样就会缓存这些元数据。

# 元信息接口及方法说明

# 包含的类

类名 描述
com.eos.runtime.metadata.MetaDataHelper 元数据的助手类,可以获取指定类型的元数据信息
com.eos.runtime.metadata.IServerMetaData Server的元数据
com.eos.runtime.metadata.IApplicationMetaData 应用的元数据
com.eos.runtime.metadata.ISDOTypeMetaData 实体的元数据
com.eos.runtime.metadata.IContributionMetaData 构件包的元数据
com.eos.runtime.metadata.IBizMetaData 逻辑流的元数据

说明

MetaDataHelper类中的方法会每次都去获取Server运行时的最新元数据,这样会消耗性能。对于没有动态更新的应用来说,建议使用通过IServerMetaData或IApplicationMetaData获取所包含的逻辑流等元数据,因为这样会缓存这些元数据。

# 类的方法

# com.eos.runtime.metadata.MetaDataHelper

  • 类的说明 元数据的助手类,可以获取指定类型的元数据信息。

  • 类的方法

方法 说明
IServerMetaData[] getManagedServerMetaDatas() 获取主管Server下的所有被管Server元数据
IApplicationMetaData[] getApplicationMetaDatas(IServerMetaData serverMetaData) 获取指定Server下的应用的元数据,如果为null,则取当前server下应用的元数据
IContributionMetaData[] getContributionMetaDatas(IApplicationMetaData applicationMetaData) 获取应用下的Contribution元数据,如果为null,则认为是当前应用
ISDOTypeMetaData[] getSDOTypeMetaDatas(IApplicationMetaData applicationMetaData) 获取应用下的实体类型的元数据,如果为null,则认为是当前应用
IBizMetaData[] getBizMetaDatas(IContributionMetaData contributionMetaData) 获取构件包下的逻辑流的元数据,如果为null,则认为是当前构件包

# com.eos.runtime.metadata.IServerMetaData

  • 类的说明 server的元数据。

  • 类的方法

方法 说明
String getName() 获取Server名称
String getIp() 获得IP地址
boolean isAdminServer() 是否是主管Server
int getAdminPort() 获得管理端口
String getGroupName() 获取Server所在组的名称
boolean isGroup() 是否是组里的server
IApplicationMetaData[] getApplicationMetaDatas() 获取Server里的应用元数据
String[] getApplicationNames() 获取Server里的应用的名称
IApplicationMetaData getApplicationMetaData(String applicationName) 获取指定应用的元数据

# com.eos.runtime.metadata.IApplicationMetaData

  • 类的说明 应用的元数据。

  • 类的方法

方法 说明
String getName() 获取应用名称
String getWebContext() 获取应用的Web上下文
int getStatus() 获取应用的状态,状态的常量定义见com.eos.system.application.mbean.ApplicationModel.DEPLOY_STATUS_NO_DEPLOY等的以DEPLOY_STATUS_开头常量定义
IContributionMetaData[] getContributionMetaDatas() 获取应用下的构件包的元数据
String[] getContributionNames() 获取应用下的构件包的名称
IContributionMetaData getContributionMetaData(String contributionName) 获取指定的构件包的元数据
ISDOTypeMetaData[] getSDOTypeMetaDatas() 获取应用下SDO实体的元数据
String[] getSDOTypeNames() 获取应用下SDO实体的名称
String[] getSDOURIs() 获取应用下SDO实体的targetNamespace列表
ISDOTypeMetaData[] getSDOTypeMetaDatas(String uri) 获取应用下targetNamespace里包含的SDO实体
ISDOTypeMetaData getSDOTypeMetaData(String typeName) 获取应用下指定的SDO实体的元数据
IServerMetaData getServerMetaData() 获取应用所属的server的元数据

# com.eos.runtime.metadata.ISDOTypeMetaData

  • 类的说明 实体的元数据。

  • 类的方法

方法 说明
String getName() 获取实体的类型名称
String getURI() 获取类型所属的URI
String getShortName() 获取类型的短名称
boolean isOpenable() 是否是可动态扩展的
ISDOPropertyMetaData[] getPropertyMetaDatas() 获取实体里的所有属性元数据
String[] getPropertyNames() 获取实体里的所有属性名称
ISDOPropertyMetaData getPropertyMetaData(String propertyName) 获取实体里的指定属性元数据
IApplicationMetaData getApplicationMetaData() 获取实体所属应用的元信息

# com.eos.runtime.metadata.ISDOPropertyMetaData

  • 类的说明 实体属性的元数据。

  • 类的方法

方法 说明
String getName() 获取属性名称
ISDOTypeMetaData getTypeMetaData() 属性对应类型元数据
boolean isMany() 是否是多值
ISDOTypeMetaData getContainerTypeMetaData() 获取包含该属性的实体的元数据

# com.eos.runtime.metadata.IContributionMetaData

  • 类的说明 构件包的元数据。

  • 类的方法

方法 说明
方法 说明
String getName() 获取构件包的名称
String getLocation() 获取构件包的绝对路径
String getWebContext() 获取构件包的webcontext
String getVersion() 获取构件包的版本
String[] getRequiredContributionNames() 获取依赖的构件包名称
String getDisplayName() 获取构建包的显示名
IBizMetaData[] getBizMetaDatas() 获取构件包下的逻辑流的元数据
String[] getBizNames() 获取构件包下的逻辑流的名称
IBizMetaData getBizMetaData(String bizName) 获取构件包下指定的逻辑流的元数据
IApplicationMetaData getApplicationMetaData() 获取构件所属应用的元信息

# com.eos.runtime.metadata.IBizMetaData

  • 类的说明 逻辑流的元数据。

  • 类的方法

方法 说明
String getName() 获取逻辑流名称
ITypeMetaData[] getParameterTypes() 获取逻辑流输入参数类型
ITypeMetaData[] getReturnTypes() 获取逻辑流返回值类型
String[] getParameterNames() 获取逻辑流输入参数的名称
String[] getReturnNames() 获取逻辑流返回值的名称
IContributionMetaData getContributionMetaData() 获取逻辑流所属的构件包元数据

# com.eos.runtime.metadata.IOperationMetaData

  • 类的说明 逻辑流的元数据。

  • 类的方法

方法 说明
String getName() 获取操作名称
ITypeMetaData[] getParameterTypes() 获取操作输入参数类型
ITypeMetaData[] getReturnTypes() 获取操作返回值类型

# 分布式调用

# 系统内服务调用

本章节所描述的是同一个系统内的不同应用之间的调用规范:

  • provider:服务提供端在api-module定义好feignClient和相关api接口,并在impl-module中实现这些接口,api-module可直接打成jar供其他consumer依赖即可。
  • consumer:服务调用端首先引入服务端提供的api-jar,然后直接使用注解@Autowired把feignClient注入到当前应用中使用即可,调用端无需自己实现feignClient以及相关api接口

provider服务提供端

api模块pom引入相关依赖:eos服务自带此依赖,非eos服务需要引入

<dependency>
    <groupId>com.primeton.eos</groupId>
    <artifactId>eos-server-starter</artifactId>
</dependency>

检查配置

配置文件进行feign配置

feign.hystrix.enabled=true

启动类是否有@EnableFeignClients和@EnableHystrix注解

参数 说明
@EnableFeignClients springcloud原生注解,扫描和注册feign客户端bean定义
@EnableHystrix springcloud原生注解,启动熔断降级服务
feign.hystrix.enabled 是否开启hystrix。true:开启,false:不开启

feignClient编写

在api模块创建feignclient接口,代码示例如下:

package com.primeton.eos.feign;

import com.primeton.eos.po.Stock;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;


@FeignClient(name = "STOCK-MANAGE", configuration = FeignAutoConfiguration.class, fallback = StockServerFallback.class)
public interface StockServerFeignClient {
    @RequestMapping(value = "/api/stock/{versionIds}", method = GET)
    Stock[] listStockByProductVersionIds(@PathVariable("versionIds") String versionIds);

    @RequestMapping(value = "/api/stock/bulk-delete", method = POST)
    void batchStockByVersionIds(@RequestBody Long[] versionIds);

    @RequestMapping(value = "/api/stock/{versionId}", method = POST)
    void deleteStockByVersionId(@PathVariable("versionId") Long versionId);
}

创建对应的fallback

@Component
public class StockServerFallback implements StockServerFeignClient {
    @Override
    public Stock[] listStockByProductVersionIds(String versionIds) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return null;
    }

    @Override
    public void batchStockByVersionIds(Long[] versionIds) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return;
    }

    @Override
    public void deleteStockByVersionId(Long versionId) {
        //TODO 这里的fallback用户可根据自身实际业务场景来定具体的fallback逻辑
        return;
    }
}

# consumer服务调用端

pom中引入依赖

在pom中引入provider-api模块即可

在开发中的用法

注解使用@autowrite注入即可,代码示例如下:

    @Autowired
    private StockServerFeignClient feignClient;

    public void delete(String[] productIds) {
        if (productIds == null) {
            return;
        }
        mapper.deleteBatch(productIds);
        versionMapper.deleteBatchByProductIds(productIds);

        Map<String,Object> param = new HashMap<>();
        List<Long> versionIds = new ArrayList<>();
        Arrays.stream(productIds).forEach(productId -> {
            param.put("productId",productId);
            List<ProductVersionVO> list = versionMapper.listByProductId(param);
            getVersionIds(list, versionIds);
        });

        //系统间不同应用调用:同步删除对应库存信息
        feignClient.batchStockByVersionIds(versionIds.toArray(new Long[versionIds.size()]));
    }

# 跨系统服务调用

本章节所描述的是不同系统间的应用之间的调用规范:

  • provider:服务提供端无需做特殊处理,正常提供rest api即可
  • consumer:服务调用端通过resttemplate请求provider所在系统的网关,然后由网关route转发到provider

provider相关配置

服务提供者,发布api并授权,获取订阅码

服务提供者将服务通过governor发布到当前系统中的网关上,并授权给服务消费者以及生成服务订阅凭证,操作如下: 服务发布:

img

服务订阅授权:

img

网关配置路由:

img

consumer相关配置

订阅凭证和网关path配置

因为服务调用端与服务提供端分属不同的系统,运行期不能直接对服务进行访问(会被拦截,无权访问),需要通过网关路由转发服务调用请求,因此需要配置网关的PATH,供服务调用端发送服务请求,调用服务提供端发布的API。在运行期,网关PATH和订阅凭证可能会变更,因此不宜写死在代码中,需要使用Governor微服务管理平台提供的能力对应用中的网关PATH配置进行动态刷新。要使用Governor的配置能力,

img

配置完成之后,在开发中使用@RefreshScope+@Value读取配置即可,如配置有变化应用会监听配置进行热更新处理。

增加sudkey配置类

ApiSubKeyConfig:用来配置apisubkey,跨系统调用api需要在head中添加apisubkey用来给网关验证调用者是否有该api调用的权限,subKey可选择存储在数据库、配置中心、配置文件等等,存储方式可自定义,用法唯一,本示例是根据subKey配置在配置文件中写的(支持nacos热更新),如来源是其他地方,只需添加对应subKey获取方式即可

package com.primeton.eos.config;

import com.primeton.eos.dap.sdk.api.webclient.SDKApiSubscriberProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@RefreshScope
public class ApiSubKeyConfig implements SDKApiSubscriberProvider {

    @Value("${provider.gateway.path}")
    private String gatewayPath;

    @Value("${product-stock.subkey}")
    private String subkey;

    @Override
    public ApiSubscriber getSubscriber(String host, String requestPath) {
        if (gatewayPath.indexOf(host) != -1) {
            return new ApiSubscriber(subkey);
        } else {
            return new ApiSubscriber();
        }
    }
} 

使用sdkResttemplate进行远程调用:

@Service
@RefreshScope
public class OrderServiceImpl {

    @Value("${provider.gateway.path}")
    private String gatewayPath;

	@Value("${product-stock.subkey}")
    private String subkey;

    @Autowired
	@Qualifier("sdkRestTemplate")   //sdkRestTemplate
	@LoadBalanced
	private RestTemplate sdkRestTemplate;
	
	@Override
	public OrderVO queryOrderDetail(Long orderId) {
	    OrderVO vo = new OrderVO();
	    Order order = orderRepository.findById(orderId).orElse(null);
	
	    try {
	        String versionIds = getVersionIds(order);
	        //跨系统调用
	        List<ProductVersionVO> products =
	                Arrays.asList(sdkRestTemplate.getForObject(gatewayPath + API_PATH_PRODUCT_LIST_PRODUCT_VERSION + "/{versionIds}", ProductVersionVO[].class, versionIds));
	        vo.setProducts(products);
	    } catch (Exception e) {
	        //记录日志
	        logger.error("Error queryOrderDetail in '{}', error info '{}'", orderId, e.getMessage(), e);
	    }
	
	    BeanUtils.copyProperties(order, vo);
	    return vo;
	}
}

自此,跨系统调用配置完成

上次更新: 2023/7/20下午12:25:28