[编织消息框架]数值与逻辑分离

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 }
PropertiesFactory
 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 }
QMConfig

源码解读

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编程艺术

分离的根本目的是让开发者专注写引擎部分,无需关注太多业务上的边界,条件等

以上是关于[编织消息框架]数值与逻辑分离的主要内容,如果未能解决你的问题,请参考以下文章

[编织消息框架][分层模型设计]会话与节点

[编织消息框架][netty源码分析]7 Unsafe 实现类NioSocketChannelUnsafe职责与实现

[编织消息框架][JAVA核心技术]异常基础

[编织消息框架][优化系统]突破连接上限(上)

[编织消息框架]前言

[编织消息框架][消息处理模式]管道模式