标签:ntb 简单 pack eth rest username container void druid
回溯BRMS开发教程中的那张“业务变现加速器”架构图,考虑下面的问题
按照上次的《jboss规则引擎KIE Drools 6.3.0 Final 教程》,一起来看一个实际的场景
做到以下几点是否就可以“全得”?
延续上一个教程内的课后作业:
对于我们来说,我们把工作按照“业务规则”和“因子”相分离的原则来进行设计。
先设计“因子”,我们一开始想把mybatis功能加入Kie Drools可以动态上传的因子中,这样我们的因子只要业务在发生变化时,每次重新上传一个新的因子,我们的规则就可以使用新的因子了,因此我们的SQL、DAO、SERVICE似乎都可以做到动态了。
但是,我们又想到了使用Spring + MyBatis来结合,这样不是更好吗?那么我们先不来说Drools是否可以启用Spring + MyBatis的功能,要让Drools使用外部jar,Drools是有这个功能的。
Drools是基于Maven Repository来实现发布或者是和外部jar进行交互的。
上传完后就可以用了吗?
NO,错!
因为上传完后,你上传的第三方jar只是跑到了以下这个目录里去了
和eclipse一样,要想在工程中使用它们,你还需要在你的工程中引用它们
我们有了这些个jar文件,我们就可以让我们的规则中的因子具有读取数据库的能力了是不是?
那我们来开发一个使用mybatis功能来读取数据库的例子吧。
当然,这很简单,不少人会说了,因此很快我们把spring + mybatis结合了一下然后我们在代码里使用
ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "classpath:/spring/xxx-conf.xml");
因为,drools里自带的一些jar和spring所依赖的相关的jar产生了冲突,因此到目前为止你不能在drools WB(workbench)项目中使用spring,而只能拿spring来简化你在前一篇教程中调用KieSession时进行集成(由如jdbcTemplate功能)。
这使得我们的设计进入了一个僵局。。。。。。
打破僵局!
那我们换一种思路来考虑:
在规则中我们主要的还是使用select,不可能在规则中对DB进行INSERT、UPDATE、DELETE操作,这直接违反了“迪米特法则”-即封装原理。
而如果使用mybatis对我们写sql来说比较有宜,但是如果不结合spring仅使用mybatis我们需要注意的一个是session pool的使用和线程安全的问题,另一个就是要在finally块中及时close mybatis的session。
因此。。。我们需要做的就是让开发人员:
session = SqlSessionManager.newInstance(sqlSessionFactory);
package org.sky.drools.sql.datasource; import java.io.InputStream; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SessionFactory { private final static Logger logger = LoggerFactory.getLogger(SessionFactory.class); private static SessionFactory instance = null; public static SqlSessionFactory factory = null; public synchronized static SessionFactory getInstance() throws Exception { if (instance == null) { String resource = "/mybatis/conf/mybatis-conf.xml"; InputStream confInputStream = null; try { //reader = Resources.getResourceAsReader(resource); //logger.info("path====" + this.getClass().getResourceAsStream(resource).toString()); confInputStream = SessionFactory.class.getResourceAsStream(resource); factory = new SqlSessionFactoryBuilder().build(confInputStream); } catch (Exception e) { logger.error("init /resources/mybatis/conf/mybatis-conf.xml error: " + e.getMessage(), e); } instance = new SessionFactory(); } return instance; } }
package org.sky.drools.dao.proxy; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionManager; import org.sky.drools.sql.datasource.IsSession; import org.sky.drools.sql.datasource.SessionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DAOProxyFactory implements InvocationHandler { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private Object targetObject; public Object createProxyInstance(Object targetObject) { this.targetObject = targetObject; return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSessionFactory sqlSessionFactory = null; Object result = null; SqlSession session = null; try { Field[] fields = targetObject.getClass().getDeclaredFields(); if (fields.length > 1) { throw new Exception("[mybatis]--->Expected only 1 myBatis SqlSession in [" + targetObject.getClass() + "] but found more than 1"); } for (int i = 0; i < fields.length; i++) { if (fields[i].isAnnotationPresent(IsSession.class)) { Field field = fields[i]; String fieldName = field.getName(); StringBuffer methodName = new StringBuffer(); methodName.append("set"); methodName.append(fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1)); logger.debug("[mybatis]--->init mybatis session start......"); logger.debug("[mybatis]--->the methodName [" + methodName.toString()+"] will be set mybatis session"); Method m = targetObject.getClass().getMethod(methodName.toString(), SqlSession.class); sqlSessionFactory = SessionFactory.getInstance().factory; logger.debug("[mybatis]--->get db connection from connection pool"); session = SqlSessionManager.newInstance(sqlSessionFactory); logger.debug("[mybatis]--->get db connection done"); session = sqlSessionFactory.openSession(); m.invoke(targetObject, session); logger.debug("[mybatis]--->Done mybatis session......"); } } logger.debug("[mybatis]--->open mybatis session and begin Query......"); result = method.invoke(targetObject, args); } catch (Exception e) { throw new Exception("[mybatis]--->Init SqlSession Error:" + e.getMessage(), e); } finally { try { session.close(); logger.debug("[mybatis]--->close mybatis session"); } catch (Exception e) { } } return result; }
DAOProxyFactory factory = new DAOProxyFactory(); StudentService aopService = (StudentService) factory.createProxyInstance(new StudentServiceImpl()); int age = aopService.getAge("ymk");
public int getAge(String userId) throws Exception { UserInfoMapper userMapper = batisSession.getMapper(UserInfoMapper.class); int age = userMapper.selelctUser(userId); return age; }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="org.sky.drools.sql.datasource.DruidDataSourceFactory"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.0.101:3306/mk?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="mk" /> <property name="password" value="aaaaaa" /> <property name="maxActive" value="20" /> <property name="initialSize" value="5" /> <property name="minIdle" value="1" /> <property name="testWhileIdle" value="true" /> <property name="validationQuery" value="SELECT 1" /> <property name="testOnBorrow" value="true" /> <property name="testOnReturn" value="true" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/mapper/UserInfoMapper.xml" /> <mapper resource="mybatis/mapper/ApplicantListMapper.xml" /> </mappers> </configuration>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.sky.drools.dao.mapper.UserInfoMapper"> <select id="selelctUser" parameterType="String" resultType="int"> SELECT age FROM user_info WHERE user_id=#{userId} </select> </mapper>
public class UserInfoBean implements Serializable private int age = 0; private String applicant; private String userId; private boolean validFlag = false; } 没什么好多说的,我们为这个Bean提供了一组field,同时我们会为每个私有成员生成一对set/get方法。 关键在于getAge()方法的覆盖: public int getAge() throws Exception { int age = 0; try { DAOProxyFactory factory = new DAOProxyFactory(); UserInfoService stdService = (UserInfoService) factory.createProxyInstance(new UserInfoServiceImpl()); age = stdService.getAge(userId); System.out.println(age); } catch (Exception e) { System.err.println(e.getMessage()); throw new Exception("mybatis error: " + e.getMessage(), e); } return age; }
package org.sky.drools.service; import org.apache.ibatis.session.SqlSession; import org.sky.drools.dao.mapper.ApplicantListMapper; import org.sky.drools.dao.mapper.UserInfoMapper; import org.sky.drools.sql.datasource.IsSession; public class UserInfoServiceImpl implements UserInfoService { @IsSession private SqlSession batisSession = null; public void setBatisSession(SqlSession batisSession) { this.batisSession = batisSession; } public int getAge(String userId) throws Exception { UserInfoMapper userMapper = batisSession.getMapper(UserInfoMapper.class); int age = userMapper.selelctUser(userId); return age; } public int existInList(String userId) throws Exception { ApplicantListMapper listMapper = batisSession.getMapper(ApplicantListMapper.class); int result = listMapper.selelctUserInApplicant(userId); return result; } }
把因子打包上传至规则引擎
别忘了在工程中引用该上传的包
package org.sky.drools.dbrulz; no-loop declare User age : int; validFlag : boolean; end rule "init studentbean" salience 1000 when u : UserInfoBean() then User user=new User(); user.setAge(u.getAge()); System.out.println("valid applicant for["+u.getUserId()+"] and validFlag is["+u.isValidFlag()+"]"); insert(user); end rule "less than < 17" when u : User ( age <17); facts : UserInfoBean() then facts.setApplicant("0"); end rule "less than < 45" when u : User ( age <46 && age >=17); facts : UserInfoBean() then facts.setApplicant("7000000"); end rule "less than < 70" when u : User ( age <70 && age >=46); facts : UserInfoBean() then facts.setApplicant("5000000"); end
一切似乎那么着美好,一切似乎那么的顺利,我们把这个工程build一下然后来制作我们的调用端吧。
我们使用spring mvc制作了一个标准的web工程
按照业务、因子动态分离的原则我们上述的行为是做不到全动态的,我们顶多只能算是一个嵌入式的应用。
但是。。。我们的努力还是没有白费!!!
因为我们的业务已经可以做到全动态了。
所以,我们的想法就是要让规则真正的运行在远程,而对于调用者来说,只需要传入一个状态、一个message得到一个返回值好可,一切规则相关的运行不应该在本地应用(业务应用web服务器)。
于是,我们发觉了DROOLS的另一个套件 KIE-SERVER。
KIE组件真正有用的,其实是3个:
我们把kie-server-6.3.0.Final-webc.war提练出来,它就是用于安装于tomcat7下的可独立运行的KIE-SERVER。(webc=web container-即container模式又指可用于tomcat安装)
把kie-server-6.3.0.Final-webc.war扔到tomcat7的webapp目录下。
清理tomcat7 lib目录下所需的缺失的那些个jar
之前我们装过KIE-WB(WorkBench),因此我们找到tomcat/conf目录下的resources.properties文件。
这个文件里之前我们使用的是h2即一个内存数据库,现在我们来把它改成正式环境吧,
resources.properties文件内容如下:
resource.ds1.className=bitronix.tm.resource.jdbc.lrc.LrcXADataSource resource.ds1.uniqueName=jdbc/jbpm resource.ds1.minPoolSize=10 resource.ds1.maxPoolSize=20 #resource.ds1.driverProperties.driverClassName=org.h2.Driver #resource.ds1.driverProperties.url=jdbc:h2:mem:jbpm #resource.ds1.driverProperties.user=sa #resource.ds1.driverProperties.password= resource.ds1.driverProperties.driverClassName=com.mysql.jdbc.Driver resource.ds1.driverProperties.url=jdbc:mysql://192.168.0.101:3306/drools?useUnicode=true&characterEncoding=UTF-8 resource.ds1.driverProperties.user=kie resource.ds1.driverProperties.password=aaaaaa resource.ds1.allowLocalTransactions=true
<Realm className="org.apache.catalina.realm.LockOutRealm" > <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm>
<Realm className="org.apache.catalina.realm.LockOutRealm" lockOutTime="1" failureCount="999999"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm>
找到tomcat/conf/tomcat-user文件,找到如下这段:
增加一个角色为kie-role,如下:
<?xml version=‘1.0‘ encoding=‘utf-8‘?> <tomcat-users> <role rolename="admin"/> <role rolename="analyst"/> <role rolename="user"/> <role rolename="kie-server"/> <user username="kieserver" password="aaaaaa" roles="kie-server,admin"/> <user username="tomcat" password="tomcat" roles="admin,manager,manager-gui,kie-server,analyst"/> </tomcat-users>
修改catalina.sh文件
export CATALINA_OPTS=“-Dbtm.root=$CATALINA_HOME -Dorg.jbpm.cdi.bm=java:comp/env/BeanManager \ -Dbitronix.tm.configuration=$CATALINA_HOME/conf/btm-config.properties \ -Djbpm.tsr.jndi.lookup=java:comp/env/TransactionSynchronizationRegistry \ -Djava.security.auth.login.config=$CATALINA_HOME/webapps/kie-drools/WEB-INF/classes/login.config \ -Dorg.kie.server.persistence.ds=java:comp/env/jdbc/jbpm \ -Dorg.kie.server.persistence.tm=org.hibernate.service.jta.platform.internal.BitronixJtaPlatform \ -Dorg.kie.server.id=kie-server \ -Dorg.kie.server.controller.user=kieserver \ -Dorg.kie.server.controller.pwd=aaaaaa \ -Dorg.kie.server.location=http://192.168.0.101:8080/kie-server/services/rest/server \ -Dorg.kie.server.controller=http://192.168.0.101:8080/kie-drools/rest/controller \ -Dorg.jbpm.server.ext.disabled=true -Dorg.kie.demo=false"
此处,比原有安装KIE-WB时多出了几个参数: -Dorg.kie.server.persistence.ds 告诉KIE-SERVER和KIE-WB使用一个数据源,并启用了基于bitronix的分布式事务 -Dorg.kie.server.id=kie-server 告诉JVM,KIE SERVER的实例名 -Dorg.kie.server.controller.user=kieserver 不解释了(看tomcat-user.xml) -Dorg.kie.server.controller.pwd=aaaaaa 不解释了(看tomcat-user.xml) -Dorg.kie.server.location=http://192.168.0.101:8080/kie-server/services/rest/server 你的tomcat\webapp下kieserver工程的所在 把/services/rest/server照抄 -Dorg.kie.server.controller=http://192.168.0.101:8080/kie-drools/rest/controller 你的tomcat\webapp下kie-wb的所在 把/rest/controller照抄,controller与kie的rest/server会进行心跳同步 -Dorg.jbpm.server.ext.disabled=true 在kie-web中不启用jbpm功能
让我们打开KIE-WB来一探kie-drools + kie-server的究竟吧。
输入 http://192.168.0.101:8080/kie-drools/,你可以键入你在tomcat-user.xml中有kie-server角色的用户的登录信息。
此时,在这里面你可以看到。。。咦,我们怎么多了一个KIE-SERVER出来?
你可以动态注册一个KIE SERVER通过
此处的信息请参照:http://192.168.0.101:8080/kie-server/services/rest/server 显示的信息
现在因为有了KIE SERVER,于是我们直接在此规则上多加一步
在规则开始处修改如下:
declare User age : int; validFlag : boolean; end rule "init studentbean" salience 1000 when $u:UserInfoBean(userId!=null) //$u:UserInfoBean(); then System.out.println("valid applicant for["+$u.getUserId()+"] and validFlag is["+$u.isValidFlag()+"]"); User userVO=new User(); userVO.setAge( $u.getAge()); userVO.setValidFlag($u.isValidFlag()); insert(userVO); End
rule "not a valid applicant" when user:User(!validFlag) && u:UserInfoBean(userId!=null) //u : UserInfoBean( !validFlag ); then u.setApplicant("0"); end
package org.sky.drools.dbrulz; declare User age : int; validFlag : boolean; end rule "init studentbean" salience 1000 when $u:UserInfoBean(userId!=null) then System.out.println("valid applicant for["+$u.getUserId()+"] and validFlag is["+$u.isValidFlag()+"]"); User userVO=new User(); userVO.setAge( $u.getAge()); userVO.setValidFlag($u.isValidFlag()); insert(userVO); end rule "less than < 17" when user: User(age<17) && u:UserInfoBean(userId!=null) then u.setApplicant("0"); end rule "less than < 45" when (user: User(age<46 && age>=17)) && u:UserInfoBean(userId!=null) then System.out.println("set applicant for "+u.getUserId()+" to 7000000"); u.setApplicant("7000000"); end rule "less than < 70" when (user:User(age<70 && age>=46)) && u:UserInfoBean(userId!=null) then System.out.println("set applicant for "+u.getUserId()+" to 5000000"); u.setApplicant("5000000"); end rule "not a valid applicant" when user:User(!validFlag) && u:UserInfoBean(userId!=null) then u.setApplicant("0"); end
看看我们是如何改变UserInfoBean中的validFlag的。
我们先是加了一个mapper,它会从数据库表applicant_list中根据user_id进行count(),以下是isValidFlag的具体实现
因此,我们实现了:假如一个申请人即使他的年龄符合年龄审核规则,但是如果它并不存在于applicant_list表中的话那么他可以申请的保额依旧为“0”元这样的一个规则。
当然,我们也可以不用做两个mapper + 2个service方法而是在原有的mapper中加入一条:
SELECT COUNT(1) FROM USER_INFO WHERE USER_ID=‘ymk‘ AND EXISTS (SELECT USER_ID FROM APPLICANT_LIST WHERE USER_ID=‘ymk‘);
别忘了save你的工程
BUILD(build时把请把版本改成2.0.0)
开始创建KIE SERVER的CONTAINER
看到postman 输出SUCCESS字样后,我们输入如下的网址:
http://192.168.0.101:8080/kie-server/services/rest/server/containers
看到以下信息即宣告我们的container布署成功。接下去我们就可以使用postman来发请求了。
在你看到注册成功后,依旧使用chrome->postman来真正运行我们的远程规则吧
POST请求的JSON报文如下:
{ "commands": [ { "insert": { "return-object":true, "out-identifier":"UserInfoBean", "object": { "org.sky.drools.dbrulz.UserInfoBean": { "userId": "jason" } } } }, { "fire-all-rules": "" } ] }
Kie Server中的container即可以commit也可以delete,如果我们需要重新STOP,STAR一个Container,不仅仅是需要在KIE SERVER中
同时,还需要使用postman发送一个json请求,请注意下面的截图
Container更新请按照如下几步:
我们接着把userId分别换成 ymk, sala2个名字,我们都得到了相应的结果,关键的是来看后台的日志。
由于我们在后台加入了相应的日志,每次调用getAge()时都会有一个输出,可以清楚的看到,我们的DB调用为3次,因为我们进行了3次POST请求。
如果DB操作为15次(5条规则被触发了3次)那就不对了。
刚才我们这一系列的步骤全部在服务器完成,而在修改规则、上传FACT甚至包括对DAO层、SEVICE层“大修”,我们的规则服务器始终是不需要重启的。
而调用该规则服务仅仅是通过下面这样的JASON
{ "commands": [ { "insert": { "return-object":true, "out-identifier":"UserInfoBean", "object": { "org.sky.drools.dbrulz.UserInfoBean": { "userId": "jason" } } } }, { "fire-all-rules": "" } ] }
如果有人忽略了这一章,那么你会犯一个巨大的错误。
来看什么叫Stateless Session和Stateful Session。
即有状态和无状态。
通过以上章节,我们使用的是Rest API来访问的规则,换句话说就是使用的是http来访问的。
那么我们来说,有一群人在访问规则,有一堆Legacy System在访问规则。
假设我们使用的是Stateful Session,我们来看这样的一个场景:
A、B、C同时访问我们的规则,使用的是Stateful(如果你的工程不做任何设置,那么它BUILD和Deploy出来的东东就是Stateful Session访问)来访问我们的规则。
A传入了userId=“ymk”(可申请的保额应为7,000,000)
B传入了UserId=“sala” (可申请的保额应为5,000,000)
C传入了userId=“Jason” (可申请的保额应为0-因为不在applicant_list名单中)
接下来的事情好玩了,A可能得到可申请保额为0,B可能得到可申请保额为5,000,000。
为什么会这样乱窜?
原因是你的工程没有做任何设置,它默认提供给REST API访问时启用的是Stateful Session,即所有请求共享Session。
而面对Http场景的应用,我们需要的是Session隔离,即Stateless Session。
那我们就来看如何把我们的工程真正的设置成Stateless Session吧。
我们的工程布署成了“兼顾Stateless Session”和“Stateful Session”后我们的POST的JSON变成如下格式
第一步: 上传slf4j至KIE DROOLS
第二步: 在规则中设置一个全局变量,在规则中的最开始,package下如此声明:
global org.slf4j.Logger logger
然后你就可以在规则中到处logger.info了。
但是,这边有一个前提!!!
即:此规则如果在KIE的Test Scenario中运行的话,此处logger.info调用会抛空指针异常,要让规则中的logger.info正常使用,你必须使用JAVA代码的方式如下调用:
private final Logger logger = LoggerFactory.getLogger(this.getClass()); StatelessKieSession kieSession = getStatelessKieSession(); kieSession.setGlobal(logger);; kieSession.execute();
jboss规则引擎KIE Drools 6.3.0-高级讲授篇
标签:ntb 简单 pack eth rest username container void druid
原文地址:http://blog.csdn.net/lifetragedy/article/details/60755213