diff --git a/article_convert.html b/article_convert.html deleted file mode 100644 index d080cd859..000000000 --- a/article_convert.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 技术详解 03 -- Convert高性能序列化

- -

-         Convert是个重复造轮子的组件,却是个飞速的轮子。Redkale之所以重复造轮子主要追求性能和需要与网络数据的序列化很好的结合(Convert与ByteBuffer的结合)。 Convert在API设计思路上也与其他同类型的框架不一样,配置与序列化方法是分开的,大部分场景下配置项是固定的,因此不同的配置会动态生成对应的处理类以保证性能。 -

-

-         从包结构可以看出,Convert分三层:序列化与反序列化的抽象基础包;JSON包、BSON包。即使boolean、int、String这种基础数据类型都不是包含在Convert基础包中。以下是详细的结构图: -

-

-

-         从上图可以看出,JSON与BSON是Convert基础包的实现,主要是实现Reader与Writer类。若需要实现XML格式的序列化,可以以JSON为参考自己编写。对于不是内置(ext子包下的数据类型)的且没有自定义处理(Encoder、Decoder)的数据类型, Convert会动态生成ObjectEncoder、ObjectDecoder对象。 -

-

Convert 的性能

-

-         一切不谈性能的框架都是在耍流氓!下面以JSON为例,与其他主流的JSON框架做个简单的性能比较(测试环境: DELL的普通笔记本),fastjson一直号称是Java性能最好的JSON解析框架,其使用Benchmark是 https://github.com/eishay/jvm-serializers/wiki,Redkale根据该数据编写了MediaContent类。实例由MediaContent.createDefault()方法所得。 -

-

-

-         由上图第一份报告看出,以MediaContent对象进行比较,fastjson的反序列化性能是最好的,gson性能最差。redkale、fastjson、jackson在序列化方面差距不大。第二份报告的测试对象是将第一份的MediaContent对象中所有的数值改成负数进行测试的,可以看出fastjson对于负数的处理性能很差,只是比gson稍强。第三份报告的测试对象是ConvertRecord对象,该对象的特点是包含int[]、long[]、List、Map数据。从结果可以看出fastjson的反序列化性能最差。redkale与jackson一直保持高性能。
-         综合起来看,Redkale的Convert性能是最好的。数据相差50毫秒以下的差距基本可以忽略,因为每次测试的结果会上下波动几十毫秒。 -

-
-

-         转载请注明出处:https://redkale.org/article_convert.html -

- - - -
- - - - diff --git a/article_creator.html b/article_creator.html deleted file mode 100644 index 7bf7a87e1..000000000 --- a/article_creator.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 技术详解 02 -- Creator构建对象

- -

-         org.redkale.util.Creator是采用ASM技术来实现代替反射构造函数的对象构建类。在根据流反序列化成对象、数据表记录转换成对象时都需要构建对象。常见的处理办法是利用反射,如Gson框架中反序列化是通过反射进行对象创建。众所周知反射的性能是比较低的,所以Redkale需要自实现一个对象构建类。 -

-

-         Creator是一个接口, 只有一个public T create(Object... params)方法,可变参数既适合空参数的Constructor也适合含参数的Constructor。得利于Java 8的新语法特性可以在接口上加上静态方法,Creator对象可以通过Creator.create(Class clazz)方法创建。构建原理是通过Constructor的参数来动态创建的。 -

-
        Constructor<T> constructor0 = null;
-        SimpleEntry<String, Class>[] constructorParameters0 = null; //构造函数的参数
-
-        if (constructor0 == null) {  // 1、查找public的空参数构造函数
-            for (Constructor c : clazz.getConstructors()) {
-                if (c.getParameterCount() == 0) {
-                    constructor0 = c;
-                    constructorParameters0 = new SimpleEntry[0];
-                    break;
-                }
-            }
-        }
-        if (constructor0 == null) {  // 2、查找public带ConstructorProperties注解的构造函数
-            for (Constructor c : clazz.getConstructors()) {
-                ConstructorProperties cp = (ConstructorProperties) c.getAnnotation(ConstructorProperties.class);
-                if (cp == null) continue;
-                SimpleEntry<String, Class>[] fields = ConstructorParameters.CreatorInner.getConstructorField(clazz, cp.value());
-                if (fields != null) {
-                    constructor0 = c;
-                    constructorParameters0 = fields;
-                    break;
-                }
-            }
-        }
-        if (constructor0 == null) {  // 3、查找public且不带ConstructorProperties注解的构造函数
-            List<Constructor> cs = new ArrayList<>();
-            for (Constructor c : clazz.getConstructors()) {
-                if (c.getAnnotation(ConstructorProperties.class) != null) continue;
-                if (c.getParameterCount() < 1) continue;
-                cs.add(c);
-            }
-            //优先参数最多的构造函数
-            cs.sort((o1, o2) -> o2.getParameterCount() - o1.getParameterCount());
-            for (Constructor c : cs) {
-                SimpleEntry<String, Class>[] fields = ConstructorParameters.CreatorInner.getConstructorField(clazz, Type.getConstructorDescriptor(c));
-                if (fields != null) {
-                    constructor0 = c;
-                    constructorParameters0 = fields;
-                    break;
-                }
-            }
-        }
-        if (constructor0 == null) {  // 4、查找非private带ConstructorProperties的构造函数
-            for (Constructor c : clazz.getDeclaredConstructors()) {
-                if (Modifier.isPublic(c.getModifiers()) || Modifier.isPrivate(c.getModifiers())) continue;
-                ConstructorProperties cp = (ConstructorProperties) c.getAnnotation(ConstructorProperties.class);
-                if (cp == null) continue;
-                SimpleEntry<String, Class>[] fields = ConstructorParameters.CreatorInner.getConstructorField(clazz, cp.value());
-                if (fields != null) {
-                    constructor0 = c;
-                    constructorParameters0 = fields;
-                    break;
-                }
-            }
-        }
-        if (constructor0 == null) {  // 5、查找非private且不带ConstructorProperties的构造函数
-            List<Constructor> cs = new ArrayList<>();
-            for (Constructor c : clazz.getDeclaredConstructors()) {
-                if (Modifier.isPublic(c.getModifiers()) || Modifier.isPrivate(c.getModifiers())) continue;
-                if (c.getAnnotation(ConstructorProperties.class) != null) continue;
-                if (c.getParameterCount() < 1) continue;
-                cs.add(c);
-            }
-            //优先参数最多的构造函数
-            cs.sort((o1, o2) -> o2.getParameterCount() - o1.getParameterCount());
-            for (Constructor c : cs) {
-                SimpleEntry<String, Class>[] fields = ConstructorParameters.CreatorInner.getConstructorField(clazz, Type.getConstructorDescriptor(c));
-                if (fields != null) {
-                    constructor0 = c;
-                    constructorParameters0 = fields;
-                    break;
-                }
-            }
-        }
-                
-

-         从以上代码可以看出,根据优先级选择Constructor,为了减少学习成本,Creator直接重用了java.beans.ConstructorProperties注解,又因ConstructorProperties只能标记在Constructor上,因此定义一个Creator.ConstructorParameters注解,用于标记在Creator的create方法上。 -

-
public class Record {
-
-    private final int id;
-
-    private String name;
-
-    @ConstructorProperties({"id", "name"})
-    Record(int id, String name) {
-        this.id = id;
-        this.name = name;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-}
-
-
-Record.class通过ASM自动构建与Record同package的Creator类如下:
-
-public final class Record_DynCreator implements Creator<Record> {
-
-    @Override
-    @Creator.ConstructorParameters({"id", "name"})
-    public Record create(Object... params) {
-        if (params[0] == null) params[0] = 0;
-        return new Record((Integer) params[0], (String) params[1]);
-    }
-}
-

-         如上代码,若构造参数是primitive类,而Creator.create传入的参数可能是null,因此需要给null的primitive对象赋予默认值0。细心的人可能发现了Record的构造函数并不是public的,虽然Record_DynCreator与Record在同一package,但由于两者不是同一个ClassLoader,故不能直接new Record。Redkale曲线救国,通过URLClassLoader的私有方法在Record.class的ClassLoader加载Record_DynCreator。 -

-
if (loader instanceof URLClassLoader && !Modifier.isPublic(constructor.getModifiers())) {
-    try {
-        final URLClassLoader urlLoader = (URLClassLoader) loader;
-        final URL url = new URL("memclass", "localhost", -1, "/" + newDynName.replace('/', '.') + "/", new URLStreamHandler() {
-            @Override
-            protected URLConnection openConnection(URL u) throws IOException {
-                return new URLConnection(u) {
-                    @Override
-                    public void connect() throws IOException {
-                    }
-
-                    @Override
-                    public InputStream getInputStream() throws IOException {
-                        return new ByteArrayInputStream(bytes);
-                    }
-                };
-            }
-        });
-        Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
-        addURLMethod.setAccessible(true);
-        addURLMethod.invoke(urlLoader, url);
-        resultClazz = urlLoader.loadClass(newDynName.replace('/', '.'));
-    } catch (Throwable t) { //异常无需理会, 使用下一种loader方式
-        t.printStackTrace();
-    }
-}
-

-         如上代码,构建一个虚拟协议的URL来实现加载,若Record.class的ClassLoader不是URLClassLoader导致resultClazz为null则会抛出异常。 -

-

-         Creator是一个典型通过ASM构建一个简单功能地动态类,同类型还有 org.redkale.util.Attributeorg.redkale.util.Copier。 -

-
-

-         转载请注明出处:https://redkale.org/article_creator.html -

- - - -
- - - - diff --git a/article_parents.html b/article_parents.html deleted file mode 100644 index 0b41bef20..000000000 --- a/article_parents.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 技术详解 01 -- 双亲委托模型

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/article_regain.html b/article_regain.html deleted file mode 100644 index feb197bfa..000000000 --- a/article_regain.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 让你重新认识Java

- -

-         Java 已经22岁了,依靠强大的功能、庞大的开发社区和无人能及的生态系统,长期占据世界编程语言排行榜首,成为当之无愧的业界之王。本人在大学时期被这种很有艺术性的开发语言所吸引,果断抛弃C,学习方式很简单,只看JDK API源码,直到现在都是如此。刚毕业就一直从事Java开发方面的工作,至今也有十来年了。从JSP、WebWork到Struts、JSF,从JDBC、Hibernate到TopLink、JPA。从NIO、Mina到Netty、Grizzly。很多框架都用过,研究过。后来渐渐觉得各阶段主流的框架功能很强大,设计理念也很好,但是我们大部分情况只使用其中一小部分功能,框架在实现上性能也普遍一般(除少数追求性能的框架),且适应新版JDK发布的版本普及也会滞后两三年以上。慢慢地发现用了那么多整合的框架并没有比JBuilder时代的开发速度敏捷多少,很多时候在原始的Servlet、JDBC基础上做一定的封装就能满足大部分的需求。个人觉得一个优秀的框架除了保证性能和稳定性,也要注重简易性,而不是满足开发者需求的同时大量依赖包、繁琐的配置(过多的配置性的注解也是配置)和臃肿的jar刷其存在感。在长期看JDK API源码、学习开源框架设计理念和参考JavaEE规范接口设计的习惯下自然有了自己的思路和设计想法,开始学写自己的框架。于是,在2015年框架基本雏形出来并命名为Redkale,2016年正式开源。
-

- -

  太臃肿

-

-         如今在Java界,Tomcat、Struts2、Hibernate、MyBatis、Jetty、Spring MVC/Spring Boot/Spring XXX这些框架大家都耳熟能详,似乎不懂其中一二都不好意思说自己会Java,Spring已经成了Java的代名词,很多四五年以上的开发人员只识开源框架不识JDK,谈起这些框架的原理、优缺点,各种SOA、集群、分布式、事务、微服务概念头头是道,都可以出书了,但是写出来的代码或做出来的数据结构设计,堪比实习生。2004年发布正式版的Spring以其轻量著称,一战成名,打败了J2EE,成为Java事实上的"标准"。然而经过十几年的发展,其生态越来越庞大,功能的增多,复杂的配置,兼容性,历史包袱,使得整个框架体系越来越臃肿,还有不计其数的衍生插件,已经不比当年J2EE轻多少了。在那个JBuilder还是主流IDE的年代,建一个Web系统还是比较简单,除了Tomcat比较大点,其他都算轻量。现在很多人建一个Web项目,需要复杂的Maven建工程,一行代码还没开始写,仅仅是导入SSH/SSM这些框架就需要好几十M,还得进行一堆SSH配置,简单的日志都需要配备log4j/snf4j。可能只是简单的管理系统,必须要搞这么复杂吗? 现在一个1-2M的框架大家都认为是轻量级,MyBatis是JDBC基础上的再次封装,jar包大小近6M,但比Hibernate还是轻量些。这些只是基础框架,如果系统还需要其他功能性的框架(Lucene、Mail、Json) 会使开发包更大。同时大量的开源框架抑制了新JDK的普及,NIO出来都十五年, AIO(NIO.2)也出来六年了,如今还有很多人把NIO给贴上高性能的标签,JDK 5——Java第一个重大更新的版本在2004年发布,直到五-六年后Spring的注解方式才得到普及(Spring旧版本xml方式普及度高), Java第二个重大更新的版本JDK 8发布也有三年了,大部分人还停留在只识语法糖Lambda的程度,在JDK功能越来越强大的情况下,开发Java系统并不简化多少。相对于现在流行的NodeJs做个Web系统来说,Java就是个巨无霸。其实呢,Java无需这么复杂,真的可以很简单。 -

- -

  异步呢

-

-         用Java开发异步系统是件很难的事情,首先Java规范中异步接口很少,Servlet 3.0虽然支持异步,也出来好几年了,但现在直接用Servlet写代码的人已经不多,而基于Servlet的Struts、Spring Boot这些框架又没把异步太当回事,基本都是实现的Servet同步方法。其次JDBC更是迄今为止还没有异步接口,基于JDBC的Hibernate、MyBatis同样不会有异步接口。再者开源框架以异步接口为主的凤毛麟角。 所以Java程序员在写业务代码时基本都是用同步方式,特别是最耗时的数据源操作JDBC无法异步。 -

-
@WebServlet(value = {"/order/*"}, comment = "订单模块")
-public class  OrderServlet extends HttpBaseServlet {
-    
-    @Resource
-    private OrderService service;
-    
-    @HttpMapping(url = "/order/find", comment = "查询单个订单")
-    @HttpParam(name = "#", type = long.class, comment = "订单ID")
-    public void logout(HttpRequest req, HttpResponse resp) throws IOException {
-        long orderid =  req.getRequstURILastPath(0L);
-        resp.finishJson(service.findOrder(orderid)); 
-    }
-}
-
-
-@Comment("订单服务")
-public class OrderService implements Service {
-
-    @Resource
-    private DataSource source;
-
-    @Comment("查询单个订单")
-    public Order findOrder(long orderid) {
-        return source.find(Order.class, orderid);
-    }
-}
-                
-

-         以上范例是大多数Java开发者的常规写法,Servlet调用Service,Serivce调用数据库操作返回结果,都是同步操作, 而基于JDBC的开发使得最耗时的数据库操作占用了大量的线程时间,即使HTTP使用NIO、AIO(NIO.2)都收效甚微。而在NodeJs里,IO操作都是异步的。如下: -

-
http.post("/url", {id:10}, function(request, response) {
-  service.getResource(request, function(result){
-    response.write(JSON.stringify(result));
-    response.end();
-  });
-});
-
-var mysql = require('mysql');
-var pool  = mysql.createPool(config);
-pool.getConnection(function(err, connection) {
-  connection.query( 'SELECT * FROM table', function(err, rows) {
-    connection.end();
-  });
-});
-                
-

-         这也是Nodejs做些小系统比Java还快。Java在语言和IO上的性能优势弥补不了同步来带的线程消耗。 -

- -

  Redkale

-

-         Redkale 一个全新设计的Java异步微服务框架。集HTTP、WebSocket、REST、JSON、RPC、DB操作、依赖注入等功能于一身,其 redkale-1.6.1.jar 包大小仅790K,且不依赖任何第三方包。大部分框架自身就很庞大,满足了开发者的需求同时也带来复杂的配置。如同上班只有三四里路,要买辆汽车代步,开车轻松的同时会带来车的保险保养,堵车,加油,下车库,找停车位等副作用, 其效果还不如一辆自行车来得简单高效。Redkale 返璞归真,抛弃沉重的历史包袱(javax.servlet、JPA等),基于JDK 8设计微服务架构,尽可能挖掘新JDK的优势,在保证高性能的情况下追求框架的简易性。其核心分三层结构:接入层Servlet、逻辑层Service、数据层Source。 并且三层都支持异步接口,从HTTP接收请求到Service处理逻辑再到Source拉取数据,全程都可异步,最大限度的提高CPU使用率。为适应前后台分工的开发场景,Redkale提供方便的API文档生成功能,无需编写标准的Doc文档也能让前端开发人员知道后台API接口。
-

-

  Servlet

-

-         在API设计上,Redkale下足了功夫,敢于抛弃标准。其HTTP服务不再是javax.servlet——J2EE中使用最多的规范的实现,在接口形式上与NodeJs的HTTP模块很类似,易于操作。Servlet规范对于现在的应用来说过于体系化。时下移动APP、发达的前端、高性能浏览器、静动分离、REST、分布式这些因素已经让JSP、PHP、ASP这些通过后台生成页面的集中式开发框架显得不适时宜。NodeJs中的HTTP模块源码不过十几K,而Java里HTTP服务主流还是Tomcat——一个8M的重型机器。 Redkale摒弃了已经落伍的规范和功能(JSP、InputStream/OutputStream操作、Session对象等),弱化Web概念,HTTP与SNCP服务一样,只是接入层,无需做过重的设计。
-         其他语言的HTTP框架在处理请求后大多是以response关闭整个请求处理的方式为主,而Servlet规范却是在service方法执行后关闭,Servlet3.0规范虽然支持异步,显然与其他语言框架比,比较生涩难懂,其API使用方式还需与同步方式区别对待,且接口设计本身就会导致实现上性能不佳,而response关闭的方式天然的把同步与异步等同对待,无论是当前线程还是另开线程处理请求,都由response来结束请求处理。同样Servlet规范中WebSocket接口设计上也是过于复杂,且只是针对协议本身的实现,没有考虑大多开发者的使用场景,WebSocket本质上就像一个keepalive的Http请求,雷同HttpServlet。所以Redkale在设计WebSocket时尽量与HttpServlet形态保持一致,同时还集成了分布式功能,让开发者也可以很简单的实现多部署。
-

-

  Service

-

-         Redkale所有API设计中最精简的当属RPC功能,RPC没有直接调用的API,其功能依附在Service。RPC或类似功能的框架在Java里一直是比较重量级的,从古老的Corba、RMI到后来的EJB、WebService,还有其他很多RPC开源框架,都有着复杂的配置和大量API学习成本,有些还需要区分客户端和服务端(如WebService)。 Service不仅只是个逻辑层的规范定义,还集成了很强大的RPC和异步调用功能,远程模式的Service就是RPC功能,系统在依赖注入过程中创建Service时通过基本的IP配置自动识别是创建本地模式的Service还是远程模式的Service,远程模式的Service使用的就是RPC,但在代码层Service的调用本地模式与远程模式完全一样。更神奇的是,带有异步回调函数CompletionHandler 或返回类型为CompletableFuture的Service方法同样能执行远程模式。这种RPC的简易性是其他框架都无可匹敌的。REST风格的Service在接口设计上尽量减少注解性的配置,同时保留灵活性,减少HttpServlet的编写。
-

-

  Source

-

-         除了开发类似数据库管理工具,大部分情况下开发者只用基本的增删改查操作,Hibernate、MyBatis这些框架对于对象新增/更新/删除和主键查找对象等接口定义都比较简单,但是带有过滤条件的操作,就变得难用。MyBatis需要配各种SQL,大量if else,Hibernate需要写HQL或SQL,JPA规范需要写JPQL或使用Criteria功能。过滤查询、翻页查询、过滤修改、局部修改、过滤删除都是很常见的操作,而主流ORM框架对这类操作进行简化程度很有限。Redkale 结合常规的使用场景,以JavaBean的方式提供过滤功能,无需编写SQL或类似SQL的配置,使过滤性的操作(删改查)API变得异常简单。DataSource另一个亮点是分表分库操作与单表操作的API一样。同时每个操作都提供成异步接口,但是又限于JDBC的同步性,DataSource的默认实现提供的异步接口也只是JDBC的同步操作,若开发者追求性能极致的话,可以使用AIO(NIO.2)技术编写不基于JDBC的DataSource异步实现。 DataSource提供的缓存功能还能保持进程间的同步(得利于RPC)。
-

-

  思维

-

-         Redkale 追求大道至简,不仅提供高性能的功能和简易的API,还带来不同的设计思维。作为一名有追求的开发者,不能只停留在API层面,更多的是需要掌握设计能力,一个好的设计方案往往能少写很多代码。Java里很多规范和框架就是考虑太过全面,为了迎合各种良莠不齐的想法和设计。比如HTTP服务,只是系统的一个接入层,有必须设计ServletConfig、ServletContextListener、HttpSessionListener等这么多API吗,开发者在系统初期养成定义全局的BaseHttpServlet这些基类的习惯就可以控制很多东西,普通功能也无需去使用什么拦截器或AOP功能。提交表单前先将表单数据转换成JSON字符串传给后台,后台的接口既可用于Web也可用于APP,非要按原始表单提交,那只能使用Struts这类笨重的框架了。再如DB操作,开发者设计好的数据结构可以把关系型数据库当NoSQL数据库操作,会发现对JDBC做一定的封装就可以基本杜绝写SQL。非要写存储过程、关联五六张表进行复杂查询,再好的DB框架都满足不了你。再如Date,Date对象的本质是long值,很多人习惯性的数据库就用Date类型,这样会增加很多麻烦,增加数据库的通用性难度,JSON还需要提供各种DateFormat,如果使用long类型,时间只交给页面去format就简单很多,long的性能也更好。说了这么多,只是想表达一个观点,开发时摆脱传统思维的桎栲,换个思路去思考,很多东西会变得很简单。
-

-

-         想了解更多关于Redkale的资料, 请访问Redkale官网: :http://redkale.org。 -

-
-

-         转载请注明出处:https://redkale.org/article_regain.html -

- - - -
- - - - diff --git a/article_source.html b/article_source.html deleted file mode 100644 index 59c1306af..000000000 --- a/article_source.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 技术详解 04 -- DataSource简易的DB操作

- -

-         长期以来,Hibernate和Mybatis一直是大家使用最多的持久层开发框架。针对这两种框架网络上是各种比较,各种讨论优缺点。其实这两个框架(算上前身ibatis)都是2002年左右发布, 迄今已有15载已笨重不堪,一个mybatis.jar包大小6M左右,Hibernate更是巨大,毫无轻巧灵活可言,提供简化SQL操作的同时带来了复杂繁琐的配置和高学习门槛。而Redkale的Source组件非常轻量级,通过十多个interfaceenum和十多个class完成常见的DB操作功能。
-         Source组件在接口设计上参考了JPA接口,为了降低学习成本,部分注解仍沿用javax.persistence中的类,以多个注解结合一个主操作类 DataSource 的方式提供API。在没有IDE自动生成代码的插件支持的情况下Redkale提供了一个Demo代码 AutoClassCreator 能很方便的将数据库表生成Entity类。 -

-

-         。
-         Source组件在接口设计上参考了JPA接口,为了降低学习成本,部分注解仍沿用javax.persistence中的类,以多个注解结合一个主操作类 DataSource 的方式提供API。在没有IDE自动生成代码的插件支持的情况下Redkale提供了一个Demo代码 AutoClassCreator 能很方便的将数据库表生成Entity类。 -

-

-          -

-
Constructor<T> constructor0 = null;
-for (Constructor c : clazz.getConstructors()) {  //优先找public 的构造函数
-if (c.getParameterCount() == 0) {
-        constructor0 = c;
-        break;
-
-    }
-}
-if (constructor0 == null) {//其次找非private带ConstructorProperties的构造函数
-    for (Constructor c : clazz.getDeclaredConstructors()) {
-        if (Modifier.isPrivate(c.getModifiers())) continue;
-        if (c.getAnnotation(ConstructorProperties.class) != null) {
-            constructor0 = c;
-            break;
-        }
-    }
-}
-if (constructor0 == null) {//再次找非private且带-parameters编译项的构造函数 java 8以上才支持
-    for (Constructor c : clazz.getDeclaredConstructors()) {
-        if (Modifier.isPrivate(c.getModifiers())) continue;
-        Parameter[] params = c.getParameters();
-        if (params.length == 0) continue;
-        boolean flag = true;
-        for (Parameter param : params) {
-            try {
-                clazz.getDeclaredField(param.getName());
-            } catch (Exception e) {
-                flag = false;
-                break;
-            }
-        }
-        if (flag) {
-            constructor0 = c;
-            break;
-        }
-    }
-}
-if (constructor0 == null) {//最后找非private的空构造函数
-    for (Constructor c : clazz.getDeclaredConstructors()) {
-        if (Modifier.isPrivate(c.getModifiers())) continue;
-        if (c.getParameterCount() == 0) {
-            constructor0 = c;
-            break;
-        }
-    }
-}
-

-         从以上代码可以看出,根据优先级选择Constructor,为了减少学习成本,Creator直接重用了java.beans.ConstructorProperties注解,又因ConstructorProperties只能标记在Constructor上,因此定义一个Creator.ConstructorParameters注解,用于标记在Creator的create方法上。 -

-
public class Record {
-
-    private final int id;
-
-    private String name;
-
-    @ConstructorProperties({"id", "name"})
-    Record(int id, String name) {
-        this.id = id;
-        this.name = name;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-}
-
-
-Record.class通过ASM自动构建与Record同package的Creator类如下:
-
-public final class Record_DynCreator implements Creator<Record> {
-
-    @Override
-    @Creator.ConstructorParameters({"id", "name"})
-    public Record create(Object... params) {
-        if (params[0] == null) params[0] = 0;
-        return new Record((Integer) params[0], (String) params[1]);
-    }
-}
-

-         如上代码,若构造参数是primitive类,而Creator.create传入的参数可能是null,因此需要给null的primitive对象赋予默认值0。细心的人可能发现了Record的构造函数并不是public的,虽然Record_DynCreator与Record在同一package,但由于两者不是同一个ClassLoader,故不能直接new Record。Redkale曲线救国,通过URLClassLoader的私有方法在Record.class的ClassLoader加载Record_DynCreator。 -

-
if (loader instanceof URLClassLoader && !Modifier.isPublic(constructor.getModifiers())) {
-    try {
-        final URLClassLoader urlLoader = (URLClassLoader) loader;
-        final URL url = new URL("memclass", "localhost", -1, "/" + newDynName.replace('/', '.') + "/", new URLStreamHandler() {
-            @Override
-            protected URLConnection openConnection(URL u) throws IOException {
-                return new URLConnection(u) {
-                    @Override
-                    public void connect() throws IOException {
-                    }
-
-                    @Override
-                    public InputStream getInputStream() throws IOException {
-                        return new ByteArrayInputStream(bytes);
-                    }
-                };
-            }
-        });
-        Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
-        addURLMethod.setAccessible(true);
-        addURLMethod.invoke(urlLoader, url);
-        resultClazz = urlLoader.loadClass(newDynName.replace('/', '.'));
-    } catch (Throwable t) { //异常无需理会, 使用下一种loader方式
-        t.printStackTrace();
-    }
-}
-

-         如上代码,构建一个虚拟协议的URL来实现加载,若Record.class的ClassLoader不是URLClassLoader导致resultClazz为null则会抛出异常。 -

-

-         Creator是一个典型通过ASM构建一个简单功能地动态类,同类型还有 org.redkale.util.Attributeorg.redkale.util.Copier。 -

-
-

-         转载请注明出处:https://redkale.org/article_creator.html -

- - - -
- - - - diff --git a/articles.html b/articles.html deleted file mode 100644 index 26c78b841..000000000 --- a/articles.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -

- Redkale 入门教程 02 -- REST敏捷开发 -
- -

- -

- Redkale 入门教程 01 -- Hello Word! -
- -

- -

- Redkale 技术详解 03 -- Convert高性能序列化 -
- -

- -

- Redkale 技术详解 02 -- Creator构建对象 -
- -

- -

- Redkale 技术详解 01 -- 双亲委托模型 -
- -

- -
- - -
- - - - diff --git a/code.html b/code.html deleted file mode 100644 index 69b01dc57..000000000 --- a/code.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
- -

Github 源码

- -

