diff --git a/README.md b/README.md index 93456e62a..eb3129a7b 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,27 @@ -

项目介绍

+项目介绍

      Redkale (中文名: 红菜苔,一种湖北特产蔬菜) 是基于Java 11全新的微服务框架, 包含HTTP、WebSocket、TCP/UDP、数据序列化、数据缓存、依赖注入等功能。 本框架致力于简化集中式和微服务架构的开发,在增强开发敏捷性的同时保持高性能。

RedKale 有如下主要特点:
    -
  1. 大量使用Java 8新特性(接口默认值、Stream、Lambda、JDk8内置的ASM等)
  2. +
  3. 大量使用Java 8+新特性(接口默认值、Stream、Lambda、内置的ASM、HttpClient等)
  4. 提供HTTP服务,同时内置JSON功能与限时缓存功能
  5. TCP层完全使用NIO,并统一TCP与UDP的接口换
  6. 提供分布式与集中式部署的无缝切换
  7. 提供类似JPA功能,包含数据缓存自动同步、分表分库与简洁的数据层操作接口
  8. 可以动态修改已依赖注入的资源
  9. -
+ Redkale 设计理念

-       作为一个全新的微服务框架,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的主导思维。 +       作为一个全新的微服务框架,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服务不支持JSP, JSP其实算是一个落后的技术,现在是一个多样化终端的时代,终端不只局限于桌面程序和PC浏览器,还有原生App、混合式App、微信端、移动H5、提供第三方接口等各种形式的终端,这些都不是JSP能方便兼顾的,而HTTP+JSON作为通用性接口可以避免重复开发,模版引擎的功能加上各种强大的JS框架足以取代JSP。Redkale在功能上做了筛选,不会为了迎合主流而提供,而是以良好的设计思想为指导。这是Redkale的主导思维。

