package com.videtek.vacp.common.utils;
import Ice.ObjectPrx;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author hehaifeng
* @date 2018-6-6 10:55:30
* @version 1.0
* 作用:此类用于方便ice客户端进行ice通信
*
* Locator 地址不能写死在代码里,属于共用部分配置。
* Communicator 是重量级全局共享的对象,涉及线程池,Socket连接等,每次的创建和销毁既影响性能又容易产生泄露问题
* Ice Object 的标识符 (Identity)与类名有99%的可能是重合的,Identity+Prx相当于 Object Proxy的类名
*
* 这里有一个隐含的契约式编程,即Ice Object 的Proxy 名字与在IceGrid中定义的 Object Identity 保持之前提到的这种关系(99%相似),契约这样的做法,
* 省去了很多不必要的代码,这也是契约式编程在很多地方都被或多或少地使用的重要原因
*
* 针对一个 iceServerHostUrl 创建一个工具类对象,该对象可以在整个服务过程中持续使用
*
* 使用示例:车综里图片识别的写法
* public class TestDemo {
* private static final Logger logger = LoggerFactory.getLogger(ImageServiceImpl.class);
*
* private static IceClientUtil iceClientUtil = new IceClientUtil("carDetectSDK:tcp -h 192.168.101.90 -p 12570");
*
* @Test
* public void testIceClientUtil(){
* // 建议从配置文件里取
* String iceServerHostUrl="carDetectSDK:tcp -h 192.168.101.90 -p 12570";
* // 可创建整个类甚至整个服务的 工具类对象,长期重复使用
* CarOutputInfo[] result=null;
* try{
* SDKInfoPrx sdkInfoPrx = (SDKInfoPrx)iceClientUtil.getServicePrx(SDKInfoPrx.class);
* String imgUrl = "http://192.168.101.90:5000/9EE72232-F5F6-445E-874C-AE82A06A13AC/Snapshot/51C27FB5-6C1E-44FA-960D-AAF0EADFFECF.jpg";
* result = sdkInfoPrx.detect(imgUrl);
* }catch (Exception e){
* e.printStackTrace();
* logger.error("Ice请求服务异常:"+e);
* }
* String mm = result[0].vehicleFeature;
* System.out.println(mm);
* // 使用完毕可以主动释放资源,如果没关闭,超过闲置时间也会自动释放资源
* iceClientUtil.closeCommunicator(true);
* }
* }
*
*/
public class IceClientUtil{
/**
* ice 服务端的 服务名称 通信协议 ip 和端口号
* carDetectSDK:tcp -h 192.168.101.90 -p 12570
*/
private String iceServerHostUrl;
/**
* Ice 客户端和服务端传输文件的最大限制
*/
private String iceMessageSizeMax= "10240";
/**
* 消息超时时间
*/
private String iceOverrideTimeout= "60000";
/**
* 连接超时时间
*/
private String iceOverrideConnectTimeout= "60000";
/**
* 线程闲置时间 超过闲置时间自动关闭线程 单位秒
*/
private long idleTimeOutSeconds =3000;
/**
* 必须的构造方法
* @param iceServerHostUrl ice的服务名称,通信协议,ip 端口
* 示例:carDetectSDK:tcp -h 192.168.101.90 -p 12570
*/
public IceClientUtil(String iceServerHostUrl){
this.iceServerHostUrl=iceServerHostUrl;
}
public IceClientUtil(String iceServerHostUrl, String iceMessageSizeMax, String iceOverrideTimeout, String iceOverrideConnectTimeout, long idleTimeOutSeconds) {
this.iceServerHostUrl = iceServerHostUrl;
this.iceMessageSizeMax = iceMessageSizeMax;
this.iceOverrideTimeout = iceOverrideTimeout;
this.iceOverrideConnectTimeout = iceOverrideConnectTimeout;
this.idleTimeOutSeconds = idleTimeOutSeconds;
}
public String getIceServerHostUrl() {
return iceServerHostUrl;
}
/**
* 设置最大传输限制
*/
public void setIceMessageSizeMax(String iceMessageSizeMax) {
this.iceMessageSizeMax = iceMessageSizeMax;
}
/**
* 设置消息响应超时时间
*/
public void setIceOverrideTimeout(String iceOverrideTimeout) {
this.iceOverrideTimeout = iceOverrideTimeout;
}
/**
* 设置连接超时时间
*/
public void setIceOverrideConnectTimeout(String iceOverrideConnectTimeout) {
this.iceOverrideConnectTimeout = iceOverrideConnectTimeout;
}
/**
* 设置线程闲置时间,如果超过这个闲置时间程序没有被调用,程序会主动释放资源
*/
public void setIdleTimeOutSeconds(long idleTimeOutSeconds) {
this.idleTimeOutSeconds = idleTimeOutSeconds;
}
/**
* 通信调度对象
*/
private static volatile Ice.Communicator ic =null;
/**
* 存放 ObjectPrx 服务对象的集合
*/
private static Map<Class, ObjectPrx> cls2PrxMap = new HashMap<>();
/**
* 最后存取时间标记
*/
private static volatile long lastAccessTimestamp;
/**
* 守护线程 后面自己创建的线程内部类
*/
private volatile MonitorThread nonitorThread;
/**
* 延迟加载 Communicator
*/
private Ice.Communicator getIceCommunictor(){
// 如果 通信调度器对象为null
if (ic==null){
// 锁定当前调用对象
synchronized (this){
if (ic==null){
// 创建 ic 调度器对象
ic = Ice.Util.initialize(initIData());
// 创建守护线程
createMonitorThread();
}
}
}
lastAccessTimestamp =System.currentTimeMillis();
return ic;
}
/**
* 创建守护线程
*/
private void createMonitorThread() {
// 创建线程
nonitorThread = new MonitorThread();
// 将其设置成守护线程
nonitorThread.setDaemon(true);
// 启动守护线程
nonitorThread.start();
}
/**
* 关闭Conmuicator,释放资源
*/
public void closeCommunicator(boolean removeServiceCache){
// 锁定当前类的字节码对象
synchronized (this){
// 如果ic不为null则安全关闭
if (ic!=null){
// 调用方法安全关闭
safeShutdown();
// 中断守护线程
nonitorThread.interrupt();
// 如果决定要关闭 Communicator对象(removeServiceCache=true),并且cls2PrxMap对象不为空
if (removeServiceCache && !cls2PrxMap.isEmpty()){
try {
// 清空 cls2PrxMap集合
cls2PrxMap.clear();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
}
private void safeShutdown(){
try{
// 调用线程的shutdown()方法停止接受新的任务,当前任务执行完毕后就停止
ic.shutdown();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}finally {
// 最后销毁ic
ic.destroy();
// 为进一步却表不会内存泄露,给它null值交给 GC
ic=null;
}
}
/**
* 用反射方式创建 Object Proxy
* 创建客户端代理对象
* @param communicator ic 通信调度器对象
* @param serviceCls 所用方法接口类 serviceCls : 接口类字节码 SDKInfoPrx.Class
* @return SDKInfoPrx向上转型成的ObjectPrx的对象 (已经是加入通信调度的对象)
*/
private ObjectPrx createIceProxy(Ice.Communicator communicator,Class serviceCls){
// 定义客户端代理对象 ObjectPrx 是所有客户端代理接口的父类
ObjectPrx proxy =null;
// 获取 serviceCls 的类全名 示例:com.videtek.ice.carDetectSDK.SDKInfoPrx
String clsName =serviceCls.getName();
// 获取简单名字 示例: SDKInfoPrx
String serviceName =serviceCls.getSimpleName();
// 获取 SDKInfoPrx 中最后一次出现 Prx 的下标 SDKInfoPrx 中pos =7
int pos =serviceName.lastIndexOf("Prx");
// 如果 serviceName 里没有 Prx 则 pos 会等于 -1
if (pos<=0){
throw new IllegalArgumentException("无效的 ObjectPrx class , class 名必须以 Prx 结尾!");
}
// 截取前面的一部分 示例:SDKInfoPrx -- 》 SDKInfoPrx
String realSvName =serviceName.substring(0,pos);
try{
// 创建基础 Ice.ObjectPrx 对象 realSvName:SDKInfoPrx
Ice.ObjectPrx base =communicator.stringToProxy(iceServerHostUrl);
// 通过反射获取接口实现类对象
// clsName+"Helper" --》 "com.videtek.ice.carDetectSDK.SDKInfoPrx"+"Helper" --> com.videtek.ice.carDetectSDK.SDKInfoPrxHelper
//获取SDKInfoPrxHelper 对象 向上转型成 ObjectPrx 类型(所有接口的父类型)
proxy =(ObjectPrx)Class.forName(clsName+"Helper").newInstance();
// 通过反射 获取 SDKInfoPrxHelper 对象里的 uncheckedCast()方法
Method m1 =proxy.getClass().getDeclaredMethod("uncheckedCast",ObjectPrx.class);
// 通过 反射的方式获取 利用SDKInfoPrxHelper对象的
// public static SDKInfoPrx uncheckedCast(Ice.ObjectPrx __obj, String __facet){
// return uncheckedCastImpl(__obj, __facet, SDKInfoPrx.class, SDKInfoPrxHelper.class);
// }
// 方法获取 SDKInfoPrx对象,并向上转型成 ObjectPrx对象
proxy = (ObjectPrx)m1.invoke(proxy,base);
// 将 SDKInfoPrx对象返回
return proxy;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 用于客户端API获取ice服务的示例场景
* @param serviceCls SDKInfoPrx.Class
* @return 包含了 id通信调度器的 并向上转型成 ObjectPrx 对象的 SDKInfoPrx 的实现类对象
*/
public ObjectPrx getServicePrx(Class serviceCls){
// 从 cls2PrxMap里取 serviceCls
ObjectPrx proxy = cls2PrxMap.get(serviceCls);
// 如果集合里有该 serviceCls 对应的 ObjetcPrx 对象 proxy!=null
// 说明 之前该对象被使用过,可以继续使用
if (proxy!=null){
// 将最近存取时间设置为当前系统时间
lastAccessTimestamp =System.currentTimeMillis();
// 并且将该 ObjectPrx 对象返回给客户使用
return proxy;
}
// 如果 proxy==null 说明 该对象是第一次被使用 就使用createIceProxy(getIceCommunictor(),serviceCls);方法创建一个ObjectPrx对象
proxy =createIceProxy(getIceCommunictor(),serviceCls);
// 将创建的对象放入 cls2PrxMap集合中 key值:SDKInfoPrx.Class vlaue值:可通信的SDKInfoPrx对象向上转型的 ObjectPrx对象
cls2PrxMap.put(serviceCls,proxy);
// 将最近存取时间设置为当前系统时间
lastAccessTimestamp =System.currentTimeMillis();
// 将服务用的 ObjectPrx对象返回给用户
return proxy;
}
/**
* 内部类创建守护线程
* 主要是防止客户用完后忘记关闭,导致内存泄露
*/
class MonitorThread extends Thread{
@Override
public void run(){
// 如果当前线程没有被中断,如果被中断说明客户调用了closeCommunicator(true)方法关闭了线程
while (!Thread.currentThread().isInterrupted()){
try{
// 让当前线程休眠5000秒
Thread.sleep(5000L);
// 如果最后存取时间加上闲置时间小于当前系统时间,说明该线程已经超时,则将其关闭
if (lastAccessTimestamp+idleTimeOutSeconds*1000L<System.currentTimeMillis()){
// 关闭该线程 传值true表示主动关闭
closeCommunicator(true);
}
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
/**
* 规范请求参数
*/
private Ice.InitializationData initIData() {
Ice.Properties properties = Ice.Util.createProperties();
// 设置最大传输信息量
properties.setProperty("Ice.MessageSizeMax",iceMessageSizeMax);
// 消息超时时间
properties.setProperty("Ice.Override.Timeout", iceOverrideTimeout);
// 连接超时时间
properties.setProperty("Ice.Override.ConnectTimeout", iceOverrideConnectTimeout);
Ice.InitializationData iData = new Ice.InitializationData();
iData.properties = properties;
return iData;
}
}