        Redkale Github 源码

- -

Javadoc API

- -

        在线 Javadoc API

- - - -
- - - - diff --git a/convert.html b/convert.html deleted file mode 100644 index 38abd4706..000000000 --- a/convert.html +++ /dev/null @@ -1,431 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - - - - -
-

Convert 组件介绍

- -

         Convert 是一个比较独立的组件,仅依赖于util包。提供Java对象的序列化与反序列化功能。支持JSON(JavaScript Object Notation)、BSON(Binary Stream Object Notation)两种格式化。 两种格式使用方式完全一样,其性能都大幅度超过其他JSON框架。同时JSON内置于HTTP服务中,BSON也是SNCP协议数据序列化的基础。

- -

Convert 快速上手

-

        本介绍仅以JSON为例(BSON与JSON使用方式雷同)。其操作类主要是JsonConvert,配置类主要是JsonFactory、ConvertColumn。JsonFactory采用同ClassLoader类似的双亲委托方式设计。

-

        JsonConvert 序列化encode方法:

- -
    public String convertTo(final Object value);
-
-    public String convertTo(final Type type, final Object value);
-
-    public void convertTo(final OutputStream out, final Object value);
-
-    public void convertTo(final OutputStream out, final Type type, final Object value);
-
-    public ByteBuffer[] convertTo(final java.util.function.Supplier<ByteBuffer> supplier, final Object value);
-
-    public ByteBuffer[] convertTo(final java.util.function.Supplier<ByteBuffer> supplier, final Type type, final Object value);
-
-    public void convertTo(final JsonWriter writer, final Object value);
-
-    public void convertTo(final JsonWriter writer, final Type type, final Object value);
- -

        JsonConvert 反序列化decode方法:

-
    public <T> T convertFrom(final Type type, final String text);
-
-    public <T> T convertFrom(final Type type, final char[] text);
-
-    public <T> T convertFrom(final Type type, final char[] text, final int start, final int len);
-
-    public <T> T convertFrom(final Type type, final InputStream in);
-
-    public <T> T convertFrom(final Type type, final ByteBuffer... buffers);
-
-    public <T> T convertFrom(final Type type, final JsonReader reader);
- -

        Convert 与 ByteBuffer 的结合

-

        从以上的方法可以看出,与其他JSON框架相比Convert多了与ByteBuffer结合的方法。特别是convertTo方法加了Supplier<ByteBuffer>方法,这么做是为了提高数据传输的性能。在大部分情况下JSON序列化得到的数据流是为了传输出去,常见的场景就是HTTP+JSON接口。Convert提供ByteBuffer接口会大量减少中间临时数据的产生。大部分输出JSON数据的方法如下: -

-
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        String json = new Gson().toJson(record);
-        resp.setContentType("text/json; charset=UTF-8");
-        resp.getOutputStream().write(json.getBytes("UTF-8"));
-    }
-

        几乎所有的JSON框架提供的接口以String作为返回结果为主,其内在都是以char[]作为JsonWriter的载体。以Gson为例,Gson拼接JSON默认使用的是StringWriter,StringWriter的扩容策略是翻倍。为了方便计算,假设一个对象转换成JSON字符串大小为了10K。Gson在转换过程中产生的临时的char[]的大小: 16 + 32 + 64 + 128 + 256 + 512 + 1K + 2K + 4K + 8K + 16K = 32K, char[]转换成最终的String结果又会产生10K的char[], 最后在response输出时又会产生10K的byte[](方便计算不考虑双字节),也就是说整个对象输出过程中会产生52K的临时数据。而且常见的HTTP服务器(如实现java-servlet规范的服务器)不会把底层的ByteBuffer对象池暴露给上层。所以以String为输出结果的JSON方法都会产生5倍于数据体积大小(其他低于1倍扩容策略的框架会产生更多)的垃圾数据。
-         Redkale框架的HTTP服务内置了Convert的JSON接口,避免了大量的垃圾数据产生。Redkale的HTTP是基于AIO(NIO.2)实现且存在ByteBuffer对象池,response的finishJson系列方法将HTTP服务的ByteBuffer对象池传给Convert, 使Convert在序列化过程中直接以UTF-8编码方式输出到ByteBuffer里,输出结束后将ByteBuffer交给对象池回收,从而减少大量构建bye[]、char[]所产生的临时数据。

-
    protected void execute(HttpRequest req, HttpResponse resp) throws IOException {
-        resp.finishJson(record);
-    }
-
- -

        Convert 基本用法:

-
    public class UserRecord {
-
-        private int userid;
-
-        private String username = "";
-
-        private String password = "";
-
-        public UserRecord() {
-        }
-
-        /** 以下省略getter setter方法 */
-    }
-
-    public static void main(String[] args) throws Exception {
-        UserRecord user = new UserRecord();
-        user.setUserid(100);
-        user.setUsername("redkalename");
-        user.setPassword("123456");
-        final JsonConvert convert = JsonConvert.root();
-        String json = convert.convertTo(user);
-        System.out.println(json);  //应该是 {"password":"123456","userid":100,"username":"redkalename"}
-        UserRecord user2 = convert.convertFrom(UserRecord.class, json);
-        System.out.println(convert.convertTo(user2)); //应该也是 {"password":"123456","userid":100,"username":"redkalename"}
-        
-        /**
-         * 以下功能是为了屏蔽password字段。
-         * 等价于 public String getPassword() 加上 @ConvertColumn :
-         *
-         *      @ConvertColumn(ignore = true, type = ConvertType.JSON)
-         *      public String getPassword() {
-         *          return password;
-         *      }
-         **/
-        final JsonFactory childFactory = JsonFactory.root().createChild();
-        childFactory.register(UserRecord.class, true, "password"); //屏蔽掉password字段使其不输出
-        childFactory.reloadCoder(UserRecord.class); //重新加载Coder使之覆盖父Factory的配置
-        final JsonConvert childConvert = childFactory.getConvert();
-        json = childConvert.convertTo(user);
-        System.out.println(json);  //应该是 {"userid":100,"username":"redkalename"}
-        user2 = childConvert.convertFrom(UserRecord.class, json);
-        System.out.println(childConvert.convertTo(user2)); //应该也是 {"userid":100,"username":"redkalename"}
-    }
-

        在Redkale里存在默认的JsonConvert、BsonConvert对象。 只需在所有Service、Servlet中增加依赖注入资源。

-
public class XXXService implements Service {
-
-    @Resource
-    private JsonConvert jsonConvert;
-
-    @Resource
-    private BsonConvert bsonConvert;
-}
-
-public class XXXServlet extends HttpServlet {
-
-    @Resource
-    private JsonConvert jsonConvert;
-
-    @Resource
-    private BsonConvert bsonConvert;
-    
-}
-                
-

        同一类型数据通过设置新的JsonFactory可以有不同的输出。

-
public class UserSimpleInfo {
-
-    private int userid;
-
-    private String username = "";
-
-    @ConvertColumn(ignore = true, type = ConvertType.JSON)
-    private long regtime; //注册时间
-
-    @ConvertColumn(ignore = true, type = ConvertType.JSON)
-    private String regaddr = ""; //注册IP
-
-    /** 以下省略getter setter方法 */
-}
-
-
-
-public class UserInfoServlet extends HttpBaseServlet {
-
-    @Resource
-    private UserSerice service;
-
-    @Resource
-    private JsonFactory jsonRootFactory;
-
-    @Resource
-    private JsonConvert detailConvert;
-
-    @Override
-    public void init(HttpContext context, AnyValue config) {
-        final JsonFactory childFactory = jsonRootFactory.createChild();
-        childFactory.register(UserSimpleInfo.class, false, "regtime", "regaddr"); //允许输出注册时间与注册地址
-        childFactory.reloadCoder(UserSimpleInfo.class); //重新加载Coder使之覆盖父Factory的配置
-        this.detailConvert = childFactory.getConvert();
-    }
-
-    // 获取他人基本信息
-    @HttpMapping(url = "/user/info/")
-    public void info(HttpRequest req, HttpResponse resp) throws IOException {
-        int userid = Integer.parseInt(req.getRequstURILastPath());
-        UserSimpleInfo user = service.findUserInfo(userid);
-        resp.finishJson(user);  // 不包含用户的注册时间和注册地址字段信息
-    }
-
-    //获取用户自己的信息
-    @HttpMapping(url = "/user/myinfo")
-    public void mydetail(HttpRequest req, HttpResponse resp) throws IOException {
-        int userid = req.currentUser().getUserid(); //获取当前用户ID
-        UserSimpleInfo user = service.findUserInfo(userid);
-        resp.finishJson(detailConvert, user);  // 包含用户的注册时间和注册地址字段信息
-    }
-}
-                
-
- -

        Convert 支持带参数构造函数。

-

         1. public 带参数的构造函数加上 @ConstructorParameters 注解:

-
public class UserRecord {
-
-    private int userid;
-
-    private String username = "";
-
-    private String password = "";
-
-    @ConstructorParameters({"userid", "username", "password"})
-    public UserRecord(int userid, String username, String password) {
-        this.userid = userid;
-        this.username = username;
-        this.password = password;
-    }
-
-    public int getUserid() {
-        return userid;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-}
-

         2. 自定义Creator:

-
public class UserRecord {
-
-    private int userid;
-
-    private String username = "";
-
-    private String password = "";
-
-    UserRecord(int userid, String username, String password) {
-        this.userid = userid;
-        this.username = username;
-        this.password = password;
-    }
-
-    public int getUserid() {
-        return userid;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    /**
-     * 自定义Creator方法。
-     * 1) 方法名可以随意。
-     * 2) 方法必须是static。声明为private的方法只能被当前类使用,若想方法被子类使用,需要声明protected
-     * 3)方法的参数必须为空。
-     * 4)方法的返回类型必须是Creator。
-     *
-     * @return
-     */
-    private static Creator<UserRecord> creator() {
-        return new Creator<UserRecord>() {
-            @Override
-            @ConstructorParameters({"userid", "username", "password"}) //带参数的构造函数必须有ConstructorParameters注解
-            public UserRecord create(Object... params) {
-                return new UserRecord((params[0] == null ? 0 : (Integer) params[0]), (String) params[1], (String) params[2]);
-            }
-        };
-    }
-}
-                
-

        通常JavaBean类默认有个public空参数的构造函数,因此大部分情况下不要自定义Creator,其实只要不是private的空参数构造函数Convert都能自动支持(其他的框架都仅支持public的构造函数),只有仅含private的构造函数才需要自定义Creator。带参数的构造函数就需要标记@ConstructorParameters,常见于Immutable Object。
-
- -

        Convert 支持自定义Decode、Encode。

-

        1. 通过ConvertFactory显式的注册:

-
public class FileSimpleCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, File> {
-
-    public static final FileSimpleCoder instance = new FileSimpleCoder();
-
-    @Override
-    public void convertTo(W out, File value) {
-        out.writeString(value == null ? null : value.getPath());
-    }
-
-    @Override
-    public File convertFrom(R in) {
-        String path = in.readString();
-        return path == null ? null : new File(path);
-    }
-}
-
-JsonFactory.root().register(java.io.File.class, FileSimpleCoder.instance);
-
-BsonFactory.root().register(java.io.File.class, FileSimpleCoder.instance);
-                
- -

        2. 通过JavaBean类自定义静态方法自动加载:

-
public class InnerCoderEntity {
-
-    private final String val;
-
-    private final int id;
-
-    private InnerCoderEntity(int id, String value) {
-        this.id = id;
-        this.val = value;
-    }
-
-    public static InnerCoderEntity create(int id, String value) {
-        return new InnerCoderEntity(id, value);
-    }
-
-    /**
-     * 该方法提供给Convert组件自动加载。
-     * 1) 方法名可以随意。
-     * 2) 方法必须是static
-     * 3)方法的参数有且只能有一个, 且必须是org.redkale.convert.ConvertFactory或子类。
-     * —3.1) 参数类型为org.redkale.convert.ConvertFactory 表示适合JSON和BSON。
-     * —3.2) 参数类型为org.redkale.convert.json.JsonFactory 表示仅适合JSON。
-     * —3.3) 参数类型为org.redkale.convert.bson.BsonFactory 表示仅适合BSON。
-     * 4)方法的返回类型必须是org.redkale.convert.Decodeable/org.redkale.convert.Encodeable/org.redkale.convert.SimpledCoder
-     * 若返回类型不是org.redkale.convert.SimpledCoder, 就必须提供两个方法: 一个返回Decodeable 一个返回 Encodeable。
-     *
-     * @param factory
-     * @return
-     */
-    private static SimpledCoder<Reader, Writer, InnerCoderEntity> createConvertCoder(final org.redkale.convert.ConvertFactory factory) {
-        return new SimpledCoder<Reader, Writer, InnerCoderEntity>() {
-
-            //必须与EnMember[] 顺序一致
-            private final DeMember[] deMembers = new DeMember[]{
-                DeMember.create(factory, InnerCoderEntity.class, "id"),
-                DeMember.create(factory, InnerCoderEntity.class, "val")};
-
-            //必须与DeMember[] 顺序一致
-            private final EnMember[] enMembers = new EnMember[]{
-                EnMember.create(factory, InnerCoderEntity.class, "id"),
-                EnMember.create(factory, InnerCoderEntity.class, "val")};
-
-            @Override
-            public void convertTo(Writer out, InnerCoderEntity value) {
-                if (value == null) {
-                    out.writeObjectNull(InnerCoderEntity.class);
-                    return;
-                }
-                out.writeObjectB(value);
-                for (EnMember member : enMembers) {
-                    out.writeObjectField(member, value);
-                }
-                out.writeObjectE(value);
-            }
-
-            @Override
-            public InnerCoderEntity convertFrom(Reader in) {
-                if (in.readObjectB(InnerCoderEntity.class) == null) return null;
-                int index = 0;
-                final Object[] params = new Object[deMembers.length];
-                while (in.hasNext()) {
-                    DeMember member = in.readFieldName(deMembers); //读取字段名
-                    in.readBlank(); //读取字段名与字段值之间的间隔符,JSON则是跳过冒号:
-                    if (member == null) {
-                        in.skipValue(); //跳过不存在的字段的值, 一般不会发生
-                    } else {
-                        params[index++] = member.read(in);
-                    }
-                }
-                in.readObjectE(InnerCoderEntity.class);
-                return InnerCoderEntity.create(params[0] == null ? 0 : (Integer) params[0], (String) params[1]);
-            }
-        };
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getVal() {
-        return val;
-    }
-
-    @Override
-    public String toString() {
-        return JsonConvert.root().convertTo(this);
-    }
-
-}
-                
-

        由上可以看出,Convert的自定义配置完全符合面向对象思想,提倡在JavaBean内部去自定义非常规的构造函数或Decode、Encode方法,通过ConvertFactory显式配置的方式通常用于非自己定义的数据类(如 java.io.File)。

-
- -

BSON的协议格式

-

        BSON类似Java自带的Serializable, 其格式如下:
-                 1). 基本数据类型: 直接转换成byte[]
-                 2). SmallString(无特殊字符且长度小于256的字符串): length(1 byte) + byte[](utf8); 通常用于类名、字段名、枚举。
-                 3). String: length(4 bytes) + byte[](utf8);
-                 4). 数组: length(4 bytes) + byte[]...
-                 5). Object:
-                         1. realclass (SmallString) (如果指定格式化的class与实体对象的class不一致才会有该值, 该值可以使用@ConvertEntity给其取个别名)
-                         2. 空字符串(SmallString)
-                         3. SIGN_OBJECTB 标记位,值固定为0xBB (short)
-                         4. 循环字段值:
-                                 4.1 SIGN_HASNEXT 标记位,值固定为1 (byte)
-                                 4.2 字段类型; 1-9为基本类型&字符串; 101-109为基本类型&字符串的数组; 127为Object
-                                 4.3 字段名 (SmallString)
-                                 4.4 字段的值Object
-                         5. SIGN_NONEXT 标记位,值固定为0 (byte)
-                         6. SIGN_OBJECTE 标记位,值固定为0xEE (short)
-

- - -
- - - - diff --git a/course01_hello.html b/course01_hello.html deleted file mode 100644 index afed2b6eb..000000000 --- a/course01_hello.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 01 -- Hello Word!

- -

-         Redkale 可以说是Java界最精简的框架,不到1M的jar包可以替代Tomcat、Spring/Spring Boot、Hibernate/MyBatis、JackJson/fastjson、Netty的集合,四两拨千斤。低调开源一年多,经过两次大的改善后终于达到让自己满意的地步。Redkale不仅仅提供简易的API,还附有很多不同于传统思维的设计思路。由于时间有限,一年多也没写入门教程,现在开始抽点时间写一些教程,希望能给想学Redkale的同学一点帮助。 废话不多说,下面进入正题。
-

- -

下载Redkale

-

-         源码可以从 https://github.com/redkalehttp://git.oschina.net/redkale/redkale 下载 。
-         jar包可以从 http://search.maven.orghttps://repo1.maven.org/maven2/org/redkale/redkale/ 下载最新版本的包。
-         当前最新版为 1.8, 下载 redkale-1.8.0.tar.gz 放在本地。
-

- -

创建工程

-

-         本人使用NetBeans很多年了,所以本教程以NetBeans来创建工程, 使用Eclipse的同学请自行参考。 -

-

-         IntelliJ IDEA 使用者见 Redkale 入门教程 01 -- Hello Word!(IntelliJ IDEA Maven版)。 - -

-

-         首先创建一个"Java应用程序"项目, 注意: 不管是否Web项目,都不要创建Web应用程序。 -

-

-

-         创建完空项目后,将 redkale-1.8.0.tar.gz 解压覆盖到项目的目录下。 -

-

-

-         点击项目右键进入“属性”-> “库”中,点击 “添加JAR/文件夹”找到项目lib目录下的redkale-1.8.0.jar 并导入。 -

-

-

-         点击“源”,在下面测试包中导入项目目录下的conf目录,这样方便编辑conf下的配置文件(在上面src中导入会打包进jar中)。 -

-

-

-         点击“运行”,在主类中输入 "org.redkale.boot.Application",然后点击“确定”。 -

-

-

-         编写HelloService类。 -

-
package com.redkale.demo;
-
-import org.redkale.net.http.*;
-import org.redkale.service.Service;
-
-@RestService(automapping = true)
-public class HelloService implements Service {
-
-    public String sayHello() {
-        return "Hello World!";
-    }
-
-    public String hi(String name) {
-        return "Hi, " + name + "!";
-    }
-}
-                
- -

-         此类提供两个方法:say 和 hi。编写完后按"F6" 直接运行。 -

-

-

-         在浏览器输入: http://127.0.0.1:6060/pipes/hello/say 可以看到结果: -

-

-

-         在浏览器输入: http://127.0.0.1:6060/pipes/hello/hi?name=Redkale 可以看到结果: -

-

-

-         访问地址的端口6060和前缀pipes是通过conf/application.xml文件进行配置: -

-
<application port="5050">     
-    
-    <!--  详细配置说明见: http://redkale.org/redkale.html#redkale_confxml  -->
-    
-    <resources>       
-        <!-- 
-        <properties>
-            <property name="system.property.convert.json.tiny" value="true"/>
-        </properties>
-        -->
-    </resources> 
-        
-    <server protocol="HTTP" host="0.0.0.0" port="6060" root="root">      
-              
-        <services autoload="true"/>
-        
-        <!-- base指定的自定义HttpServlet子类必须标记@HttpUserType, 不设置base则视为没有当前用户信息设置  -->
-        <rest path="/pipes" autoload="true" /> 
-        
-        <servlets path="/pipes" autoload="true" />
-        
-    </server>
-    
-</application>
-                
- -

-         至此,一个简单的Hello服务就开发和调试完成了。可以看出,代码简单很多,不需要太多配置、maven和其他依赖包。
-         可能有人会疑惑: HelloServie为什么能分配到hello前缀? sayHello为什么会映射到/pipes/hello/say 请求? Redkale为了减少Annotation配置采取了一些默认值的策略, 在Redkale里, 一个Service视为一个模块或服务,所以需要有模块(服务)名的概念,用于服务管理和鉴权,通常需要通过@RestServie.name来指定模块名,没有指定则默认将Service类名的Service字样之前的字符串视为模块名,如HelloService和HelloServiceImpl都会采用hello作为模块名。方法名的默认值策略也是类似,将模块名字样之前的字符串作为方法名,如sayHello和sayHelloMe 都会用say作为默认值。参数名如果没有指定@RestParam.name会自动采用代码的变量名。 完全标记Rest注解的HelloService源码如下: -

-
package com.redkale.demo;
-
-import org.redkale.net.http.*;
-import org.redkale.service.Service;
-
-@RestService(name = "hello")
-public class HelloService implements Service {
-
-    @RestMapping(name = "say")
-    public String sayHello() {
-        return "Hello World!";
-    }
-
-    @RestMapping(name = "hi")
-    public String hi(@RestParam(name = "name") String name) {
-        return "Hi, " + name + "!";
-    }
-
-}
-                
-
-

-         这段代码与上面那段是等价的。部署也很简单, 将项目目录中dist目录下的redkale-demo.jar复制到lib下,运行bin/start.bat 即可启动HTTP服务。 -

-

-         转载请注明出处:https://redkale.org/course01_hello.html -

- - - -
- - - - diff --git a/course01_helloidea.html b/course01_helloidea.html deleted file mode 100644 index 0b5c41c31..000000000 --- a/course01_helloidea.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 01 -- Hello Word!(IntelliJ IDEA Maven版)

- -

-         Redkale 可以说是Java界最精简的框架,不到1M的jar包可以替代Tomcat、Spring/Spring Boot、Hibernate/MyBatis、JackJson/fastjson、Netty的集合,四两拨千斤。低调开源一年多,经过两次大的改善后终于达到让自己满意的地步。Redkale不仅仅提供简易的API,还附有很多不同于传统思维的设计思路。由于时间有限,一年多也没写入门教程,现在开始抽点时间写一些教程,希望能给想学Redkale的同学一点帮助。 废话不多说,下面进入正题。
-

- -

创建工程

-

-         首先创建一个Maven项目。 -

-

-

-

-         创建完项目后会自动打开pom.xml文件, 按下Alt+Insert组合键添加redkale依赖。 -

-

-

-         然后将redkale下的bin和conf文件夹(可以通过github或者maven里redkale-1.9.5.tar.gz解压得到)复制到工程的目录下。 -

-

-

-         编写HelloService类。 -

-
package com.redkale.examples.hello;
-
-import org.redkale.net.http.*;
-import org.redkale.service.Service;
-
-@RestService(automapping = true)
-public class HelloService implements Service {
-
-    public String sayHello() {
-        return "Hello World!";
-    }
-
-    public String hi(String name) {
-        return "Hi, " + name + "!";
-    }
-}
-                
-

- -

-         然后开始调试运行。 -

-

-

-

-

-

-

-         在浏览器输入: http://127.0.0.1:6060/pipes/hello/say 可以看到结果: -

-

-

-         在浏览器输入: http://127.0.0.1:6060/pipes/hello/hi?name=Redkale 可以看到结果: -

-

-

-         访问地址的端口6060和前缀pipes是通过conf/application.xml文件进行配置: -

-
<application port="2121">     
-    
-    <!--  详细配置说明见: http://redkale.org/redkale.html#redkale_confxml  -->
-    
-    <resources>       
-        <!-- 
-        <properties>
-            <property name="system.property.convert.json.tiny" value="true"/>
-        </properties>
-        -->
-    </resources> 
-        
-    <server protocol="HTTP" host="0.0.0.0" port="6060" root="root">      
-              
-        <services autoload="true"/>
-        
-        <!-- base指定的自定义HttpServlet子类必须标记@HttpUserType, 不设置base则视为没有当前用户信息设置  -->
-        <rest path="/pipes" autoload="true" /> 
-        
-        <servlets path="/pipes" autoload="true" />
-        
-    </server>
-    
-</application>
-                
- -

-         至此,一个简单的Hello服务就开发和调试完成了。可以看出,代码简单很多,不需要太多配置、maven和其他依赖包。
-         可能有人会疑惑: HelloServie为什么能分配到hello前缀? sayHello为什么会映射到/pipes/hello/say 请求? Redkale为了减少Annotation配置采取了一些默认值的策略, 在Redkale里, 一个Service视为一个模块或服务,所以需要有模块(服务)名的概念,用于服务管理和鉴权,通常需要通过@RestServie.name来指定模块名,没有指定则默认将Service类名的Service字样之前的字符串视为模块名,如HelloService和HelloServiceImpl都会采用hello作为模块名。方法名的默认值策略也是类似,将模块名字样之前的字符串作为方法名,如sayHello和sayHelloMe 都会用say作为默认值。参数名如果没有指定@RestParam.name会自动采用代码的变量名。 完全标记Rest注解的HelloService源码如下: -

-
package com.redkale.examples.hello;
-
-import org.redkale.net.http.*;
-import org.redkale.service.Service;
-
-@RestService(name = "hello")
-public class HelloService implements Service {
-
-    @RestMapping(name = "say")
-    public String sayHello() {
-        return "Hello World!";
-    }
-
-    @RestMapping(name = "hi")
-    public String hi(@RestParam(name = "name") String name) {
-        return "Hi, " + name + "!";
-    }
-
-}
-                
-
-

-         这段代码与上面那段是等价的。部署也很简单, 将项目编译后的jar和redkale-1.9.5.jar复制到lib下(也可通过修改pom.xml让maven来处理),运行bin/start.bat 即可启动HTTP服务。 -

-

-         本工程源码可以在 https://github.com/redkale/redkale-examples/tree/master/redkale-helloword 下载。 -

-

-         转载请注明出处:https://redkale.org/course01_helloidea.html -

- - - -
- - - - diff --git a/course02_dbconn.html b/course02_dbconn.html deleted file mode 100644 index f8d8b7c97..000000000 --- a/course02_dbconn.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 02 -- DataSource 连接

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course02_rest.html b/course02_rest.html deleted file mode 100644 index afdc9f54a..000000000 --- a/course02_rest.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 02 -- REST敏捷开发

- -

-         REST是 Redkale 的主要功能之一,REST提供的功能是根据Service自动生成HttpServlet,需要注意的是 Redkale里的REST与标准的RESTfull规范完全不同,仅仅名称类似。标准的REST规范比较死板,只是在请求URL和Method上做文章,功能单一。Redkale里的REST功能是很强大的, 无论登陆、鉴权、文件上传下载、WebSocket都可以REST化,几乎完全可以省去HttpServlet。
-

