百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Dubbo3.0是如何利用SPI机制来提升框架的扩展性

toyiye 2024-06-28 09:59 15 浏览 0 评论

一、什么是可扩展性

可扩展性是一种设计理念,代表了我们对未来的一种预想,我们希望在现有的架构或设计基础上,当未来某些方面发生变化的时候,我们能够以最小的改动来适应这种变化。

二、可扩展性的优点

可扩展性的优点主要表现模块之间解耦,它符合开闭原则,对扩展开放,对修改关闭。当系统增加新功能时,不需要对现有系统的结构和代码进行修改,仅仅新增一个扩展即可。

三、扩展实现方式

一般来说,系统会采用 Factory、IoC、OSGI 等方式管理扩展(插件)生命周期。考虑到 Dubbo 的适用面,不想强依赖 Spring 等 IoC 容器。 而自己造一个小的 IoC 容器,也觉得有点过度设计,所以选择最简单的 Factory 方式管理扩展(插件)。在 Dubbo 中,所有内部实现和第三方实现都是平等的。

四、Dubbo 扩展的特性

  1. 按需加载。Dubbo 的扩展能力不会一次性实例化所有实现,而是用那个扩展类则实例化那个扩展类,减少资源浪费。
  2. 增加扩展类的 IOC 能力。Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动地完成该依赖对象的注入功能。
  3. 增加扩展类的 AOP 能力。Dubbo 扩展能力会自动地发现扩展类的包装类,完成包装类的构造,增强扩展类的功能。
  4. 具备动态选择扩展实现的能力。Dubbo 扩展会基于参数,在运行时动态选择对应的扩展类,提高了 Dubbo 的扩展能力。
  5. 可以对扩展实现进行排序。能够基于用户需求,指定扩展实现的执行顺序。
  6. 提供扩展点的 Adaptive 能力。该能力可以使得一些扩展类在 consumer 端生效,一些扩展类在 provider 端生效。

五、Dubbo 扩展加载流程

主要步骤为 4 个:

  • 读取并解析配置文件
  • 缓存所有扩展实现
  • 基于用户执行的扩展名,实例化对应的扩展实现
  • 进行扩展实例属性的 IOC 注入以及实例化扩展的包装类,实现 AOP 特性

六、Dubbo 扩展的应用

Dubbo 扩展能力使得 Dubbo 项目很方便地切分成一个一个的子模块,实现热插拔特性。用户完全可以基于自身需求,替换 Dubbo 原生实现,来满足自身业务需求。

七、源码分析

我们从流程的最上层ExtensionAccessor类来进行分析

其中比较核心的方法有3个:

getExtensionLoader:获取扩展加载组件

getExtension:获取扩展实现类对象

getAdaptiveExtension:获取自适应扩展实现类对象

1、获取扩展加载组件

这一块比较重要,我就直接贴代码了,其中最主要的是构建一个实现类,我们就直接跳到createExtensionLoader方法中去

/**
 * 获取接口的ExtensionLoader,在去通过ExtensionLoader获取扩展实现类
 * 第一步:先去缓存获取
 * 第二步:缓存没有则会判断使用范围是否为SELF,是则尝试自己直接创建
 * 第三步:尝试通过父组件获取
 * 第四步:最后都没有获取则直接创建
 * @param type 加了@SPI注解的接口class
 * @param <T>
 * @return
 */
@Override
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    checkDestroyed();
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
            ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    // 1. find in local cache
    /** 按照接口的class类型去获取extensionLoadersMap中的缓存数据 */
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);

    /** 根据接口的class类型去获取接口的使用范围 */
    ExtensionScope scope = extensionScopeMap.get(type);
    if (scope == null) {
        /** 如果为空获取接口class里打的SPI注解,去获取SPI中的使用范围赋值并保存至extensionScopeMap缓存 */
        SPI annotation = type.getAnnotation(SPI.class);
        scope = annotation.scope();
        extensionScopeMap.put(type, scope);
    }

    /** 如果获取出来的ExtensionLoader为空,并且使用范围为SELF */
    if (loader == null && scope == ExtensionScope.SELF) {
        // create an instance in self scope
        /** 构建ExtensionLoader */
        loader = createExtensionLoader0(type);
    }

    // 2. find in parent
    /** 如果loader为空,则说明使用范围不为SELF,上一个判断逻辑没有进去 */
    if (loader == null) {
        /** 判断父组件是否为空,不为空,则依赖父组件进行获取ExtensionLoader */
        if (this.parent != null) {
            loader = this.parent.getExtensionLoader(type);
        }
    }

    // 3. create it
    /** 最后面还是没有则直接创建 */
    if (loader == null) {
        loader = createExtensionLoader(type);
    }

    return loader;
}

