• Drools入门系列(一)HelloWorld
  • Drools入门系列(二)HelloWorld详解之Sample.drl
  • Drools入门系列(三)HelloWorld详解之kmodule.xml
  • Drools入门系列(四)HelloWorld详解之JUnit Test类
  • Drools入门系列(五)KIE概论
  • Drools入门系列(六)KIE之基础API详解
  • Drools入门系列(七)KIE之kmodule.xml
  • Drools入门系列(八)以编码方式完成kmodule的定义

Drools入门系列(一)HelloWorld

1、什么是Drools

Drools是用Java语言编写的开放源码的规则引擎。

那什么是规则引擎呢?参考自 百度百科 里面的定义:

规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。

Drools使用RETE算法对规则进行求值,在Drools6.0(当前最新版本)中还引进了PHREAK算法,Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。Drools 还具有其他优点:

  • 非常活跃的社区支持
  • 易用
  • 快速的执行速度
  • 在 Java 开发人员中流行
  • 与 Java Rule Engine API(JSR 94)兼容

2、一些说明

目前本人正在学习Drools过程中,准备编写一个系列文章,来记录自己的学习过程和学习心得。由于本人目前也是新手,因而文章中不可避免有着一些错误的理解,这个只有希望读者自己来判断了。

本系列文章基于当前最新版本Drools 6.0.1 Final版本。

在学习Drools的过程中,我也会编写一些学习的例子,这些例子都放在GitHub上:

https://github.com/XiongZhijun/nut-drools.git

然后我会通过一些tag来标记一些例子,读者可以自己checkout相应的tag来查看例子,一般这些tag都会有相应的注释的。

Git入门学习可以参考:Git入门资料

3、Hello World

从GitHub上下载nut-drools工程(使用上节里面的地址),checkout training_1标签,就可以看到HelloWorld的例子。工程结构如下:

drools-helloworld-project
这是一个典型的Maven工程,包含pom.xml文件,有src/main/java、src/main/resources,以及相应的测试目录。其中:

src/main/resources/META-INF :该目录中存放了一个kmodule.xml文件,该文件中声明了若干已经定义了的规则、流程文件。
src/main/resources :该目录的子目录dtables和rules中存放了定义了规则的规则文件,本例中包含了两种定义规则的方式,一种是通过DRL(后缀.drl)文件来定义的,一种是通过Excel文件(后缀.xls)来定义的。
src/test/java :该目录中定义了单元测试用例,就是直接测试运行规则的。
现在可以直接执行单元测试,查看测试结果了。

4、Drools and Eclipse

Drools提供了Eclipse插件,可以在 http://www.jboss.org/drools/downloads 页面进行下载“Drools and jBPM Tools”,在这个下载包里面就包含有Eclipse插件。

装好插件后可以使用Drools透视图,然后就可以直接创建Drools Project了:

new-drools-project-1

new-drools-project-2

new-drools-project-3
在这一步可以选择一些例子,这样在工程创建好之后就会有相应的例子程序了,上面提供的HelloWorld就是这个里面的自动创建的例子。

new-drools-project-4
输入好GroupId、AtifactId、Version后点击Finish就可以创建好一个工程了。

大家可以注意到这个工程也是一个Maven结构的工程,除了没有pom.xml之外。不知道没有pom.xml文件这个是Drools有自己的考虑之外呢,还是这是一个bug。

PS:drools 6.5 的时候,是可以使用Eclipse插件进行创建maven工程了。

Drools入门系列(二)——HelloWorld详解之Sample.drl

我们来先看一下一个标准的规则文件定义文件是怎么样的:

package os.nut.droolsimport os.nut.drools.Message;rule "Hello World"whenm : Message( status == Message.HELLO, myMessage : message )thenSystem.out.println( myMessage );m.setMessage( "Goodbye cruel world" );m.setStatus( Message.GOODBYE );update( m );
endrule "GoodBye"whenMessage( status == Message.GOODBYE, myMessage : message )thenSystem.out.println( myMessage );
end

Java程序员可以很清楚看出来,这个规则文件跟一个Java文件非常类似,里面包含了很多Java语句,不用怀疑,这些就是Java代码,而不是类Java语法的代码。