- - - - - - - - - - - - - - - - - - -
注解类名功能描述
@RestService标记Service需要REST化。
@RestMapping标记请求的方法名,同一方法上可重复标记(需确保name值在Service中是唯一的)。
其标记的Service方法只能throws IOException或不抛异常
@RestConvert标记请求的方法名上,对返回值以JSON形式输出时进行字段的屏蔽或开启进行设置。
@RestConvertCoder标记请求的方法名上,对返回值以JSON形式输出时对类指定的字段的进行设置。
@RestParam获取常规参数值, 字段类型可以是 基础数据类型/Flipper/CompletionHandler/String/JavaBean
name='&'    表示当前用户(@HttpUserType)
name='#'    表示截取uri最后一段
name='#xxx:'    表示从uri中/pipes/xxx:v/截取xxx:的值
@RestHeader通过request.getHeader获取参数值, 字段类型只能是String
@RestCookie通过request.getCookie获取Cookie值, 字段类型只能是String
@RestBody通过request.getBody获取参数值, 字段类型只能是String/byte[]/JavaBean
@RestSessionid获取sessionid, 字段类型只能是String
@RestAddress获取客户端IP地址, 字段类型只能是String
@RestURI获取请求URL, 字段类型只能是String
@RestUploadFile获取上传文件, 字段类型只能是File/File[]/byte[]
WebSocket
@RestWebSocket标记WebSocket需要REST化,只能标记在WebSocket的子类上
@RestOnMessageWebSocket的消息路由,类似@RestMapping,不同的请求映射到不同的方法
-

-         标记@RestService的Service对象在服务启动时会自动生成原始的HttpServlet加载到当前的HttpServer中,其HttpServlet的请求URL规则为:path + "/" + @RestService.catalog() + "/" + @RestService.name() + "/*", 其中path为application.xml文件中<rest>节点的path属性值。节点rest和servlets设计一个path属性是为了所有动态请求加个前缀方便静动分离,没有采取.jsp .do那种刷存在感的后缀方式也是处于安全考虑,外界根据URL无法判断后台使用的是什么语言或框架开发。
-         REST会根据Service方法的返回类型不同做出不同的结果输出。 - - - - - - - - -
返回类型功能描述
void以RetResult.success()的JSON形式输出。
String以字符串输出。
File以下载文件形式输出。
HttpResult将HttpHeader、HttpCookie、Result统一输出
CompletableFuture异步输出,根据CompletableFuture.get()结果类型进行不同形式输出
JavaBean/其他以JSON形式输出
-

-

-         要开启REST功能需要在application.xml对应的HttpServer下加入rest节点: -

-

-

-         如果需要加入鉴权功能更,需要自定义HttpServlet基类,并重载preExecute、authenticate方法。详情见 BaseHttpServlet -

-

用户登陆范例

-
@RestService(name = "user", comment = "用户服务")
-public class UserService extends AbstractService {
-
-    @RestMapping(name = "login", auth = false, comment = "用户登陆")
-    public RetResult login(String account, String password, @RestHeader(name = "User-Agent") String agent,
-        @RestSessionid(create = true) String sessionid, @RestAddress String clientAddr) {
-        if (!"redkale".equals(account)) {
-            return new RetResult(1001, "账号不是redkale");
-        }
-        if (!"123456".equals(password)) {
-            return new RetResult(1002, "密码错误");
-        }
-        System.out.println("用户(" + account + ")的回话ID为: " + sessionid + ", 通过客户端(" + agent + ")在" + clientAddr + "登陆。");
-        return RetResult.success();
-    }
-}
-            
-

-         浏览器输入: http://127.0.0.1:6060/pipes/user/login?account=redkale&password=123456
-         返回结果: {"retcode":0,"success":true}
-         后台打印: 用户(redkale)的回话ID为: 6110627db03b2b4a2c45a2fcb2aa1403, 通过客户端(Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36)在127.0.0.1登陆。 -

-

-         REST的注解不仅可以注解在Service的方法参数上, 还可以在Service的方法的JavaBean参数的类里的字段进行注解。 -

-
public class LoginBean {
-
-    private String account;
-
-    private String password;
-
-    @RestHeader(name = "User-Agent")
-    private String agent;
-
-    @RestSessionid(create = true)
-    private String sessionid;
-
-    @RestAddress
-    private String clientAddr;
-
-    @Override
-    public String toString() {
-        return JsonConvert.root().convertTo(this);
-    }
-
-    /** 以下省略getter setter方法 */
-
-}
-
-@RestService(name = "user", comment = "用户服务")
-public class UserService extends AbstractService {
-
-    @RestMapping(name = "login", auth = false, comment = "用户登陆")
-    public RetResult login(LoginBean bean) {
-        if (bean == null) {
-            return new RetResult(1000, "没有登陆信息");
-        }
-        if (!"redkale".equals(bean.getAccount())) {
-            return new RetResult(1001, "账号不是redkale");
-        }
-        if (!"123456".equals(bean.getPassword())) {
-            return new RetResult(1002, "密码错误");
-        }
-        System.out.println("用户(" + bean.getAccount() + ")通过客户端(" + bean.getAgent() + ")在" + bean.getClientAddr() + "登陆。");
-        return RetResult.success();
-    }
-}
-            
- -

-         浏览器输入: http://127.0.0.1:6160/pipes/user/login?bean={account:redkale,password:123456}
-         返回结果: {"retcode":0,"success":true}
-         后台打印: 用户(redkale)通过客户端(Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36)在127.0.0.1登陆。 -

-

-         如果用户登陆成功后需要跳转到首页或者还可能需要输出一些Cookie值,就需要使用HttpResult作为返回结果,例如: -

-
@RestService(name = "user", comment = "用户服务")
-public class UserService extends AbstractService {
-
-    @RestMapping(name = "login", auth = false, comment = "用户登陆")
-    public HttpResult login(LoginBean bean) {
-        if (bean == null) {
-            return new HttpResult<>(new RetResult(1000, "没有登陆信息"));
-        }
-        if (!"redkale".equals(bean.getAccount())) {
-            return new HttpResult<>(new RetResult(1001, "账号不是redkale"));
-        }
-        if (!"123456".equals(bean.getPassword())) {
-            return new HttpResult<>(new RetResult(1002, "密码错误"));
-        }
-        System.out.println("用户(" + bean.getAccount() + ")通过客户端(" + bean.getAgent() + ")在" + bean.getClientAddr() + "登陆。");
-        return new HttpResult().header("Location", "/index.html").cookie(new HttpCookie("curraccount", bean.getAccount()));
-    }
-}
-            
- -

文件上传

-
@RestService(name = "file", comment = "文件服务")
-public class FileService extends AbstractService {
-
-    private static final Logger logger = Logger.getLogger(FileService.class.getSimpleName());
-
-    private static final String format = "%1$tY%1$tm%1$td%1$tH%1$tM%1$tS";
-
-    @RestMapping(name = "upload", auth = false, comment = "文件上传,不鉴权")
-    public RetResult upload(@RestUploadFile(maxLength = 1 * 1024 * 1024, fileNameReg = ".*\\.doc$") File tmpFile) throws IOException {
-        if (tmpFile == null) return new RetResult(1001, "没有上传文件或文件大小超过1M或文件不是.doc后缀");
-        //按日期命名 如 file-20170601133520.doc
-        try {
-            logger.finest("用户上传的文件名为: " + MultiContext.getFileName(tmpFile));
-            File destFile = new File("D:/docs/file-" + String.format(format, System.currentTimeMillis()) + ".doc");
-            destFile.getParentFile().mkdirs();
-            if (!tmpFile.renameTo(destFile)) { //tmpFile与destFile不在同一盘符下会导致renameTo失败
-                java.nio.file.Files.copy(tmpFile.toPath(), destFile.toPath(), StandardCopyOption.ATOMIC_MOVE);
-            }
-        } finally { //如果发生异常,将临时文件从{APP_HOME}/tmp 目录下删除
-            tmpFile.delete();
-        }
-        return RetResult.success();
-    }
-}
-            
-

-         WebSocket与其REST化将在以后的章节详细介绍。 -

-
-

-         转载请注明出处:https://redkale.org/course02_rest.html -

- - - -
- - - - diff --git a/course03_userauth.html b/course03_userauth.html deleted file mode 100644 index 08e28f2a8..000000000 --- a/course03_userauth.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 03 -- 用户鉴权

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course04_cachedb.html b/course04_cachedb.html deleted file mode 100644 index 4b219eb47..000000000 --- a/course04_cachedb.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 04 -- CacheSource缓存数据源

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course05_dbaction.html b/course05_dbaction.html deleted file mode 100644 index af06f26bc..000000000 --- a/course05_dbaction.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 05 -- DataSource 增删改查

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course06_dbdis.html b/course06_dbdis.html deleted file mode 100644 index 27609b75d..000000000 --- a/course06_dbdis.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 06 -- DataSource 分库分表

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course07_convert.html b/course07_convert.html deleted file mode 100644 index c5d48085a..000000000 --- a/course07_convert.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 07 -- Convert

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course08_httpservlet.html b/course08_httpservlet.html deleted file mode 100644 index f31fda290..000000000 --- a/course08_httpservlet.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 08 -- HttpServlet

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course09_fileup.html b/course09_fileup.html deleted file mode 100644 index 660624572..000000000 --- a/course09_fileup.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 09 -- 文件上传下载

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course10_websocket.html b/course10_websocket.html deleted file mode 100644 index 3efb4798a..000000000 --- a/course10_websocket.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 10 -- WebSocket

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/course11_sncp.html b/course11_sncp.html deleted file mode 100644 index 9933bc523..000000000 --- a/course11_sncp.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 入门教程 11 -- SNCP分布式部署

- -

-         Redkale 里大量使用了双亲委托模型,序列化的ConvertFactory、依赖注入的ResourceFactory、服务管理的WatchFactory均采用双亲委托模型。用于优先加载自定义的处理类,同时也保证两个同级的子Factory不会相互干扰。
-

- -

ClassLoader类加载

-

-         双亲委托模型最经典的例子就是JVM的类加载器ClassLoader。每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是包含关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
-         ClassLoader采用双亲委托有两个好处:避免类的重复加载和保证类的安全性。由类加载的顺序可以看出父加载器加载过的类在子加载器中不会被重复加载,同时也保证了安全性,一些非系统的class是不可靠的,若定义一个恶意的java.io.File类覆盖JDK自带的类会带来不安全性。而使用双亲委托机制的话该File类永远不会被调用,因为委托BootStrapClassLoader加载后会加载JDK中的File类而不会加载自定义的这个。 -

- -

Redkale 双亲委托

-

-         ConvertFactory、ResourceFactory、WatchFactory三者的双亲委托模型设计完全一样。下面以ConvertFactory为例说明,ConvertFactory的搜索顺序与ClassLoader相反,ClassLoader为了避免类的重复而先加载父加载器后加载子加载器,ConvertFactory为了优先加载自定义的Encoder和Decoder先搜索自身的ConvertFactory,找不到再从父ConvertFactory中搜索。 -

-
public final <E> Encodeable<W, E> findEncoder(final Type type) {
-    Encodeable<W, E> rs = (Encodeable<W, E>) encoders.get(type);
-    if (rs != null) return rs;
-    return this.parent == null ? null : this.parent.findEncoder(type);
-}
-

-         当搜索不到Encoder、Decoder时,自身的ConvertFactory会自动创建一个ObjectEncoder、ObjectDecoder。 -

-
public final <E> Encodeable<W, E> loadEncoder(final Type type) {
-    Encodeable<W, E> encoder = findEncoder(type);
-    if (encoder != null) return encoder;
-    if (type instanceof GenericArrayType) return new ArrayEncoder(this, type);
-    Class clazz;
-    if (type instanceof ParameterizedType) {
-        final ParameterizedType pts = (ParameterizedType) type;
-        clazz = (Class) (pts).getRawType();
-    } else if (type instanceof TypeVariable) {
-        TypeVariable tv = (TypeVariable) type;
-        Type t = Object.class;
-        if (tv.getBounds().length == 1) {
-            t = tv.getBounds()[0];
-        }
-        if (!(t instanceof Class)) t = Object.class;
-        clazz = (Class) t;
-    } else if (type instanceof Class) {
-        clazz = (Class) type;
-    } else {
-        throw new ConvertException("not support the type (" + type + ")");
-    }
-    encoder = findEncoder(clazz);
-    if (encoder != null) return encoder;
-    return createEncoder(type, clazz);
-}
-

-         大部分情况下Convert的处理对象会根据JavaBean类自定生成,而有些场景需要覆盖处理类,这样需要子ConvertFactory,如 Convert基本用法 例子中使用JsonFactory.root().createChild()重定义。且与JsonFactory.root()中的定义可以并存,也不会产出冲突。 -

-

-         Redkale可以启动多个协议Server服务(配置文件中含多个server节点),为避免冲突,每个非SNCP的Server的ResourceFactory也是独立的。 -

-
public NodeServer(Application application, Server server) {
-    this.application = application;
-    this.resourceFactory = application.getResourceFactory().createChild();
-    this.server = server;
-    this.logger = Logger.getLogger(this.getClass().getSimpleName());
-}
-

-         双亲委托模型既可让同级子Factory保持独立,也可重用父Factory内的配置,因此在Redkale这种支持多Server、多种配置的场景下很是适合。 -

-
-

-         转载请注明出处:https://redkale.org/article_parents.html -

- - - -
- - - - diff --git a/doc/Convert比较报告.pptx b/doc/Convert比较报告.pptx deleted file mode 100644 index e6dec47ad..000000000 Binary files a/doc/Convert比较报告.pptx and /dev/null differ diff --git a/doc/Convert结构图.vsdx b/doc/Convert结构图.vsdx deleted file mode 100644 index 4fc6c23c9..000000000 Binary files a/doc/Convert结构图.vsdx and /dev/null differ diff --git a/doc/Redkale开发手册.doc b/doc/Redkale开发手册.doc deleted file mode 100644 index 9b28a0193..000000000 Binary files a/doc/Redkale开发手册.doc and /dev/null differ diff --git a/doc/分布式架构图.vsdx b/doc/分布式架构图.vsdx deleted file mode 100644 index 5d7b378e0..000000000 Binary files a/doc/分布式架构图.vsdx and /dev/null differ diff --git a/images/convertimg_1.png b/images/convertimg_1.png deleted file mode 100644 index 552f92597..000000000 Binary files a/images/convertimg_1.png and /dev/null differ diff --git a/images/convertimg_2.png b/images/convertimg_2.png deleted file mode 100644 index 695b44efa..000000000 Binary files a/images/convertimg_2.png and /dev/null differ diff --git a/images/distributeimg_1.png b/images/distributeimg_1.png deleted file mode 100644 index 212cfcd9c..000000000 Binary files a/images/distributeimg_1.png and /dev/null differ diff --git a/images/distributeimg_2.png b/images/distributeimg_2.png deleted file mode 100644 index ce81095dc..000000000 Binary files a/images/distributeimg_2.png and /dev/null differ diff --git a/images/distributeimg_3.png b/images/distributeimg_3.png deleted file mode 100644 index 10d65293c..000000000 Binary files a/images/distributeimg_3.png and /dev/null differ diff --git a/images/distributeimg_4.png b/images/distributeimg_4.png deleted file mode 100644 index f1a88c94f..000000000 Binary files a/images/distributeimg_4.png and /dev/null differ diff --git a/images/distributeimg_5.png b/images/distributeimg_5.png deleted file mode 100644 index 5c45727af..000000000 Binary files a/images/distributeimg_5.png and /dev/null differ diff --git a/images/hello_01.png b/images/hello_01.png deleted file mode 100644 index 34dc59bc6..000000000 Binary files a/images/hello_01.png and /dev/null differ diff --git a/images/hello_02.png b/images/hello_02.png deleted file mode 100644 index 404d7fee6..000000000 Binary files a/images/hello_02.png and /dev/null differ diff --git a/images/hello_03.png b/images/hello_03.png deleted file mode 100644 index a9b6185d4..000000000 Binary files a/images/hello_03.png and /dev/null differ diff --git a/images/hello_04.png b/images/hello_04.png deleted file mode 100644 index 5a580ccce..000000000 Binary files a/images/hello_04.png and /dev/null differ diff --git a/images/hello_05.png b/images/hello_05.png deleted file mode 100644 index 338b1dec2..000000000 Binary files a/images/hello_05.png and /dev/null differ diff --git a/images/hello_06.png b/images/hello_06.png deleted file mode 100644 index e9d6376b5..000000000 Binary files a/images/hello_06.png and /dev/null differ diff --git a/images/hello_07.png b/images/hello_07.png deleted file mode 100644 index cc14abc7f..000000000 Binary files a/images/hello_07.png and /dev/null differ diff --git a/images/hello_08.png b/images/hello_08.png deleted file mode 100644 index b622112e9..000000000 Binary files a/images/hello_08.png and /dev/null differ diff --git a/images/hello_09.png b/images/hello_09.png deleted file mode 100644 index 5bb870b29..000000000 Binary files a/images/hello_09.png and /dev/null differ diff --git a/images/hello_10.png b/images/hello_10.png deleted file mode 100644 index 5dfa97b59..000000000 Binary files a/images/hello_10.png and /dev/null differ diff --git a/images/hello_11.png b/images/hello_11.png deleted file mode 100644 index 6185bac58..000000000 Binary files a/images/hello_11.png and /dev/null differ diff --git a/images/hello_12.png b/images/hello_12.png deleted file mode 100644 index c7270456c..000000000 Binary files a/images/hello_12.png and /dev/null differ diff --git a/images/hello_21.png b/images/hello_21.png deleted file mode 100644 index b7fcb76f7..000000000 Binary files a/images/hello_21.png and /dev/null differ diff --git a/images/hello_22.png b/images/hello_22.png deleted file mode 100644 index 6ef5e397f..000000000 Binary files a/images/hello_22.png and /dev/null differ diff --git a/images/hello_23.png b/images/hello_23.png deleted file mode 100644 index 9a665b88d..000000000 Binary files a/images/hello_23.png and /dev/null differ diff --git a/images/hello_24.png b/images/hello_24.png deleted file mode 100644 index 7fc3a7858..000000000 Binary files a/images/hello_24.png and /dev/null differ diff --git a/images/hello_25.png b/images/hello_25.png deleted file mode 100644 index 88dbc26e8..000000000 Binary files a/images/hello_25.png and /dev/null differ diff --git a/images/hello_26.png b/images/hello_26.png deleted file mode 100644 index cd9a763f9..000000000 Binary files a/images/hello_26.png and /dev/null differ diff --git a/images/hello_27.png b/images/hello_27.png deleted file mode 100644 index 2fea24a8b..000000000 Binary files a/images/hello_27.png and /dev/null differ diff --git a/images/hello_28.png b/images/hello_28.png deleted file mode 100644 index ddb11b1c7..000000000 Binary files a/images/hello_28.png and /dev/null differ diff --git a/images/hello_29.png b/images/hello_29.png deleted file mode 100644 index f3441fcc3..000000000 Binary files a/images/hello_29.png and /dev/null differ diff --git a/images/hello_30.png b/images/hello_30.png deleted file mode 100644 index cd3bcfcc3..000000000 Binary files a/images/hello_30.png and /dev/null differ diff --git a/images/hello_31.png b/images/hello_31.png deleted file mode 100644 index 1cf73c5ad..000000000 Binary files a/images/hello_31.png and /dev/null differ diff --git a/images/hello_32.png b/images/hello_32.png deleted file mode 100644 index 2fa9d20d9..000000000 Binary files a/images/hello_32.png and /dev/null differ diff --git a/images/hello_33.png b/images/hello_33.png deleted file mode 100644 index 0de5640dd..000000000 Binary files a/images/hello_33.png and /dev/null differ diff --git a/images/hello_34.png b/images/hello_34.png deleted file mode 100644 index 5dfa97b59..000000000 Binary files a/images/hello_34.png and /dev/null differ diff --git a/images/hello_35.png b/images/hello_35.png deleted file mode 100644 index 6185bac58..000000000 Binary files a/images/hello_35.png and /dev/null differ diff --git a/images/rest_01.png b/images/rest_01.png deleted file mode 100644 index 3db158f3c..000000000 Binary files a/images/rest_01.png and /dev/null differ diff --git a/index.html b/index.html deleted file mode 100644 index 6242077bd..000000000 --- a/index.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 介绍

- -

        Redkale(中文名: 红菜苔,湖北特产蔬菜)是基于Java 8全新的微服务开源框架, 包含HTTP、WebSocket、TCP/UDP、数据序列化、数据缓存、依赖注入等功能。 - 本框架致力于简化集中式和微服务架构的开发,在增强开发敏捷性的同时保持高性能。
- Redkale 有如下主要特点:
-         1、大量使用Java 8新特性(接口默认值、Stream、Lambda、JDk8内置的ASM等)
-         2、提供HTTP服务,同时内置JSON功能与限时缓存功能
-         3、TCP层完全使用NIO.2,并统一TCP与UDP的接口
-         4、提供分布式与集中式部署的无缝切换
-         5、提供类似JPA功能,包含数据缓存自动同步、分表分库与简洁的数据层操作接口
-         6、支持依赖注入的资源的动态修改
-         7、Servlet、Service、Source组件均支持异步接口
-

- -

设计理念

-

        作为一个全新的微服务框架,Redkale在接口定义上使用了Java 8大量的新语法,接口有默认实现、接口带静态方法、重复注解等特性,同时在设计上与主流框架有很大不同。Redkale是按组件形式设计的,而非以容器为主,几乎每个子包都是能提供独立功能的组件。如Tomcat是按容器设计的,所有web资源/配置由Tomcat控制,开发者很能难控制到Tomcat内部,而Redkale的HTTP服务只是个组件,开发者既可以自己启动和配置HttpServer,也可以把Redkale当成容器通过Redkale进程来初始化服务。Spring的Ioc容器也是如此,Redkale提供的依赖注入仅通过ResouceFactory一个类来控制,非常轻量,并且可动态更改已注入的资源。Spring提倡控制反转思想,而自身的容器却让开发者很难控制。Redkale是一个既能以组件形式也能以容器形式存在的框架。从整体上看,Redkale的架构分两层:接口和默认实现。开发者若想替换掉Redkale内置的HTTP服务而使用符合JavaEE规范的HttpServlet, 可以采用自定义协议基于JSR 340(Servlet 3.1)来实现自己的HTTP服务;若想使用Hibernate作为数据库操作,可以写一个自己的DataSource实现类;JSON的序列化和反序列化也可以使用第三方的实现;Memcached或Redis也可以作为另一个CacheSource的实现替换Redkale的默认实现。这其实包含了控制反转的思想,让框架里的各个组件均可让开发者控制。
-         与主流框架比,功能上Redkale显得很简单,这体现了Redkale的简易性,而并非是不足,从一个良好的设计习惯或架构上来看,有些常用功能是不需要提供的,如Redkale的HTTP服务不支持HTTPS和JSP,HTTPS比HTTP多了一层加密解密,这种密集型的计算不是Java的专长,通常提供HTTP服务的架构不会将Java动态服务器放在最前端,而是在前方会放nginx或apache,除了负载均衡还能静动分离,因此HTTPS的加解密应交给nginx这样的高性能服务器处理。Redkale再提供HTTPS服务就显得鸡肋。JSP其实算是一个落后的技术,现在是一个多样化终端的时代,终端不只局限于桌面程序和PC浏览器,还有原生App、混合式App、微信端、移动H5、提供第三方接口等各种形式的终端,这些都不是JSP能方便兼顾的,而HTTP+JSON作为通用性接口可以避免重复开发,模版引擎的功能加上各种强大的JS框架足以取代JSP。Redkale在功能上做了筛选,不会为了迎合主流而提供,而是以良好的设计思想为指导。这是Redkale的主导思维。

- - -

- -

        Convert包是Redkale内一个独立的组件, 用于数据的序列化与反序列化。包分三块:基本包、JSON包、BSON(Binary Stream Object Notation)包。基本包可以用于扩展其他序列化格式(如: XML),其JSON性能是其他任何JSON框架不能媲美的,对于非常规的POJO类也提供了方便的自定义接口。BSON用于数据的二进制序列化与反序列化,支持很复杂的泛型数据,是SNCP协议的基础。

- - -

- -

        Redkale提供DataSource类对数据层进行操作,其功能类似JPA。最大程度的简化数据层的操作,免去SQL/JPQL语句的编写。同时提供过滤查询与JavaBean的结合、读写分离、数据库热切换、本地/远程部署、数据分表分库、进程间缓存自动同步等功能。
-       CacheSource是缓存的统一接口,像JDBC接口定义,不管是内存模式还是Memcached、Redis或其他缓存系统, 只要提供对应的实现就可使用,每个接口都提供同步和异步两种方式。

- - -

- -

        HTTP组件是基于异步NIO.2实现的,HttpRequest的输入与HttpResponse的输出均是异步操作,HttpRequest内置JSON参数的获取和文件上传功能,HttpResponse提供数据对象和文件的数据,同时集成了REST ,因此并不遵循JSR 340规范(Servlet 3.1)且也没有实现JSP规范。 HTTP Server只提供四个对象:HttpContext、HttpRequest、HttpResponse、HttpServlet。 传统Session则由数据层实现。Redkale提倡HTTP+JSON接口(网站、PC客户端、APP移动端、第三方接口均可统一接口), 故内置了JSON序列化与反序列化接口,同时内置HTTP缓存机制。
-       Redkale 的WebSocket服务接口不同于JSR 340(Servlet 3.1), 除了提供基本的WebSocket功能, 还提供分布式与集中式部署, 当部署多个WebSocket进程时,通过配置文件可以实现WebSocket之间连接信息的数据同步。

- - -

- -

        SNCP(Service Node Communicate Protocol)是Redkale独有的RPC协议, 主要用于进程间的数据传输,支持泛型和子类的数据转换。开发人员通过配置文件可以轻易的将Service由本地模式变成远程模式远程模式Service使用SNCP协议与其他进程的Service通信。开发人员无需对远程通信接口使用类似Mina的第三方包进行开发。SNCP是Redkale的核心功能,其微服务架构都是基于本地模式Service远程模式Service

- - -

- -

        WATCH协议其实就是HTTP协议,主要用于进程的微服务和监控管理。接口以HTTP形式提供, Redkale自带部分常规的Watch功能,如动态更新DataSource配置,动态加载Service、Servlet、Filter,查看Server基本信息等。开发者也可自定义WatchService进行监控或管理操作。

- - -

- -

        Net、Service、DataSource、CacheSource都支持异步接口,本地模式远程模式的Service均支持。

- -

Java 源码

-

           码云 源码    https://gitee.com/redkale

-         Github 源码    https://github.com/redkale
-   
-         在线 Javadoc API

- -
-

      Redkale官网:  https://redkale.org

- - - -
- - - - diff --git a/logo.png b/logo.png deleted file mode 100644 index a91096896..000000000 Binary files a/logo.png and /dev/null differ diff --git a/logo_96.png b/logo_96.png deleted file mode 100644 index b21cbd349..000000000 Binary files a/logo_96.png and /dev/null differ diff --git a/logo_old.png b/logo_old.png deleted file mode 100644 index 0ce609352..000000000 Binary files a/logo_old.png and /dev/null differ diff --git a/net.html b/net.html deleted file mode 100644 index 219db473f..000000000 --- a/net.html +++ /dev/null @@ -1,960 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Net 组件介绍

-

        Net 组件是基于AIO(NIO.2)的一套TCP/UDP的网络框架,且只提供异步接口。org.redkale.net 是所有网络协议服务的基础包。Redkale内置HTTP和远程模式Service依赖的SNCP(Service Node Communicate Protocol)协议的实现包。Redkale启动的<server>节点服务都是基于Net组件实现的协议。下面详细介绍 HTTP服务SNCP协议

- -

HTTP 服务

-

        Redkale自实现的HTTP服务接口并不遵循Java EE规范JSR 340(Servlet 3.1),Redkale提倡的是HTTP+JSON的服务接口方式因此没有实现JSP规范,HTTP+JSON服务接口几乎适合所有类型的客户端(PC应用程序、PC Web、微信H5、移动APP、移动Web)开发。其与JSR 340(Servlet 3.1)的主要区别如下:
-                 1、内置参数的JSON反序列化和响应结果的序列化接口。
-                 2、对数值型的参数和header值提供简易接口。
-                 3、不支持JSP,提倡的是HTTP+JSON接口方式。
-                 4、无HttpSession对象,session可通过配置CacheSource实现。
-                 5、内置文件上传接口,且可自实现断点续传。
-                 6、对响应结果内容可以进行短期缓存从而减少数据源操作的压力。
-                 7、内置WebSocket的集群与组功能,且提供伪WebSocket连接功能。
-                 8、HttpResponse只能异步输出。
-

-

        编写Redkale的HttpServlet与 JSR 340中的javax.servlet.http.HttpServlet 基本相同,只需继承 org.redkale.net.http.HttpServlet, 比较好的习惯是一个项目先定义一个项目级的BaseServlet类。

-         一个典型的BaseSerlvet实现: -

-
@HttpUserType(UserInfo.class)
-public class BaseSerlvet extends HttpServlet {
-
-    protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
-
-    protected final boolean fine = logger.isLoggable(Level.FINE);
-
-    @Resource(name = "APP_TIME") //[Redkale内置资源]  进程的启动时间
-    protected long serverCreateTime;
-
-    @Resource //[Redkale内置资源]
-    protected JsonConvert jsonConvert;
-
-    @Resource //[Redkale内置资源]
-    protected JsonFactory jsonFactory;
-
-    //[Redkale内置资源], 当前进程的根目录,字段类型可以是 String、java.io.File、java.nio.file.Path
-    @Resource(name = "APP_HOME")
-    protected File home;
-
-    //[Redkale内置资源], 当前Http Server的web页面的根目录,字段类型可以是 String、java.io.File、java.nio.file.Path
-    @Resource(name = "SERVER_ROOT")
-    protected File webroot;
-
-    @Resource
-    private UserService service;
-
-    //在调用authenticate之前调用, 必须在此处设置currentUser用户信息
-    //该方法可以用于判断请求源是否合法或加入一些全局的拦截操作
-    @Override
-    public void preExecute(final HttpRequest request, final HttpResponse response) throws IOException {
-        if (!request.getHeader("User-Agent", "").contains("Redkale-Agent")) {  //只用移动APP的接口可以判断User-Agent是否正确
-            response.addHeader("retcode", "10001");
-            response.addHeader("retmessage", "User-Agent error");
-            response.finish(201, "{'success':false, 'message':'User-Agent error, must be Redkale-Agent'}");
-            return;
-        }
-        //可以加上一些统计操作
-        if (fine) response.recycleListener((req, resp) -> {  //记录处理时间太长的请求操作
-                long e = System.currentTimeMillis() - request.getCreatetime();
-                if (e > 500) logger.fine("耗时居然用了 " + e + " 毫秒. 请求为: " + req);
-            });
-        final String sessionid = request.getSessionid(false);
-        if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
-        response.nextEvent();
-    }
-
-    //一般用于判断用户的登录态, 返回false表示鉴权失败
-    //moduleid值来自 @WebServlet.moduleid()用于定义模块ID; actionid值自来@HttpMapping.actionid()用于定义操作ID; 需要系统化的鉴权需要定义这两个值
-    @Override
-    public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
-        UserInfo info = request.currentUser();
-        if (info == null) {
-            response.finishJson(RetCodes.retResult(RetCodes.RET_USER_UNLOGIN));
-            return;
-        } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
-            response.finishJson(RetCodes.retResult(RetCodes.RET_USER_AUTH_ILLEGAL));
-            return;
-        }
-        response.nextEvent();
-    }
-}
-                
- -

        继承HttpServlet的子类可以使用其自带的鉴权、请求分支、缓存等功能, 一个典型的操作用户HttpServlet:

-
@WebServlet(value = {"/user/*"}, comment = "用户模块服务")  //拦截所有 /user/ 开头的请求
-public class UserServlet extends BaseSerlvet {
-
-    @Resource
-    private UserService service;
-
-    //登录操作 
-    //因为HttpMapping的判断规则用的是String.startsWith,所以HttpMapping.url不能用正则表达式,只能是getRequestURI的前缀
-    //且同一个HttpServlet类内的所有HttpMapping不能存在包含关系, 如 /user/myinfo 和 /user/myinforecord 不能存在同一HttpServlet中。
-    @HttpMapping(url = "/user/login", auth = false, comment = "用户登录")
-    @HttpParam(name = "bean", type = LoginBean.class, comment = "登录参数对象")
-    public void login(HttpRequest req, HttpResponse resp) throws IOException {
-        LoginBean bean = req.getJsonParameter(LoginBean.class, "bean"); //获取参数
-        RetResult<UserInfo> result = service.login(bean); //登录操作, service内部判断bean的合法性
-        resp.finishJson(result); //输出结果
-    }
-
-    //获取当前用户信息
-    //未登录的请求会被BaseSerlvet.authenticate方法拦截,因此能进入该方法说明用户态存在
-    @HttpMapping(url = "/user/myinfo", comment = "获取当前用户信息")
-    public void myinfo(HttpRequest req, HttpResponse resp) throws IOException {
-        UserInfo user = service.current(req.getSessionid(false));
-        //或者使用 user = req.getAttribute("_current_userinfo"); 因为BaseSerlvet.authenticate方法已经将UserInfo注入到_current_userinfo属性中
-        resp.finishJson(user);  //输出用户信息
-    }
-
-    //获取指定用户ID的用户信息, 请求如: /user/username/43565443
-    // 翻页查询想缓存就需要将翻页信息带进url: /user/query/page:2/size:50 。
-    @HttpMapping(url = "/user/userinfo/", comment = "获取指定用户ID的用户信息")
-    @HttpParam(name = "#", type = int.class, comment = "用户ID")
-    public void userinfo(HttpRequest req, HttpResponse resp) throws IOException {
-        UserInfo user = service.findUserInfo(Integer.parseInt(req.getRequstURILastPath()));
-        resp.finishJson(user);  //输出用户信息
-    }
-
-    //更新个人头像
-    @HttpMapping(url = "/user/updateface", comment = "更新用户头像")
-    public void updateface(HttpRequest req, HttpResponse resp) throws IOException {
-        UserInfo user = service.current(req.getSessionid(false));
-        for (MultiPart part : req.multiParts()) { //遍历上传文件列表 
-            byte[] byts = part.getContentBytes(5 * 1024 * 1024L);  //图片大小不能超过5M,超过5M返回null
-            if (byts == null) {
-                resp.finishJson(new RetResult(102, "上传的文件过大"));
-            } else {
-                BufferedImage img = ImageIO.read(new ByteArrayInputStream(byts));
-                //... 此处处理并存储头像图片
-                resp.finishJson(RetResult.SUCCESS);  //输出成功信息
-            }
-            return; //头像图片仅会传一个, 只需要读取一个即可返回
-        }
-        resp.finishJson(new RetResult(103, "没有上传图片"));
-    }
-}
-                
-

        如上,所有/user/前缀的请求都会进入UserServlet,若没匹配的则返回505错误,为了方便以后编写前方静动分离服务器转发规则,比较好的习惯是将项目中所有动态Servlet加一个固定前缀,在 application.xml 里设置path即可。

-
<server protocol="HTTP" port="6060" root="root"> 
-    <services autoload="true" />  
-    <servlets path="/pipes" autoload="true"/>
-</server>
-

        如上, 配置了/pipes 前缀后,客户端发送Servlet请求需带上前缀,请求当前用户信息的url就变成:/pipes/user/myinfo 。

-
- -

   API Doc

-

        在小型互联网公司里,基本是追求敏捷开发,很少先花时间设计接口文档再进行开发,通常都是直接进行数据库设计和开发,开发完后需要开发人员需要编写接口文档提供给前端人员开发。为了减少文档的编写工作量和强制开发人员对接口进行注释,Redkale提供了apidoc命令,apidoc遍历当前进程中所有标记@WebServlet的可用HttpServlet,根据Servlet中@HttpMapping、@HttpParam生成json对象并输出文件。在系统运行时执行apidoc命令会在进程根目录下生成apidoc.json、apidoc.html文件。apidoc.html的模板可以定制, 只需在conf/目录下存放一个 apidoc-template.html 文件,且文件中必须包含关键字 ${content} 即可实现接口文档的自定义。
-         为了能正确显示接口文档,开发人员需要在编写HttpServlet时,在每个@HttpMapping方法上添加comment属性和@HttpParam注解, 一个方法上可以注解多个@HttpParam, 如上面的 UserServlet 。RestServlet是由RestService自动生成,@HttpMapping、@HttpParam在REST组件中存在对应的注解@RestMapping、@RestParam,因此在编写RestService时需要加上@RestMapping、@RestParam且加上comment属性。如范例: HelloService
-

- -

   WebSokcet 服务

-

        WebSokcet协议遵循IETF RFC 6455,其接口并不符合Java EE中的WebSocket的接口规范。
-         一个WebSocket连接对应一个WebSocket实体,即一个WebSocket会绑定一个TCP连接。且有两种模式:
-         协议上符合HTML5规范, 其流程顺序如下:
-                  1.1 onOpen               若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断登录态。
-                  1.2 createUserid     若返回null,视为WebSocket的连接不合法,强制关闭WebSocket连接;通常用于判断用户权限是否符合。
-                  1.3 onConnected      WebSocket成功连接后在准备接收数据前回调此方法。
-                  1.4 onMessage/onFragment+    WebSocket接收到消息后回调此消息类方法。
-                  1.5 onClose              WebSocket被关闭后回调此方法。
-         此模式下 以上方法都应该被重载。
-

-

        实现一个WebSocket服务需要继承 org.redkale.net.http.WebSocketServlet,以下是一个简单的聊天范例:

-
@WebServlet("/ws/chat")
-public class ChatWebSocketServlet extends WebSocketServlet {
-
-    @Resource
-    private UserService userService;
-
-    @Override
-    public void init(HttpContext context, AnyValue conf) {
-        System.out.println("本实例的WebSocketNode: " + super.node);
-    }
-
-    @Override
-    public void destroy(HttpContext context, AnyValue conf) {
-        System.out.println("关闭了ChatWebSocketServlet");
-    }
-
-    @Override
-    protected WebSocket<Integer, ChatMessage> createWebSocket() {
-
-        return new WebSocket<Integer, ChatMessage>() {
-
-            private UserInfo user;
-
-            @Override
-            public void onMessage(ChatMessage message, boolean last) { //text 接收的格式:{"receiveid":2000001,"content":"Hi Redkale!"}
-                message.sendid = user.getUserid(); //将当前用户设为消息的发送方
-                message.sendtime = System.currentTimeMillis(); //设置消息发送时间
-                //给接收方发送消息, 即使接收方在其他WebSocket进程节点上有链接,Redkale也会自动发送到其他链接进程节点上。
-                super.sendMessage(message, message.receiveid);
-            }
-
-            @Override
-            protected CompletableFuture<Integer> createUserid() { //创建用户ID
-                this.user = userService.current(String.valueOf(super.getSessionid()));
-                return CompletableFuture.completedFuture(this.user == null ? null : this.user.getUserid());
-            }
-
-            @Override
-            public CompletableFuture<String> onOpen(HttpRequest request) {
-                //以request中的sessionid字符串作为WebSocket的sessionid
-                return CompletableFuture.completedFuture(request.getSessionid(false)); 
-            }
-
-        };
-    }
-
-    public static class ChatMessage {
-
-        public int sendid; //发送方用户ID
-
-        public int receiveid; //接收方用户ID        
-
-        public String content; //文本消息内容
-
-        public long sendtime;  //消息发送时间
-    }
-}
-                
- -

  . HttpRequest 对象

