[编织消息框架]数值与逻辑分离
Posted solq321
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[编织消息框架]数值与逻辑分离相关的知识,希望对你有一定的参考价值。
为什么要分离?
业务需求是不停地变,如果把条件写进代码里,当用户需求变时要改代码发版本更新才能生效,这过程无疑是漫长的
就算是在开发期,不停的变开发者精力耗光在沟通,小修改上,无法专注逻辑部分
分离的根本目的是让开发者专注写引擎部分,无需关注太多业务上的边界,条件等
需要分离什么类型数值?
如活动开启时间,购买满足条件,购买上限等 这些不确定用户具体需求,全都可以弄成动态获取
分离技术实现有很多
如使用数据库mysql等
linux 常用的配置文本config
表格csv,json文件等
本项目用的是 java Properties 跟单例
1 import java.io.FileInputStream; 2 import java.io.InputStream; 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Type; 6 import java.util.Collection; 7 import java.util.Collections; 8 import java.util.HashSet; 9 import java.util.Map; 10 import java.util.Properties; 11 import java.util.Set; 12 13 import javax.script.ScriptEngine; 14 import javax.script.ScriptEngineManager; 15 16 import org.apache.commons.lang3.reflect.TypeUtils; 17 18 import com.eyu.onequeue.reflect.anno.FieldValue; 19 import com.eyu.onequeue.util.SerialUtil; 20 21 /** 22 * @author solq 23 */ 24 public class PropertiesFactory { 25 static final ScriptEngine se = new ScriptEngineManager().getEngineByName("javascript"); 26 27 public static <T> T initField(Class<T> clz, String file) { 28 T ret = null; 29 try { 30 Properties pro = getProperties(file); 31 ret = clz.newInstance(); 32 Field[] fields = clz.getDeclaredFields(); 33 Set<Method> methods = new HashSet<>(); 34 Collections.addAll(methods, clz.getDeclaredMethods()); 35 // Field modifiersField = Field.class.getDeclaredField("modifiers"); 36 // modifiersField.setAccessible(true); 37 for (Field f : fields) { 38 FieldValue anno = f.getAnnotation(FieldValue.class); 39 if (anno == null) { 40 continue; 41 } 42 43 String fileName = anno.value(); 44 String tmp = (String) pro.get(fileName); 45 if (tmp == null) { 46 continue; 47 } 48 Object value = tmp; 49 Type type = f.getGenericType(); 50 if (type.equals(Integer.TYPE)) { 51 value = se.eval(tmp); 52 } else if (type.equals(Double.TYPE)) { 53 value = se.eval(tmp); 54 } else if (type.equals(Float.TYPE)) { 55 value = se.eval(tmp); 56 } else if (type.equals(Long.TYPE)) { 57 value = se.eval(tmp); 58 } else if (type.equals(Short.TYPE)) { 59 value = ((Integer) se.eval(tmp)).shortValue(); 60 } else if (type.equals(Byte.TYPE)) { 61 value = ((Integer) se.eval(tmp)).byteValue(); 62 } else if (type.equals(Boolean.TYPE)) { 63 value = se.eval(tmp); 64 } else if (TypeUtils.isAssignable(type, Map.class)) { 65 value = SerialUtil.readValue(tmp, type); 66 }else if (TypeUtils.isAssignable(type, Collection.class)) { 67 value = SerialUtil.readValue(tmp, type); 68 }else if (TypeUtils.isAssignable(type, Class.class)) { 69 value = Class.forName(tmp); 70 }else if (TypeUtils.isArrayType(type)) { 71 value = SerialUtil.readArray(tmp, type); 72 } 73 74 String callMethodName = "set" + f.getName(); 75 boolean flag = false; 76 try { 77 for (Method m : methods) { 78 if (m.getName().equals(callMethodName)) { 79 m.setAccessible(true); 80 m.invoke(ret, value); 81 flag = true; 82 break; 83 } 84 } 85 } catch (Exception e) { 86 87 } 88 if (flag) { 89 continue; 90 } 91 f.setAccessible(true); 92 // modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); 93 94 try { 95 f.set(ret, value); 96 } catch (Exception e1) { 97 e1.printStackTrace(); 98 } 99 } 100 } catch (Exception e1) { 101 e1.printStackTrace(); 102 } 103 return ret; 104 } 105 106 public static Properties getProperties(String file) { 107 Properties pro = new Properties(); 108 String path = ClassLoader.getSystemResource(file).getPath(); 109 try (InputStream fs = new FileInputStream(path);) { 110 pro.load(fs); 111 } catch (Exception e) { 112 e.printStackTrace(); 113 } 114 return pro; 115 } 116 }
1 /*** 2 * qm config 所有配置 属性不能加 final 否则 jvm 会优化 3 * 4 * @author solq 5 */ 6 public class QMConfig { 7 private static QMConfig instance = null; 8 9 public static QMConfig getInstance() { 10 if (instance != null) { 11 return instance; 12 } 13 synchronized (QMConfig.class) { 14 if (instance != null) { 15 return instance; 16 } 17 instance = PropertiesFactory.initField(QMConfig.class, "qm.properties"); 18 } 19 return instance; 20 } 21 22 // other 23 24 /** 启动服务模式 影响 记录存储目录 **/ 25 public boolean SERVER_MODEL = true; 26 /** 集群服务器 <address> client端不能为NULL **/ 27 @FieldValue("QM.CLUSTER_LIST") 28 /** 本地名称,多应用不能相同,注册时用到 client端不能为NULL **/ 29 public String LOCALNAME; 30 /** 集群服务器 <address> client端不能为NULL **/ 31 @FieldValue("QM.CLUSTER_LIST") 32 public Set<String> CLUSTER_LIST; 33 /** 订阅信息 topic : groupId 填写开启 **/ 34 @FieldValue("QM.TOPIC_INFO") 35 public Set<String> TOPIC_INFO; 36 /** 日志保存路径 <订阅,路径> consume端不能为NULL **/ 37 @FieldValue("QM.CONSUME_LOG_SAVE_DIRS") 38 public Map<String, String> CONSUME_LOG_SAVE_DIRS = new HashMap<>(1); 39 40 public Collection<QSubscribe> getTopics() { 41 Collection<QSubscribe> ret = new HashSet<>(); 42 if (TOPIC_INFO == null) { 43 return ret; 44 } 45 for (String str : TOPIC_INFO) { 46 String[] s = str.split(":"); 47 ret.add(QSubscribe.of(s[0], s[1])); 48 } 49 return ret; 50 } 51 52 /** rpc param compress **/ 53 @FieldValue("QM.COMPRESS_SIZE") 54 public int COMPRESS_SIZE = 1024; 55 56 // NETTY 57 /** server port **/ 58 @FieldValue("QM.NETTY_SERVER_PORT") 59 public int NETTY_SERVER_PORT = 8080; 60 /** server bossgroup **/ 61 @FieldValue("QM.NETTY_SERVER_BOSSGROUP") 62 public int NETTY_SERVER_BOSSGROUP = 1; 63 /** server workergroup **/ 64 @FieldValue("QM.NETTY_SERVER_WORKERGROUP") 65 public int NETTY_SERVER_WORKERGROUP = 4; 66 /** server socket option **/ 67 @FieldValue("QM.NETTY_SERVER_SESSION_OPTION") 68 public Map<String, ?> NETTY_SERVER_SESSION_OPTION; 69 /** child socket option **/ 70 @FieldValue("QM.NETTY_SERVER_CHILD_SESSION_OPTION") 71 public Map<String, ?> NETTY_SERVER_CHILD_SESSION_OPTION; 72 /** 处理模型实现 **/ 73 @FieldValue("QM.NETTY_SERVER_ACCEPTOR") 74 public Class<? extends ServerSocketChannel> NETTY_SERVER_ACCEPTOR = NioserverSocketChannel.class; 75 76 .............................. 77 78 }
源码解读
PropertiesFactory
25行 用到JavaScript
为什么解释字符串部分用到 ScriptEngine ?
有些配置 如 QM.COMPRESS_SIZE = 1024*2 是个公式运算,要用引擎才能执行
108行通过ClassLoader来定位某个资源文件路径,然后再以文件读取的方式加载,不用ClassLoader加载,原因ClassLoader加载过有缓存,不能在线重载配置
initField整个函数用到java 反射技术,动态生成类,看似很多条件处理,按类型划分其实不多,常用处理五种
1.java基本类型 如int,long,short,byte,double,float,boolean,string等
2.数组同集合 array,collection
3.map字典
4.class类
5.java object
注意:java.lang.reflect.Type 非常重要,能保留泛型信息
处理Type工具 org.apache.commons.lang3.reflect.TypeUtils
QMConfig
17行 是个单例使用时必须初始化配置文件 qm.properties 才能正常使用
@FieldValue 注解的值对应配置文件上的KEY,命名风格 QM.+属性名 保持一致
如果配置文件上没有填写,以属性默认值为准,减少执行期初始化代码
具体每个属性作为在那里,以后分析每个模块会讲到
小结:
使用properties 跟单例可以做出很好的效果,看了大多数框架花大多精力在配置上,有时复杂的不一定是最优
重复一下观点 出自UNIX编程艺术
分离的根本目的是让开发者专注写引擎部分,无需关注太多业务上的边界,条件等
以上是关于[编织消息框架]数值与逻辑分离的主要内容,如果未能解决你的问题,请参考以下文章