上面这个规则文件里面包含4个部分:package、import和两个rule:

  • package:package语句的定义跟Java里面的package语句类似,唯一不同的就是在DRL文件中package后面跟的包名是不需要跟文件目录有对应关系的,上例就可以看出来这个不同:package定义是os.nut.drools,而所在的目录是rules。

  • import:import语句的含义跟java中是一样的,就是如果在本文件中需要使用某些类的话,需要通过import语句引入进来,如果需要的类在 package定义的包中就不需要再引入了,这个跟Java的概念是一致的。在上例中的import语句其实是没有必要的。

  • rule:上例定义了两个rule,也就是定义了两个规则。一个规则以rule关键字开始,以end关键字结束。上面一个rule包含了三个部分,分别是name、when、then。

  • name紧跟在rule关键字之后,可以是一个以引号(双引号、单引号均可)包含的字符串,可以包含空格等字符,如果字符串只包含字母、数字、下划线(也就是Java变量命名规则)的话,也可以不用引号,但是推荐使用引号。

  • when语句的意思就是执行下面then语句的条件,也就是说当when条件满足的情况下,才会执行then,类似于Java里面的if语句,为什么用when而不用if呢,这是因为when代表的意思是当什么“ 事件 ”发生时,当什么“ 事实 ”存在时,然后执行then。

  • then语句就是执行的动作,就是当什么事件发生,或者什么事实存在时,执行的动作序列。then后面的语句也就是Action。

  • 事件和事实是什么东西呢?在Drools里面这两个分别称之为“Event”和“Fact”,“事件”其实也是“事实”,只不过是一种特殊的事实而已。

  • 事实是什么东西呢?一个事实其实就是一个POJO,只不过这个Java对象是存放在一个特殊的空间里面,这个空间就是“Working Memory”,所有存放在Working Memory里面的对象都是事实(Fact)。when语句就是检查在Working Memory里面是不是存在满足条件的事实。

  • 事件呢?怎么个特殊法?这个我们可以暂时不用管它,到后面学习CEP的时候自然就会理解了。CEP是什么?现在不用管它!
    when语句解读:

m : Message( status == Message.HELLO, myMessage : message )

上面是rule “Hello World”的when语句。这个语句是什么意思呢?它的意思就是:

当存在一个Message对象,并且这个Message的status字段值为Message.HELLO的时候,就可以执行下面的then语句了。用自然语言描述就是:当存在一个状态为HELLO的消息的事实时,就执行下面的动作,否则就不做。

其中Message()就是执行类型匹配,意思就是要求Working Memory中存在类型为Message的对象(事实),然后status==Message.HELLO语句呢,就是约束条件,表示该Message对象的status字段为HELLO才符合条件。

另外的m和myMessage分别表示什么呢?m加冒号的意思是将这个Message对象赋值给m,而myMessage加冒号表示将这个Message对象的message字段的值赋值给myMessage变量。然后在下面的then语句中使用这些定义的变量了。
then语句解读:

System.out.println( myMessage );
m.setMessage( "Goodbye cruel world" );
m.setStatus( Message.GOODBYE );
update( m );

这个例子里面前三句都是普通的Java语句,唯一不同的就是下面这个update语句,这个语句的意思就是通知规则引擎m对象发生变化了,m是什么?m就是一个存放在Working Memory里面的一个Message事实,这句话就是说m这个事实发生了变化,那么规则引擎就需要重新进行规则运算,在本例中就是会在执行了update之后执行下面的“GoodBye”规则。

为什么执行“GoodBye”规则?GoodBye规则需要匹配的是status为GOODBYE的Message事实,但是一开始并没有这样的事实存在,只有当“Hello World”规则执行到了update语句的时候,更新了Message事实,这个时候规则引擎重新运算规则,WorkingMemory中就存在status为GOODBYE的Message事实了,“GoodBye”规则就会运行了,这个从控制台输出中就可以看出来了。


Drools入门系列(三)——HelloWorld详解之kmodule.xml

kmodule.xml文件存放在src/main/resources/META-INF/文件夹下。

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"><kbase name="rules" packages="rules"><ksession name="ksession-rules"/></kbase><kbase name="dtables" packages="dtables"><ksession name="ksession-dtables"/></kbase>
</kmodule>