-
public class HttpRequest {
-
-    //获取客户端地址IP
-    public SocketAddress getRemoteAddress();
-
-    //获取客户端地址IP, 与getRemoteAddres() 的区别在于:
-    //本方法优先取header中指定为RemoteAddress名的值,没有则返回getRemoteAddres()的getHostAddress()。
-    //本方法适用于服务前端有如nginx的代理服务器进行中转,通过getRemoteAddres()是获取不到客户端的真实IP。
-    public String getRemoteAddr();
-
-    //获取请求内容指定的编码字符串
-    public String getBody(Charset charset);
-
-    //获取请求内容的UTF-8编码字符串
-    public String getBodyUTF8();
-
-    //获取请求内容的byte[]
-    public byte[] getBody();
-    
-    //获取请求内容的JavaBean对象
-    public <T> T getBodyJson(java.lang.reflect.Type type);
-
-    //获取请求内容的JavaBean对象
-    public <T> T getBodyJson(JsonConvert convert, java.lang.reflect.Type type);
-    
-    //获取文件上传对象
-    public MultiContext getMultiContext();
-
-    //获取文件上传信息列表 等价于 getMultiContext().parts();
-    public Iterable<MultiPart> multiParts() throws IOException;
-
-    //设置当前用户信息, 通常在HttpServlet.preExecute方法里设置currentUser
-    //数据类型由@HttpUserType指定
-    public <T> HttpRequest setCurrentUser(T user);
-    
-    //获取当前用户信息 数据类型由@HttpUserType指定
-    public <T> T currentUser();
-    
-    //获取模块ID,来自@HttpServlet.moduleid()
-    public int getModuleid();
-    
-    //获取操作ID,来自@HttpMapping.actionid()
-    public int getActionid();
-    
-    //获取sessionid
-    public String getSessionid(boolean autoCreate);
-
-    //更新sessionid
-    public String changeSessionid();
-
-    //指定值更新sessionid
-    public String changeSessionid(String newsessionid);
-
-    //使sessionid失效
-    public void invalidateSession();
-
-    //获取所有Cookie对象
-    public java.net.HttpCookie[] getCookies();
-
-    //获取Cookie值
-    public String getCookie(String name);
-
-    //获取Cookie值, 没有返回默认值
-    public String getCookie(String name, String defaultValue);
-
-    //获取协议名 http、https、ws、wss等
-    public String getProtocol();
-
-    //获取请求方法 GET、POST等
-    public String getMethod();
-
-    //获取Content-Type的header值
-    public String getContentType();
-
-    //获取请求内容的长度, 为-1表示内容长度不确定
-    public long getContentLength();
-
-    //获取Connection的Header值
-    public String getConnection();
-
-    //获取Host的Header值
-    public String getHost();
-
-    //获取请求的URL
-    public String getRequestURI();
-
-    //截取getRequestURI最后的一个/后面的部分
-    public String getRequstURILastPath();
-
-    // 获取请求URL最后的一个/后面的部分的short值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: short type = request.getRequstURILastPath((short)0); //type = 2
-    public short getRequstURILastPath(short defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的short值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: short type = request.getRequstURILastPath((short)0); //type = 2
-    public short getRequstURILastPath(int radix, short defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的int值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: int type = request.getRequstURILastPath(0); //type = 2
-    public int getRequstURILastPath(int defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的int值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: int type = request.getRequstURILastPath(0); //type = 2
-    public int getRequstURILastPath(int radix, int defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的float值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: float type = request.getRequstURILastPath(0.f); //type = 2.f
-    public float getRequstURILastPath(float defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的long值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: long type = request.getRequstURILastPath(0L); //type = 2
-    public long getRequstURILastPath(long defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的long值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: long type = request.getRequstURILastPath(0L); //type = 2
-    public long getRequstURILastPath(int radix, long defvalue);
-
-    // 获取请求URL最后的一个/后面的部分的double值   <br>
-    // 例如请求URL /pipes/record/query/2   <br>
-    // 获取type参数: double type = request.getRequstURILastPath(0.0); //type = 2.0
-    public double getRequstURILastPath(double defvalue);
-
-    //从prefix之后截取getRequestURI再对"/"进行分隔
-    public String[] getRequstURIPaths(String prefix);
-
-    // 获取请求URL分段中含prefix段的值
-    // 例如请求URL /pipes/record/query/name:hello
-    // 获取name参数: String name = request.getRequstURIPath("name:", "none");
-    public String getRequstURIPath(String prefix, String defaultValue);
-
-    // 获取请求URL分段中含prefix段的short值
-    // 例如请求URL /pipes/record/query/type:10
-    // 获取type参数: short type = request.getRequstURIPath("type:", (short)0);
-    public short getRequstURIPath(String prefix, short defaultValue);
-
-    // 获取请求URL分段中含prefix段的short值
-    // 例如请求URL /pipes/record/query/type:a
-    // 获取type参数: short type = request.getRequstURIPath(16, "type:", (short)0); type = 10
-    public short getRequstURIPath(int radix, String prefix, short defvalue);
-
-    // 获取请求URL分段中含prefix段的int值
-    // 例如请求URL /pipes/record/query/offset:2/limit:50
-    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
-    // 获取limit参数: int limit = request.getRequstURIPath("limit:", 20);
-    public int getRequstURIPath(String prefix, int defaultValue);
-
-    // 获取请求URL分段中含prefix段的int值
-    // 例如请求URL /pipes/record/query/offset:2/limit:10
-    // 获取offset参数: int offset = request.getRequstURIPath("offset:", 1);
-    // 获取limit参数: int limit = request.getRequstURIPath(16, "limit:", 20); // limit = 16
-    public int getRequstURIPath(int radix, String prefix, int defaultValue);
-
-    // 获取请求URL分段中含prefix段的float值   
-    // 例如请求URL /pipes/record/query/point:40.0  
-    // 获取time参数: float point = request.getRequstURIPath("point:", 0.0f);
-    public float getRequstURIPath(String prefix, float defvalue);
-
-    // 获取请求URL分段中含prefix段的long值
-    // 例如请求URL /pipes/record/query/time:1453104341363/id:40
-    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
-    public long getRequstURIPath(String prefix, long defaultValue);
-
-    // 获取请求URL分段中含prefix段的long值
-    // 例如请求URL /pipes/record/query/time:1453104341363/id:40
-    // 获取time参数: long time = request.getRequstURIPath("time:", 0L);
-    public long getRequstURIPath(int radix, String prefix, long defvalue);
-
-    // 获取请求URL分段中含prefix段的double值   <br>
-    // 例如请求URL /pipes/record/query/point:40.0   <br>
-    // 获取time参数: double point = request.getRequstURIPath("point:", 0.0);
-    public double getRequstURIPath(String prefix, double defvalue);
-
-    //获取所有的header名
-    public AnyValue getHeaders();
-    
-    //将请求Header转换成Map
-    public Map<String, String> getHeadersToMap(Map<String, String> map);
-    
-    //获取所有的header名
-    public String[] getHeaderNames();
-
-    // 获取指定的header值
-    public String getHeader(String name);
-
-    //获取指定的header值, 没有返回默认值
-    public String getHeader(String name, String defaultValue);
-
-    //获取指定的header的json值
-    public <T> T getJsonHeader(Type type, String name);
-
-    //获取指定的header的json值
-    public <T> T getJsonHeader(JsonConvert convert, Type type, String name);
-
-    //获取指定的header的boolean值, 没有返回默认boolean值
-    public boolean getBooleanHeader(String name, boolean defaultValue);
-
-    // 获取指定的header的short值, 没有返回默认short值
-    public short getShortHeader(String name, short defaultValue);
-
-    // 获取指定的header的short值, 没有返回默认short值
-    public short getShortHeader(int radix, String name, short defaultValue);
-
-    // 获取指定的header的short值, 没有返回默认short值
-    public short getShortHeader(String name, int defaultValue);
-
-    // 获取指定的header的short值, 没有返回默认short值
-    public short getShortHeader(int radix, String name, int defaultValue);
-
-    //获取指定的header的int值, 没有返回默认int值
-    public int getIntHeader(String name, int defaultValue);
-
-    //获取指定的header的int值, 没有返回默认int值
-    public int getIntHeader(int radix, String name, int defaultValue);
-
-    // 获取指定的header的long值, 没有返回默认long值
-    public long getLongHeader(String name, long defaultValue);
-
-    // 获取指定的header的long值, 没有返回默认long值
-    public long getLongHeader(int radix, String name, long defaultValue);
-
-    // 获取指定的header的float值, 没有返回默认float值
-    public float getFloatHeader(String name, float defaultValue);
-
-    //获取指定的header的double值, 没有返回默认double值
-    public double getDoubleHeader(String name, double defaultValue);
-
-    //获取请求参数总对象
-    public AnyValue getParameters();
-    
-    //将请求参数转换成Map
-    public Map<String, String> getParametersToMap(Map<String, String> map);
-    
-    //将请求参数转换成String, 字符串格式为: bean1={}&amp;id=13&amp;name=xxx
-    //不会返回null,没有参数返回空字符串
-    public String getParametersToString();
-    
-    //将请求参数转换成String, 字符串格式为: prefix + bean1={}&amp;id=13&amp;name=xxx
-    //拼接前缀, 如果无参数,返回的字符串不会含有拼接前缀
-    //不会返回null,没有参数返回空字符串
-    public String getParametersToString(String prefix);
-    
-    //获取所有参数名
-    public String[] getParameterNames();
-
-    //获取指定的参数值
-    public String getParameter(String name);
-
-    //获取指定的参数值, 没有返回默认值
-    public String getParameter(String name, String defaultValue);
-
-    //获取指定的参数json值
-    public <T> T getJsonParameter(Type type, String name);
-
-    //获取指定的参数json值
-    public <T> T getJsonParameter(JsonConvert convert, Type type, String name);
-
-    //获取指定的参数boolean值, 没有返回默认boolean值
-    public boolean getBooleanParameter(String name, boolean defaultValue);
-
-    //获取指定的参数short值, 没有返回默认short值
-    public short getShortParameter(String name, short defaultValue);
-
-    //获取指定的参数short值, 没有返回默认short值
-    public short getShortParameter(int radix, String name, short defaultValue);
-
-    //获取指定的参数short值, 没有返回默认short值
-    public short getShortParameter(int radix, String name, int defaultValue);
-
-    //获取指定的参数int值, 没有返回默认int值
-    public int getIntParameter(String name, int defaultValue);
-
-    //获取指定的参数int值, 没有返回默认int值
-    public int getIntParameter(int radix, String name, int defaultValue);
-
-    //获取指定的参数long值, 没有返回默认long值
-    public long getLongParameter(String name, long defaultValue);
-
-    //获取指定的参数long值, 没有返回默认long值
-    public long getLongParameter(int radix, String name, long defaultValue);
-
-    //获取指定的参数float值, 没有返回默认float值
-    public float getFloatParameter(String name, float defaultValue);
-
-    //获取指定的参数double值, 没有返回默认double值
-    public double getDoubleParameter(String name, double defaultValue);
-
-    //获取翻页对象 同 getFlipper("flipper", false, 0);
-    public org.redkale.source.Flipper getFlipper();
-
-    //获取翻页对象 同 getFlipper("flipper", needcreate, 0);
-    public org.redkale.source.Flipper getFlipper(boolean needcreate);
-
-    //获取翻页对象 同 getFlipper("flipper", false, maxLimit);
-    public org.redkale.source.Flipper getFlipper(int maxLimit);
-
-    //获取翻页对象 同 getFlipper("flipper", needcreate, maxLimit)
-    public org.redkale.source.Flipper getFlipper(boolean needcreate, int maxLimit);
-
-    //获取翻页对象 http://redkale.org/pipes/records/list/offset:0/limit:20/sort:createtime%20ASC
-    //http://redkale.org/pipes/records/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
-    //以上两种接口都可以获取到翻页对象
-    public org.redkale.source.Flipper getFlipper(String name, boolean needcreate, int maxLimit);
-
-    //获取HTTP上下文对象
-    public HttpContext getContext();
-
-    //获取所有属性值, servlet执行完后会被清空
-    public Map<String, Object> getAttributes();
-
-    //获取指定属性值, servlet执行完后会被清空
-    public <T> T getAttribute(String name);
-
-    //删除指定属性
-    public void removeAttribute(String name);
-
-    //设置属性值, servlet执行完后会被清空
-    public void setAttribute(String name, Object value);
-
-    //获取request创建时间
-    public long getCreatetime();
-}
-                
- - -

  . HttpResponse 对象

-
public class HttpResponse {
-
-    //增加Cookie值
-    public HttpResponse addCookie(HttpCookie... cookies);
-
-    //增加Cookie值
-    public HttpResponse addCookie(Collection<HttpCookie> cookies);
-
-    //创建CompletionHandler实例,将非字符串对象以JSON格式输出,字符串以文本输出
-    public CompletionHandler createAsyncHandler();
-
-    //传入的CompletionHandler子类必须是public,且保证其子类可被继承且completed、failed可被重载且包含空参数的构造函数
-    public <H extends CompletionHandler> H createAsyncHandler(Class<H> handlerClass);
-
-    //获取ByteBuffer生成器
-    public Supplier<ByteBuffer> getBufferSupplier();
-
-    //设置状态码
-    public void setStatus(int status);
-
-    //获取状态码
-    public int getStatus();
-
-    //获取 ContentType
-    public String getContentType();
-
-    //设置 ContentType
-    public HttpResponse setContentType(String contentType);
-
-    //获取内容长度
-    public long getContentLength();
-
-    //设置内容长度
-    public HttpResponse setContentLength(long contentLength);
-
-    //设置Header值
-    public HttpResponse setHeader(String name, Object value);
-
-    //添加Header值
-    public HttpResponse addHeader(String name, Object value);
-
-    //添加Header值
-    public HttpResponse addHeader(Map<String, ?> map);
-
-    //跳过header的输出
-    //通常应用场景是,调用者的输出内容里已经包含了HTTP的响应头信息,因此需要调用此方法避免重复输出HTTP响应头信息。
-    public HttpResponse skipHeader();
-
-    //异步输出指定内容
-    public <A> void sendBody(ByteBuffer buffer, A attachment, CompletionHandler<Integer, A> handler);
-
-    //异步输出指定内容
-    public <A> void sendBody(ByteBuffer[] buffers, A attachment, CompletionHandler<Integer, A> handler);
-
-    //关闭HTTP连接,如果是keep-alive则不强制关闭
-    public void finish();
-
-    //强制关闭HTTP连接
-    public void finish(boolean kill);
-
-    //将对象以JSON格式输出
-    public void finishJson(Object obj);
-
-    //将对象数组用Map的形式以JSON格式输出
-    //例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
-    public void finishMapJson(final Object... objs);
-
-    //将对象以JSON格式输出
-    public void finishJson(JsonConvert convert, Object obj);
-
-    //将对象数组用Map的形式以JSON格式输出
-    //例如: finishMap("a",2,"b",3) 输出结果为 {"a":2,"b":3}
-    public void finishMapJson(final JsonConvert convert, final Object... objs);
-
-    //将对象以JSON格式输出
-    public void finishJson(Type type, Object obj);
-
-    //将对象以JSON格式输出
-    public void finishJson(final JsonConvert convert, final Type type, final Object obj);
-
-    //将对象以JSON格式输出
-    public void finishJson(final Object... objs);
-
-    //将RetResult对象以JSON格式输出
-    public void finishJson(final org.redkale.service.RetResult ret);
-
-    //将RetResult对象以JSON格式输出
-    public void finishJson(final JsonConvert convert, final org.redkale.service.RetResult ret);
-
-    //将CompletableFuture的结果对象以JSON格式输出
-    public void finishJson(final CompletableFuture future);
-
-    //将CompletableFuture的结果对象以JSON格式输出
-    public void finishJson(final JsonConvert convert, final CompletableFuture future);
-
-    //将CompletableFuture的结果对象以JSON格式输出
-    public void finishJson(final JsonConvert convert, final Type type, final CompletableFuture future);
-
-    //将HttpResult的结果对象以JSON格式输出
-    public void finishJson(final HttpResult result);
-
-    //将HttpResult的结果对象以JSON格式输出
-    public void finishJson(final JsonConvert convert, final HttpResult result);
-
-    //将指定字符串以响应结果输出
-    public void finish(String obj);
-
-    //以指定响应码附带内容输出, message 可以为null
-    public void finish(int status, String message);
-
-    //将结果对象输出
-    public void finish(final Object obj);
-
-    //将结果对象输出
-    public void finish(final Convert convert, final Object obj);
-
-    //将结果对象输出
-    public void finish(final Convert convert, final Type type, final Object obj);
-
-    //以304状态码输出
-    public void finish304();
-
-    //以404状态码输出
-    public void finish404();
-
-    //将指定byte[]按响应结果输出
-    public void finish(final byte[] bs);
-
-    //将指定ByteBuffer按响应结果输出
-    public void finish(ByteBuffer buffer);
-
-    //将指定ByteBuffer按响应结果输出
-    //kill   输出后是否强制关闭连接
-    public void finish(boolean kill, ByteBuffer buffer);
-
-    //将指定ByteBuffer数组按响应结果输出
-    public void finish(ByteBuffer... buffers);
-
-    //将指定ByteBuffer数组按响应结果输出
-    //kill   输出后是否强制关闭连接
-    public void finish(boolean kill, ByteBuffer... buffers);
-
-    //将指定文件按响应结果输出
-    public void finish(File file) throws IOException;
-
-    //将文件按指定文件名输出
-    public void finish(final String filename, File file) throws IOException;
-
-    //HttpResponse回收时回调的监听方法
-    public void recycleListener(BiConsumer<HttpRequest, HttpResponse> recycleListener);
-
-}
-                
- - -

  . WebSocket 对象

-
public abstract class WebSocket<G, T> {
-
-    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> send(Object message);
-
-    //给自身发送消息, 消息类型是key-value键值对  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMap(Object... messages);
-
-    //给自身发送消息, 消息类型是String或byte[]或可JavaBean对象  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> send(Object message, boolean last);
-
-    //给自身发送消息, 消息类型是key-value键值对  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMap(boolean last, Object... messages);
-
-    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> send(Convert convert, Object message);
-
-    //给自身发送消息, 消息类型是JavaBean对象  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> send(Convert convert, Object message, boolean last);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Object message, G... userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Object message, Stream<G> userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Convert convert, Object message, G... userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Convert convert, Object message, Stream<G> userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Object message, boolean last, G... userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Object message, boolean last, Stream<G> userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Convert convert, Object message, boolean last, G... userids);
-
-    //给指定userid的WebSocket节点发送 二进制消息/文本消息/JavaBean对象消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendMessage(Convert convert, Object message, boolean last, Stream<G> userids);
-
-    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final Object message);
-
-    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message);
-
-    //给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Object message);
-
-    //给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Convert convert, final Object message);
-
-    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final Object message, boolean last);
-
-    //给所有人广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final Convert convert, final Object message, boolean last);
-
-    //给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(final WebSocketRange wsrange, final Object message, boolean last);
-
-    //给符合条件的人群广播消息, 返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> broadcastMessage(WebSocketRange wsrange, Convert convert, final Object message, boolean last);
-
-    //给指定userid的WebSocket节点发送操作
-    public CompletableFuture<Integer> sendAction(final WebSocketAction action, Serializable... userids);
-
-    //广播操作, 给所有人发操作指令
-    public CompletableFuture<Integer> broadcastAction(final WebSocketAction action);
-
-    //获取用户在线的SNCP节点地址列表,不是分布式则返回元素数量为1,且元素值为null的列表
-    public CompletableFuture<Collection<InetSocketAddress>> getRpcNodeAddresses(final Serializable userid);
-
-    //获取在线用户的详细连接信息
-    public CompletableFuture<Map<InetSocketAddress, List<String>>> getRpcNodeWebSocketAddresses(final Serializable userid);
-
-    //发送PING消息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendPing();
-
-    //发送PING消息,附带其他信息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendPing(byte[] data);
-
-    //发送PONG消息,附带其他信息  返回结果0表示成功,非0表示错误码
-    public CompletableFuture<Integer> sendPong(byte[] data);
-
-    //强制关闭用户的所有WebSocket
-    public CompletableFuture<Integer> forceCloseWebSocket(Serializable userid);
-
-    //更改本WebSocket的userid
-    public CompletableFuture<Void> changeUserid(final G newuserid);
-
-    //获取指定userid的WebSocket数组, 没有返回null  此方法用于单用户多连接模式
-    protected Stream<WebSocket> getLocalWebSockets(G userid);
-
-    //获取指定userid的WebSocket数组, 没有返回null 此方法用于单用户单连接模式
-    protected WebSocket findLocalWebSocket(G userid);
-
-    //获取当前进程节点所有在线的WebSocket
-    protected Collection<WebSocket> getLocalWebSockets();
-
-    //获取ByteBuffer资源池
-    protected Supplier<ByteBuffer> getByteBufferSupplier();
-
-    //返回sessionid, null表示连接不合法或异常,默认实现是request.sessionid(true),通常需要重写该方法
-    protected CompletableFuture<String> onOpen(final HttpRequest request);
-
-    //创建userid, null表示异常, 必须实现该方法
-    protected abstract CompletableFuture<G> createUserid();
-
-    //WebSocket.broadcastMessage时的过滤条件
-    protected boolean predicate(WebSocketRange wsrange);
-
-    //WebSokcet连接成功后的回调方法
-    public void onConnected();
-
-    //ping后的回调方法
-    public void onPing(byte[] bytes);
-
-    //pong后的回调方法
-    public void onPong(byte[] bytes);
-
-    //接收到消息的回调方法
-    public void onMessage(T message, boolean last);
-
-    //接收到文本消息的回调方法
-    public void onMessage(String message, boolean last);
-
-    //接收到二进制消息的回调方法
-    public void onMessage(byte[] bytes, boolean last);
-
-    //关闭的回调方法,调用此方法时WebSocket已经被关闭
-    public void onClose(int code, String reason);
-
-    //获取当前WebSocket下的属性
-    public T getAttribute(String name);
-
-    //移出当前WebSocket下的属性
-    public T removeAttribute(String name);
-
-    //给当前WebSocket下的增加属性
-    public void setAttribute(String name, Object value);
-
-    //获取当前WebSocket所属的userid
-    public G getUserid();
-
-    //获取当前WebSocket的会话ID, 不会为null
-    public Serializable getSessionid();
-
-    //获取客户端直接地址, 当WebSocket连接是由代理服务器转发的,则该值固定为代理服务器的IP地址
-    public SocketAddress getRemoteAddress();
-
-    //获取客户端真实地址 同 HttpRequest.getRemoteAddr()
-    public String getRemoteAddr();
-
-    //获取WebSocket创建时间
-    public long getCreatetime();
-
-    //获取最后一次发送消息的时间
-    public long getLastSendTime();
-
-    //获取最后一次读取消息的时间
-    public long getLastReadTime();
-
-    //获取最后一次ping的时间
-    public long getLastPingTime();
-
-    //显式地关闭WebSocket
-    public void close();
-
-    //WebSocket是否已关闭
-    public boolean isClosed();
-}
-                
- -

SNCP 协议

-

-         SNCP(Service Node Communicate Protocol)协议是Redkale独有的一种传输协议,用于进程之间的通信,即请求方的远程模式Service与响应方的Service之间的通信。是RPC(远程过程调用协议)的同类型协议,主要区别在于Redkale里SNCP几乎是透明的,写一个普通的Service通过配置即可实现远程调用,而不需要专门针对远程写接口。SNCP服务的配置与HTTP差不多,只是SNCP不需要Servlet,SncpServlet是通过Service动态生成的。
-         SNCP的数据包分包头和包体。包头描述请求的Service信息,请求包的包体描述参数的BSON值,响应包的包体描述回调的参数对象和结果对象的BSON值
-     包头长度固定,其结构如下: -

- - - - - - - - - - -
字 段占用字节数描 述
序列号8请求的唯一序列号
包头长度2值固定为60
Service版本号4值为Service.version()
Service资源hash值16Service资源名class.getName():@Resource.name()的MD5值
Service方法hash值16Service方法method.toString()的MD5值
发送方地址6前4字节为地址,后2字节为端口号
包体长度4整个包体的长度
结果码4请求方的值固定为0,响应方的值视为错误码,为0表示成功,非0为失败。
-

-     包体数据结构为 ([参数序号][参数BSON值])* N + [0][结果对象BSON]。 其中参数序号从1开始,只有当Service的方法存在@RpcCall回调才会有参数BSON值,序号为0表示为结果对象的BSON值。若方法为void返回类型,则不存在结果对象BSON值。 -

- -

自定义协议

-

        协议的网络框架包含五种对象:
-                 Context     : 协议上下文对象
-                 Request    : 服务请求对象
-                 Response : 服务响应对象
-                 Servlet      : 服务逻辑处理对象
-                 Server       : 服务监听对象
-         通常自定义协议需要继承上面五种对象类,同时为了让Redkale能识别和加载自定义协议服务需要继承 org.redkale.boot.NodeServer 并指明 @NodeProtocol,实现可以参考 基于SOCKS5协议的反向代理服务器 -

- - - -
- - - - diff --git a/plugins.html b/plugins.html deleted file mode 100644 index 827d02989..000000000 --- a/plugins.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
- -

Redkale 插件介绍

- -

         org.redkalex 是官方插件的根包名。 redkale-plugins 提供了一些常用的插件。

- -

支付插件

- -

        org.redkalex.pay - 包提供微信、支付宝、银联等支付实现,详情点击这里

- - - -
- - - - diff --git a/redkale.html b/redkale.html deleted file mode 100644 index 89d97ecea..000000000 --- a/redkale.html +++ /dev/null @@ -1,887 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Redkale 功能

- -

        Redkale虽然只有1M左右大小,但是麻雀虽小五脏俱全。既可作为服务器使用,也可当工具包使用。作为独立的工具包提供以下功能:
-                 1、convert包提供JSON的序列化和反序列化功能,类似Gson、Jackson。
-                 2、convert包提供Java对象二进制的序列化和反序列化功能,类似Protobuf。
-                 3、source包提供很简便的数据库操作功能,类似JPA、Hibernate。
-                 4、net包提供TCP/UDP服务功能, 类似Mina。
-                 5、net.http提供HTTP服务, 类似Tomcat、Netty。
-                 6、ResourceFactory提供轻量级的依赖注入功能, 类似Google Guice。
-

- -

Java 源码

-

        Github 源码    https://github.com/redkale

- -

Redkale 服务器

-

        Redkale作为服务器的目录如下:
-                 bin   : 存放启动/关闭/apidoc脚本(start.sh、shutdown.sh、apidoc.sh、start.bat、shutdown.bat、apidoc.bat)
-                 conf : 存放服务器所需配置文件:
-                                      application.xml: 服务配置文件 (必需);
-                                      logging.properties:日志配置文件 (可选);
-                                      persistence.xml:数据库配置文件 (可选);
-                 lib    : 存放服务所依赖且不需要热更新的第三方包,redkale.jar 放在此处。
-                 libs  : 存放自己的业务jar或需要热更新的jar。
-                 logs : logging.properties 配置中默认的日志存放目录。
-                 root : application.xml 配置中HTTP服务所需页面的默认根目录。
-

- -

        Redkale启动的流程如下:
-                 1、加载 application.xml 并解析。
-                 2、初始化 <resources> 节点中的资源。
-                 3、解析所有的 <server> 节点。
-                 4、初始化并启动所有<server> 节点的Server服务 (优先加载SNCP协议的Server,最后加载WATCH协议的Server)。
-                 5、初始化单个Server:
-                           5.1、扫描classpath加载所有可用的Service实现类(没有标记为@AutoLoad(false)的类)并实例化,然后相互依赖注入。
-                           5.2、Service实例在依赖注入过程中加载所需的DataSource、CacheSource资源。
-                           5.3、调用所有本地模式Service的init方法。
-                           5.4、扫描classpath加载所有可用的Servlet实现类(没有标记为@AutoLoad(false)的类)并实例化 (WebSocketServlet优先)。
-                           5.5、给所有Servlet依赖注入所需的Service。
-                           5.6、调用所有Servlet的init方法。
-                           5.7、启动Server的服务监听。
-                 6、启动进程本身的监听服务。
-

- -

基于Redkale的开发与调试

-

        基于Redkale创建一个Java应用程序工程(即使是Web项目也不要创建Java-Web工程),引用redkale.jar 并创建Redkale所需的几个目录和文件。一个普通的Web项目只需要编写业务层的Service和接入层的HttpServlet的代码。数据库DataSource通过配置文件进行设置。
-         编写完代码可以通过启动脚本进行调试, 也可以在IDE设置项目的主类为 org.redkale.boot.Application 或者工程内定义主类进行启动调试: -

-
public final class Bootstrap {
-
-    public static void main(String[] args) throws Exception {
-        org.redkale.boot.Application.main(args);
-    }
-}
-

        若需要调试单个Service,可以通过 Application.singleton 方法进行调试:

-
    public static void main(String[] args) throws Exception {
-        UserService service = Application.singleton(UserService.class);
-        LoginBean bean = new LoginBean();
-        bean.setAccount("myaccount");
-        bean.setPassword("123456");
-        System.out.println(service.login(bean));
-    }
-

        Application.singleton 运行流程与通过bin脚本启动的流程基本一致,区别在于singleton运行时不会启动Server和Application自身的服务监听。Redkale提倡接入层(Servlet)与业务层(Service)分开,Service在代码上不能依赖于Servlet,因此调试Service自身逻辑时不需要启动接入层服务(类似WebSocket依赖Servlet的功能除外)。
-         注: Application.singleton的参数Service类不能是抽象类、接口或不存在非final的public方法。

- -

Redkale的依赖注入

-

        Redkale内置的依赖注入实现很简单,只有三个注解和一个类: javax.annotation.Resource、org.redkale.util.ResourceType、org.redkale.util.ResourceListener、org.redkale.util.ResourceFactory,采用反射技术,依赖注入通常不会在频繁的操作中进行,因此性能要求不会很高。其中前两个是注解,ResourceFactory是主要操作类,主要提供注册和注入两个接口。ResourceFactory的依赖注入不仅提供其他依赖注入框架的常规功能,还能动态的自动更新通过inject注入的资源。

-
public class AService {
-
-    @Resource(name = "property.id")
-    private String id;
-
-    @Resource(name = "property.id") //property.开头的资源名允许String自动转换成primitive数值类型
-    private int intid;
-
-    @Resource(name = "bigint")
-    private BigInteger bigint;
-
-    @Resource(name = "seqid")
-    private int seqid;
-
-    @Resource
-    private BService bservice;
-
-    @Override
-    public String toString() {
-        return "{id:\"" + id + "\", intid: " + intid + ", bigint:" + bigint + "}";
-    }
-
-    /** 以下省略getter setter方法 */
-}
-
-public class BService {
-
-    @Resource(name = "property.id")
-    private String id;
-
-    @Resource
-    private AService aservice;
-
-    private String name = "";
-
-    @java.beans.ConstructorProperties({"name"})
-    public BService(String name) {
-        this.name = name;
-    }
-
-    @Override
-    public String toString() {
-        return "{name:\"" + name + "\", id: " + id + ", aserivce:" + aservice + "}";
-    }
-
-    /** 以下省略getter setter方法 */
-}
-
-
-public static void main(String[] args) throws Exception {
-    ResourceFactory factory = ResourceFactory.root();
-    factory.register("property.id", "2345"); //注入String类型的property.id
-    AService aservice = new AService();
-    BService bservice = new BService("eee");
-
-    factory.register(aservice);  //放进Resource池内,默认的资源名name为""
-    factory.register(bservice);  //放进Resource池内,默认的资源名name为""
-
-    factory.inject(aservice);  //给aservice注入id、bservice,bigint没有资源,所以为null
-    factory.inject(bservice);  //给bservice注入id、aservice
-    System.out.println(aservice); //输出结果为:{id:"2345", intid:2345, bigint:null, bservice:{name:eee}}
-    System.out.println(bservice); //输出结果为:{name:"eee", id:2345, aserivce:{id:"2345", intid:2345, bigint:null, bservice:{name:eee}}}
-
-    factory.register("seqid", 200); //放进Resource池内, 同时ResourceFactory会自动更新aservice的seqid值
-    System.out.println(factory.find("seqid", int.class));   //输出结果为:200
-    factory.register("bigint", new BigInteger("66666")); //放进Resource池内, 同时ResourceFactory会自动更新aservice对象的bigint值   
-    System.out.println(aservice); //输出结果为:{id:"2345", intid:2345, bigint:66666, bservice:{name:eee}}可以看出seqid与bigint值都已自动更新
-
-    factory.register("property.id", "6789"); //更新Resource池内的id资源值, 同时ResourceFactory会自动更新aservice、bservice的id值
-    System.out.println(aservice); //输出结果为:{id:"6789", intid:6789, bigint:66666, bservice:{name:eee}}
-    System.out.println(bservice); //输出结果为:{name:"eee", id:6789, aserivce:{id:"6789", intid:6789, bigint:66666, bservice:{name:eee}}}
-
-    bservice = new BService("ffff");
-    factory.register(bservice);   //更新Resource池内name=""的BService资源, 同时ResourceFactory会自动更新aservice的bservice对象
-    factory.inject(bservice);
-    System.out.println(aservice); //输出结果为:{id:"6789", intid: 6789, bigint:66666, bservice:{name:ffff}}
-
-}
-

        如上例,通过ResourceFactory.inject注入的对象都会自动更新资源的变化,若不想自动更新可以使用带boolean autoSync参数的register系列方法(autoSync传false)注册新资源。

- -

Redkale 架构部署

-

        通常一个系统会分为三层:接入层、业务层、数据层。对应到Redkale的组件是: Servlet、Service、Source。大部分系统提供的是HTTP服务,为了方便演示Redkale从集中式到分布式的变化,以一个简单的HTTP服务作为范例。
-         开发一个极简单的小论坛系统。包含三个模块:
-                 用户模块    UserSerivice:     提供用户注册、登录、更新资料等功能, UserServlet作为接入层。
-                 帖子模块 ForumSerivice:     提供看帖、发帖、删帖等功能, ForumServlet作为接入层。
-                 通知模块  NotifySerivice:     提供用户操作、回帖等消息通知功能, NotifyWebSocket是WebSocket的Servlet, 且name为 ws_notify,作为接入层。
-         其中数据源有:
-                    DataSource:    在persistence.xml里配置的数据库Source的name为demodb ,三个模块都需要使用demodb。
-                 CacheSource:    仅供UserSerivice用于存放session的缓存Service,name为 usersessions, 且session只存放用户ID( int 类型)。
-

-
-

        1、单点部署

-

        在早期用户量很少或者开发、调试环境中只需部署一个进程就可满足需求。

-

-

        如上图,所有模块的HttpServlet、Service与Source数据库操作全部署在一起。 application.xml作简单的配置即可:

-
<application port="5050">       
-    <server protocol="HTTP" port="6060" root="root"> 
-        <services autoload="true" />  
-        <servlets autoload="true"/>
-    </server>     	
-</application>
-
- -

        2、多点部署

-

        在生产环境需要避免单点问题,一个服务一般会部署多套。在此做个简单的容灾部署,最前端部署一个nginx作反向代理和负载均衡服务器,后面部署两套系统。

-

-

        如上图,两个进程间的Serivce都是本地模式,两者会通过SNCP服务保持数据同步,若DataSource开启了数据缓存也会自动同步。两套的配置文件相同,配置如下:

-
<application port="5050">   
-    <resources>	
-        <group name="ALL">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.120" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- HTTP 监听 Server -->
-    <server protocol="HTTP" port="6060" root="root"> 
-        <!-- 前端配置了nginx,需要配置才能获取客户端真实的IP地址 -->
-        <request>
-            <remoteaddr value="request.headers.X-RemoteAddress"/>
-        </request>
-        <services autoload="true" groups="ALL"/>	
-        <servlets autoload="true" />
-    </server> 
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true"  groups="ALL">
-            <!-- 有WebSocketServlet的服务必须配置WebSocketNodeService,且Redkale同时会自动创建一个同名(ws_notify)的 CacheSource -->
-            <service name="ws_notify" value="org.redkale.service.WebSocketNodeService"/>
-            <!-- 存在DataSource必须配置DataSourceService -->
-            <service name="demodb" value="org.redkale.service.DataSourceService"/>
-            <!-- 存放用户HTTP session信息的CacheSource -->
-            <service name="usersessions" value="org.redkale.service.CacheSourceService">
-                <property name="key-type" value="java.lang.String"/>
-                <property name="value-type" value="java.lang.Integer"/>
-            </service>
-        </services>
-    </server>	
-</application>
-
- -

        3、分层部署

-

        随着业务的复杂度增加,接入层与业务层混在一起会越来越难部署和维护,因此需要进行分层部署。

-

-

        如上图,对HttpServlet与Service进行了分离。每个接入层的Service都是远程模式,业务层只需提供SNCP供远程调用。

-

        接入层中每个进程的配置相同,配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="ALL">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.120" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- HTTP 监听 Server -->
-    <server protocol="HTTP" port="6060" root="root"> 
-        <!-- 前端配置了nginx,需要配置才能获取客户端真实的IP地址 -->
-        <request>
-            <remoteaddr value="request.headers.X-RemoteAddress"/>
-        </request>
-        <services autoload="true" groups="ALL">
-            <!-- 有WebSocketServlet的服务必须配置WebSocketNodeService,且Redkale同时会自动创建一个同名(ws_notify)的 CacheSource -->
-            <service name="ws_notify" value="org.redkale.service.WebSocketNodeService"/>
-        </services>
-        <servlets autoload="true" />
-    </server> 	
-</application>
-

        业务层中每个进程的配置相同,配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="ALL">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.120" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true" groups="ALL">
-            <!-- 有WebSocketServlet的服务必须配置WebSocketNodeService,且Redkale同时会自动创建一个同名(ws_notify)的 CacheSource -->
-            <service name="ws_notify" value="org.redkale.service.WebSocketNodeService"/>
-            <!-- 存在DataSource必须配置DataSourceService -->
-            <service name="demodb" value="org.redkale.service.DataSourceService"/>
-            <!-- 存放用户HTTP session信息的CacheSource -->
-            <service name="usersessions" value="org.redkale.service.CacheSourceService">
-                <property name="key-type" value="java.lang.String"/>
-                <property name="value-type" value="java.lang.Integer"/>
-            </service>
-        </services>
-    </server>	
-</application>
-
- -

        4、微服务部署

-

        当用户量和发帖量增加到上百万的时候,明显地将所有模块的服务部署到一个进程里是不行的。 因此需要将Service服务都独立部署形成微服务架构。

-

-

        如上图,将Serivice都独立部署并进行容灾部署,当然如果有需要,Servlet之间、Source都可以各自分离独立部署。不同类型的Service之间都是远程模式调用。

-

        接入层中每个进程的配置相同,配置如下:

-
<application port="5050">   
-    <resources>	
-        <group name="USER_SERVICE">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.111" port="7070"/>
-        </group>
-        <group name="NOTIFY_SERVICE">
-            <node addr="192.168.50.120" port="7070"/>
-            <node addr="192.168.50.121" port="7070"/>
-        </group>
-        <group name="FORUM_SERVICE">
-            <node addr="192.168.50.130" port="7070"/>
-            <node addr="192.168.50.131" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- HTTP 监听 Server -->
-    <server protocol="HTTP" port="6060" root="root"> 
-        <!-- 前端配置了nginx,需要配置才能获取客户端真实的IP地址 -->
-        <request>
-            <remoteaddr value="request.headers.X-RemoteAddress"/>
-        </request>
-        <services autoload="true">	
-            <service value="org.redkale.demo.UserService" groups="USER_SERVICE"/>
-            <service value="org.redkale.demo.NotifyService" groups="NOTIFY_SERVICE"/>
-            <service value="org.redkale.demo.ForumService" groups="FORUM_SERVICE"/>
-            <!-- 有WebSocketServlet的服务必须配置WebSocketNodeService,且Redkale同时会自动创建一个同名(ws_notify)的 CacheSource -->
-            <service name="ws_notify" value="org.redkale.service.WebSocketNodeService" groups="NOTIFY_SERVICE"/>
-        </services>
-        <servlets autoload="true" />
-    </server> 	
-</application>
-

        用户模块UserService服务群中各个进程的配置相同,配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="USER_SERVICE">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.111" port="7070"/>
-        </group>
-        <group name="NOTIFY_SERVICE">
-            <node addr="192.168.50.120" port="7070"/>
-            <node addr="192.168.50.121" port="7070"/>
-        </group>
-        <group name="FORUM_SERVICE">
-            <node addr="192.168.50.130" port="7070"/>
-            <node addr="192.168.50.131" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true">	
-            <service value="org.redkale.demo.NotifyService" groups="NOTIFY_SERVICE"/>
-            <service value="org.redkale.demo.ForumService" groups="FORUM_SERVICE"/>
-            <!-- 存在DataSource必须配置DataSourceService -->
-            <service name="demodb" value="org.redkale.service.DataSourceService" groups="USER_SERVICE"/>
-            <!-- 存放用户HTTP session信息的CacheSource -->
-            <service name="usersessions" value="org.redkale.service.CacheSourceService" groups="USER_SERVICE">
-                <property name="key-type" value="java.lang.String"/>
-                <property name="value-type" value="java.lang.Integer"/>
-            </service>
-        </services>
-    </server> 	
-</application>
-

        通知模块NotifyService服务群中各个进程的配置相同,配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="USER_SERVICE">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.111" port="7070"/>
-        </group>
-        <group name="NOTIFY_SERVICE">
-            <node addr="192.168.50.120" port="7070"/>
-            <node addr="192.168.50.121" port="7070"/>
-        </group>
-        <group name="FORUM_SERVICE">
-            <node addr="192.168.50.130" port="7070"/>
-            <node addr="192.168.50.131" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true">	
-            <service value="org.redkale.demo.UserService" groups="USER_SERVICE"/>
-            <service value="org.redkale.demo.ForumService" groups="FORUM_SERVICE"/>
-            <!-- 有WebSocketServlet的服务必须配置WebSocketNodeService,且Redkale同时会自动创建一个同名(ws_notify)的 CacheSource -->
-            <service name="ws_notify" value="org.redkale.service.WebSocketNodeService" groups="NOTIFY_SERVICE"/>
-            <!-- 存在DataSource必须配置DataSourceService -->
-            <service name="demodb" value="org.redkale.service.DataSourceService" groups="NOTIFY_SERVICE"/>
-        </services>
-    </server> 	
-</application>
-

        帖子模块ForumService服务群中各个进程的配置相同,配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="USER_SERVICE">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.111" port="7070"/>
-        </group>
-        <group name="NOTIFY_SERVICE">
-            <node addr="192.168.50.120" port="7070"/>
-            <node addr="192.168.50.121" port="7070"/>
-        </group>
-        <group name="FORUM_SERVICE">
-            <node addr="192.168.50.130" port="7070"/>
-            <node addr="192.168.50.131" port="7070"/>
-        </group>
-    </resources>
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true">	
-            <service value="org.redkale.demo.UserService" groups="USER_SERVICE"/>
-            <service value="org.redkale.demo.NotifyService" groups="NOTIFY_SERVICE"/>
-            <!-- 存在DataSource必须配置DataSourceService -->
-            <service name="demodb" value="org.redkale.service.DataSourceService" groups="FORUM_SERVICE"/>
-        </services>
-    </server> 	
-</application>
-
-

        5、API网关式部署

-

        随着用户量到了上千万时,一个UserService的服务进程是无法提供全部用户服务。 因此可以考虑按用户段进行分布式部署。将192.168.50.110、192.168.50.111上的UserService服务改成网关式的服务。下面是以 Service本地模式介绍中的UserService 为范例进行编写:

-
@ResourceType({UserService.class})
-public class UserServiceGateWay extends UserService {
-
-    @Resource(name = "userservice_reg")
-    private UserService regUserService;  //只用于注册的服务节点
-
-    @Resource(name = "userservice_mob")
-    private UserService mobUserService;  //只用于查询手机号码对应的userid的服务节点
-
-    @Resource(name = "userservice_node01")
-    private UserService userService01;  //userid小于2000000的用户的服务节点
-
-    @Resource(name = "userservice_node02")
-    private UserService userService02;  //userid小于4000000的用户的服务节点
-
-    @Resource(name = "userservice_node03")
-    private UserService userService03;  //userid小于6000000的用户的服务节点
-
-    @Resource(name = "userservice_node04")
-    private UserService userService04;  //userid大于6000000的用户的服务节点
-
-    private UserService getService(int userid) {
-        if (userid <= 200_0000) return userService01;
-        if (userid <= 400_0000) return userService02;
-        if (userid <= 600_0000) return userService03;
-        return userService04;
-    }
-
-    @Override
-    public UserInfo findUserInfo(int userid) {
-        return this.getService(userid).findUserInfo(userid);
-    }
-
-    @Override
-    public RetResult<UserInfo> login(LoginBean bean) { //手机号码用long存储,0表示无手机号码
-        int userid = mobUserService.findUserid(bean.getMobile());
-        if (userid < 1) return new RetResult<>(10001, "not found mobile " + bean.getMobile());
-        return this.getService(userid).login(bean);
-    }
-
-    @Override
-    public void register(UserInfo user) {
-        regUserService.register(user); //会生成userid
-        this.getService(user.getUserid()).putUserInfo(user);
-    }
-
-    @Override
-    public UserInfo updateUsername(int userid, String username) {
-        return this.getService(userid).updateUsername(userid, username);
-    }
-}
-                
-

        从代码看出,UserServiceGateWay继承了UserService, 确保了UserService对外的服务接口不变,上面代码是用户量在600-800万之间的写法,通过简单的用户ID分段,根据不同用户ID调不同的服务节点。

-

-

        如上图,网关下的UserService部署分三类: userservice_reg只用于注册用户;userservice_mob提供查询手机号码与用户ID间的关系的服务;userservice_node按用户段提供已有用户的服务。且每个UserService的实例在UserServiceGateWay都是远程模式。每种类型可以部署多个节点(为了结构图简单,上图每个类型只部署一个节点)。UserServiceGateWay(192.168.50.110、192.168.50.111)的配置如下:

-
<application port="5050">  
-    <resources>	
-        <group name="USER_SERVICE_REG">
-            <node addr="192.168.70.110" port="7070"/>
-        </group>
-        <group name="USER_SERVICE_MOB">
-            <node addr="192.168.70.150" port="7070"/>
-        </group>
-        <group name="USER_SERVICE_NODE01">
-            <node addr="192.168.70.201" port="7070"/>
-        </group>
-        <group name="USER_SERVICE_NODE02">
-            <node addr="192.168.70.202" port="7070"/>
-        </group>
-        <group name="USER_SERVICE_NODE03">
-            <node addr="192.168.70.203" port="7070"/>
-        </group>
-        <group name="USER_SERVICE_NODE04">
-            <node addr="192.168.70.204" port="7070"/>
-        </group>
-        
-        <group name="USER_SERVICE">
-            <node addr="192.168.50.110" port="7070"/>
-            <node addr="192.168.50.111" port="7070"/>
-        </group>        
-    </resources>
-
-    <!-- SNCP 监听 Server -->
-    <server protocol="SNCP" port="7070"> 
-        <services autoload="true">	
-            <!-- 配置UserService网关 -->
-            <service name="" value="org.redkale.demo.UserServiceGateWay" groups="USER_SERVICE"/>
-            <!-- 配置UserService分段节点 -->
-            <service name="userservice_reg" value="org.redkale.demo.UserService" groups="USER_SERVICE_REG"/>
-            <service name="userservice_mob" value="org.redkale.demo.UserService" groups="USER_SERVICE_MOB"/>
-            <service name="userservice_node01" value="org.redkale.demo.UserService" groups="USER_SERVICE_NODE01"/>
-            <service name="userservice_node02" value="org.redkale.demo.UserService" groups="USER_SERVICE_NODE02"/>
-            <service name="userservice_node03" value="org.redkale.demo.UserService" groups="USER_SERVICE_NODE03"/>
-            <service name="userservice_node04" value="org.redkale.demo.UserService" groups="USER_SERVICE_NODE03"/>
-        </services>
-    </server> 
-</application>
-
-

        由以上几种部署方式的范例可以看出,Redkale提供了非常强大的架构,集中式到微服务架构不需要增加修改一行代码即可随意切换,即使网关式部署也只是新增很少的代码就可切换,且不影响其他服务。真正可以做到敏捷开发,复杂的系统都可如小系统般快速地开发出来。
-         为了降低接入层与业务层代码的耦合, 可以将Service分接口与实现两个类,接入层只加载接口包、业务层使用实现包。 -

-
- -

application.xml 配置说明

-
<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-    文件说明:
-        ${APP_HOME} 指当前程序的根目录APP_HOME
-        没注明唯一的节点可多个存在
-        required: 被声明required的属性值不能为空
-        
-                                            group
-                                         /  /  \  \  
-                                      /    /    \   \   
-                                   /     /       \    \                      
-                               node1   node2   node3  node4
-                             /       \                             
-                          /             \
-                       /                   \
-                    /                         \
-             serviceid1                       serviceid2
-            /       \                        /           \
-  serviceid1_name1  serviceid1_name2   serviceid2_name1  serviceid2_name2    
--->
-<!--     
-    nodeid: int     进程的节点ID,用于分布式环境,一个系统中节点ID必须全局唯一,使用cluster时框架会进行唯一性校验
-    name:           进程的名称,用于监控识别,命名规则: 字母、数字、下划线、短横、点
-    address:        本地局域网的IP地址, 默认值为默认网卡的ip,当不使用默认值需要指定值,如192.168.1.22
-    port: required  程序的管理Server的端口,用于关闭或者与监管系统进行数据交互
-    lib:            加上额外的lib路径,多个路径用分号;隔开; 默认为空。  例如: ${APP_HOME}/lib/a.jar;${APP_HOME}/lib2/b.jar;
--->
-<application nodeid="1000" port="6560" lib="">  
-
-    <!--
-       【节点全局唯一】 @since 2.3.0
-        全局Serivce执行的线程池, Application.workExecutor, 没配置该节点将自动创建一个。
-        threads: 线程数,为0表示不启用workExecutor,只用IO线程。默认: CPU核数, 核数=1的情况下默认值为2
-    -->
-    <executor threads="4"/>
-
-    <!--
-       【节点全局唯一】
-        第三方服务发现管理接口
-        type:          类名,必须是org.redkale.cluster.ClusterAgent的子类
-        waits:          注销服务后是否需要等待检查周期时间后再进行Service销毁,默认值为:false
-                        当一个Service进行服务注销后,不能立刻销毁Service,因为健康检测是有间隔时间差的,
-                        需要等待一个健康检测周期时间,让其他进程都更新完服务列表。
-                        如果使用MQ,可以设置为false,如果对服务健壮性要求高,建议设置为true
-        protocols:      服务发现可以处理的协议, 默认值为: SNCP, 多个协议用分号;隔开
-        ports:          服务发现可以处理的端口, 多个端口用分号;隔开
-        ttls:           心跳频率,多少秒一次
-        xxxx:           自定义的字段属性,例如:CacheClusterAgent有source字段; ConsulClusterAgent有apiurl字段;
-    -->
-    <cluster type="org.redkalex.cluster.consul.ConsulClusterAgent" waits="false" protocols="SNCP" ports="7070;7071" xxx="xxx" />
-
-    <!--
-        MQ管理接口配置
-        不同MQ节点所配置的MQ集群不能重复。
-        MQ跟着协议走,所以mq的属性值需要被赋值在rest节点上, 由于SncpServlet是自动生成的,故SNCP协议下,mq属性值被赋值在service/services节点上
-        name:    服务的名称,用于监控识别,多个mq节点时只能有一个name为空的节点,mq.name不能重复,命名规则: 字母、数字、下划线
-        type:   实现类名,必须是org.redkale.mq.MessageAgent的子类
-        threads:线程数,为0表示使用workExecutor。默认: CPU核数, 核数=1的情况下默认值为2,JDK 21以上版本默认使用虚拟线程池
-        rpcfirst:cluster和mq同名组件时,HttpRpcClient优先使用MQ,默认不优先走MQ。
-        coder:   MessageRecord的解析器类,必须是org.redkale.mq.MessageCoder<MessageRecord>的实现类, 
-             可对数据包进行加密解密,默认值:org.redkale.mq.MessageRecordCoder
-        MQ节点下的子节点配置没有固定格式, 根据MessageAgent实现方的定义来配置
-    -->
-    <mq name="" type="org.redkalex.mq.kafka.KafkaMessageAgent" rpcfirst="false" threads="4">
-        <servers value="127.0.0.1:9101"/>
-        <!--        
-           加载所有的MessageConsumer实例;
-           autoload="true"  默认值. 自动加载classpath下所有的MessageConsumer类  
-           autoload="false" 需要显著的指定MessageConsumer类
-           includes: 当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-           excludes: 当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开           
-        -->
-        <consumer autoload="true" includes="" excludes=""/>
-        <!--
-            MQ实现方的配置项
-          type:  配置项类型,值只能是consumer或producer
-        -->
-        <config type="consumer">
-            <property name="xxxxxx" value="XXXXXXXX"/>
-        </config>
-        <config type="producer">
-            <property name="xxxxxx" value="XXXXXXXX"/>
-        </config>
-    </mq>
-        
-    <!--
-        一个组包含多个node, 同一Service服务可以由多个进程提供,这些进程称为一个GROUP,且同一GROUP内的进程必须在同一机房或局域网内
-        name:       服务组ID,长度不能超过11个字节. 默认为空字符串。 注意: name不能包含$符号。
-        protocol:  值范围:UDP TCP, 默认TCP
-        注意: 一个node只能所属一个group。只要存在protocol=SNCP的Server节点信息, 就必须有group节点信息。
-    -->
-    <group name="" protocol="TCP">
-        <!--
-            需要将本地node的addr与port列在此处。
-            同一个<node>节点值只能存在一个<group>节点内,即同一个addr+port只能属于一个group。
-            addr: required IP地址
-            port: required 端口
-        -->
-        <node addr="127.0.0.1" port="7070"/>
-    </group>
-
-    <!--
-       Application启动的监听事件,可配置多个节点
-       value: 类名,必须是ApplicationListener的子类
-    -->
-    <listener value="org.redkalex.xxx.XXXApplicationListener"/>
-        
-    <!-- 
-        【节点全局唯一】
-        全局的参数配置, 可以通过@Resource(name="property.xxxxxx") 进行注入<property>的信息, 被注解的字段类型只能是String、primitive class
-        如果name是system.property.开头的值将会在进程启动时进行System.setProperty("yyyy", "YYYYYY")操作。
-        如果name是mimetype.property.开头的值将会在进程启动时进行MimeType.add("yyyy", "YYYYYY")操作。
-        先加载子节点property,再加载load文件, 最后加载agent的实现子类。
-        load:  加载文件,多个用;隔开。
-        其他属性: 供org.redkale.boot.PropertiesAgentProvider使用判断
-        默认置入的system.property.的有:
-           System.setProperty("redkale.convert.pool.size", "128");
-           System.setProperty("redkale.convert.writer.buffer.defsize", "4096");
-           
-        <properties>节点下也可包含非<property>节点.
-        非<property>其节点可以通过@Resource(name="properties.xxxxxx")进行注入, 被注解的字段类型只能是AnyValue、AnyValue[]
-    -->
-    <properties load="config.properties">
-        <property name="system.property.yyyy" value="YYYYYY"/>
-        <property name="xxxxxx" value="XXXXXXXX"/>
-        <property name="xxxxxx" value="XXXXXXXX"/>
-        <property name="xxxxxx" value="XXXXXXXX"/>
-    </properties>
-        
-    <!--
-        protocol: required   server所启动的协议,Redkale内置的有HTTP、SNCP、WATCH。协议均使用TCP实现; WATCH服务只能存在一个。
-        name:                服务的名称,用于监控识别,一个配置文件中的server.name不能重复,命名规则: 字母、数字、下划线
-        host:                服务所占address , 默认: 0.0.0.0
-        port:                required 服务所占端口 
-        root:                如果是web类型服务,则包含页面  默认:{APP_HOME}/root
-        lib:                 server额外的class目录, 默认为${APP_HOME}/libs/*; 
-        excludelibs:         排除lib.path与excludes中的正则表达式匹配的路径, 多个正则表达式用分号;隔开
-        charset:             文本编码, 默认: UTF-8
-        backlog:             默认10K
-        maxconns:           最大连接数, 小于1表示无限制, 默认: 0
-        maxbody:             request.body最大值, 默认: 256K
-        bufferCapacity:      ByteBuffer的初始化大小, TCP默认: 32K;  (HTTP 2.0、WebSocket,必须要16k以上); UDP默认: 8K
-        bufferPoolSize:     ByteBuffer池的大小,默认: 线程数*4
-        responsePoolSize:   Response池的大小,默认: 1024
-        aliveTimeoutSeconds: KeepAlive读操作超时秒数, 默认30, 0表示永久不超时; -1表示禁止KeepAlive
-        readTimeoutSeconds:  读操作超时秒数, 默认0, 0表示永久不超时
-        writeTimeoutSeconds: 写操作超时秒数, 默认0, 0表示永久不超时
-        interceptor:         启动/关闭NodeServer时被调用的拦截器实现类,必须是org.redkale.boot.NodeInterceptor的子类,默认为null
-    -->
-    <server protocol="HTTP" host="127.0.0.1" port="6060" root="root" lib=""> 
-        
-        <!-- 
-           【节点在<server>中唯一】
-           builder:             创建SSLContext的实现类, 可自定义,必须是org.redkale.net.SSLBuilder的子类
-           sslProvider:         java.security.Provider自定义的实现类,如第三方: org.conscrypt.OpenSSLProvider、org.bouncycastle.jce.provider.BouncyCastleProvider
-           jsseProvider:        java.security.Provider自定义的实现类,如第三方: org.conscrypt.JSSEProvider、   org.bouncycastle.jce.provider.BouncyCastleJsseProvider
-           protocol:            TLS版本,默认值: TLS
-           protocols:           设置setEnabledProtocols, 多个用,隔开 如: TLSv1.2,TLSv1.3
-           clientAuth:          WANT/NEED/NONE, 默认值: NONE
-           ciphers:             设置setEnabledCipherSuites, 多个用,隔开 如: TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256
-           keystorePass:        KEY密码
-           keystoreFile:        KEY文件 .jks
-           keystoreType:        KEY类型, 默认值为JKS
-           keystoreAlgorithm:   KEY文件的algorithm, 默认值为SunX509
-           truststorePass:      TRUST密码
-           truststoreFile:      TRUST文件
-           truststoreType:      TRUST类型, 默认值为JKS
-           truststoreAlgorithm: TRUST文件的algorithm, 默认值为SunX509
-        -->
-        <ssl builder=""/>
-        
-        <!-- 
-           加载所有的Service服务;
-           在同一个进程中同一个name同一类型的Service将共用同一个实例
-           autoload="true"  默认值. 自动加载classpath下所有的Service类  
-           autoload="false" 需要显著的指定Service类
-           mq:        所属的MQ管理器,当 protocol == SNCP 时该值才有效, 存在该属性表示Service的SNCP协议采用消息总线代理模式
-           includes: 当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-           excludes: 当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开           
-           group:     所属组的节点, 不能指定多个group, 如果配置文件中存在多个SNCP协议的Server节点,需要显式指定group属性.
-                         当 protocol == SNCP 时 group表示当前Server与哪些节点组关联。
-                         当 protocol != SNCP 时 group只能是空或者一个group的节点值。
-                         特殊值"$remote", 视为通过第三方服务注册发现管理工具来获取远程模式的ip端口信息
-        -->
-        <services autoload="true" includes="" excludes="">
-
-            <!-- 显著加载指定的Service的接口类 -->
-            <service value="com.xxx.XXX1Service"/>
-            <!-- 
-               name:   显式指定name,覆盖默认的空字符串值。 注意: name不能包含$符号。
-               mq:     所属的MQ管理器,当 protocol == SNCP 时该值才有效, 存在该属性表示Service的SNCP协议采用消息总线代理模式
-               group:  显式指定group,覆盖<services>节点的group默认值。
-               ignore: 是否禁用, 默认为false。
-            -->
-            <service value="com.xxx.XXX2Service" name="" group="xxx"/>
-            <!--   给Service增加配置属性 -->
-            <service value="com.xxx.XXX1Service"> 
-                <!-- property值在public void init(AnyValue conf)方法中可以通过AnyValue properties=conf.getAnyValue("properties")获取 -->
-                <property name="xxxxxx" value="XXXXXXXX"/>  
-                <property name="xxxxxx" value="XXXXXXXX"/>
-            </service>
-        </services>
-        
-        <!-- 
-           加载所有的Filter服务;
-           autoload="true"  默认值.  
-           autoload="false" 需要显著的指定Filter类
-           includes:       当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-           excludes:       当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开    
-        -->
-        <filters autoload="true" includes="" excludes="">
-            
-            <!-- 
-               显著加载指定的Filter类
-               value=: Filter类名。必须与Server的协议层相同,HTTP必须是HttpFilter
-               ignore: 是否禁用, 默认为false。
-            -->
-            <!-- 显著加载指定的Filter类 -->
-            <filter value="com.xxx.XXX1Filter"/>
-            
-            <!--   给Filter增加配置属性 -->
-            <filter value="com.xxx.XXX12Filter"> 
-                <!-- property值在public void init(AnyValue conf)方法中可以通过AnyValue properties=conf.getAnyValue("properties")获取 -->
-                <property name="xxxxxx" value="XXXXXXXX"/>  
-                <property name="xxxxxx" value="XXXXXXXX"/>
-            </filter>
-        </filters>
-        
-        <!-- 
-           REST的核心配置项
-           当Server为HTTP协议时, rest节点才有效。存在[rest]节点则Server启动时会加载REST服务, 节点可以多个,(WATCH协议不需要设置,系统会自动生成)
-           path:     servlet的ContextPath前缀 默认为空 【注: 开启MQ时,该字段失效】
-           base:     REST服务的BaseServlet,必须是 org.redkale.net.http.HttpServlet 的子类,且子类必须标记@HttpUserType。
-           mq:        所属的MQ管理器, 存在该属性表示RestService的请求来自于消息总线 【注: 开启MQ时,path字段失效】
-           autoload:默认值"true"  默认值. 加载当前server所能使用的Servce对象;    
-           includes:当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-           excludes:当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-        -->
-        <rest path="/pipes" base="org.redkale.net.http.HttpServlet" autoload="true" includes="" excludes="">
-            <!-- 
-               value:  Service类名,列出的表示必须被加载的Service对象
-               ignore: 是否忽略,设置为true则不会加载该Service对象,默认值为false
-            -->
-            <service value="com.xxx.XXXXService"/>
-            <!-- 
-               value:  WebSocket类名,列出的表示必须被加载且标记为@RestWebSocket的WebSocket对象
-               ignore: 是否忽略,设置为true则不会加载该RestWebSocket对象,默认值为false
-            -->
-            <websocket value="com.xxx.XXXXRestWebSocket"/>
-        </rest>
-        
-        <!--
-           【节点在<server>中唯一】
-           当Server为HTTP协议时, request节点才有效。
-           remoteaddr 节点: 替换请求方节点的IP地址, 通常请求方是由nginx等web静态服务器转发过的则需要配置该节点。
-           且value值只能是以request.headers.开头,表示从request.headers中获取对应的header值。
-           locale value值必须是request.headers.或request.parameters.开头。
-           例如下面例子获取request.getRemoteAddr()值,如果header存在X-RemoteAddress值则返回X-RemoteAddress值,不存在返回getRemoteAddress()。
-        -->
-        <request>
-            <remoteaddr value="request.headers.X-RemoteAddress"/>
-            <locale value="request.headers.locale" /> 
-            <rpc authenticator="org.redkale.net.http.HttpRpcAuthenticator的实现类"/>
-        </request>
-        
-        <!--
-           【节点在<server>中唯一】
-           当Server为HTTP协议时, response节点才有效。
-           contenttype: plain值为调用finish时的ContentType; 默认值: text/plain; charset=utf-8
-                        json值为调用finishJson时的ContentType; 默认值: application/json; charset=utf-8
-           defcookie 节点: 当response里输出的cookie没有指定domain 和path时,使用该节点的默认值。
-           addheader、setheader 的value值以request.parameters.开头则表示从request.parameters中获取对应的parameter值
-           addheader、setheader 的value值以request.headers.开头则表示从request.headers中获取对应的header值
-           例如下面例子是在Response输出header时添加两个header(一个addHeader, 一个setHeader)。
-           options 节点: 设置了该节点且auto=true,当request的method=OPTIONS自动设置addheader、setheader并返回200状态码
-           date 节点: 设置了该节点且period有值(单位:毫秒);返回response会包含Date头信息,默认为period=0
-                      period=0表示实时获取当前时间;
-                      period<0表示不设置date;
-                      period>0表示定时获取时间; 设置1000表示每秒刷新Date时间
-        -->
-        <response>
-            <content-type plain="text/plain; charset=utf-8" json="application/json; charset=utf-8"/>            
-            <defcookie domain="" path=""/>  
-            <addheader name="Access-Control-Allow-Origin" value="request.headers.Origin" />  <!-- 可多节点 -->
-            <setheader name="Access-Control-Allow-Headers" value="request.headers.Access-Control-Request-Headers"/>  <!-- 可多节点 -->
-            <setheader name="Access-Control-Allow-Credentials" value="true"/>
-            <options auto="true" />
-            <date period="0" />
-        </response>
-        <!-- 
-           【节点在<server>中唯一】
-           当Server为HTTP协议时,render才有效. 指定输出引擎的实现类
-           value: 输出引擎的实现类, 必须是org.redkale.net.http.HttpRender的子类
-           suffixs: 引擎文件名后缀,多个用;隔开,默认值为: .htel
-        -->
-        <render value="org.redkalex.htel.HttpTemplateRender" suffixs=".htel"/>
-        <!-- 
-           【节点在<server>中唯一】
-           当Server为HTTP协议时,ResourceServlet才有效. 默认存在一个有默认属性的resource-servlet节点
-           webroot: web资源的根目录, 默认取server节点中的root值
-           servlet: 静态资源HttpServlet的实现,默认使用HttpResourceServlet
-           index :  启始页,默认值:index.html
-        -->
-        <resource-servlet webroot="root" index="index.html">
-            <!--
-                【节点在<resource-servlet>中唯一】
-                资源缓存的配置, 默认存在一个含默认属性的caches节点
-                limit:     资源缓存最大容量, 默认: 0, 为0表示不缓存, 单位可以是B、K、M、G,不区分大小写
-                lengthmax: 可缓存的文件大小上限, 默认: 1M(超过1M的文件不会被缓存)
-                watch:     是否监控缓存文件的变化, 默认为false,不监控
-            -->
-            <cache  limit="0M" lengthmax="1M" watch="false"/>
-            <!--
-               支持类似nginx中的rewrite, 目前只支持静态资源对静态资源的跳转。
-               type:    匹配的类型, 目前只支持location(匹配requestURI), 默认: location
-               match:   匹配的正则表达式
-               forward: 需跳转后的资源链接
-               例如下面例子是将/xxx-yyy.html的页面全部跳转到/xxx.html
-            -->
-            <rewrite type="location" match="^/([^-]+)-[^-\.]+\.html(.*)" forward="/$1.html"/>
-        </resource-servlet>
-        <!-- 
-           加载所有的Servlet服务;
-           path:            servlet的ContextPath前缀 默认为空
-           autoload="true"  默认值. 自动加载classpath下所有的Servlet类 
-           autoload="false" 需要显著的指定Service类
-           includes:       当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-           excludes:       当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-        -->
-        <servlets path="/pipes" autoload="true" includes="" excludes="">
-            <!-- 
-               显著加载指定的Servlet类
-               value=: Servlet类名。必须与Server的协议层相同,HTTP必须是HttpServlet
-               ignore: 是否禁用, 默认为false。
-            -->
-            <servlet value="com.xxx.XXX1Servlet" />
-            <servlet value="com.xxx.XXX2Servlet" />
-            <servlet value="com.xxx.XXX3Servlet" >
-                <property name="xxxxxx" value="XXXXXXXX"/>
-                <property name="yyyyyy" value="YYYYYYYY"/>
-            </servlet>
-        </servlets>
-    </server>
-    
-    <server protocol="SNCP" host="127.0.0.1" port="7070" root="root" lib=""> 
-        <!-- 参数完全同上 -->
-        <services autoload="true" includes="" excludes="" />
-    </server>
-    
-</application>
-
- - - - -
- - - - diff --git a/service.html b/service.html deleted file mode 100644 index 2dcb0c20b..000000000 --- a/service.html +++ /dev/null @@ -1,642 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Service 组件介绍

- -

         Service 是Redkale最核心的组件,依赖于Convert、SNCP协议、Resource依赖注入。Service主要处理业务逻辑和操作数据层,是微服务架构中的单一原子服务。每一个Service实例分两种模式: 本地模式远程模式。其模式由 conf/application.xml 文件来配置。开发人员在调用过程中通常不需要区分当前Service实例是哪种模式。
-          为了能确保本地模式与远程模式自由切换,对Service的实现类有一定的约束:
-                  1、Service实现类会被继承,不能修饰为 final
-                  2、带@RpcMultiRun注解的方法会被重载,不能修饰为 final
-          Redkale进程启动时扫描可加载的Service实现类,根据配置文件配置的模式采用ASM技术动态生成相应的Service临时类进行实例化,并注册到ResourceFactory同其他Service、Servlet依赖注入。 -

- -

Service 本地模式

-

        以一个简单的UserService类范例来说明动态生成的两种模式临时类。UserService提供查询用户、注册、登陆、修改用户名功能:

- -
public class UserService implements Service {
-
-    //用户简单信息缓存
-    private final Map<Integer, UserInfo> users = new ConcurrentHashMap<>();
-
-    //存放手机号码与userid的对应关系
-    private final Map<Long, Integer> mobileToUserids = new ConcurrentHashMap<>();
-
-    public final String testLocalNodeName() {
-        return "本地节点名";
-    }
-
-    //查询用户信息
-    public UserInfo findUserInfo(int userid) {
-        return users.get(userid);
-    }
-
-    //根据手机号码查询用户ID,没有返回0
-    public int findUserid(long mobile) {
-        Integer rs = mobileToUserids.get(mobile);
-        return rs == null ? 0 : rs;
-    }
-
-    //缓存用户信息
-    public void putUserInfo(UserInfo user) {
-        users.put(user.getUserid(), user);
-        if (user.getMobile() > 0) mobileToUserids.put(user.getMobile(), user.getUserid());
-    }
-
-    //登录
-    public RetResult<UserInfo> login(LoginBean bean) {
-        // 登陆逻辑
-        return new RetResult<>(100);
-    }
-
-    //注册
-    @RpcMultiRun
-    public void register(UserInfo user) {
-        this.users.put(user.getUserid(), user);
-    }
-
-    //更新用户名
-    @RpcMultiRun(diffrun = false)
-    public UserInfo updateUsername(int userid, String username) {
-        UserInfo user = this.users.get(userid);
-        if (user != null) user.setUsername(username);
-        return user;
-    }
-}
-                
- -

        动态生成的本地模式UserService:

-
@Resource(name = "")
-@SncpDyn(remote = false)
-@ResourceType(UserService.class)
-public final class _DynLocalUserService extends UserService {
-
-    private SncpClient _redkale_client;
-
-    @Override
-    public void register(UserInfo user) {
-        this._redkale_register(true, true, true, user);
-    }
-
-    @SncpDyn(remote = false, index = 0)
-    public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
-        if (selfrunnable) super.register(user);
-        if (_redkale_client == null) return;
-        if (samerunnable) _redkale_client.remoteSameGroup(0, true, false, false, user);
-        if (diffrunnable) _redkale_client.remoteDiffGroup(0, true, true, false, user);
-    }
-
-    @Override
-    public UserInfo updateUsername(int userid, String username) {
-        return this._redkale_updateUsername(true, true, false, userid, username);
-    }
-
-    @SncpDyn(remote = false, index = 1)
-    public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
-        UserInfo rs = super.updateUsername(userid, username);
-        if (_redkale_client == null) return null;
-        if (samerunnable) _redkale_client.remoteSameGroup(1, true, false, false, userid, username);
-        if (diffrunnable) _redkale_client.remoteDiffGroup(1, true, true, false, userid, username);
-        return rs;
-    }
-}
-                
-

        由以上等价的代码可以看出来,本地模式Service会重载被@RpcMultiRun注解的方法。@RpcMultiRun有以下几个参数:

-
public @interface RpcMultiRun {
-
-    boolean selfrun() default true; //当前本地实例是否运行指定操作;只有当指定操作的方法的返回值为void时,该值才能为true,否则忽略。
-
-    boolean samerun() default true;  //是否同组节点运行指定操作
-
-    boolean diffrun() default true; //是否不同组节点运行指定操作
-
-    boolean async() default true; //分布式运行是否采用异步模式
-} 
-

        在动态生成的远程模式UserService时会根据不同参数生成相应的方法。若一个Service类没有含@RpcMultiRun注解的方法,那么动态类只会重载toString方法。当UserService服务仅需要部署一个进程,由于没有其他等同服务的进程因此在UserService实例化时_redkale_client会赋值为null。

-
<resources>  
-    <group name="GROUP-A">
-        <node addr="192.168.10.111" port="7070"/>
-        <node addr="192.168.10.112" port="7070"/>
-        <node addr="192.168.10.113" port="7070"/>
-    </group>
-    <group name="GROUP-B">            
-        <node addr="192.168.20.121" port="7070"/>
-        <node addr="192.168.20.122" port="7070"/>
-    </group>
-    <group name="GROUP-C">            
-        <node addr="192.168.30.131" port="7070"/>
-        <node addr="192.168.30.132" port="7070"/>
-    </group>
-</resources>
-
-
-<!-- 配置UserService的节点组  --->
-<service name="" value="org.redkale.demo.user.UserService" groups="GROUP-A;GROUP-B;GROUP-C"/>
-                
-

        如上配置,若当前进程所在IP是192.168.10.111,则UserService采用本地模式加载。执行register方法时, 先本地执行超类的register,然后远程执行同组的进程(192.168.10.112、192.168.10.113),最后远程执行异组的进程(192.168.20.121、192.168.20.122、192.168.30.131、192.168.30.132)。若当前进程所在IP是192.168.10.100,则UserService采用远程模式加载。需要注意的一点是,每个IP所在的服务必须开通SNCP协议服务以便能接收远程的调用请求。

- -

Service 远程模式

-

        动态生成的远程模式UserService:

-
@Resource(name = "")
-@SncpDyn(remote = true)
-@ResourceType(UserService.class)
-public final class _DynRemoteUserService extends UserService {
-
-    private SncpClient _redkale_client;
-
-    @SncpDyn(remote = false, index = 0)
-    public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
-        _redkale_client.remote(0, selfrunnable, samerunnable, diffrunnable, user);
-    }
-
-    @SncpDyn(remote = false, index = 1)
-    public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
-        return _redkale_client.remote(1, selfrunnable, samerunnable, diffrunnable, userid, username);
-    }
-
-    @Override
-    public UserInfo findUserInfo(int userid) {
-        return _redkale_client.remote(2, userid);
-    }
-
-    @Override
-    public int findUserid(long mobile) {
-        return _redkale_client.remote(3, mobile);
-    }
-
-    @Override
-    public RetResult<UserInfo> login(LoginBean bean) {
-        return _redkale_client.remote(4, bean);
-    }
-
-    @Override
-    public void putUserInfo(UserInfo user) {
-        _redkale_client.remote(5, user);
-    }
-
-    @Override
-    public void register(UserInfo user) {
-        _redkale_client.remote(6, user);
-    }
-
-    @Override
-    public UserInfo updateUsername(int userid, String username) {
-        return _redkale_client.remote(7, userid, username);
-    }
-}
-                
-

        由以上代码可以看出来,远程模式Service是根据本地模式Service临时类动态生成的。远程类执行方法时通过SNCP协议将参数序列化并带上当前方法信息传输到远程服务器上,执行完后将结果流反序列化并返回, 其流程与WebService类似。

- -

  远程模式的@RpcCall回调

-

        与WebService的区别除了更具性能的二进制的数据格式,更差异的是远程模式的Service允许修改参数本身的内容。范例如下:

-
/**
- * 由于该方法在处理过程中修改了参数bean的内容,为了保证本地模式与远程模式的一致性,需要提供@RpcCall回调接口
- *
- * @param bean
- * @return
- */
-public RetResult<UserInfo> login(@RpcCall(RpcCallLoginBeanAttribute.class) LoginBean bean) {
-    bean.setLogintime(System.currentTimeMillis());
-    bean.setSessionid("SID" + System.currentTimeMillis());
-    // 登陆逻辑
-    return new RetResult<>(100);
-}
-
-
-
-/**  RpcCallLoginBeanAttribute 的实现  **/
-public class RpcCallLoginBeanAttribute implements Attribute<LoginBean, Object[]> {
-
-    @Override
-    public Object[] get(LoginBean obj) {
-        return new Object[]{obj.getLogintime(), obj.getSessionid()};
-    }
-
-    @Override
-    public void set(LoginBean obj, Object[] value) {
-        obj.setLogintime((Long) value[0]);
-        obj.setSessionid((String) value[1]);
-    }
-
-    @Override
-    public Class<? extends Object[]> type() {
-        return Object[].class; //
-    }
-
-    @Override
-    public Class<LoginBean> declaringClass() {
-        return LoginBean.class; //
-    }
-
-    @Override
-    public String field() {
-        return ""; //可以随意值
-    }
-}
-                
-

        生成远程模式Service时发现参数带有@RpcCall注解的方法,在远程调用返回结果时会进行回调处理。

- -

Service REST

-

        RestService提供类似Spring Boot的功能。开启REST功能的HTTP Server在实例化标记为@RestService的Service后自动生成对应的HttpServlet,免去开发人员编写HttpServlet的工作量。主要通过@RestService@RestMapping@RestParam这三个注解来实现,同时为了获取其他类型的参数也有@RestAddress@RestCookie@RestHeader@RestSessionid@RestBody 提供其扩展功能。

-

-     @RestService :
-

-
/**
- * 只能依附在Service类上,name默认为Service的类名小写并去掉Service字样及后面的字符串 (如HelloService/HelloServiceImpl,的默认路径为 hello)。
- */
-@Target({TYPE})
-@Retention(RUNTIME)
-public @interface RestService {
-
-    /**
-     * 模块名, 只能是模块名,不能含特殊字符, 只能小写字母+数字,且不能以数字开头
-     *
-     * @return 模块名
-     */
-    String name() default "";
-
-    /**
-     * 目录名, 不能含特殊字符, 只能小写字母+数字,且不能以数字开头
-     *
-     * @return 目录名
-     */
-    String catalog() default "";
-
-    /**
-     * 模块ID值,鉴权时用到, 对应&#64;WebServlet.moduleid
-     *
-     * @return 模块ID值
-     */
-    int moduleid() default 0;
-
-    /**
-     * 没有标记&#64;RestMapping的方法是否转换, 默认为false
-     *
-     * @return 默认false
-     */
-    boolean automapping() default false;
-
-    /**
-     * 是否屏蔽该类的转换
-     *
-     * @return 默认false
-     */
-    boolean ignore() default false;
-
-    /**
-     * 同&#64;WebServlet的repair属性
-     *
-     * @return 默认true
-     */
-    boolean repair() default true;
-
-    /**
-     * 备注描述
-     *
-     * @return 备注描述
-     */
-    String comment() default "";
-}
-                
- -

-     @RestMapping :
-

-
/**
- * 只能依附在Service实现类的public方法上
- * value默认为"/" + Service的类名去掉Service及后面字样的小写字符串 (如HelloService,的默认路径为/hello)。
- */
-@Target({METHOD})
-public @interface RestMapping {
-
-    boolean ignore() default false; //是否屏蔽该方法的转换
-
-    //请求的方法名, 不能含特殊字符
-    //默认为方法名的小写(若方法名以createXXX、updateXXX、deleteXXX、queryXXX、findXXX且XXXService为Service的类名将只截取XXX之前)
-    String name() default "";
-
-    String comment() default ""; //备注描述, 对应@HttpMapping.comment 
-
-    boolean auth() default true; //是否鉴权,默认需要鉴权, 对应@HttpMapping.auth 
-
-    int cacheseconds() default 0; //结果缓存的秒数, 为0表示不缓存, 对应@HttpMapping.cacheseconds
-
-    int actionid() default 0; //操作ID值,鉴权时用到, 对应@HttpMapping.actionid
-
-    String[] methods() default {};  //允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法, 对应@HttpMapping.methods
-}
-                
- -

-     @RestParam :
-

-
/**
- * 只能依附在Service类的方法的参数上
- */
-@Target({PARAMETER})
-public @interface RestParam {
-
-    //参数名 name='&'表示当前用户;
-    //参数名 name='#'表示截取uri最后一段; 
-    //参数名 name='#xxx:'表示从uri中/pipes/xxx:v/截取xxx:的值; 
-    String name(); 
-
-    int radix() default 10; //转换数字byte/short/int/long时所用的进制数, 默认10进制
-
-    boolean required() default true; //参数是否必传 
-
-    String comment() default ""; //备注描述, 对应@HttpMapping.comment 
-}
-                
-

        开启REST功能的步骤很简单:在 application.xml<server> 节点下增加<rest>指明RestServlet的子类。

- -
<!-- 
-   REST的核心配置项, 存在[rest]节点则Server启动时会加载REST服务, 当Server为SNCP协议时,则SncpServer会变成REST的HttpServer, 节点可以多个
-   path:     servlet的ContextPath前缀 默认为空
-   base:     REST服务的BaseServlet,必须是org.redkale.net.http.HttpServlet的子类,且子类必须标记 @HttpUserType。
-   autoload:默认值"true"  默认值. 加载当前server所能使用的Servce对象;    
-   includes:当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-   excludes:当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
--->
-<rest path="/pipes" base="org.redkale.net.http.HttpServlet" autoload="true" includes="" excludes="">
-    <!-- 
-       value:  Service类名,列出的表示必须被加载的Service对象
-       ignore: 是否忽略,设置为true则不会加载该Service对象,默认值为false
-    -->
-    <service value="com.xxx.XXXXService"/>
-</rest>
-                
-

        Redkale中的REST并非严格遵循标准的REST规范,在尽量满足规范的同时也考虑到开发的灵活性和简易性。以下通过简单的实例来说明其特性。

-

-         通常配置都需要编写一个 org.redkale.net.http.HttpServlet 子类,主要用于获取当前用户信息和鉴权,且必须指定具体的User对象类。开发者的实现类可以参考 redkale-demo 中的BaseServlet类,以下是一个简单的范例:
-

-
@HttpUserType(UserInfo.class)
-public class SimpleRestServlet extends HttpServlet {
-
-    protected static final RetResult RET_UNLOGIN = RetCodes.retResult(RetCodes.RET_USER_UNLOGIN);
-
-    protected static final RetResult RET_AUTHILLEGAL = RetCodes.retResult(RetCodes.RET_USER_AUTH_ILLEGAL);
-
-    @Resource
-    private UserService userService;
-
-    @Override
-    public void preExecute(HttpRequest request, HttpResponse response) throws IOException {
-        final String sessionid = request.getSessionid(false);
-        if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
-        response.nextEvent();
-    }
-
-    //普通鉴权
-    @Override
-    public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
-        UserInfo info = request.currentUser();
-        if (info == null) {
-            response.finishJson(RET_UNLOGIN);
-            return;
-        } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
-            response.finishJson(RET_AUTHILLEGAL);
-            return;
-        }
-        response.nextEvent();
-    }
-
-}
-                
-

-         REST的设置方式有两种: 一种采用默认REST注解,一种是显式的设置。
-

-
public class HelloBean implements FilterBean {
-
-    private int helloid;
-
-    @RestHeader(name = "User-Agent")
-    private String useragent; //从Http Header中获取浏览器信息
-    
-    @RestCookie(name = "hello-cookie")
-    private String rescookie;  //从Cookie中获取名为hello-cookie的值
-
-    @RestAddress
-    private String clientaddr;  //客户端请求IP
-
-    @RestSessionid
-    private String sessionid;  //用户Sessionid, 未登录时为null
-
-    /** 以下省略getter setter方法 */
-}
-                
- -
public class HelloEntity {
-
-    @Id
-    private int helloid;
-
-    private String helloname;
-
-    private int creator;
-
-    private long updatetime;
-
-    private long createtime;
-
-    @RestHeader(name = "hello-res")
-    private String resname;
-
-    @RestAddress
-    private String clientaddr;
-
-    /** 以下省略getter setter方法 */
-}
-                
- -
/**
- * 类说明:
- * Flipper : Source组件中的翻页对象
- * UserInfo :当前用户类
- * HelloEntity: Hello模块的实体类
- * HelloBean: Hello模块实现FilterBean的过滤Bean类
- *
- */
-@RestService(name = "hello", moduleid = 0, repair = true, ignore = false, comment = "Hello服务模块")
-public class HelloService implements Service {
-
-    @Resource
-    private DataSource source;
-
-    //增加记录
-    @RestMapping(name = "create", auth = false, comment = "创建Hello对象")
-    public RetResult<HelloEntity> createHello(UserInfo info, @RestParam(name = "bean", comment = "Hello对象") HelloEntity entity) {
-        entity.setCreator(info == null ? 0 : info.getUserid()); //设置当前用户ID
-        entity.setCreatetime(System.currentTimeMillis());
-        source.insert(entity);
-        return new RetResult<>(entity);
-    }
-
-    //删除记录
-    @RestMapping(name = "delete", auth = false, comment = "根据id删除Hello对象")
-    public void deleteHello(@RestParam(name = "#", comment = "Hello对象id") int id) { //通过 /hello/delete/1234 删除对象
-        source.delete(HelloEntity.class, id);
-    }
-
-    //修改记录
-    @RestMapping(name = "update", auth = false, comment = "修改Hello对象")
-    public void updateHello(@RestParam(name = "bean", comment = "Hello对象") HelloEntity entity) { //通过 /hello/update?bean={...} 修改对象
-        entity.setUpdatetime(System.currentTimeMillis());
-        source.update(entity);
-    }
-
-    //查询列表
-    //通过 /hello/query/offset:0/limit:20?bean={...} 获取结果
-    @RestMapping(name = "query", auth = false, comment = "查询Hello对象列表")
-    public Sheet<HelloEntity> queryHello(@RestParam(name = "bean", comment = "过滤条件") HelloBean bean, Flipper flipper) {
-        return source.querySheet(HelloEntity.class, flipper, bean);
-    }
-
-    //查询单个
-    @RestMapping(name = "find", auth = false, comment = "根据id查找单个Hello对象")
-    public HelloEntity findHello(@RestParam(name = "#", comment = "Hello对象id") int id) {  //通过 /hello/find/1234 查询对象
-        return source.find(HelloEntity.class, id);
-    }
-
-    //异步查询单个
-    @RestMapping(name = "asyncfind", auth = false, comment = "根据id查找单个Hello对象")
-    public CompletableFuture<HelloEntity> findAsyncHello(@RestParam(name = "#") int id) {  //通过 /pipes/hello/asyncfind/1234 查询对象
-        return source.findAsync(HelloEntity.class, id);
-    }
-}
-                
- -

-         根据默认命名规则可以看出,以上范例生成的HttpServlet与去掉所有@RestService、@RestMapping、@RestParam后的Service生成的是完全相同的。 REST根据Service会动态生成HttpServlet,以上范例自动生成的HttpServlet如下:
-

- -
@WebServlet(value = {"/hello/*"}, repair = true)
-public class _DynHelloRestServlet extends SimpleRestServlet {
-
-    @Resource
-    private HelloService _service;
-
-    @Resource
-    private Map<String, HelloService> _servicemap;
-
-    @HttpMapping(url = "/hello/create", auth = false, comment = "创建Hello对象")
-    @HttpParam(name = "bean", type = HelloEntity.class, comment = "Hello对象")
-    public void create(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
-        bean.setClientaddr(req.getRemoteAddr());
-        bean.setResname(req.getHeader("hello-res"));
-        UserInfo user = currentUser(req);
-        RetResult<HelloEntity> result = service.createHello(user, bean);
-        resp.finishJson(result);
-    }
-
-    @HttpMapping(url = "/hello/delete/", auth = false, comment = "根据id删除Hello对象")
-    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
-    public void delete(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        int id = Integer.parseInt(req.getRequstURILastPath());
-        service.deleteHello(id);
-        resp.finishJson(RetResult.success());
-    }
-
-    @HttpMapping(url = "/hello/update", auth = false, comment = "修改Hello对象")
-    @HttpParam(name = "bean", type = HelloEntity.class, comment = "Hello对象")
-    public void update(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
-        bean.setClientaddr(req.getRemoteAddr());
-        bean.setResname(req.getHeader("hello-res"));
-        service.updateHello(bean);
-        resp.finishJson(RetResult.success());
-    }
-
-    @HttpMapping(url = "/hello/query", auth = false, comment = "查询Hello对象列表")
-    @HttpParam(name = "bean", type = HelloBean.class, comment = "过滤条件")
-    public void query(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        HelloBean bean = req.getJsonParameter(HelloBean.class, "bean");
-        bean.setClientaddr(req.getRemoteAddr());
-        bean.setUseragent(req.getHeader("User-Agent"));
-        bean.setRescookie(req.getCookie("hello-cookie"));
-        bean.setSessionid(req.getSessionid(false));
-        Flipper flipper = req.getFlipper();
-        Sheet<HelloEntity> result = service.queryHello(bean, flipper);
-        resp.finishJson(result);
-    }
-
-    @HttpMapping(url = "/hello/find/", auth = false, comment = "根据id查找单个Hello对象")
-    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
-    public void find(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        int id = Integer.parseInt(req.getRequstURILastPath());
-        HelloEntity bean = service.findHello(id);
-        resp.finishJson(bean);
-    }
-
-    @HttpMapping(url = "/hello/asyncfind/", auth = false, comment = "根据id查找单个Hello对象")
-    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
-    public void asyncfind(HttpRequest req, HttpResponse resp) throws IOException {
-        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
-        int id = Integer.parseInt(req.getRequstURILastPath());
-        resp.finishJson(service.findAsyncHello(id));
-    }
-}  
-                
- - -

Service 异步调用

-

