zoukankan      html  css  js  c++  java
  • [编织消息框架][JAVA核心技术]数值与逻辑分离

    为什么要分离?

    业务需求是不停地变,如果把条件写进代码里,当用户需求变时要改代码发版本更新才能生效,这过程无疑是漫长的

    就算是在开发期,不停的变开发者精力耗光在沟通,小修改上,无法专注逻辑部分

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

    需要分离什么类型数值?

    如活动开启时间,购买满足条件,购买上限等 这些不确定用户具体需求,全都可以弄成动态获取

    分离技术实现有很多

    如使用数据库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编程艺术

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

    作者:solq
    博客地址:http://www.cnblogs.com/solq111
    博客版权:本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。
    如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!
    再次感谢您耐心的读完本篇文章。 淘宝店: 海豚极货店 QQ群:9547527java技术交流
    如果你热爱生活、热爱编程、热爱吉他。扫一扫加我微信
    我的新书《编织消息框架》目前进行中,感谢大家关注!
    知识共享许可协议本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。


    [编织消息框架]目录

    [编织消息框架]前言

    [编织消息框架][设计协议]bit基础

    [编织消息框架][设计协议]位运算

    [编织消息框架][设计协议]大小端模式

    [编织消息框架][设计协议]包

    [编织消息框架][设计协议]优化long,int转换

    [编织消息框架][设计协议]解决粘包半包(上)

    [编织消息框架][设计协议]解决粘包半包(中)

    [编织消息框架][设计协议]解决粘包半包(下)

    [编织消息框架][设计协议]opCode

    [编织消息框架][消息处理模式]请求响应模式

    [编织消息框架][消息处理模式]发布订阅模式

    [编织消息框架][消息处理模式]推拉模式

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

    [编织消息框架][分层模型设计]系统与应用

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

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

    [编织消息框架][JAVA核心技术]异常应用

  • 相关阅读:
    十七、mysql数据库备份
    消费端ACK和重回队列
    RabbitMQ TTL、死信队列
    消费端限流策略
    029异常处理
    028class_part2
    027class_part1
    026json和pickle,xml模块
    025__name__变量和目录结构规范
    024模块的概念
  • 原文地址:https://www.cnblogs.com/solq111/p/6475615.html
Copyright © 2011-2022 走看看