这个kmodule.xml的文件的定义非常简单,其实也很容易理解:

  • 一个kmodule里面包含了两个kbase,这个也是我们这个例子里面的两个用例,分别对应drl规则文件的例子,一个是对应Excel表格的规则例子。

  • 每一个kbase都有一个name,可以取任意字符串,但是不能重名。

  • 然后都有一个packages,可以看到packages里面的字符串其实就是src/main/resources下面的文件夹的名称,或者叫包名,规则引擎会根据这里定义的包来查找规则定义文件。可以同时定义多个包,以逗号分隔开来就行。

  • 每一个kbase下面可以包含多个ksession,当然本例中都自定义了一个。

  • 每一个ksession都有一个name,名字也可以是任意字符串,但是也不能重复。

  • kbase和ksession里面的name属性是全局不能重复的。

  • kbase和ksession中其实还有很多其它的属性,但是在这里不是很重要,就先不提了,后面我们会一一讲解的。

这样一个kmodule.xml文件就建立好了。

看完这个大家肯定都会有疑问:kmodule、kbase、ksession是什么东东?有什么含义吗?在接下来的章节中会一一解答的,这里大家知道这样的东西,然后依葫芦画瓢就可以了,在初学过程中这样也就够了。


Drools入门系列(四)——HelloWorld详解之JUnit Test类


在本例中,有两个测试类,分别是DroolsTest和DecisionTableTest,分别对应DRL规则文件的测试和Excel表格规则的测试。两者的结果是一样的。基于Excel的方式我们先不管它,后面我会开辟专门的章节来讲述。
DroolsBaseTest.java

public abstract class DroolsBaseTest {protected KieServices kieServices;protected KieContainer kieContainer;@Beforepublic void setUp() {kieServices = KieServices.Factory.get();kieContainer = kieServices.getKieClasspathContainer();}}

这是一个抽象类,就是将一些单元测试的公共的代码抽取到了本类中,在这里定义了两个对象kieServices和kieContainer,这个是我们执行规则时必备的两个对象,这两个对象的具体意义,我们后面再讨论,这里只需要知道他们是我们执行规则必备的对象就可以了。

DroolsTest.java

public class DroolsTest extends DroolsBaseTest {@Testpublic void test() {KieSession kSession = kieContainer.newKieSession("ksession-rules");Message message = new Message();message.setMessage("Hello World");message.setStatus(Message.HELLO);kSession.insert(message);kSession.fireAllRules();}
}

这个单元测试类继承自DroolsBaseTest,演示了一个规则运行的例子。

  • 利用kieContainer对象创建一个新的KieSession,创建session的时候我们传入了一个name:“ksession-rules”,这个字符串很眼熟吧,这个就是我们定义的kmodule.xml文件中定义的ksession的name。

  • KieSession就是一个到规则引擎的链接,通过它就可以跟规则引擎通讯,并且发起执行规则的操作。

  • 然后通过kSession.fireAllRules方法来通知规则引擎执行规则。

这样一个完整的Drools例子就完成了,包含了规则定义(DRL文件编写)、模块定义(kmodule.xml编写)、执行代码编写三个过程。


Drools入门系列(五)——KIE概论

1、引言

在上一章节我们用到了几个类和他们的对象:KieServices、KieContainer、KieSession,新入门的人肯定很困惑,这几个类都是干啥的,都有什么作用啊?然后再kmodule.xml配置文件里面配置了kbase、ksession,这些东西都是什么玩意?本章以及后面可能的几章就是要解决这些问题。

2、什么是KIE?

KIE是jBoss里面一些相关项目的统称,下图就是KIE代表的一些项目,其中我们比较熟悉的就有jBPM和Drools。

这些项目都有一定的关联关系,并且存在一些通用的API,比如说涉及到构建(building)、部署(deploying)和加载(loading)等方面的,这些API就都会以KIE作为前缀来表示这些是通用的API。前面看到的一些KieServices、KieContainer、KieSession类就都是KIE的公共API。

总的来说,就是jBoss通过KIE将jBPM和Drools等相关项目进行了一个整合,统一了他们的使用方式。像KieServices这些KIE类就是整合后的结果,在Drools中这样使用,在jBPM里面也是这样使用。

3、KIE项目生命周期

一个Drools应用项目其实就是一个KIE项目,KIE的生命周期其实就是Drools和jBPM这些项目的生命周期。

KIE项目生命周期包含:编写(Author)、构建(Build)、测试(Test)、部署(Deploy)、使用(Utilize)、执行(Run)、交互(Work)、管理(Manage)。