        远程模式不仅对@RpcCall注解进行处理,而且对方法含有 CompletionHandler 的参数或返回类型为CompletableFuture也进行异步特殊处理。异步调用对远程模式非常有意义,可以减少同步方式对当前线程的占用时间。也给Source组件的异步调用提供了基础。

-
    @Override
-    public <T> void updateAsync(final CompletionHandler<Void, T[]> handler, @RpcAttachment final T... values) {
-        source.update(values);
-        if (handler != null) handler.completed(null, values);
-    }
-

-         如上图源码,异步接口(含CompletionHandler参数或返回类型为CompletableFuture)与同步接口执行流程相同。当Service为远程模式时,调用异步接口时,通信接口发到远程服务器时CompletionHandler参数的值传null,返回数据后再调用本地的CompletionHandler参数值执行。
-         异步调用方式是提高服务并发性的有效手段,特别是在远程模式Service比较多的情况下效果更明显。以HTTP服务为例,在Tomcat刚刚改版成NIO的时候,网上随处可见都是大谈NIO性能比BIO多好,认为BIO与NIO的不同,是BIO往往会引入多线程,每个连接一个单独的线程;NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。而事实上呢,通常还是通过增加线程数来提高并发量。为什么NIO作用不大呢, 因为一个HTTP动态请求耗时最多是业务逻辑层,特别是操作数据库,IO操作的耗时比重小得多,只有在静态资源请求这种纯IO操作才能体现NIO、AIO(NIO.2)的优势。举例一个简单的数据查询请求,采用BIO方式耗时(为了方便比较将所耗时间扩大几倍)如下:
-                 1、服务器TCP连接开始到进入HttpServlet,耗时 10ms
-                 2、用户态判断和参数验证,                         耗时 10ms
-                 3、调用远程数据源(DataSource)查询数据,耗时 150ms
-                 4、数据序列化与response的IO输出,          耗时 10ms
-         如上描述,一个请求处理耗时 180ms,同时占用一个线程的时间也是 180ms。若换成NIO使IO耗时减少,为了方便计算假设IO耗时为0(实际情况是不可能的), 那么步骤1、4的耗时忽略不计,线程的占用时间由180ms变成160ms。 假设数据查询接口IO操作本身耗时也是10ms,那么有140ms是用于等待。若采用DataSource异步接口, 则140ms的等待时间可以释放当前线程资源。虽然整个请求的处理时间还是180ms,但是线程的占用时间却只有20ms。可以看出减少耗时多的步骤的等待时间才能事半功倍,大幅度地提高性能。异步接口的主要作用是远程请求在等待过程中释放当前线程资源,大大减少线程数。 -

- - - -
- - - - diff --git a/source.html b/source.html deleted file mode 100644 index 52de544e8..000000000 --- a/source.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Source 组件介绍

- -

