在很多时候,我们都需要搭建一个通用的动态的平台,就是说在以后的扩展当中不需要修改任何的代码就达到动态的扩展一个功能或者是一个服务,然而在OSGI环境下很容易做到这一点,下面来说说怎么实现:
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi</artifactId>
<version>3.0.0</version>
</dependency>
1、首先我们创建一个服务注册的类,这个类继承org.osgi.service.cm的ManagedServiceFactory接口,ManagedServiceFactory这个接口里面有两个方法,
updated:这就是当在容器中监听到有变化,那么马上进行改变,比如,当我们添加一个bundle,更新一个bundle,增加一个配置文件cfg,更新配置文件里面的内容,都会执行改方法,然而,我们可以重写这个方法来实现我们自己所希望的功能,就好像上面说的动态服务都可以在此实现。
deleted:这方法刚好跟update方法相反。
public class RegisterService implements ManagedServiceFactory{
private static final Logger LOG = LoggerFactory.getLogger(RegisterService.class);
private static Map<String, ServiceRegistration> existingServices = new HashMap<>();
private static Map<String, ServiceRegistration> updateStatusServices = new HashMap<>();
private static Map<String, String> PidNameMap = new HashMap<>();
private static Map<String, String> PidGWNameMap = new HashMap<>();
private BundleContext context;
private IPaymentOrdersDALService paymentOrderDALService;
public BundleContext getContext() {
return context;
}
@Override
public String getName() {
return "PaymentGateWay";
}
@Override
public void updated(String pid, Dictionary dictionary) throws ConfigurationException {
String name = null;
if (dictionary == null) {
LOG.warn("文件中属性为空,无法创建服务");
return;
}
//注册网关服务
registerGWService(pid, dictionary);
//注册回调服务
registerCallBackService(pid, dictionary);
}
/**
* 注册网关服务
* @param pid
* @param dctnr
*/
private void registerGWService(String pid, Dictionary dctnr){
Object gwNameObj = dctnr.get("gwName");
Object gwServiceNameObj = dctnr.get("gwServiceName");//网关服务实例
String gwName;//网关名称,用于创建实例,如:com.payment.lklk.paygw.abc.AbcProcessor
String gwServiceName;//服务名称,用于发布服务,如:jmust/abc,"/"后面是网关名称
if (null == gwNameObj || null == gwServiceNameObj) {
LOG.warn("文件中gwName参数或gwServiceName参数没有配置,无法创建服务!");
return;
} else if ("".equals(gwNameObj.toString()) || "".equals(gwServiceNameObj.toString())) {
LOG.warn("文件中gwName参数或gwServiceName为空,无法创建服务!");
return;
} else {
gwName = gwNameObj.toString();
gwServiceName = gwServiceNameObj.toString();
}
//是否已经注册过同名服务
if (existingServices.containsKey(gwServiceName)) {
if (gwServiceName.equals(PidNameMap.get(pid))) {
//同一配置文件修改的情况
LOG.info("更新已有的服务实例\"" + gwServiceName + "\"");
ServiceRegistration ppcService;
ppcService = existingServices.get(gwServiceName);
if (null != ppcService) {
ppcService.unregister();
LOG.info("Unregister GwService Instance:" + gwServiceName);
} else {
LOG.warn("No GwService Instance:" + gwServiceName);
}
existingServices.remove(gwServiceName);
//PidNameMap.remove(pid);
} else {
//不同配置文件的服务实例名冲突
LOG.error("服务实例名\"" + gwServiceName + "\"和其他配置文件里的服务实例名冲突,保留原有服务实例。");
return;
}
}
//先清除当前配置文件原来对应的服务资源
String LegacyServiceName = PidNameMap.get(pid);
if (LegacyServiceName != null) {
ServiceRegistration ppcService = existingServices.get(LegacyServiceName);
if (ppcService != null) {
ppcService.unregister();
existingServices.remove(LegacyServiceName);
}
PidNameMap.remove(pid);
}
//设置服务相关属性
ServiceRegistration ppcService;
Dictionary props = new Hashtable();
props.put("gwServiceName", gwServiceName);
Object serviceInstance = null;
try {
Class serviceClass = getServiceClass(gwName);//根据gwName动态创建类实例
Constructor con = serviceClass.getConstructor(Dictionary.class); //将参数通过构造函数接受
serviceInstance = con.newInstance(dctnr);
} catch (Exception ex) {
LOG.error("创建实体类" + gwName + "失败" + ex);
}
if (serviceInstance == null) {
LOG.error("实体类实例为空,创建" + gwName + "实例失败");
return;
}
ppcService = context.registerService("org.apache.camel.Processor", serviceInstance, props);
existingServices.put(gwServiceName, ppcService);
PidNameMap.put(pid, gwServiceName);
}
/**
* 注册网关状态更新服务
* @param pid
* @param dctnr
*/
private void registerCallBackService(String pid, Dictionary dctnr) {
Object gwCallBackInstanceObj = dctnr.get("gwCallBackInstance");//网关状态更新服务实例
Object gwServiceNameObj = dctnr.get("gwServiceName");//网关状态更新服务名
String gwName;//网关状态更新实例名称,用于创建实例,如:com.payment.lklk.paygw.abc.UpdateStatus
String gwServiceName;//服务名称,用于发布服务,如:jmust/abc,"/"后面是网关名称
if (null == gwCallBackInstanceObj || null == gwServiceNameObj) {
LOG.warn("文件中gwName参数或gwCallBackInstance参数没有配置,无法创建网关状态更新服务!");
return;
} else if ("".equals(gwCallBackInstanceObj.toString()) || "".equals(gwServiceNameObj.toString())) {
LOG.warn("文件中gwName参数或gwCallBackInstance为空,无法创建网关状态更新服务!");
return;
} else {
gwName = gwCallBackInstanceObj.toString();
gwServiceName = gwServiceNameObj.toString();
}
//是否已经注册过同名服务
if (updateStatusServices.containsKey(gwServiceName)) {
if (gwServiceName.equals(PidGWNameMap.get(pid))) {
//同一配置文件修改的情况
LOG.info("更新已有的状态更新服务实例\"" + gwServiceName + "\"");
ServiceRegistration ppcService;
ppcService = updateStatusServices.get(gwServiceName);
if (null != ppcService) {
ppcService.unregister();
LOG.info("Unregister StatusUpdateService Instance:" + gwServiceName);
} else {
LOG.warn("No StatusUpdateService Instance:" + gwServiceName);
}
updateStatusServices.remove(gwServiceName);
//PidNameMap.remove(pid);
} else {
//不同配置文件的服务实例名冲突
LOG.error("服务实例名\"" + gwServiceName + "\"和其他配置文件里的服务实例名冲突,保留原有服务实例。");
return;
}
}
//先清除当前配置文件原来对应的服务资源
String LegacyServiceName = PidGWNameMap.get(pid);
if (LegacyServiceName != null) {
ServiceRegistration ppcService = updateStatusServices.get(LegacyServiceName);
if (ppcService != null) {
ppcService.unregister();
updateStatusServices.remove(LegacyServiceName);
}
PidGWNameMap.remove(pid);
}
//设置服务相关属性
ServiceRegistration ppcService;
Dictionary props = new Hashtable();
props.put("gwServiceName", gwServiceName);
Object serviceInstance = null;
try {
Class serviceClass = getServiceClass(gwName);
Constructor con = serviceClass.getConstructor(Dictionary.class,IPaymentOrdersDALService.class); //将参数传进构造函数中,让构造函数去接收进行初始化,注意了,serviceClass需要有一个不带参数的构造函数和一个带参数的构造函数,否则会报错
serviceInstance = con.newInstance(dctnr,paymentOrderDALService);
} catch (Exception ex) {
LOG.error("创建实体类" + gwName + "失败" + ex);
}
if (serviceInstance == null) {
LOG.error("实体类实例为空,创建" + gwName + "实例失败");
return;
}
ppcService = context.registerService("com.jmust.features.contract.IPaymentUpdateStatus", serviceInstance, props);
updateStatusServices.put(gwServiceName, ppcService);
PidGWNameMap.put(pid, gwServiceName);
}
@Override
public void deleted(String pid) {
//卸载网关服务
ServiceRegistration ppcService;
String ServiceName = PidNameMap.get(pid);
if (ServiceName != null) {
ppcService = existingServices.get(ServiceName);
if (ppcService != null) {
ppcService.unregister();
}
existingServices.remove(ServiceName);
PidNameMap.remove(pid);
}
//卸载状态更新服务
ServiceRegistration updateService;
String updateServiceName = PidGWNameMap.get(pid);
if (updateServiceName != null) {
updateService = updateStatusServices.get(updateServiceName);
if (updateService != null) {
updateService.unregister();
}
updateStatusServices.remove(updateServiceName);
PidGWNameMap.remove(pid);
}
}
public void setContext(BundleContext context) {
this.context = context;
}
public void setPaymentOrderDALService(IPaymentOrdersDALService paymentOrderDALService) {
this.paymentOrderDALService = paymentOrderDALService;
}
/**
* 获取服务对应实例类
* @param clazz
* @return
* @throws ClassNotFoundException
*/
private Class<?> getServiceClass(String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
}
}
注意:pom文件中,我们需要动态的引入包
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.0.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-ManifestVersion>2</Bundle-ManifestVersion>
<Bundle-Name>${project.groupId}.${project.artifactId}_${maven.build.timestamp}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Bundle-Vendor>${project.groupId}</Bundle-Vendor>
<DynamicImport-Package>*</DynamicImport-Package>
<Import-Package>
org.osgi.framework,org.slf4j,org.apache.camel,org.osgi.service.cm,com.jmust.features.contract;version="[1.0.0,2.0.0)",
com.jmust.features.contract;version="[1.0.0,2.0.0)"
</Import-Package>
</instructions>
</configuration>
</plugin>
2.将服务通过发布出去,看在blueprint中怎么设置
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd">
<reference id="feturesOrderDALService" interface="com.jmust.fetures.contract.IfeturesOrdersDALService"></reference>
<bean id="registerServiceBean" class="com.jmust.fetures.register.feturesGwFactory">
<property name="context" ref="blueprintBundleContext"/>
<property name="feturesOrderDALService" ref="feturesOrderDALService" />
</bean>
<service id="RegisterFactoryService" ref="registerServiceBean" interface="org.osgi.service.cm.ManagedServiceFactory" >
<service-properties>
<entry key="service.pid" value="PaymentGateWay" />
</service-properties>
</service>
</blueprint>
3.创建一个cfg文件,以后会有很多这样的cfg文件的,扔一个这样子的文件就会通过执行上面的注册服务的类类动态的创建对应的服务,通过类名,反射找到对应的类来构建不一样的服务。
gwName=com.payment.lklk.paygw.abc.AbcProcessor
gwCallBackInstanceObj=com.payment.lklk.paygw.abc.UpdateStatus
gwServiceName=jmust/abc
account=1234567890
password=*********
4.在此,我们怎么用我们这些动态创建出来的服务呢?两种方式,下面举个例子
在xml这样子用
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd">
<reference id="abcService" interface="org.apache.camel.Processor" filter="gwServiceName=jmust/abc"/>
<camelContext id="ABCCamel"
xmlns="http://camel.apache.org/schema/blueprint">
<route>
<from uri="direct-vm:target" />
<to uri="log:1234567890" />
<to uri="bean:abcService" />
</route>
</camelContext>
</blueprint>
程序里这样子用:
首先在blueprint中监听服务列表
<bean id="callBackBean" class="com.jmust.features.callback.CallBackProcessor">
<property name="bundleContext" ref="blueprintBundleContext"/>
</bean>
<reference-list availability="optional" interface="com.jmust.features.contract.IPaymentUpdateStatus" >
<reference-listener ref="callBackBean" bind-method="bindService" unbind-method="unbindService"/>
</reference-list>
public void bindService(ServiceReference reference){
if(reference != null){
IPaymentUpdateStatus paymentUpdateStatus = (IPaymentUpdateStatus)bundleContext.getService(reference);
if(null != paymentUpdateStatus){
String serviceName = reference.getProperty("gwServiceName").toString();
LOG.info("【BindService】:"+serviceName);
serviceMap.put(serviceName,paymentUpdateStatus);
}
}
}
public void unbindService(ServiceReference reference){
if(reference != null){
IPaymentUpdateStatus paymentUpdateStatus = (IPaymentUpdateStatus)bundleContext.getService(reference);
if(null != paymentUpdateStatus){
String serviceName = reference.getProperty("gwServiceName").toString();
LOG.info("【UnBindService】:"+serviceName);
serviceMap.remove(serviceName);
}
}
}
就这样子,就可以正常使用了。