编写:编写就是编写规则文件或者流程文件;
构建:就是构建一个可以发布部署的组件,在KIE中就是构建一个jar文件;
测试:在部署到应用程序之前需要对规则或者流程进行测试;
部署:就是将jar部署到应用程序,KIE利用Maven仓库来进行发布和部署;
使用:就是加载jar文件,并通过KieContainer对jar文件进行解析,然后创建KieSession;
执行:系统通过KieSession对象的API跟Drools引擎进行交互,执行规则或者流程;
交互:用户通过命令行或者UI跟引擎进行交互;
管理:管理KieSession或者KieContainer对象。

4、KIE & Maven

通过前面的知识我们了解到Drools工程其实就是一个Maven工程,有着Maven工程标准的结构,然后Drools在这个基础上也定义了一个自己的存储结构:

drools的标准存储结构就是在src/main/resources文件夹下面存储规则文件(包括DRL文件和Excel文件),然后在META-INF文件夹下面创建一个kmodule.xml文件用来存储规则定义声明。

Drools项目最终都是打包成jar然后进行发布部署的(KIE项目生命周期提到的),这样定义工程结构和打包发布方式的根本原因就是——Maven!

上图描述了KIE项目(包括Drools)的打包、发布、部署过程,就是一个KIE项目按照上面定义的工程结构进行设计开发,然后通过mvn deploy命令发布到Maven仓库,然后应用程序可以通过mvn install将发布好的jar包下载安装到本地应用程序中,最后通过KieServices等API就可以直接使用这些发布好的规则了。

为什么我们写的JUnit Test类里面驱动一个规则的代码非常简单,就是因为Drools定义了上面的一套规范,按照规范来编写、发布、部署规则之后就可以确保以最简单的方式来使用Drools等KIE项目。这也是惯例优于配置的一种体现。

所以我们说一个Drools项目工程就是一个Maven项目工程,或者说一个KIE项目工程就是一个Maven工程。

KIE也提供了一种策略,能够让应用程序在运行时,能够动态监测Maven仓库中Drools项目jar组件的版本更新情况,然后可以根据配置动态更新Drools发布包,实现热插拔功能,这个是通过KieScanner API实现的。


Drools入门系列(六)——KIE之基础API详解

在有些术语使用的时候,我有时候会用KIE项目、KIE引擎或者Drools项目、Drools引擎,大家应该理解KIE是Drools等项目的一个统称,所以在大多数情况下KIE或者特指Drools都是差不多的。

现在我们开始了解KIE的相关API,在这个helloworld例子中,我们接触过如下这些类和接口:

我们通过KieServices对象得到一个KieContainer,然后KieContainer根据session name来新建一个KieSession,最后通过KieSession来运行规则。

KieServices:

该接口提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。

KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作。

KieContainer:

可以理解KieContainer就是一个KieBase的容器,KieBase是什么呢?

KieBase:

KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。
KieSession:

KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算。我们通过KieContainer创建KieSession是一种较为方便的做法,其实他本质上是从KieBase中创建出来。的。

KieSession就是应用程序跟规则引擎进行交互的会话通道。

创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。

较为完善的类关系如下:

KieRepository:

KieRepository是一个单例对象,它是一个存放KieModule的仓库,KieModule由kmodule.xml文件定义(当然不仅仅只是用它来定义)。

KieProject:

KieContainer通过KieProject来初始化、构造KieModule,并将KieModule存放到KieRepository中,然后KieContainer可以通过KieProject来查找KieModule定义的信息,并根据这些信息构造KieBase和KieSession。

ClasspathKieProject:

ClasspathKieProject实现了KieProject接口,它提供了根据类路径中的META-INF/kmodule.xml文件构造KieModule的能力,也就是我们能够基于Maven构造Drools组件的基本保障之一。

意味着只要我们按照前面提到过的Maven工程结构组织我们的规则文件或流程文件,我们就能够只用很少的代码完成模型的加载和构建。
现在我们知道了可以通过ClasspathKieProject来解析kmodule.xml文件来构建KieModule,那么整个过程是如何进行的呢?kmodule.xml里面的kbase、ksession和KieBase和KieSession又是什么关系呢?下一章节我们继续。


Drools入门系列(七)——KIE之kmodule.xml

一个标准的kmodule.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"><kbase name="rules" packages="rules"><ksession name="ksession-rules"/></kbase><kbase name="dtables" packages="dtables"><ksession name="ksession-dtables"/></kbase>
</kmodule>