         Source 主要为数据源提供简易的操作接口,使底层的具体数据源(传统数据库、文件系统、内存数据库、Memcached/Redis缓存)对上层是透明的。其提供两种类型的数据源:DataSource 和 CacheSource。DataSource 为数据库或内存数据库,提供类似JPA、Hibernate的接口与功能。CacheSource 为缓存数据提供类似Memcached、Redis的接口和功能。两者也提供了异步接口(基于远程模式Service)。

- -

DataSource 入门

-

-         JPA虽已提供了简洁成熟的数据库操作接口,但当数据、业务量很庞大的时候就显得捉襟见肘,与JPA相比,DataSource有以下几个特点:
- -                  1、简易的过滤查询接口,但仅支持简单的表关联查询。
-                  2、简化分表分库操作。
-                  3、通过watch组件动态更改数据库连接参数。
-                  4、读写分离的简易配置。
-                  5、提供异步接口。
-         数据库操作方面常见的是过滤查询操作,JPA规范中的JPQL虽然简化了SQL,但是对于动态产生的过滤条件,开发人员还是无法免去组装过滤条件的过程(无论JPQL还是CriteriaQuery), DataSource定义了FilterBean接口可以省略组装条件的过程,FilterNode提供了类似CriteriaQuery的功能,且这两种对象都可以序列化,给远程模式Service提供了基础,微服务架构提倡服务之间尽量降低耦合,因此DataSource仅支持简单的关联查询,复杂的表关联查询或统计应放在数据分析系统中。一个服务通常部署多个进程,若用JPA的缓存则进程之间的缓存无法同步,而DataSource采用SNCP协议即可方便地达到自动同步缓存功能。JPA无法在主数据库异常时动态切换到备份数据库,source.properties文件更改后已运行中的进程不会自动切换,需要开启watch组件通过watch动态更改正在运行进程中的配置。
-         为了降低学习成本,DataSource重用了JPA里的部分注解与配置文件,使用方法基本相同,与JPA用法的区别是注解只能标记于字段,不能标记在方法上。 -

- - - - - - - - - - - - - - -
注解类名功能描述
org.redkale.persistence.Cacheable标记Entity类是否需要缓存,与JPA用法一致
org.redkale.persistence.Column标记字段,只使用其name()、insertable()、updatable()属性
org.redkale.persistence.EntityJPA的Entity类必须标记为@Entity, 而Redkale不强制要求,该注解一般较少使用
org.redkale.persistence.Id标记主键字段,与JPA用法一致
org.redkale.persistence.Table标记表的别名,与JPA用法一致
org.redkale.persistence.Transient标记是否为表对应的字段,与JPA用法一致
以下是Redkale自定义的注解
@VirtualEntity用于非数据库表对应的Entity类,且仅用于开启缓存模式的DataSource
@DistributeTable标记表进行分表分库存储, 与DistributeTableStrategy接口结合使用
@FilterColumn用于FilterBean过滤类的字段设置
@FilterJoinColumn用于FilterBean过滤类的关联表字段设置
@FilterGroup用于FilterBean过滤类的过滤条件分组设置
-

-         操作数据源主要使用的对象有 DataSource、FilterBean、FilterNode。DataSource 提供的接口分几种系列: -

- - - - - - - - - - - - - -
系列方法功能描述
insert插入数据
delete删除数据
update更新数据
updateColumn更新数据的部分字段
getNumberXXX统计查询,用于查询字段的总和、最大值、平均值等数据
queryColumnXXX单个字段数据查询和字段的统计查询
find查找单个对象
queryList查询对象的List集合
querySheet查询对象的Sheet页式集合
directXXX直接运行SQL语句,用于复杂的关联查询与更新(仅限DataSqlSource)
-

-         以上接口除了directXXX,其他都有等同的异步接口。insert、delete、update接口与JPA同名接口用法一样。DataSource提供了丰富的查询接口,且有独特的翻页查询功能。每以系列的方法主要重载三类: 单个字段过滤、FilterBean过滤和FilterNode过滤。
-         返回类型为CompletableFuture的接口均为异步接口
-         开发者可以借鉴 Redkale-demo 中的 AutoClassCreator的代码根据数据表自动生成Entity代码。 -

-

    过滤条件

-

        FilterBean、FilterNode对象用于构造过滤条件。FilterBean可以转化为FilterNode。FilterBean主要用于接收外界构建的过滤条件,而FilterNode为了构建内部的过滤条件且降低过滤条件变化的耦合性,FilterNode中name值以#开头的视为虚拟字段,不会构建成过滤条件,仅供分布分库的DistributeTableStrategy策略使用。

-
public class UserBean implements FilterBean {
-
-    private int userid;
-
-    @FilterColumn(express = FilterExpress.LIKE)
-    private String userName;
-
-    private Range age;
-
-    public UserBean(int userid, String userName, Range age) {
-        this.userid = userid;
-        this.userName = userName;
-        this.age = age;
-    }
-
-    /** 以下省略getter setter方法 */
-
-}
-
-
-
- new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于 
- FilterNodes.eq("userid", 200001).like("userName", "redkale").between("age", new Range.IntRange(14, 36))
- 
- new UserBean(200001,"redkale",new IntRange(14,36)) 等价于 "WHERE userid=200001 AND userName LIKE '%redkale%' AND age BETWEEN 14 AND 36"
- new UserBean(200001, "redkale", null) 等价于 "WHERE userid = 200001 AND userName LIKE '%redkale%'"
- new UserBean(0, "redkale", null) 等价于 "WHERE userName LIKE '%redkale%'"
-
-                
-