在extensionLoadersMap中添加新构建的ExtensionLoader,并获取后返回。

ExtensionLoader初始化方法

2、获取扩展实现类对象

先从缓存中获取实现类的对象,如果为空则调用createExtension方法进行构建

这一块代码是这个里面最重要的一块,这个里面包含了获取类对象,根据类对象获取对象实例,对实例对象进行依赖注入,生成包装类等;

private T createExtension(String name, boolean wrap) {
    /**
     * 根据名称从加载出来的那些实现类里拿出来一个具体的class
     * 根据接口拿到的实现类,在构建实现类的对象注入给接口类型的变量
     * 通过指定的配置文件读取和解析去拿到ExtensionClasses,通过实现类的短名称去获取对应的class对象
     */
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        /** 获取缓存中的数据 */
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            /**
             * 如果缓存没有命中则会去构建一个实现类的对象
             * 其实就是通过反射技术拿到一个对应的实现类的对象实例
             */
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            /** 在根据clazz获取实例对象 */
            instance = (T) extensionInstances.get(clazz);
            /** 对实例对象进行初始化之前的后处理 */
            instance = postProcessBeforeInitialization(instance, name);
            /** 依赖注入:dubbo SPI机制也扮演了类试Spring容器的机制,会托管你的实现类的对象,对其进行依赖注入 */
            injectExtension(instance);
            /** 对实例对象进行初始化之后的后处理 */
            instance = postProcessAfterInitialization(instance, name);
        }

        /** 如果需要对这个实现类的实例进行包装则进行后续处理 */
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    /** 获取Wrapper注解 */
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) ||
                        ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                            !ArrayUtils.contains(wrapper.mismatches(), name));
                    /** 如果需要通过Wrapper进行包装 */
                    if (match) {
                        /** 将instance做一层包装 */
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        /** 进行后处理操作 */
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
            type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

获取类对象

将各种数据缓存到入参extensionClasses的map数据结构中去

数据加载好了之后就会直接返回这个map,再从map中获取数据

再根据类对象获取对象实例,先从缓存中获取,如果为空则调用createExtensionInstance进行构建,构建完成之后放入extensionInstances进行缓存,在从缓存中获取

根据类对象创建的对象实例

获取完实例对象之后再进行依赖注入

依赖注入之后会判断实例对象是否需要进行包装,如果需要包装则将实例对象传入进去,作为属性值在生成一个包装类MockClusterWrapper

3、获取自适应扩展实现类对象

其中会有许多和获取扩展实现类对象一样的代码,我们只分析不同的代码流程

这里我们发现除了获取自适应的扩展类之外,其余的代码和之前都是一样的,实例化,依赖注入,初始化等,所以我们这里就只看getAdaptiveExtensionClass方法了。

这里就会根据一定的规则去通过代码去动态生成相应的类,后面我会贴这个类的所有代码细节,相当的繁琐。我们大致看一眼知道有这么一回事就可以了,在直接将类返回进行实例化,依赖注入等,在进行返回的;

public class AdaptiveClassCodeGenerator {

    private static final Logger logger = LoggerFactory.getLogger(AdaptiveClassCodeGenerator.class);

    private static final String CLASSNAME_INVOCATION = "org.apache.dubbo.rpc.Invocation";

    private static final String CODE_PACKAGE = "package %s;\n";

    private static final String CODE_IMPORTS = "import %s;\n";

    private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";

    private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";

    private static final String CODE_METHOD_ARGUMENT = "%s arg%d";

    private static final String CODE_METHOD_THROWS = "throws %s";

    private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";

    private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";

    private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";

    private static final String CODE_EXT_NAME_NULL_CHECK = "if(extName == null) "
                    + "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n";

    private static final String CODE_INVOCATION_ARGUMENT_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); "
                    + "String methodName = arg%d.getMethodName();\n";


    private static final String CODE_SCOPE_MODEL_ASSIGNMENT = "ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), %s.class);\n";
    private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)scopeModel.getExtensionLoader(%s.class).getExtension(extName);\n";

    private static final String CODE_EXTENSION_METHOD_INVOKE_ARGUMENT = "arg%d";

    private final Class<?> type;

    private String defaultExtName;

    public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
        this.type = type;
        this.defaultExtName = defaultExtName;
    }

    /**
     * test if given type has at least one method annotated with <code>Adaptive</code>
     */
    private boolean hasAdaptiveMethod() {
        return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
    }

    /**
     * generate and return class code
     */
    public String generate() {
        return this.generate(false);
    }

    /**
     * generate and return class code
     * @param sort - whether sort methods
     */
    public String generate(boolean sort) {
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        /** 动态拼接类代码的字符串 */
        StringBuilder code = new StringBuilder();
        /** 根据接口的包名生成对应的包名 */
        code.append(generatePackageInfo());
        /** 生成对应的导入信息 */
        code.append(generateImports());
        /** 生成类声明 */
        code.append(generateClassDeclaration());

        Method[] methods = type.getMethods();
        if (sort) {
            /** 对方法进行排序 */
            Arrays.sort(methods, Comparator.comparing(Method::toString));
        }
        for (Method method : methods) {
            /** 生成对应的方法 */
            code.append(generateMethod(method));
        }
        code.append('}');

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }

    /**
     * generate package info
     */
    private String generatePackageInfo() {
        return String.format(CODE_PACKAGE, type.getPackage().getName());
    }

    /**
     * generate imports
     */
    private String generateImports() {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format(CODE_IMPORTS, ScopeModel.class.getName()));
        builder.append(String.format(CODE_IMPORTS, ScopeModelUtil.class.getName()));
        return builder.toString();
    }

    /**
     * generate class declaration
     */
    private String generateClassDeclaration() {
        return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
    }

    /**
     * generate method not annotated with Adaptive with throwing unsupported exception
     */
    private String generateUnsupported(Method method) {
        return String.format(CODE_UNSUPPORTED, method, type.getName());
    }

    /**
     * get index of parameter with type URL
     */
    private int getUrlTypeIndex(Method method) {
        int urlTypeIndex = -1;
        Class<?>[] pts = method.getParameterTypes();
        for (int i = 0; i < pts.length; ++i) {
            if (pts[i].equals(URL.class)) {
                urlTypeIndex = i;
                break;
            }
        }
        return urlTypeIndex;
    }

    /**
     * generate method declaration
     */
    private String generateMethod(Method method) {
        String methodReturnType = method.getReturnType().getCanonicalName();
        String methodName = method.getName();
        String methodContent = generateMethodContent(method);
        String methodArgs = generateMethodArguments(method);
        String methodThrows = generateMethodThrows(method);
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
    }

    /**
     * generate method arguments
     */
    private String generateMethodArguments(Method method) {
        Class<?>[] pts = method.getParameterTypes();
        return IntStream.range(0, pts.length)
                        .mapToObj(i -> String.format(CODE_METHOD_ARGUMENT, pts[i].getCanonicalName(), i))
                        .collect(Collectors.joining(", "));
    }

    /**
     * generate method throws
     */
    private String generateMethodThrows(Method method) {
        Class<?>[] ets = method.getExceptionTypes();
        if (ets.length > 0) {
            String list = Arrays.stream(ets).map(Class::getCanonicalName).collect(Collectors.joining(", "));
            return String.format(CODE_METHOD_THROWS, list);
        } else {
            return "";
        }
    }

    /**
     * generate method URL argument null check
     */
    private String generateUrlNullCheck(int index) {
        return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index);
    }

    /**
     * generate method content
     */
    private String generateMethodContent(Method method) {
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            return generateUnsupported(method);
        } else {
            int urlTypeIndex = getUrlTypeIndex(method);

            // found parameter in URL type
            if (urlTypeIndex != -1) {
                // Null Point check
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));
            }

            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            boolean hasInvocation = hasInvocationArgument(method);

            code.append(generateInvocationArgumentNullCheck(method));

            code.append(generateExtNameAssignment(value, hasInvocation));
            // check extName == null?
            code.append(generateExtNameNullCheck(value));

            code.append(generateScopeModelAssignment());
            code.append(generateExtensionAssignment());

            // return statement
            code.append(generateReturnAndInvocation(method));
        }

        return code.toString();
    }

    /**
     * generate code for variable extName null check
     */
    private String generateExtNameNullCheck(String[] value) {
        return String.format(CODE_EXT_NAME_NULL_CHECK, type.getName(), Arrays.toString(value));
    }

    /**
     * generate extName assigment code
     */
    private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
        // TODO: refactor it
        String getNameCode = null;
        for (int i = value.length - 1; i >= 0; --i) {
            if (i == value.length - 1) {
                if (null != defaultExtName) {
                    if (!"protocol".equals(value[i])) {
                        if (hasInvocation) {
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        } else {
                            getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                        }
                    } else {
                        getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    }
                } else {
                    if (!"protocol".equals(value[i])) {
                        if (hasInvocation) {
                            getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                        } else {
                            getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                        }
                    } else {
                        getNameCode = "url.getProtocol()";
                    }
                }
            } else {
                if (!"protocol".equals(value[i])) {
                    if (hasInvocation) {
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                        getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                    }
                } else {
                    getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                }
            }
        }

        return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
    }

    /**
     * @return
     */
    private String generateScopeModelAssignment() {
        return String.format(CODE_SCOPE_MODEL_ASSIGNMENT, type.getName());
    }

    private String generateExtensionAssignment() {
        return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), type.getName());
    }

    /**
     * generate method invocation statement and return it if necessary
     */
    private String generateReturnAndInvocation(Method method) {
        String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";

        String args = IntStream.range(0, method.getParameters().length)
                .mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
                .collect(Collectors.joining(", "));

        return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
    }

    /**
     * test if method has argument of type <code>Invocation</code>
     */
    private boolean hasInvocationArgument(Method method) {
        Class<?>[] pts = method.getParameterTypes();
        return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
    }

    /**
     * generate code to test argument of type <code>Invocation</code> is null
     */
    private String generateInvocationArgumentNullCheck(Method method) {
        Class<?>[] pts = method.getParameterTypes();
        return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
                        .mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
                        .findFirst().orElse("");
    }

    /**
     * get value of adaptive annotation or if empty return splitted simple name
     */
    private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
        String[] value = adaptiveAnnotation.value();
        // value is not set, use the value generated from class name as the key
        if (value.length == 0) {
            String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
            value = new String[]{splitName};
        }
        return value;
    }

    /**
     * get parameter with type <code>URL</code> from method parameter:
     * <p>
     * test if parameter has method which returns type <code>URL</code>
     * <p>
     * if not found, throws IllegalStateException
     */
    private String generateUrlAssignmentIndirectly(Method method) {
        Class<?>[] pts = method.getParameterTypes();

        Map<String, Integer> getterReturnUrl = new HashMap<>();
        // find URL getter method
        for (int i = 0; i < pts.length; ++i) {
            for (Method m : pts[i].getMethods()) {
                String name = m.getName();
                if ((name.startsWith("get") || name.length() > 3)
                        && Modifier.isPublic(m.getModifiers())
                        && !Modifier.isStatic(m.getModifiers())
                        && m.getParameterTypes().length == 0
                        && m.getReturnType() == URL.class) {
                    getterReturnUrl.put(name, i);
                }
            }
        }

        if (getterReturnUrl.size() <= 0) {
            // getter method not found, throw
            throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
                    + ": not found url parameter or url attribute in parameters of method " + method.getName());
        }

        Integer index = getterReturnUrl.get("getUrl");
        if (index != null) {
            return generateGetUrlNullCheck(index, pts[index], "getUrl");
        } else {
            Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
            return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
        }
    }

    /**
     * 1, test if argi is null
     * 2, test if argi.getXX() returns null
     * 3, assign url with argi.getXX()
     */
    private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
        // Null point check
        StringBuilder code = new StringBuilder();
        code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
                index, type.getName()));
        code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
                index, method, type.getName(), method));

        code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
        return code.toString();
    }

}

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码