-      
详情请访问:    https://redkale.org
+      详情请访问:    https://redkale.org -      
基本文档:    https://redkale.org/articles.html
+      基本文档:    https://redkale.org/articles.html -      
欢迎加入Redkale QQ群: 527523235
+      欢迎加入Redkale QQ群: 527523235   diff --git a/pom.xml b/pom.xml index d7a917351..c83f83d74 100644 --- a/pom.xml +++ b/pom.xml @@ -15,10 +15,10 @@ 11 5.7.0 - 3.2.0 - 3.8.0 - 3.0.0-M5 - 3.0.0-M5 + 3.2.2 + 3.9.0 + 3.0.0-M6 + 3.0.0-M6 diff --git a/src/main/java/org/redkale/boot/Application.java b/src/main/java/org/redkale/boot/Application.java index 9b2ec0242..0ef5bef7f 100644 --- a/src/main/java/org/redkale/boot/Application.java +++ b/src/main/java/org/redkale/boot/Application.java @@ -732,7 +732,10 @@ public final class Application { if (key.startsWith("redkale.datasource[") || key.startsWith("redkale.cachesource[")) { sourceProperties.put(key, value); } else if (key.startsWith("system.property.")) { - System.setProperty(key.substring("system.property.".length()), value); + String propName = key.substring("system.property.".length()); + if (System.getProperty(propName) == null) { //命令行传参数优先级高 + System.setProperty(propName, value); + } } else if (key.startsWith("mimetype.property.")) { MimeType.add(key.substring("mimetype.property.".length()), value); } else if (key.startsWith("property.")) { diff --git a/src/main/java/org/redkale/boot/LoggingFileHandler.java b/src/main/java/org/redkale/boot/LoggingFileHandler.java index 15a981d2d..d5017c8fc 100644 --- a/src/main/java/org/redkale/boot/LoggingFileHandler.java +++ b/src/main/java/org/redkale/boot/LoggingFileHandler.java @@ -50,6 +50,7 @@ public class LoggingFileHandler extends LoggingBaseHandler { public LoggingConsoleHandler() { super(); + setFormatter(new LoggingFormater()); configure(); } diff --git a/src/main/java/org/redkale/convert/Convert.java b/src/main/java/org/redkale/convert/Convert.java index 6d4041728..942eb06bc 100644 --- a/src/main/java/org/redkale/convert/Convert.java +++ b/src/main/java/org/redkale/convert/Convert.java @@ -42,6 +42,13 @@ public abstract class Convert { return writer; } + protected S fieldFunc(S writer, BiFunction mapFieldFunc, BiFunction objFieldFunc, Function objExtFunc) { + writer.mapFieldFunc = mapFieldFunc; + writer.objFieldFunc = objFieldFunc; + writer.objExtFunc = objExtFunc; + return writer; + } + public abstract Convert newConvert(final BiFunction objFieldFunc); public abstract Convert newConvert(final BiFunction objFieldFunc, Function objExtFunc); diff --git a/src/main/java/org/redkale/convert/ConvertFactory.java b/src/main/java/org/redkale/convert/ConvertFactory.java index 14e9a8228..96c982b88 100644 --- a/src/main/java/org/redkale/convert/ConvertFactory.java +++ b/src/main/java/org/redkale/convert/ConvertFactory.java @@ -61,6 +61,8 @@ public abstract class ConvertFactory { private final Set skipIgnores = new HashSet(); + final Set ignoreMapColumns = new HashSet(); + //key:需要屏蔽的字段;value:排除的字段名 private final ConcurrentHashMap> ignoreAlls = new ConcurrentHashMap(); @@ -707,7 +709,9 @@ public abstract class ConvertFactory { if (set == null) { ignoreAlls.put(type, new HashSet<>(Arrays.asList(excludeColumns))); } else { - set.addAll(Arrays.asList(excludeColumns)); + synchronized (set) { + set.addAll(Arrays.asList(excludeColumns)); + } } } @@ -716,17 +720,47 @@ public abstract class ConvertFactory { if (set == null) { ignoreAlls.put(type, new HashSet<>(excludeColumns)); } else { - set.addAll(new ArrayList(excludeColumns)); + synchronized (set) { + set.addAll(new ArrayList(excludeColumns)); + } } } public final void register(final Class type, boolean ignore, String... columns) { + if (type == Map.class) { + synchronized (ignoreMapColumns) { + if (ignore) { + for (String column : columns) { + ignoreMapColumns.add(column); + } + } else { + for (String column : columns) { + ignoreMapColumns.remove(column); + } + } + } + return; + } for (String column : columns) { register(type, column, new ConvertColumnEntry(column, ignore)); } } public final void register(final Class type, boolean ignore, Collection columns) { + if (type == Map.class) { + synchronized (ignoreMapColumns) { + if (ignore) { + for (String column : columns) { + ignoreMapColumns.add(column); + } + } else { + for (String column : columns) { + ignoreMapColumns.remove(column); + } + } + } + return; + } for (String column : columns) { register(type, column, new ConvertColumnEntry(column, ignore)); } diff --git a/src/main/java/org/redkale/convert/MapEncoder.java b/src/main/java/org/redkale/convert/MapEncoder.java index 3da1c978b..c80e1d31c 100644 --- a/src/main/java/org/redkale/convert/MapEncoder.java +++ b/src/main/java/org/redkale/convert/MapEncoder.java @@ -7,7 +7,8 @@ package org.redkale.convert; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.Map; +import java.util.*; +import java.util.function.BiFunction; /** * Map的序列化操作类 @@ -32,6 +33,8 @@ public class MapEncoder implements Encodeable> { protected final Object lock = new Object(); + protected final Set ignoreMapColumns; + public MapEncoder(final ConvertFactory factory, final Type type) { this.type = type; try { @@ -43,6 +46,9 @@ public class MapEncoder implements Encodeable> { this.keyEncoder = factory.getAnyEncoder(); this.valueEncoder = factory.getAnyEncoder(); } + synchronized (factory.ignoreMapColumns) { + this.ignoreMapColumns = factory.ignoreMapColumns.isEmpty() ? null : new HashSet<>(factory.ignoreMapColumns); + } } finally { inited = true; synchronized (lock) { @@ -74,11 +80,15 @@ public class MapEncoder implements Encodeable> { } } } + Set ignoreColumns = this.ignoreMapColumns; + BiFunction mapFieldFunc = (BiFunction) out.mapFieldFunc; if (out.writeMapB(values.size(), (Encodeable) keyEncoder, (Encodeable) valueEncoder, value) < 0) { boolean first = true; for (Map.Entry en : values.entrySet()) { + if (ignoreColumns != null && ignoreColumns.contains(en.getKey())) continue; + V v = mapFieldFunc == null ? en.getValue() : mapFieldFunc.apply(en.getKey(), en.getValue()); if (!first) out.writeArrayMark(); - writeMemberValue(out, member, en.getKey(), en.getValue(), first); + writeMemberValue(out, member, en.getKey(), v, first); if (first) first = false; } } diff --git a/src/main/java/org/redkale/convert/Writer.java b/src/main/java/org/redkale/convert/Writer.java index 052321df8..42b1d91f1 100644 --- a/src/main/java/org/redkale/convert/Writer.java +++ b/src/main/java/org/redkale/convert/Writer.java @@ -25,6 +25,9 @@ public abstract class Writer { //convertTo时是否以指定Type的ObjectEncoder进行处理 protected Type specify; + //对某个key值进行动态处理,仅供MapEncoder使用 + protected BiFunction mapFieldFunc; + //对某个字段值进行动态处理 protected BiFunction objFieldFunc; diff --git a/src/main/java/org/redkale/convert/json/JsonConvert.java b/src/main/java/org/redkale/convert/json/JsonConvert.java index 3a09676a7..5453841b5 100644 --- a/src/main/java/org/redkale/convert/json/JsonConvert.java +++ b/src/main/java/org/redkale/convert/json/JsonConvert.java @@ -58,16 +58,25 @@ public class JsonConvert extends TextConvert { } @Override - public JsonConvert newConvert(final BiFunction fieldFunc) { - return newConvert(fieldFunc, null); + public JsonConvert newConvert(final BiFunction objFieldFunc) { + return newConvert(objFieldFunc, null); } @Override - public JsonConvert newConvert(final BiFunction fieldFunc, Function objExtFunc) { + public JsonConvert newConvert(final BiFunction objFieldFunc, Function objExtFunc) { return new JsonConvert(getFactory(), tiny) { @Override protected S configWrite(S writer) { - return fieldFunc(writer, fieldFunc, objExtFunc); + return fieldFunc(writer, objFieldFunc, objExtFunc); + } + }; + } + + public JsonConvert newConvert(BiFunction mapFieldFunc, final BiFunction objFieldFunc, Function objExtFunc) { + return new JsonConvert(getFactory(), tiny) { + @Override + protected S configWrite(S writer) { + return fieldFunc(writer, mapFieldFunc, objFieldFunc, objExtFunc); } }; } diff --git a/src/main/java/org/redkale/mq/HttpMessageRequest.java b/src/main/java/org/redkale/mq/HttpMessageRequest.java index 87fbaffe6..04644f5d3 100644 --- a/src/main/java/org/redkale/mq/HttpMessageRequest.java +++ b/src/main/java/org/redkale/mq/HttpMessageRequest.java @@ -34,8 +34,10 @@ public class HttpMessageRequest extends HttpRequest { return this; } - public void setRequestURI(String uri) { + @Override + public HttpMessageRequest setRequestURI(String uri) { this.requestURI = uri; + return this; } @Override diff --git a/src/main/java/org/redkale/net/PrepareServlet.java b/src/main/java/org/redkale/net/PrepareServlet.java index cf713aa28..32023a36c 100644 --- a/src/main/java/org/redkale/net/PrepareServlet.java +++ b/src/main/java/org/redkale/net/PrepareServlet.java @@ -62,6 +62,7 @@ public abstract class PrepareServlet newservlets = new HashSet<>(servlets); newservlets.remove(servlet); this.servlets = newservlets; + doAfterRemove(servlet); } } @@ -95,8 +96,9 @@ public abstract class PrepareServlet newmappings = new HashMap<>(mappings); - newmappings.remove(key); + S s = newmappings.remove(key); this.mappings = newmappings; + doAfterRemove(s); } } } @@ -112,9 +114,13 @@ public abstract class PrepareServlet { this.jsonConvert = context.getJsonConvert(); } + protected Request(Request request) { + this.context = request.context; + this.bsonConvert = request.bsonConvert; + this.jsonConvert = request.jsonConvert; + this.createTime = request.createTime; + this.keepAlive = request.keepAlive; + this.pipelineIndex = request.pipelineIndex; + this.pipelineCount = request.pipelineCount; + this.pipelineOver = request.pipelineOver; + this.hashid = request.hashid; + this.channel = request.channel; + } + protected Request copyHeader() { return null; } diff --git a/src/main/java/org/redkale/net/http/HttpFilter.java b/src/main/java/org/redkale/net/http/HttpFilter.java index 40a72bacd..baacbd77f 100644 --- a/src/main/java/org/redkale/net/http/HttpFilter.java +++ b/src/main/java/org/redkale/net/http/HttpFilter.java @@ -21,4 +21,24 @@ public abstract class HttpFilter extends Filter[] forbidURIPredicates; //禁用的URL的Predicate, 必须与 forbidURIMaps 保持一致 + private HttpServlet lastRunServlet; + private List removeHttpServlet(final Predicate predicateEntry, final Predicate> predicateFilter) { List servlets = new ArrayList<>(); synchronized (allMapStrings) { @@ -91,6 +93,7 @@ public class HttpPrepareServlet extends PrepareServlet { } } + protected HttpRequest setMethod(String method) { + this.method = method; + this.getmethod = "GET".equalsIgnoreCase(method); + return this; + } + + protected HttpRequest setRequestURI(String requestURI) { + this.requestURI = requestURI; + return this; + } + + protected HttpRequest setRemoteAddr(String remoteAddr) { + this.remoteAddr = remoteAddr; + return this; + } + + protected HttpRequest setParameter(String name, String value) { + this.params.put(name, value); + return this; + } + + protected HttpRequest setHeader(String name, String value) { + this.headers.put(name, value); + return this; + } + protected static String toDecodeString(ByteArray array, int offset, int len, final Charset charset) { byte[] content = array.content(); int start = offset; @@ -2471,14 +2497,14 @@ public class HttpRequest extends Request { } /** - * 获取翻页对象 同 getFlipper("flipper", needcreate, 0); + * 获取翻页对象 同 getFlipper("flipper", autoCreate, 0); * - * @param needcreate 无参数时是否创建新Flipper对象 + * @param autoCreate 无参数时是否创建新Flipper对象 * * @return Flipper翻页对象 */ - public org.redkale.source.Flipper getFlipper(boolean needcreate) { - return getFlipper(needcreate, 0); + public org.redkale.source.Flipper getFlipper(boolean autoCreate) { + return getFlipper(autoCreate, 0); } /** @@ -2493,44 +2519,46 @@ public class HttpRequest extends Request { } /** - * 获取翻页对象 同 getFlipper("flipper", needcreate, maxLimit) + * 获取翻页对象 同 getFlipper("flipper", autoCreate, maxLimit) * - * @param needcreate 无参数时是否创建新Flipper对象 + * @param autoCreate 无参数时是否创建新Flipper对象 * @param maxLimit 最大行数, 小于1则值为Flipper.DEFAULT_LIMIT * * @return Flipper翻页对象 */ - public org.redkale.source.Flipper getFlipper(boolean needcreate, int maxLimit) { - return getFlipper("flipper", needcreate, maxLimit); + public org.redkale.source.Flipper getFlipper(boolean autoCreate, int maxLimit) { + return getFlipper("flipper", autoCreate, maxLimit); } /** - * 获取翻页对象 https://redkale.org/pipes/users/list/offset:0/limit:20/sort:createtime%20ASC
- * https://redkale.org/pipes/users/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
- * 以上两种接口都可以获取到翻页对象 + * 获取翻页对象 https://redkale.org/pipes/users/list?flipper={'offset':0,'limit':20, 'sort':'createtime ASC'}
* * * @param name Flipper对象的参数名,默认为 "flipper" - * @param needcreate 无参数时是否创建新Flipper对象 + * @param autoCreate 无参数时是否创建新Flipper对象 * @param maxLimit 最大行数, 小于1则值为Flipper.DEFAULT_LIMIT * * @return Flipper翻页对象 */ - public org.redkale.source.Flipper getFlipper(String name, boolean needcreate, int maxLimit) { + public org.redkale.source.Flipper getFlipper(String name, boolean autoCreate, int maxLimit) { org.redkale.source.Flipper flipper = getJsonParameter(org.redkale.source.Flipper.class, name); if (flipper == null) { - if (maxLimit < 1) maxLimit = org.redkale.source.Flipper.DEFAULT_LIMIT; - int limit = getRequstURIPath("limit:", 0); - int offset = getRequstURIPath("offset:", 0); - String sort = getRequstURIPath("sort:", ""); - if (limit > 0) { - if (limit > maxLimit) limit = maxLimit; - flipper = new org.redkale.source.Flipper(limit, offset, sort); - } +// if (maxLimit < 1) maxLimit = org.redkale.source.Flipper.DEFAULT_LIMIT; +// String limitstr = getParameter("limit"); +// if (limitstr != null && !limitstr.isEmpty()) { +// String offsetstr = getParameter("offset"); +// if (offsetstr != null && !offsetstr.isEmpty()) { +// int limit = Integer.parseInt(limitstr); +// int offset = Integer.parseInt(offsetstr); +// String sort = getParameter("sort"); +// if (limit > maxLimit) limit = maxLimit; +// flipper = new org.redkale.source.Flipper(limit, offset, sort); +// } +// } } else if (flipper.getLimit() < 1 || (maxLimit > 0 && flipper.getLimit() > maxLimit)) { flipper.setLimit(maxLimit); } - if (flipper != null || !needcreate) return flipper; + if (flipper != null || !autoCreate) return flipper; if (maxLimit < 1) maxLimit = org.redkale.source.Flipper.DEFAULT_LIMIT; return new org.redkale.source.Flipper(maxLimit); } diff --git a/src/main/java/org/redkale/net/http/HttpServlet.java b/src/main/java/org/redkale/net/http/HttpServlet.java index c8de29777..ff0d637a8 100644 --- a/src/main/java/org/redkale/net/http/HttpServlet.java +++ b/src/main/java/org/redkale/net/http/HttpServlet.java @@ -33,6 +33,8 @@ public class HttpServlet extends Servlet public static final int RET_METHOD_ERROR = 1200_0002; + String _actionSimpleMappingUrl; //只给HttpActionServlet使用,_actionSimpleMappingUrl不能包含正则表达式,比如 /json /createRecord, 不能是 /user/** + String _prefix = ""; //当前HttpServlet的path前缀 String _reqtopic; //根据RestService+MQ生成的值 @since 2.5.0 @@ -454,9 +456,12 @@ public class HttpServlet extends Servlet final HttpServlet servlet; - public HttpActionServlet(ActionEntry actionEntry, HttpServlet servlet) { + public HttpActionServlet(ActionEntry actionEntry, HttpServlet servlet, String actionSimpleMappingUrl) { this.action = actionEntry; this.servlet = servlet; + if (actionSimpleMappingUrl != null && !Utility.contains(actionSimpleMappingUrl, '*', '{', '[', '(', '|', '^', '$', '+', '?', '\\')) { + this._actionSimpleMappingUrl = actionSimpleMappingUrl; + } } @Override diff --git a/src/test/java/org/redkale/test/convert/MapIgnoreColumnTest.java b/src/test/java/org/redkale/test/convert/MapIgnoreColumnTest.java new file mode 100644 index 000000000..9f229b779 --- /dev/null +++ b/src/test/java/org/redkale/test/convert/MapIgnoreColumnTest.java @@ -0,0 +1,43 @@ +/* + */ +package org.redkale.test.convert; + +import java.util.*; +import org.junit.jupiter.api.*; +import org.redkale.convert.json.*; + +/** + * + * @author zhangjx + */ +public class MapIgnoreColumnTest { + + private boolean main; + + public static void main(String[] args) throws Throwable { + MapIgnoreColumnTest test = new MapIgnoreColumnTest(); + test.main = true; + test.run(); + } + + @Test + public void run() throws Exception { + Map map = new LinkedHashMap<>(); + map.put("aaa", "123"); + map.put("bbb", List.of(1, 2)); + System.out.println(JsonConvert.root().convertTo(map)); + JsonFactory factory = JsonFactory.create(); + factory.register(Map.class, true, "aaa"); + JsonConvert convert = factory.getConvert(); + String rs = "{\"bbb\":[1,2]}"; + if (!main) Assertions.assertEquals(rs, convert.convertTo(map)); + System.out.println(convert.convertTo(map)); + JsonConvert convert2 = JsonConvert.root().newConvert((k, v) -> { + if ("bbb".equals(k)) return null; + return v; + }, null, null); + if (!main) Assertions.assertEquals("{\"aaa\":\"123\",\"bbb\":null}", convert2.convertTo(map)); + System.out.println(convert2.convertTo(map)); + } + +}