        如上定义UserBean过滤条件,当非数值类字段值为null、字符串值为空、数值类字段值小于@FilterColumn.least()值(least的默认值为1)都不会构建成过滤条件。@FilterColumn.express根据字段的类型有不同的默认值,若字段类型为Collection子类或数组则express默认为FilterExpress.IN;若字段类型为Range的子类则express默认为FilterExpress.BETWEEN;其他类型则express默认为FilterExpress.EQUAL。默认字段之间是AND关系,若想使用OR关系则需要使用@FilterGroup进行标记:

-
public class UserBean implements FilterBean {
-
-    private int userid;
-
-    @FilterGroup("[OR]a")
-    @FilterColumn(express = FilterExpress.LIKE)
-    private String userName;
-
-    @FilterGroup("[OR]a")
-    private Range age;
-
-    public UserBean(int userid, String userName, Range age) {
-        this.userid = userid;
-        this.userName = userName;
-        this.age = age;
-    }
-
-    /** 以下省略getter setter方法 */
-
-}
-
-
-
- new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于 
- FilterNode orNode = FilterNodes.like("userName", "redkale").or("age", new Range.IntRange(14, 36)); 
- FilterNode node = FilterNodes.eq("userid", 200001).and(orNode);
- 
- new UserBean(200001,"redkale",new IntRange(14,36)) 等价于 "WHERE userid=200001 AND (userName LIKE '%redkale%' OR age BETWEEN 14 AND 36)"
- new UserBean(200001, "redkale", null) 等价于 "WHERE userid = 200001 AND userName LIKE '%redkale%'"
- new UserBean(0, "redkale", null) 等价于 "WHERE userName LIKE '%redkale%'"
-
-
- source.getNumberResult(User.class, FilterFunc.COUNT, null, new UserBean(0, "redkale", new IntRange(14, 36))).intValue() 等价于
- "SELECT COUNT(*) FROM user WHERE userName LIKE '%redkale%' AND  age BETWEEN 14 AND 36"
-                
-

        如上@FilterGroup 的value 必须是[OR]或者[AND]开头,没有标记@FilterGroup的字段等价于标记了@FilterGroup(value = "[AND]")。[AND]、[OR]后面的字符串为GROUP_NAME,默认的GROUP_NAME为空字符串。如上"[OR]a"可以直接使用"[OR]",有多个[OR]或者[AND]则需要加上不同的NAME。

- - -

    分表分库

-

        DataSource提供了单个实体类对应多个数据库表的功能,通常流水型的数据量比较大,单个数据库无法存储,DataSource提供了简单的分表操作,同时在接口设计上尽量减少单表操作与分表操作的差异。分表分库只需在实体类上注解@DistributeTable并实现DistributeTableStrategy分表策略即可。

-
public interface DistributeTableStrategy<T> {
-
-    /**
-     * 获取对象的表名
-     * 查询单个对象时调用本方法获取表名
-     *
-     * @param table   模板表的表名
-     * @param primary 记录主键
-     *
-     * @return
-     */
-    public String getTable(String table, Serializable primary);
-
-    /**
-     * 获取对象的表名
-     * 查询、修改、删除对象时调用本方法获取表名
-     * 注意: 需保证FilterNode过滤的结果集合必须在一个数据库表中
-     *
-     * @param table 模板表的表名
-     * @param node  过滤条件
-     *
-     * @return
-     */
-    public String getTable(String table, FilterNode node);
-
-    /**
-     * 获取对象的表名
-     * 新增对象或更新单个对象时调用本方法获取表名
-     *
-     * @param table 模板表的表名
-     * @param bean  实体对象
-     *
-     * @return
-     */
-    public String getTable(String table, T bean);
-}
-                
- -

        DistributeTableStrategy分表策略需要实现三个接口,模板表由实体类的@Table注解提供。Redkale默认实现的MySQL数据库的拷贝表结构语句,其他数据库类型需要通过指定source.properties 中的 tablenotexist-sqlstatestablecopy-sqltemplate 来配置。

-
@DistributeTable(strategy = LoginRecord.TableStrategy.class)
-public class LoginRecord extends BaseEntity {
-
-    @Id
-    @Column(comment = "主键ID; 值=create36time(9位)+'-'+UUID(32位)")
-    private String loginid = ""; //主键ID; 值=create36time(9位)+'-'+UUID(32位)
-
-    @Column(updatable = false, comment = "C端用户ID")
-    private long userid; //C端用户ID
-
-    @Column(updatable = false, comment = "登录网络类型; wifi/4g/3g")
-    private String netMode = ""; //登录网络类型; wifi/4g/3g
-
-    @Column(updatable = false, comment = "APP版本信息")
-    private String appVersion = ""; //APP版本信息
-
-    @Column(updatable = false, comment = "APP操作系统信息")
-    private String appos = ""; //APP操作系统信息
-
-    @Column(updatable = false, comment = "登录时客户端信息")
-    private String loginAgent = ""; //登录时客户端信息
-
-    @Column(updatable = false, comment = "登录时的IP")
-    private String loginAddr = ""; //登录时的IP
-
-    @Column(updatable = false, comment = "创建时间")
-    private long createTime; //创建时间
-
-    /** 以下省略getter setter方法 */
-
-
-    //创建对象
-    public static void main(String[] args) throws Throwable {
-        LoginRecord record = new LoginRecord();
-        long now = System.currentTimeMillis();
-        record.setCreateTime(now); //设置创建时间
-        record.setLoginid(Utility.format36time(now) + "-" + Utility.uuid());  //主键的生成规则
-        //....  填充其他字段
-        source.insert(record);
-    }
-
-
-    public static class TableStrategy implements DistributeTableStrategy<LoginRecord> {
-
-        private static final String dayformat = "%1$tY%1$tm%1$td"; //一天一个表
-
-        private static final String yearformat = "%1$tY";  //一年一个库
-
-        //过滤查询时调用本方法
-        @Override
-        public String getTable(String table, FilterNode node) {
-            Serializable day = node.findValue("#day");  //LoginRecord没有day字段,所以前面要加#,表示虚拟字段, 值为yyyyMMdd格式
-            if (day != null) getTable(table, (Integer) day, 0L); //存在#day参数则直接使用day值
-            Serializable time = node.findValue("createTime");  //存在createTime则使用最小时间,且createTime的范围必须在一天内,因为本表以天为单位建表
-            return getTable(table, 0, (time == null ? 0L : (time instanceof Range ? ((Range.LongRange) time).getMin() : (Long) time)));
-        }
-
-        //创建或单个查询时调用本方法
-        @Override
-        public String getTable(String table, LoginRecord bean) {
-            return getTable(table, 0, bean.getCreateTime());
-        }
-
-        //根据主键ID查询单个记录时调用本方法
-        @Override
-        public String getTable(String table, Serializable primary) {
-            String id = (String) primary;
-            return getTable(table, 0, Long.parseLong(id.substring(0, 9), 36));
-        }
-
-        private String getTable(String table, int day, long createTime) { //day为0或yyyyMMdd格式数据
-            int pos = table.indexOf('.');
-            String year = day > 0 ? String.valueOf(day / 10000) : String.format(yearformat, createTime); //没有day取createTime
-            return "platf_login_" + year + "." + table.substring(pos + 1) + "_" + (day > 0 ? day : String.format(dayformat, createTime));
-        }
-    }
-}
-                
-

         如上范例,用户登陆记录的分表分库策略为一年一个库,一个库中365张表,每天一个表。为了分表策略的三个接口均得到实现,需要对主键ID的生成规则进行一定的设计。常见的场景是查询单个用户的登录列表。上面的范例就无法满足查询单个用户的登录信息需求,而分表策略又只能根据一种规则生成,因此需要按用户维度存在另外一张表中。

-
@DistributeTable(strategy = LoginUserRecord.TableStrategy.class)
-public class LoginUserRecord extends BaseEntity {
-
-    @Id
-    @Column(comment = "记录ID; 值=userid+'-'+UUID")
-    private String seqid = ""; //记录ID; 值=userid+'-'+UUID
-
-    @Column(updatable = false, comment = "C端用户ID")
-    private long userid; //C端用户ID
-
-    @Column(comment = "LoginRecord主键")
-    private String loginid = ""; //LoginRecord主键
-
-    @Column(updatable = false, comment = "创建时间")
-    private long createTime; //创建时间
-
-    /** 以下省略getter setter方法 */
-
-    public static class TableStrategy implements DistributeTableStrategy<LoginUserRecord> {
-
-        @Override
-        public String getTable(String table, LoginUserRecord bean) {
-            return getTable(table, bean.getUserid());
-        }
-
-        @Override
-        public String getTable(String table, FilterNode node) {
-            Serializable id = node.findValue("userid");
-            if (id != null) return getTable(table, id);
-            return getHashTable(table, (Integer) node.findValue("#hash"));
-        }
-
-        @Override
-        public String getTable(String table, Serializable primary) {
-            String id = (String) primary;
-            return getHashTable(table, (int) (Long.parseLong(id.substring(0, id.indexOf('-'))) % 100));
-        }
-
-        private String getHashTable(String table, int hash) {
-            int pos = table.indexOf('.');
-            return "platf_login." + table.substring(pos + 1) + "_" + (hash > 9 ? hash : ("0" + hash));
-        }
-
-    }
-}
-                
-

         如上,表LoginUserRecord只存储用户ID与登录信息ID的关联关系,以用户ID取模100进行hash存储,获取用户登录列表时,先查询LoginUserRecord一页的数据,再根据loginid查询LoginRecord实体。常见的分表策略是时间和主键hash,例如用户信息表采用主键hash分表:

-
@DistributeTable(strategy = UserDetail.TableStrategy.class)
-public class UserDetail extends BaseEntity {
-
-    @Id
-    private long userid; //用户ID
-
-    @Column(length = 64, comment = "用户昵称")
-    private String userName = ""; //用户昵称
-
-    @Column(length = 32, comment = "手机号码")
-    private String mobile = ""; //手机号码
-
-    @Column(length = 64, comment = "密码")
-    @ConvertColumn(ignore = true, type = ConvertType.ALL)
-    private String password = ""; //密码
-
-    @Column(length = 128, comment = "备注")
-    private String remark = ""; //备注
-
-    @Column(updatable = false, comment = "创建时间")
-    private long createTime; //创建时间
-
-    /** 以下省略getter setter方法 */
-    
-
-    public static class TableStrategy implements DistributeTableStrategy<UserDetail> {
-
-        @Override
-        public String getTable(String table, UserDetail bean) {
-            return getTable(table, bean.getUserid());
-        }
-
-        @Override
-        public String getTable(String table, FilterNode node) {
-            Serializable id = node.findValue("userid");
-            if (id != null) return getTable(table, id);
-            return getHashTable(table, (Integer) node.findValue("#hash"));
-        }
-
-        @Override
-        public String getTable(String table, Serializable userid) {
-            return getHashTable(table, (int) (((Long) userid) % 100));
-        }
-
-        private String getHashTable(String table, int hash) {
-            int pos = table.indexOf('.');
-            return "platf_user." + table.substring(pos + 1) + "_" + (hash > 9 ? hash : ("0" + hash));
-        }
-
-    }
-}
-                

         如上,用户表以userid取模100进行hash分表,若需要提供根据手机号查询单个用户信息,则需要另外存在一个用户ID对应手机号码的关系表,同样可以以手机号后两位数字为hash存储。

- -

CacheSource 入门

-

         CacheSource同Memcached类似,像一个带有过期功能地Map容器,存放key-value数据。常见的使用场景就是存放HTTP的Session信息。Redkale把用户会话信息数据当做业务数据处理,而不是接入层的数据。WebSocket的连接态数据也是用CacheSource存储。key为WebSocket的groupid,value为WebSocket服务端节点的IP地址列表。

-
public class UserService implements Service {
-
-    //用户简单信息缓存
-    private final Map<Integer, UserInfo> users = new ConcurrentHashMap<>();
-
-    //使用CacheSource必须要指明泛型
-    @Resource(name = "usersessions")
-    protected CacheSource sessions;
-
-    //登录
-    public RetResult<UserInfo> login(LoginBean bean) { //bean.sessionid 在接入层进行赋值
-        UserInfo user = null;
-        // 登陆逻辑 user = ...
-        users.put(user.getUserid(), user);
-        sessions.setLong(600, bean.getSessionid(), user.getUserid()); //session过期时间设置为10分钟
-        return new RetResult<>(user);
-    }
-
-    //获取当前用户信息
-    public UserInfo current(String sessionid) { //给HTTP的BaseServlet用
-        Long userid = sessions.getexLong(sessionid);
-        return userid == null ? null : users.get(userid.intValue());
-    }
-
-    //注销
-    public void logout(String sessionid) {
-        sessions.remove(sessionid);
-    }
-}
-

        以上是个简单的范例,用于用户模块存放sessionid。

- -

source.properties 配置说明

-
-# CacheSource   @Resource(name="usersession")
-# type可以不用设置,框架会根据url判断使用哪个CacheSource实现类
-redkale.cachesource[usersession].type = org.redkalex.cache.redis.RedisCacheSource
-# 最大连接数
-redkale.cachesource[usersession].maxconns = 16
-# 节点地址
-redkale.cachesource[usersession].node[0].url = redis://127.0.0.1:6363
-# 节点密码
-redkale.cachesource[usersession].node[0].password = 12345678
-# 节点db
-redkale.cachesource[usersession].node[0].db = 0
-
-
-
-# DataSource   @Resource(name="platf")
-# type可以不用设置,框架会根据url判断使用哪个DataSource实现类,默认值: org.redkale.source.DataJdbcSource
-redkale.datasource[platf].type = org.redkale.source.DataJdbcSource
-# 是否开启缓存(标记为@Cacheable的Entity类),值目前只支持两种: ALL: 所有开启缓存。 NONE: 关闭所有缓存, 非NONE字样统一视为ALL
-redkale.datasource[platf].cachemode = ALL
-# 是否自动建表当表不存在的时候, 目前只支持mysql、postgres, 默认为false
-redkale.datasource[platf].table-autoddl = false
-# 用户
-redkale.datasource[platf].user = root
-# 密码
-redkale.datasource[platf].password = 12345678
-# 多个URL用;隔开,如分布式SearchSource需要配多个URL
-redkale.datasource[platf].url = jdbc:mysql://127.0.0.1:3306/platf?allowPublicKeyRetrieval=true&amp;rewriteBatchedStatements=true&amp;serverTimezone=UTC&amp;characterEncoding=utf8
-# 最大连接数,默认值:CPU数
-redkale.datasource[platf].maxconns = 16
-# 包含的SQL模板,相当于反向LIKE,不同的JDBC驱动的SQL语句不一样,Redkale内置了MySQL的语句
-redkale.datasource[platf].contain-sqltemplate = LOCATE(${keystr}, ${column}) > 0
-# 包含的SQL模板,相当于反向LIKE,不同的JDBC驱动的SQL语句不一样,Redkale内置了MySQL的语句
-redkale.datasource[platf].notcontain-sqltemplate = LOCATE(${keystr}, ${column}) = 0
-# 复制表结构的SQL模板,Redkale内置了MySQL的语句
-redkale.datasource[platf].tablenotexist-sqlstates = 42000;42S02
-# 复制表结构的SQL模板,Redkale内置了MySQL的语句
-redkale.datasource[platf].tablecopy-sqltemplate = CREATE TABLE IF NOT EXISTS ${newtable} LIKE ${oldtable}
-
-
-# DataSource 读写分离
-redkale.datasource[platf].read.url = jdbc:mysql://127.0.0.1:3306/platf_r?allowPublicKeyRetrieval=true&amp;rewriteBatchedStatements=true&amp;serverTimezone=UTC&amp;characterEncoding=utf8
-redkale.datasource[platf].read.user = root
-redkale.datasource[platf].read.password = 12345678
-
-redkale.datasource[platf].write.url = jdbc:mysql://127.0.0.1:3306/platf_w?allowPublicKeyRetrieval=true&amp;rewriteBatchedStatements=true&amp;serverTimezone=UTC&amp;characterEncoding=utf8
-redkale.datasource[platf].write.user = root
-redkale.datasource[platf].write.password = 12345678
-
- - - -
- - - - diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css deleted file mode 100644 index e7c72f2d7..000000000 --- a/stylesheets/stylesheet.css +++ /dev/null @@ -1,1043 +0,0 @@ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} - -* { - box-sizing: border-box; -} - -body { - padding: 0; - margin: 0; - font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 1.5; - color: #606c71; -} - -a { - color: #1e6bb8; - text-decoration: none; -} - -.anchor { - color: #90139E; -} - -a:hover { - text-decoration: none; -} - -.btn { - display: inline-block; - margin-top: 1rem; - margin-bottom: 1rem; - color: rgba(255, 255, 255, 0.7); - background-color: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); - border-style: solid; - border-width: 1px; - border-radius: 0.3rem; - transition: color 0.2s, background-color 0.2s, border-color 0.2s; -} - -.btn + .btn { - margin-left: 1rem; -} - -.btn:hover { - color: rgba(255, 255, 255, 0.8); - text-decoration: none; - background-color: rgba(255, 255, 255, 0.2); - border-color: rgba(255, 255, 255, 0.3); -} - - -@media screen and (min-width: 80em) { - .btn { - padding: 0.75rem 1rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 80em) { - .btn { - margin-top: 0; - padding: 0.4rem 0.6rem; - font-size: 0.95rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .btn { - padding: 0.6rem 0.9rem; - font-size: 0.9rem; - } -} - -@media screen and (max-width: 42em) { - .btn { - width: 43%; - margin-top: 0.4rem; - margin-bottom: 0.4rem; - margin-left: 1em; - padding: 0.5rem; - font-size: 0.9rem; - } - - .btn + .btn { - margin-top: 0.4rem; - margin-bottom: 0.4rem; - margin-left: 1em; - } -} - -.page-header { - color: #fff; - text-align: center; - /* background-image: linear-gradient(120deg, #155799, #159957); */ - background-image: linear-gradient(350deg, #330335, #AB5D7C); -} - -@media screen and (min-width: 64em) { - .page-header { - padding: 1rem 1rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .page-header { - padding: 3rem 3rem; - } -} - -@media screen and (max-width: 42em) { - .page-header { - padding: 2rem 2rem; - } -} - -.project-name { - display: inline-block; - margin-top: 0; - padding-left: 2.4rem; - text-decoration: none; - text-decoration-color: #FFFFFF; - background: url(/logo_96.png) no-repeat left center ; - background-size: 1.6em; - letter-spacing: 0; - text-shadow: 0px 1px 0px #999, 0px 1px 0px #888, 0px 2px 0px #777, 0px 2px 0px #666, 0px 3px 0px #555, 0px 3px 0px #444, 0px 4px 0px #333, 0px 4px 7px #001135; - font-style: italic; - color: #E6E6E6; - width: 18rem; -} - -@media screen and (min-width: 90em) { - .project-name { - margin-left:-2.0rem; - font-size: 3.0rem; - padding-left: 4.0rem; - width: 22rem; - } -} - -@media screen and (min-width: 80em) and (max-width: 90em) { - .project-name { - font-size: 2.4rem; - padding-left: 2.2rem; - width: 16rem; - } -} - -@media screen and (min-width: 64em) and (max-width: 80em) { - .project-name { - margin-left:0.2rem; - font-size: 1.8rem; - background-size: 2.0em; - width: 12rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-name { - font-size: 2.25rem; - } -} - -@media screen and (max-width: 42em) { - .project-name { - font-size: 1.75rem; - } -} - -.project-tagline { - margin-bottom: 2rem; - font-weight: normal; - opacity: 0.7; -} - -@media screen and (min-width: 80em) { - .project-tagline { - display: none; - font-size: 1.25rem; - } -} - -@media screen and (min-width: 64em) and (max-width: 80em) { - .project-tagline { - font-size: 1.25rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .project-tagline { - font-size: 1.15rem; - } -} - -@media screen and (max-width: 42em) { - .project-tagline { - font-size: 1rem; - - } -} - -.main-content :first-child { - margin-top: 0; -} - -.main-content img { - max-width: 100%; -} - -.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 { - margin-top: 2rem; - margin-bottom: 1rem; - font-weight: normal; - /* color: #159957; */ - color: #90139E; -} - -.main-content p { - margin-bottom: 1em; -} - -.main-content code { - padding: 2px 4px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 0.9rem; - color: #383e41; - background-color: #f3f6fa; - border-radius: 0.3rem; -} - -.main-content pre { - padding: 0.8rem; - margin-top: 0; - margin-bottom: 1rem; - font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace; - color: #567482; - word-wrap: normal; - background-color: #f3f6fa; - border: solid 1px #dce6f0; - border-radius: 0.3rem; -} - -.main-content pre > code { - padding: 0; - margin: 0; - font-size: 0.9rem; - color: #567482; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; -} - -.main-content .highlight { - margin-bottom: 1rem; -} - -.main-content .highlight pre { - margin-bottom: 0; - word-break: normal; -} - -.main-content .highlight pre, .main-content pre { - padding: 0.8rem; - overflow: auto; - font-size: 0.9rem; - line-height: 1.45; - border-radius: 0.3rem; -} - -.main-content pre code, .main-content pre tt { - display: inline; - max-width: initial; - padding: 0; - margin: 0; - overflow: initial; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; -} - -.main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after { - content: normal; -} - -.main-content ul, .main-content ol { - margin-top: 0; -} - -.main-content blockquote { - padding: 0 1rem; - margin-left: 0; - color: #819198; - border-left: 0.3rem solid #dce6f0; -} - -.main-content blockquote > :first-child { - margin-top: 0; -} - -.main-content blockquote > :last-child { - margin-bottom: 0; -} - -.main-content table { - display: block; - width: 100%; - overflow: auto; - word-break: normal; - word-break: keep-all; -} - -.main-content table th { - font-weight: bold; -} - -.main-content table th, .main-content table td { - padding: 0.4rem 1.2rem 0.4rem 1.2rem; - border: 1px solid #90139E; -} - -.main-content dl { - padding: 0; -} - -.main-content dl dt { - padding: 0; - margin-top: 1rem; - font-size: 1rem; - font-weight: bold; -} - -.main-content dl dd { - padding: 0; - margin-bottom: 1rem; -} - -.main-content hr { - height: 2px; - padding: 0; - margin: 1rem 0; - background-color: #eff0f1; - border: 0; -} - -@media screen and (min-width: 96em) { - .main-content { - max-width: 100rem; - padding: 1rem 1rem; - margin: 0 auto; - font-size: 1.1rem; - } - .main-content table { - width: 60em; - min-width: 60em; - } -} - -@media screen and (min-width: 79em) and (max-width: 96em) { - .main-content { - max-width: 86rem; - padding: 2rem 3rem; - margin: 0 auto; - font-size: 1.1rem; - } -} - -@media screen and (min-width: 64em) and (max-width: 79em) { - .main-content { - max-width: 64rem; - padding: 2rem 6rem; - margin: 0 auto; - font-size: 1.1rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .main-content { - padding: 2rem 4rem; - font-size: 1.1rem; - } -} - -@media screen and (max-width: 42em) { - .main-content { - padding: 2rem 1rem; - font-size: 1rem; - } -} -.art-date { - float: right; - font-size: 0.8rem; - margin-right: 30px; -} - -.art-desc { - font-size: 0.8rem; - padding-left: 30px; -} - -.art-title { - margin-top: 2rem; - margin-bottom: 1rem; - /* color: #159957; */ - color: #90139E; -} - -.art-list { - padding-top: 1rem; - margin-top: 1rem; - padding-left: 20px; - border-top: dashed 1px #eff0f1; -} - -.site-footer { - padding-top: 2rem; - margin-top: 2rem; - border-top: solid 1px #eff0f1; -} - -.site-footer-owner { - display: block; - font-size: 0.9rem; - font-weight: bold; -} - -.site-footer-credits { - color: #819198; -} - -@media screen and (min-width: 64em) { - .site-footer { - font-size: 1rem; - } - .art-list { - font-size: 1rem; - } -} - -@media screen and (min-width: 42em) and (max-width: 64em) { - .site-footer { - font-size: 1rem; - } - .art-list { - font-size: 1rem; - } -} - -@media screen and (max-width: 42em) { - .site-footer { - font-size: 0.9rem; - } - .art-list { - font-size: 0.9rem; - } -} - - -.pl-c /* comment */ { - color: #969896; -} - -.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, -.pl-s .pl-v /* string variable */ { - color: #0086b3; -} - -.pl-e /* entity */, -.pl-en /* entity.name */ { - color: #795da3; -} - -.pl-s .pl-s1 /* string source */, -.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { - color: #333; -} - -.pl-ent /* entity.name.tag */ { - color: #63a35c; -} - -.pl-k /* keyword, storage, storage.type */ { - color: #a71d5d; -} - -.pl-pds /* punctuation.definition.string, string.regexp.character-class */, -.pl-s /* string */, -.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, -.pl-sr /* string.regexp */, -.pl-sr .pl-cce /* string.regexp constant.character.escape */, -.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, -.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { - color: #183691; -} - -.pl-v /* variable */ { - color: #ed6a43; -} - -.pl-id /* invalid.deprecated */ { - color: #b52a1d; -} - -.pl-ii /* invalid.illegal */ { - background-color: #b52a1d; - color: #f8f8f8; -} - -.pl-sr .pl-cce /* string.regexp constant.character.escape */ { - color: #63a35c; - font-weight: bold; -} - -.pl-ml /* markup.list */ { - color: #693a17; -} - -.pl-mh /* markup.heading */, -.pl-mh .pl-en /* markup.heading entity.name */, -.pl-ms /* meta.separator */ { - color: #1d3e81; - font-weight: bold; -} - -.pl-mq /* markup.quote */ { - color: #008080; -} - -.pl-mi /* markup.italic */ { - color: #333; - font-style: italic; -} - -.pl-mb /* markup.bold */ { - color: #333; - font-weight: bold; -} - -.pl-md /* markup.deleted, meta.diff.header.from-file */ { - background-color: #ffecec; - color: #bd2c00; -} - -.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { - background-color: #eaffea; - color: #55a532; -} - -.pl-mdr /* meta.diff.range */ { - color: #795da3; - font-weight: bold; -} - -.pl-mo /* meta.output */ { - color: #1d3e81; -} - - - -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #17A05D } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ -.highlight .k { color: #0000FF; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ -.highlight .cm { color: #17A05D } /* Comment.Multiline */ -.highlight .cp { color: #17A05D } /* Comment.Preproc */ -.highlight .c1 { color: #17A05D } /* Comment.Single */ -.highlight .cs { color: #17A05D } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ -.highlight .kc { color: #0000FF; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #0000FF; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #0000FF; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #0000FF; font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { color: #0000FF; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #0000FF; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #666666 } /* Literal.Number */ -.highlight .s { color: #FF00FF } /* Literal.String */ -.highlight .na { color: #666666 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #000080 } /* Name.Class */ -.highlight .no { color: #880000 } /* Name.Constant */ -.highlight .nd { color: #AA22FF } /* Name.Decorator */ -.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #000000 } /* Name.Function */ -.highlight .nl { color: #A0A000 } /* Name.Label */ -.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #0000FF; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mb { color: #666666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666666 } /* Literal.Number.Float */ -.highlight .mh { color: #666666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666666 } /* Literal.Number.Oct */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .sd { color: #BA2121 } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .il { color: #BA2121 } /* Literal.Number.Integer.Long */ - - - - diff --git a/watch.html b/watch.html deleted file mode 100644 index 766ea6eda..000000000 --- a/watch.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - Redkale(红菜苔)--基于Java 8全新的微服务开源框架 - Redkale官网 - - - - - - - - -
-

Watch 组件介绍

- -

敬请期待……
-

- - - -
- - - -