上一章节我们提到了ClasspathKieProject根据kmodule.xml文件来构造KieModule对象。KieModule是什么呢?跟KieBase、KieSession之间又是什么关系呢?我们看一下:

上图可以以关系图的方式来理解,而不是准确表示类之间的关系。

从总的来说,左边的接口描述了一种“定义”,而右边的接口则描述的是一种运行时对象,右边的运行时对象是根据左边的定义创建出来的。

然后我们看到很多的“包含”关系,左边的关系体现的就是在kmodule.xml文件中的kmodule、kbase和ksession的定义和从上到下的包含关系。

在ClasspathKieProject类中,会根据kmodule.xml文件的定义,将其解析并生成成KieModuleModel、KieBaseModel、KieSessionModel对象,基于这个原理,那么我们也可以抛开kmodule.xml文件,通过编程的方式创建这些Model对象,这个在稍后的章节中会讲到。

在运行时,KieContainer会根据*Model对象来创建KieModule、KieBase、KieSession对象。其中KieModule和KieBase只会创建一次,而KieSession则有可能创建多次,因为KieSession的创建成本很低,同时KieSession包含了运行时的数据,所以可以销毁、创建若干次。

kmodule.xml文件中的kbase和ksession标签都有很多的属性,这些属性映射到Java对象的时候就对应着相关的对象的字段,下面我们详细了解一下有那些属性:
ps:懒得打字了,汗,大家别嫌弃
kbase的属性:

ksession的属性:

这样我们就可以通过kmodule.xml文件来定义KieModule了,ClasspathKieProject会自动解析classpath下面的所有META-INF/kmodule.xml文件,然后解析成KieModule对象供Drools引擎使用。


Drools入门系列(八)——以编码方式完成kmodule的定义

在Git里面checkout training_2这个例子,就会发现在多了一个KieFileSystemTest单元测试:

public class KieFileSystemTest {@Testpublic void test() {KieServices kieServices = KieServices.Factory.get();KieResources resources = kieServices.getResources();KieModuleModel kieModuleModel = kieServices.newKieModuleModel();//1KieBaseModel baseModel = kieModuleModel.newKieBaseModel("FileSystemKBase").addPackage("rules");//2baseModel.newKieSessionModel("FileSystemKSession");//3KieFileSystem fileSystem = kieServices.newKieFileSystem();String xml = kieModuleModel.toXML();System.out.println(xml);//4fileSystem.writeKModuleXML(xml);//5fileSystem.write("src/main/resources/rules/rule.drl", resources.newClassPathResource("kiefilesystem/KieFileSystemTest.drl"));//6KieBuilder kb = kieServices.newKieBuilder(fileSystem);kb.buildAll();//7if (kb.getResults().hasMessages(Level.ERROR)) {throw new RuntimeException("Build Errors:\n"+ kb.getResults().toString());}KieContainer kContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());assertNotNull(kContainer.getKieBase("FileSystemKBase"));KieSession kSession = kContainer.newKieSession("FileSystemKSession");kSession.fireAllRules();}
}

这个用例演示了如何利用编码的方式来构建kmodule了,整个流程很简单,就是:

  1. 先创建KieModuleModel;
  2. 再创建KieBaseModel;
  3. 然后创建 KieSessionModel;
  4. 创建完成之后可以生产一个xml文件,就是kmodule.xml文件了;
  5. 将这个xml文件写入到KieFileSystem中;
  6. 然后将规则文件等写入到KieFileSystem中;
  7. 最后通过KieBuilder进行构建就将该kmodule加入到KieRepository中了。这样就将自定义的kmodule加入到引擎中了,就可以按照之前的方法进行使用了。

参考资料

[1] http://www.tuicool.com/articles/3EFNV3M
[2] http://www.tuicool.com/articles/JV7J7zr
[3] http://www.tuicool.com/articles/ememuq
[4] http://www.tuicool.com/articles/InMjei
[5] http://www.tuicool.com/articles/b2yqeq
[6] http://www.tuicool.com/articles/jeIVjiy
[7]http://www.tuicool.com/articles/22au6zV
[8] http://www.tuicool.com/articles/qqIFvy

Drools入门系列相关推荐

  1. RabbitMQ 入门系列(10)— RabbitMQ 消息持久化、不丢失消息

    消息要保持"持久化",即不丢失,必须要使得消息.交换器.队列,必须全部 "持久化". 1. 生产者怎么确认 RabbitMQ 已经收到了消息? # 打开通道的确 ...

  2. 【JAVA零基础入门系列】Day2 Java集成开发环境IDEA

    [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...

  3. Provisioning Services 7.8 入门系列教程之十三 使用 Boot Device Management(BDM)

    续Provisioning Services 7.8 入门系列教程之十二 实现高可用性 可以使用 Boot Device Management 实用程序将 IP 和引导信息(引导设备)交付给目标设备, ...

  4. Spark入门系列(二)| 1小时学会RDD编程

    作者 | 梁云1991 转载自Python与算法之美(ID:Python_Ai_Road) 导读:本文为 Spark入门系列的第二篇文章,主要介绍 RDD 编程,实操性较强,感兴趣的同学可以动手实现一 ...

  5. 上升沿判断语句_FPGA入门系列6判断语句

    文章 简介 本系列文章主要针对FPGA初学者编写,包括FPGA的模块书写.基础语法.状态机.RAM.UART.SPI.VGA.以及功能验证等.将每一个知识点作为一个章节进行讲解,旨在更快速的提升初学者 ...

  6. xgboost 正则项_XGBoost入门系列第一讲

    Boosted Trees 介绍 XGBoost 是 "Extreme Gradient Boosting"的简称,其中"Gradient Boosting"来 ...

  7. Docker入门系列之二:使用dockerfile制作包含指定web应用的镜像

    2019独角兽企业重金招聘Python工程师标准>>> 在前一篇文章:Docker入门系列之一:在一个Docker容器里运行指定的web应用 里, 我们已经成功地将我们在本地开发的一 ...

  8. SAP PM入门系列33 - IP16 维修计划报表

    SAP PM入门系列33 - IP16 维修计划报表 对于维修计划,SAP PM模块也提供了标准查询报表IP16,方便业务人员根据需要对维修计划做查询. 执行事务代码IP16, 进入如下界面, 输入相 ...

  9. SAP PM入门系列32 - S_ALR_87013432 Display Confirmations

    SAP PM入门系列32 - S_ALR_87013432 Display Confirmations S_ALR_87013432 这个报表事务代码,用于查询维修工单(维护订单)的确认数据. 执行事 ...

最新文章

  1. java纳税服务_纳税服务系统【总结】
  2. toybox执行linux程序,VirtualBox 的命令行用法
  3. python精要(80)-wxpython(2)-helloworld
  4. 特殊类型窗体制作: 用C#实现启动欢迎界面
  5. gridview不换行,高亮显示
  6. OpenCV-数字图像处理之拉普拉斯算子
  7. 【Linux】makefile文件基础
  8. 大工17春计算机文化基础在线测试3,大工17春《计算机文化基础》在线测试3满分答案...
  9. VLAN aggregation(vlan聚合)配置
  10. linux gcc编译模式,在Linux中GCC详细模式输出说明
  11. mysql 数据库基础教程(一)
  12. 学生党蓝牙耳机避雷指南,五款比漫步者还实惠的蓝牙耳机推荐
  13. Linux常见命令tar
  14. 51单片机入门-1-最小系统基础概念
  15. linux mate桌面主题下载_使用Mate Tweak配置Mate桌面
  16. 阿里云注册商标现身说法成功率打在公屏上
  17. Mounty(卷“xxx”不可重新挂载)挂载失败解决方法
  18. 有信号但是无法连接到移动网络连接服务器,手机打电话显示无法连接到移动网络怎么回事?...
  19. 百度 android 市场,百度Q2报告:Android市场份额21.4% 同比增长890%
  20. SQL高级教程(三十)- - SQL NULL 函数

热门文章

  1. 使用Grid布局制作拼图小游戏
  2. 注册GitHub帐号
  3. Parallel Scavenge垃圾回收器
  4. 两类律师年薪可能超百万
  5. Uni-app前端开发|基于微信小程序的快递运输管理系统
  6. 韦东山老师百度贴吧问答精彩集锦
  7. win10本地安装部署ClickHouse
  8. 查询应用服务器fc端口wwn号,FC磁盘阵列 实现WWN端口绑定功能
  9. 单单训练营 第四期 第八课 行业和岗位
  10. idea开发之设置jdk编译版本