You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/05/28 13:23:36 UTC

[ignite-3] branch main updated: IGNITE-14704 Runtime code generation instead of AOT for "internal" configuration classes. (#122)

This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 653cd76  IGNITE-14704 Runtime code generation instead of AOT for "internal" configuration classes. (#122)
653cd76 is described below

commit 653cd76a2b58587d86f21c9233b6f14bccd01039
Author: ibessonov <be...@gmail.com>
AuthorDate: Fri May 28 16:23:28 2021 +0300

    IGNITE-14704 Runtime code generation instead of AOT for "internal" configuration classes. (#122)
---
 .../schemas/runner/NodeConfigurationSchema.java    |    1 -
 .../processor/internal/AbstractProcessorTest.java  |    6 +-
 .../processor/internal/ConfigSet.java              |   37 +-
 .../processor/internal/ITProcessorTest.java        |   31 +-
 .../internal/ConfigurationDescription.java         |   48 -
 .../processor/internal/ConfigurationElement.java   |   79 --
 .../processor/internal/Processor.java              |  524 +--------
 .../configuration/processor/internal/Utils.java    |   58 -
 .../configuration/ConfigurationChangerTest.java    |  128 ++-
 .../internal/util/ConfigurationUtilTest.java       |   71 +-
 .../internal/validation/ValidationUtilTest.java    |   29 +-
 .../sample/ConfigurationArrayTest.java             |   36 +-
 .../sample/ConstructableTreeNodeTest.java          |   65 +-
 .../sample/TraversableTreeNodeTest.java            |   72 +-
 .../ignite/configuration/sample/UsageTest.java     |    4 +-
 modules/configuration/pom.xml                      |    5 +
 .../ignite/configuration/ConfigurationChanger.java |   36 +-
 .../configuration/ConfigurationRegistry.java       |   38 +-
 .../org/apache/ignite/configuration/RootKey.java   |   11 +-
 .../configuration/annotation/ConfigValue.java      |    4 +-
 .../configuration/annotation/NamedConfigValue.java |    4 +-
 .../ignite/configuration/annotation/Value.java     |    4 +-
 .../configuration/internal/ConfigurationNode.java  |    2 +-
 .../internal/DynamicConfiguration.java             |   12 +-
 .../configuration/internal/DynamicProperty.java    |    2 +-
 .../ignite/configuration/internal/RootKeyImpl.java |   42 +-
 .../ignite/configuration/internal/SuperRoot.java   |   40 +-
 .../internal/asm/ConfigurationAsmGenerator.java    | 1139 ++++++++++++++++++++
 .../internal/asm/SchemaClassesInfo.java            |   67 ++
 .../internal/asm/StringSwitchBuilder.java          |  224 ++++
 .../internal/util/ConfigurationUtil.java           |    2 +-
 .../configuration/tree/ConstructableTreeNode.java  |    2 +-
 .../ignite/configuration/tree/InnerNode.java       |    3 +-
 .../client/ITMetaStorageServiceTest.java           |    4 +-
 .../vault/impl/VaultBaseContractsTest.java         |    2 +-
 35 files changed, 1848 insertions(+), 984 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java
index a79da1f..0d3d9f1 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java
+++ b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/runner/NodeConfigurationSchema.java
@@ -25,7 +25,6 @@ import org.apache.ignite.configuration.storage.ConfigurationType;
 /**
  * Local node configuration schema.
  */
-@SuppressWarnings("PMD.UnusedPrivateField")
 @ConfigurationRoot(rootName = "node", type = ConfigurationType.LOCAL)
 public class NodeConfigurationSchema {
     /** Uniq local node name. */
diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
index 80734fb..3ac5ad0 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
@@ -62,17 +62,13 @@ public class AbstractProcessorTest {
      * @return ConfigSet.
      */
     protected static ConfigSet getConfigSet(ClassName clazz, final Map<ClassName, JavaFileObject> generatedClasses) {
-        final ClassName configurationName = Utils.getConfigurationName(clazz);
-        final ClassName nodeName = Utils.getNodeName(clazz);
         final ClassName viewName = Utils.getViewName(clazz);
         final ClassName changeName = Utils.getChangeName(clazz);
 
-        final JavaFileObject configurationClass = generatedClasses.get(configurationName);
-        final JavaFileObject nodeClass = generatedClasses.get(nodeName);
         final JavaFileObject viewClass = generatedClasses.get(viewName);
         final JavaFileObject changeClass = generatedClasses.get(changeName);
 
-        return new ConfigSet(configurationClass, nodeClass, viewClass, changeClass);
+        return new ConfigSet(viewClass, changeClass);
     }
 
     /**
diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
index 08e420d..11511d5 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
@@ -32,40 +32,16 @@ import spoon.support.compiler.VirtualFile;
  * Wrapper for generated classes of the configuration schema.
  */
 public class ConfigSet {
-    /** Configuration class. */
-    private final JavaFileObject configurationClass;
-
-    /** Configuration node class. */
-    private final JavaFileObject nodeClass;
-
     /** VIEW class. */
     private final JavaFileObject viewClass;
 
     /** CHANGE class. */
     private final JavaFileObject changeClass;
 
-    /** Parsed configuration class. */
-    private final ParsedClass conf;
-
-    /** Parsed node class. */
-    private final ParsedClass node;
-
     /** Constructor. */
-    public ConfigSet(JavaFileObject configurationClass, JavaFileObject nodeClass, JavaFileObject viewClass, JavaFileObject changeClass) {
-        this.configurationClass = configurationClass;
+    public ConfigSet(JavaFileObject viewClass, JavaFileObject changeClass) {
         this.viewClass = viewClass;
         this.changeClass = changeClass;
-        this.nodeClass = nodeClass;
-
-        if (configurationClass != null)
-            this.conf = parse(configurationClass);
-        else
-            this.conf = null;
-
-        if (nodeClass != null)
-            this.node = parse(nodeClass);
-        else
-            this.node = null;
     }
 
     /**
@@ -88,16 +64,7 @@ public class ConfigSet {
      * @return {@code true} if all required classes were generated.
      */
     public boolean allGenerated() {
-        return configurationClass != null && nodeClass != null && viewClass != null && changeClass != null;
-    }
-
-    /** */
-    public ParsedClass getConfigurationClass() {
-        return conf;
-    }
-
-    public ParsedClass getNodeClass() {
-        return node;
+        return viewClass != null && changeClass != null;
     }
 
     /**
diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ITProcessorTest.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ITProcessorTest.java
index 900e5f2..2bb4b0a 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ITProcessorTest.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/configuration/processor/internal/ITProcessorTest.java
@@ -20,9 +20,6 @@ import com.google.testing.compile.Compilation;
 import com.squareup.javapoet.ClassName;
 import org.junit.jupiter.api.Test;
 
-import static org.apache.ignite.configuration.processor.internal.HasFieldMatcher.hasFields;
-import static org.apache.ignite.configuration.processor.internal.HasMethodMatcher.hasMethods;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -46,36 +43,10 @@ public class ITProcessorTest extends AbstractProcessorTest {
 
         assertNotEquals(Compilation.Status.FAILURE, status.status());
 
-        assertEquals(5, batch.generated().size());
+        assertEquals(3, batch.generated().size());
 
         final ConfigSet classSet = batch.getBySchema(testConfigurationSchema);
 
         assertTrue(classSet.allGenerated());
-
-        assertThat(
-            classSet.getNodeClass(),
-            hasFields(
-                "value1", String.class.getCanonicalName(),
-                "primitiveLong", Long.class.getCanonicalName(),
-                "primitiveInt", Integer.class.getCanonicalName(),
-                "stringArray", String[].class.getCanonicalName()
-            )
-        );
-
-        String nodeClassName = classSet.getNodeClass().getClassName();
-
-        assertThat(
-            classSet.getNodeClass(),
-            hasMethods(
-                "value1()", String.class.getCanonicalName(),
-                "primitiveLong()", long.class.getCanonicalName(),
-                "primitiveInt()", int.class.getCanonicalName(),
-                "stringArray()", String[].class.getCanonicalName(),
-                "changeValue1(java.lang.String)", nodeClassName,
-                "changePrimitiveLong(long)", nodeClassName,
-                "changePrimitiveInt(int)", nodeClassName,
-                "changeStringArray(java.lang.String[])", nodeClassName
-            )
-        );
     }
 }
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
deleted file mode 100644
index 206f4ee..0000000
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.configuration.processor.internal;
-
-import com.squareup.javapoet.TypeName;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Configuration and all it's inner fields.
- */
-public class ConfigurationDescription extends ConfigurationElement {
-    /** Inner configuration fields. */
-    private List<ConfigurationElement> fields = new ArrayList<>();
-
-    /**
-     * Constructor.
-     * @param type Configuration type.
-     * @param name Name of configuration element.
-     * @param view Configuration VIEW type.
-     * @param change Configuration CHANGE type.
-     */
-    public ConfigurationDescription(TypeName type, String name, TypeName view, TypeName change) {
-        super(type, name, view, change);
-    }
-
-    /**
-     * @return Configuration fields.
-     */
-    public List<ConfigurationElement> getFields() {
-        return fields;
-    }
-}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
deleted file mode 100644
index 809cf5a..0000000
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.configuration.processor.internal;
-
-import com.squareup.javapoet.TypeName;
-
-/**
- * Element of configuration.
- */
-public class ConfigurationElement {
-    /** Name of configuration element. */
-    private final String name;
-
-    /** Configuration type. */
-    private final TypeName type;
-
-    /** Configuration VIEW type. */
-    private final TypeName view;
-
-    /** Configuration CHANGE type. */
-    private final TypeName change;
-
-    /**
-     * Constructor.
-     * @param type Configuration type.
-     * @param name Name of configuration element.
-     * @param view Configuration VIEW type.
-     * @param change Configuration CHANGE type.
-     */
-    public ConfigurationElement(TypeName type, String name, TypeName view, TypeName change) {
-        this.type = type;
-        this.name = name;
-        this.view = view;
-        this.change = change;
-    }
-
-    /**
-     * @return Name of a configuration element.
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * @return Configuration type.
-     */
-    public TypeName getType() {
-        return type;
-    }
-
-    /**
-     * @return Configuration VIEW type.
-     */
-    public TypeName getView() {
-        return view;
-    }
-
-    /**
-     * @return Configuration CHANGE type.
-     */
-    public TypeName getChange() {
-        return change;
-    }
-}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
index 217b3b9..4bfd725 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -20,12 +20,7 @@ package org.apache.ignite.configuration.processor.internal;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -34,7 +29,6 @@ import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.Modifier;
 import javax.lang.model.element.PackageElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
@@ -42,14 +36,12 @@ import javax.lang.model.util.Elements;
 import javax.tools.Diagnostic;
 import com.squareup.javapoet.ArrayTypeName;
 import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.FieldSpec;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import com.squareup.javapoet.WildcardTypeName;
 import org.apache.ignite.configuration.NamedConfigurationTree;
 import org.apache.ignite.configuration.annotation.Config;
@@ -57,21 +49,13 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
-import org.apache.ignite.configuration.internal.NamedListConfiguration;
-import org.apache.ignite.configuration.storage.ConfigurationType;
-import org.apache.ignite.configuration.tree.ConfigurationSource;
-import org.apache.ignite.configuration.tree.ConfigurationVisitor;
-import org.apache.ignite.configuration.tree.InnerNode;
 import org.apache.ignite.configuration.tree.NamedListChange;
-import org.apache.ignite.configuration.tree.NamedListNode;
 import org.apache.ignite.configuration.tree.NamedListView;
 
 import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.FINAL;
-import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
-import static org.apache.ignite.configuration.processor.internal.Utils.suppressWarningsUnchecked;
 
 /**
  * Annotation processor that produces configuration classes.
@@ -80,12 +64,6 @@ public class Processor extends AbstractProcessor {
     /** Java file padding. */
     private static final String INDENT = "    ";
 
-    /** Wildcard (?) TypeName. */
-    private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
-
-    /** Inherit doc javadoc. */
-    private static final String INHERIT_DOC = "{@inheritDoc}";
-
     /** */
     private static final ClassName ROOT_KEY_CLASSNAME = ClassName.get("org.apache.ignite.configuration", "RootKey");
 
@@ -116,10 +94,6 @@ public class Processor extends AbstractProcessor {
     private boolean process0(RoundEnvironment roundEnvironment) {
         final Elements elementUtils = processingEnv.getElementUtils();
 
-        Map<TypeName, ConfigurationDescription> props = new HashMap<>();
-
-        List<ConfigurationDescription> roots = new ArrayList<>();
-
         // All classes annotated with @Config
         final List<TypeElement> annotatedConfigs = roundEnvironment
             .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class)).stream()
@@ -145,35 +119,15 @@ public class Processor extends AbstractProcessor {
 
             // Is root of the configuration
             final boolean isRoot = rootAnnotation != null;
-            // Configuration name
-            final String configName = isRoot ? rootAnnotation.rootName() : null;
 
             final ClassName schemaClassName = ClassName.get(packageName, clazz.getSimpleName().toString());
 
-            // Get name for generated configuration class and it's interface
-            final ClassName configClass = Utils.getConfigurationName(schemaClassName);
+            // Get name for generated configuration interface.
             final ClassName configInterface = Utils.getConfigurationInterfaceName(schemaClassName);
 
-            ConfigurationDescription configDesc = new ConfigurationDescription(
-                configClass,
-                configName,
-                Utils.getViewName(schemaClassName),
-                Utils.getChangeName(schemaClassName)
-            );
-
-            // If root, then use it's package as package for Selectors and Keys
-            if (isRoot)
-                roots.add(configDesc);
-
-            TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
-                .addSuperinterface(configInterface)
-                .addModifiers(FINAL);
-
             TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
                 .addModifiers(PUBLIC);
 
-            CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
-
             for (VariableElement field : fields) {
                 if (field.getModifiers().contains(STATIC))
                     continue;
@@ -188,12 +142,8 @@ public class Processor extends AbstractProcessor {
 
                 final String fieldName = field.getSimpleName().toString();
 
-                // Get configuration types (VIEW, INIT, CHANGE and so on)
-                final ConfigurationFieldTypes types = getTypes(field);
-
-                TypeName fieldType = types.getFieldType();
-                TypeName viewClassType = types.getViewClassType();
-                TypeName changeClassType = types.getChangeClassType();
+                // Get configuration types (VIEW, CHANGE and so on)
+                TypeName interfaceGetMethodType = getInterfaceGetMethodType(field);
 
                 final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
                 if (confAnnotation != null) {
@@ -203,17 +153,6 @@ public class Processor extends AbstractProcessor {
                                 clazz.getQualifiedName() + "." + field.getSimpleName()
                         );
                     }
-
-                    // Create DynamicConfiguration (descendant) field
-                    final FieldSpec nestedConfigField =
-                        FieldSpec
-                            .builder(fieldType, fieldName, Modifier.PRIVATE, FINAL)
-                            .build();
-
-                    configurationClassBuilder.addField(nestedConfigField);
-
-                    // Constructor statement
-                    constructorBodyBuilder.addStatement("add($L = new $T(keys, $S, rootKey, changer))", fieldName, fieldType, fieldName);
                 }
 
                 final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
@@ -224,25 +163,6 @@ public class Processor extends AbstractProcessor {
                                 clazz.getQualifiedName() + "." + field.getSimpleName()
                         );
                     }
-
-                    // Create NamedListConfiguration<> field
-                    final FieldSpec nestedConfigField = FieldSpec.builder(
-                        fieldType,
-                        fieldName,
-                        Modifier.PRIVATE,
-                        FINAL
-                    ).build();
-
-                    configurationClassBuilder.addField(nestedConfigField);
-
-                    // Constructor statement
-                    constructorBodyBuilder.addStatement(
-                        "add($L = new $T(keys, $S, rootKey, changer, (p, k) -> new $T(p, k, rootKey, changer)))",
-                        fieldName,
-                        fieldType,
-                        fieldName,
-                        Utils.getConfigurationName((ClassName) baseType)
-                    );
                 }
 
                 final Value valueAnnotation = field.getAnnotation(Value.class);
@@ -255,41 +175,19 @@ public class Processor extends AbstractProcessor {
                                 "aforementioned type."
                         );
                     }
-
-                    // Create value (DynamicProperty<>) field
-                    final FieldSpec generatedField = FieldSpec.builder(fieldType, fieldName, Modifier.PRIVATE, FINAL).build();
-
-                    configurationClassBuilder.addField(generatedField);
-
-                    // Constructor statement
-                    constructorBodyBuilder.addStatement(
-                        "add($L = new $T(keys, $S, rootKey, changer))",
-                        fieldName, fieldType, fieldName
-                    );
                 }
 
-                configDesc.getFields().add(new ConfigurationElement(fieldType, fieldName, viewClassType, changeClassType));
-
-                createGetters(configurationClassBuilder, configurationInterfaceBuilder, fieldName, types);
+                createGetters(configurationInterfaceBuilder, fieldName, interfaceGetMethodType);
             }
 
-            props.put(configClass, configDesc);
-
-            // Create VIEW, INIT and CHANGE classes
-            createPojoBindings(clazz, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
-
-            if (isRoot) {
-                ConfigurationType storageType = rootAnnotation.type();
-                createRootKeyField(configInterface, configurationInterfaceBuilder, configDesc, storageType, schemaClassName);
-            }
+            // Create VIEW and CHANGE classes
+            createPojoBindings(fields, schemaClassName, configurationInterfaceBuilder);
 
-            // Create constructors for configuration class
-            createConstructors(isRoot, configName, configurationClassBuilder, constructorBodyBuilder);
+            if (isRoot)
+                createRootKeyField(configInterface, configurationInterfaceBuilder, schemaClassName, clazz);
 
             // Write configuration interface
             buildClass(packageName, configurationInterfaceBuilder.build());
-
-            buildClass(packageName, configurationClassBuilder.build());
         }
 
         return true;
@@ -299,23 +197,20 @@ public class Processor extends AbstractProcessor {
     private void createRootKeyField(
         ClassName configInterface,
         TypeSpec.Builder configurationClassBuilder,
-        ConfigurationDescription configDesc,
-        ConfigurationType storageType,
-        ClassName schemaClassName
+        ClassName schemaClassName,
+        TypeElement realSchemaClass
     ) {
         ClassName viewClassName = Utils.getViewName(schemaClassName);
 
         ParameterizedTypeName fieldTypeName = ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, configInterface, viewClassName);
 
-        ClassName nodeClassName = Utils.getNodeName(schemaClassName);
-
         ClassName cfgRegistryClassName = ClassName.get("org.apache.ignite.configuration", "ConfigurationRegistry");
 
         FieldSpec keyField = FieldSpec.builder(fieldTypeName, "KEY", PUBLIC, STATIC, FINAL)
             .initializer(
-                "$T.newRootKey($S, $T.$L, $T::new, $T::new)",
-                cfgRegistryClassName, configDesc.getName(), ConfigurationType.class, storageType, nodeClassName,
-                Utils.getConfigurationName(schemaClassName)
+                "$T.newRootKey($T.class)",
+                cfgRegistryClassName,
+                realSchemaClass
             )
             .build();
 
@@ -325,37 +220,21 @@ public class Processor extends AbstractProcessor {
     /**
      * Create getters for configuration class.
      *
-     * @param configurationClassBuilder
      * @param configurationInterfaceBuilder
      * @param fieldName
      * @param types
      */
     private void createGetters(
-        TypeSpec.Builder configurationClassBuilder,
         TypeSpec.Builder configurationInterfaceBuilder,
         String fieldName,
-        ConfigurationFieldTypes types
+        TypeName interfaceGetMethodType
     ) {
         MethodSpec interfaceGetMethod = MethodSpec.methodBuilder(fieldName)
             .addModifiers(PUBLIC, ABSTRACT)
-            .returns(types.getInterfaceGetMethodType())
+            .returns(interfaceGetMethodType)
             .build();
-        configurationInterfaceBuilder.addMethod(interfaceGetMethod);
-
-        MethodSpec.Builder getMethodBuilder = MethodSpec.methodBuilder(fieldName)
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC, FINAL)
-            .returns(types.getInterfaceGetMethodType());
 
-        if (Utils.isNamedConfiguration(types.getFieldType())) {
-            getMethodBuilder.addAnnotation(suppressWarningsUnchecked())
-                .addStatement("return ($T)$L", NamedConfigurationTree.class, fieldName);
-        }
-        else
-            getMethodBuilder.addStatement("return $L", fieldName);
-
-        configurationClassBuilder.addMethod(getMethodBuilder.build());
+        configurationInterfaceBuilder.addMethod(interfaceGetMethod);
     }
 
     /**
@@ -363,34 +242,23 @@ public class Processor extends AbstractProcessor {
      * @param field
      * @return Bundle with all types for configuration
      */
-    private ConfigurationFieldTypes getTypes(final VariableElement field) {
-        TypeName fieldType = null;
+    private TypeName getInterfaceGetMethodType(final VariableElement field) {
         TypeName interfaceGetMethodType = null;
 
         final TypeName baseType = TypeName.get(field.asType());
 
-        TypeName unwrappedType = baseType;
-        TypeName viewClassType = baseType;
-        TypeName changeClassType = baseType;
-
         final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
         if (confAnnotation != null) {
-            fieldType = Utils.getConfigurationName((ClassName) baseType);
             interfaceGetMethodType = Utils.getConfigurationInterfaceName((ClassName) baseType);
-
-            unwrappedType = fieldType;
-            viewClassType = Utils.getViewName((ClassName) baseType);
-            changeClassType = Utils.getChangeName((ClassName) baseType);
         }
 
         final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
         if (namedConfigAnnotation != null) {
             ClassName interfaceGetType = Utils.getConfigurationInterfaceName((ClassName) baseType);
 
-            viewClassType = Utils.getViewName((ClassName) baseType);
-            changeClassType = Utils.getChangeName((ClassName) baseType);
+            TypeName viewClassType = Utils.getViewName((ClassName) baseType);
+            TypeName changeClassType = Utils.getChangeName((ClassName) baseType);
 
-            fieldType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), interfaceGetType, viewClassType, changeClassType);
             interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedConfigurationTree.class), interfaceGetType, viewClassType, changeClassType);
         }
 
@@ -398,7 +266,6 @@ public class Processor extends AbstractProcessor {
         if (valueAnnotation != null) {
             // It is necessary to use class names without loading classes so that we won't
             // accidentally get NoClassDefFoundError
-            ClassName dynPropClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicProperty");
             ClassName confValueClass = ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
 
             TypeName genericType = baseType;
@@ -407,126 +274,25 @@ public class Processor extends AbstractProcessor {
                 genericType = genericType.box();
             }
 
-            fieldType = ParameterizedTypeName.get(dynPropClass, genericType);
             interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, genericType);
         }
 
-        return new ConfigurationFieldTypes(fieldType, unwrappedType, viewClassType, changeClassType, interfaceGetMethodType);
-    }
-
-    /**
-     * Wrapper for configuration schema types.
-     */
-    private static class ConfigurationFieldTypes {
-        /** Field get method type. */
-        private final TypeName fieldType;
-
-        /** Configuration type (if marked with @ConfigValue or @NamedConfig), or original type (if marked with @Value) */
-        private final TypeName unwrappedType;
-
-        /** VIEW object type. */
-        private final TypeName viewClassType;
-
-        /** CHANGE object type. */
-        private final TypeName changeClassType;
-
-        /** Get method type for public interface. */
-        private final TypeName interfaceGetMethodType;
-
-        private ConfigurationFieldTypes(TypeName fieldType, TypeName unwrappedType, TypeName viewClassType, TypeName changeClassType, TypeName interfaceGetMethodType) {
-            this.fieldType = fieldType;
-            this.unwrappedType = unwrappedType;
-            this.viewClassType = viewClassType;
-            this.changeClassType = changeClassType;
-            this.interfaceGetMethodType = interfaceGetMethodType;
-        }
-
-        /** */
-        public TypeName getInterfaceGetMethodType() {
-            return interfaceGetMethodType;
-        }
-
-        /** */
-        public TypeName getFieldType() {
-            return fieldType;
-        }
-
-        /** */
-        public TypeName getUnwrappedType() {
-            return unwrappedType;
-        }
-
-        /** */
-        public TypeName getViewClassType() {
-            return viewClassType;
-        }
-
-        /** */
-        public TypeName getChangeClassType() {
-            return changeClassType;
-        }
+        return interfaceGetMethodType;
     }
 
     /**
-     * Create configuration class constructors.
-     *
-     * @param isRoot Flag that indincates whether current configuration is root or not.
-     * @param configName Name of the root if configuration is root, {@code null} otherwise.
-     * @param configurationClassBuilder Configuration class builder.
-     * @param constructorBodyBuilder Constructor body.
-     */
-    private void createConstructors(
-        boolean isRoot,
-        String configName,
-        TypeSpec.Builder configurationClassBuilder,
-        CodeBlock.Builder constructorBodyBuilder
-    ) {
-        MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(PUBLIC);
-
-        if (!isRoot) {
-            builder
-                .addParameter(ParameterizedTypeName.get(List.class, String.class), "prefix")
-                .addParameter(String.class, "key");
-        }
-
-        builder
-            .addParameter(ParameterizedTypeName.get(ROOT_KEY_CLASSNAME, WILDCARD, WILDCARD), "rootKey")
-            .addParameter(ClassName.get("org.apache.ignite.configuration", "ConfigurationChanger"), "changer");
-
-        if (isRoot)
-            builder.addStatement("super($T.emptyList(), $S, rootKey, changer)", Collections.class, configName);
-        else
-            builder.addStatement("super(prefix, key, rootKey, changer)");
-
-        MethodSpec constructorWithName = builder
-            .addCode(constructorBodyBuilder.build())
-            .build();
-
-        configurationClassBuilder.addMethod(constructorWithName);
-    }
-
-    /**
-     * Create VIEW, INIT and CHANGE classes and methods.
-     * @param clazz Original class for the schema.
+     * Create VIEW and CHANGE classes and methods.
      * @param fields List of configuration fields.
      * @param schemaClassName Class name of schema.
-     * @param configurationClassBuilder Configuration class builder.
      */
     private void createPojoBindings(
-        TypeElement clazz,
         List<VariableElement> fields,
         ClassName schemaClassName,
-        TypeSpec.Builder configurationClassBuilder,
         TypeSpec.Builder configurationInterfaceBuilder
     ) {
         final ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
         final ClassName changeClassName = Utils.getChangeName(schemaClassName);
 
-        ClassName dynConfClass = ClassName.get("org.apache.ignite.configuration.internal", "DynamicConfiguration");
-        TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, changeClassName);
-
-        configurationClassBuilder.superclass(dynConfViewClassType);
-
         ClassName confTreeInterface = ClassName.get("org.apache.ignite.configuration", "ConfigurationTree");
         TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
 
@@ -539,72 +305,12 @@ public class Processor extends AbstractProcessor {
 
         ClassName changeClsName = Utils.getChangeName(schemaClassName);
 
-        ClassName nodeClsName = Utils.getNodeName(schemaClassName);
-
         TypeSpec.Builder viewClsBuilder = TypeSpec.interfaceBuilder(viewClsName)
             .addModifiers(PUBLIC);
 
         TypeSpec.Builder changeClsBuilder = TypeSpec.interfaceBuilder(changeClsName)
             .addModifiers(PUBLIC);
 
-        TypeSpec.Builder nodeClsBuilder = TypeSpec.classBuilder(nodeClsName)
-            .addModifiers(FINAL)
-            .superclass(ClassName.get(InnerNode.class))
-            .addSuperinterface(viewClsName)
-            .addSuperinterface(changeClsName)
-            // Cannot use "schemaClassName" here because it can't handle inner static classes.
-            .addField(FieldSpec.builder(ClassName.get(clazz), "_spec", PRIVATE, FINAL)
-                .initializer("new $T()", ClassName.get(clazz))
-                .build()
-            );
-
-        TypeVariableName t = TypeVariableName.get("T");
-
-        MethodSpec.Builder traverseChildrenBuilder = MethodSpec.methodBuilder("traverseChildren")
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC)
-            .addTypeVariable(t)
-            .returns(TypeName.VOID)
-            .addParameter(ParameterizedTypeName.get(ClassName.get(ConfigurationVisitor.class), t), "visitor");
-
-        MethodSpec.Builder traverseChildBuilder = MethodSpec.methodBuilder("traverseChild")
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC)
-            .addTypeVariable(t)
-            .returns(t)
-            .addException(NoSuchElementException.class)
-            .addParameter(ClassName.get(String.class), "key")
-            .addParameter(ParameterizedTypeName.get(ClassName.get(ConfigurationVisitor.class), t), "visitor")
-            .beginControlFlow("switch (key)");
-
-        MethodSpec.Builder constructBuilder = MethodSpec.methodBuilder("construct")
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC)
-            .returns(TypeName.VOID)
-            .addException(NoSuchElementException.class)
-            .addParameter(ClassName.get(String.class), "key")
-            .addParameter(ClassName.get(ConfigurationSource.class), "src")
-            .beginControlFlow("switch (key)");
-
-        MethodSpec.Builder constructDefaultBuilder = MethodSpec.methodBuilder("constructDefault")
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC)
-            .returns(TypeName.BOOLEAN)
-            .addException(NoSuchElementException.class)
-            .addParameter(ClassName.get(String.class), "key")
-            .beginControlFlow("switch (key)");
-
-        MethodSpec.Builder schemaTypeBuilder = MethodSpec.methodBuilder("schemaType")
-            .addAnnotation(Override.class)
-            .addJavadoc(INHERIT_DOC)
-            .addModifiers(PUBLIC)
-            .returns(ParameterizedTypeName.get(ClassName.get(Class.class), WILDCARD))
-            .addStatement("return _spec.getClass()");
-
         ClassName consumerClsName = ClassName.get(Consumer.class);
 
         for (VariableElement field : fields) {
@@ -613,8 +319,6 @@ public class Processor extends AbstractProcessor {
             String fieldName = field.getSimpleName().toString();
             TypeName schemaFieldType = TypeName.get(field.asType());
 
-            boolean isArray = schemaFieldType instanceof ArrayTypeName;
-
             boolean leafField = isPrimitiveOrArrayOfPrimitives(schemaFieldType)
                 || !((ClassName)schemaFieldType).simpleName().contains("ConfigurationSchema");
 
@@ -624,52 +328,18 @@ public class Processor extends AbstractProcessor {
 
             TypeName changeFieldType = leafField ? schemaFieldType : Utils.getChangeName((ClassName)schemaFieldType);
 
-            TypeName nodeFieldType = leafField ? schemaFieldType.box() : Utils.getNodeName((ClassName)schemaFieldType);
-
-            TypeName namedListParamType = nodeFieldType;
-
             if (namedListField) {
                 viewFieldType = ParameterizedTypeName.get(ClassName.get(NamedListView.class), WildcardTypeName.subtypeOf(viewFieldType));
 
-                nodeFieldType = ParameterizedTypeName.get(ClassName.get(NamedListNode.class), nodeFieldType);
-
                 changeFieldType = ParameterizedTypeName.get(ClassName.get(NamedListChange.class), changeFieldType);
             }
 
             {
-                FieldSpec.Builder nodeFieldBuilder = FieldSpec.builder(nodeFieldType, fieldName, PRIVATE);
-
-                if (namedListField)
-                    nodeFieldBuilder.initializer("new $T<>($T::new)", NamedListNode.class, namedListParamType);
-
-                nodeClsBuilder.addField(nodeFieldBuilder.build());
-            }
-
-            {
-                {
-                    MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
-                        .addModifiers(PUBLIC, ABSTRACT)
-                        .returns(viewFieldType);
-
-                    viewClsBuilder.addMethod(getMtdBuilder.build());
-                }
-
-                {
-                    CodeBlock getStatement;
+                MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
+                    .addModifiers(PUBLIC, ABSTRACT)
+                    .returns(viewFieldType);
 
-                    if (isArray)
-                        getStatement = CodeBlock.builder().add("return $L.clone()", fieldName).build();
-                    else
-                        getStatement = CodeBlock.builder().add("return $L", fieldName).build();
-
-                    MethodSpec.Builder nodeGetMtdBuilder = MethodSpec.methodBuilder(fieldName)
-                        .addAnnotation(Override.class)
-                        .addModifiers(PUBLIC)
-                        .returns(leafField ? viewFieldType : nodeFieldType)
-                        .addStatement(getStatement);
-
-                    nodeClsBuilder.addMethod(nodeGetMtdBuilder.build());
-                }
+                viewClsBuilder.addMethod(getMtdBuilder.build());
             }
 
             {
@@ -680,159 +350,25 @@ public class Processor extends AbstractProcessor {
                         .addModifiers(PUBLIC, ABSTRACT)
                         .returns(changeClsName);
 
-                    if (valAnnotation != null)
+                    if (valAnnotation != null) {
+                        if (schemaFieldType instanceof ArrayTypeName)
+                            changeMtdBuilder.varargs(true);
+
                         changeMtdBuilder.addParameter(changeFieldType, fieldName);
+                    }
                     else
                         changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
 
                     changeClsBuilder.addMethod(changeMtdBuilder.build());
                 }
-
-                {
-                    MethodSpec.Builder nodeChangeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
-                        .addAnnotation(Override.class)
-                        .addModifiers(PUBLIC)
-                        .returns(nodeClsName);
-
-                    if (valAnnotation != null) {
-                        CodeBlock changeStatement;
-
-                        if (isArray)
-                            changeStatement = CodeBlock.builder().add("this.$L = $L.clone()", fieldName, fieldName).build();
-                        else
-                            changeStatement = CodeBlock.builder().add("this.$L = $L", fieldName, fieldName).build();
-
-                        nodeChangeMtdBuilder
-                            .addParameter(changeFieldType, fieldName)
-                            .addStatement(changeStatement);
-                    }
-                    else {
-                        String paramName = fieldName + "Consumer";
-                        nodeChangeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), paramName);
-
-                        if (!namedListField) {
-                            nodeChangeMtdBuilder.addStatement(
-                                "if ($L == null) $L = new $T()",
-                                fieldName,
-                                fieldName,
-                                nodeFieldType
-                            );
-                            nodeChangeMtdBuilder.addStatement("$L.accept($L)", paramName, fieldName);
-                        }
-                        else {
-                            nodeChangeMtdBuilder.addAnnotation(suppressWarningsUnchecked());
-
-                            nodeChangeMtdBuilder.addStatement("$L.accept((NamedListChange)$L)", paramName, fieldName);
-                        }
-                    }
-
-                    nodeChangeMtdBuilder.addStatement("return this");
-
-                    nodeClsBuilder.addMethod(nodeChangeMtdBuilder.build());
-                }
-            }
-
-            {
-                if (leafField) {
-                    traverseChildrenBuilder.addStatement("visitor.visitLeafNode($S, $L)", fieldName, fieldName);
-
-                    traverseChildBuilder
-                        .addStatement("case $S: return visitor.visitLeafNode(key, $L)", fieldName, fieldName);
-                }
-                else if (namedListField) {
-                    traverseChildrenBuilder.addStatement("visitor.visitNamedListNode($S, $L)", fieldName, fieldName);
-
-                    traverseChildBuilder
-                        .addStatement("case $S: return visitor.visitNamedListNode(key, $L)", fieldName, fieldName);
-                }
-                else {
-                    traverseChildrenBuilder.addStatement("visitor.visitInnerNode($S, $L)", fieldName, fieldName);
-
-                    traverseChildBuilder
-                        .addStatement("case $S: return visitor.visitInnerNode(key, $L)", fieldName, fieldName);
-                }
-            }
-
-            {
-                if (leafField) {
-                    constructBuilder.addStatement(
-                        "case $S: $L = src == null ? null : src.unwrap($T.class)",
-                        fieldName,
-                        fieldName,
-                        schemaFieldType.box()
-                    )
-                    .addStatement(INDENT + "break");
-
-                    if (valAnnotation.hasDefault()) {
-                        constructDefaultBuilder
-                            .addStatement("case $S: $L = _spec.$L" + (isArray ? ".clone()" : ""), fieldName, fieldName, fieldName)
-                            .addStatement(INDENT + "return true");
-                    }
-                    else
-                        constructDefaultBuilder.addStatement("case $S: return false", fieldName);
-                }
-                else if (namedListField) {
-                    constructBuilder
-                        .addStatement(
-                            "case $S: if (src == null) $L = new $T<>($T::new)",
-                            fieldName,
-                            fieldName,
-                            NamedListNode.class,
-                            namedListParamType
-                        )
-                        .addStatement(
-                            INDENT + "else src.descend($L = $L.copy())",
-                            fieldName,
-                            fieldName
-                        )
-                        .addStatement(INDENT + "break");
-                }
-                else {
-                    constructBuilder
-                        .addStatement(
-                            "case $S: if (src == null) $L = null",
-                            fieldName,
-                            fieldName
-                        )
-                        .addStatement(
-                            INDENT + "else src.descend($L = ($L == null ? new $T() : ($T)$L.copy()))",
-                            fieldName,
-                            fieldName,
-                            nodeFieldType,
-                            nodeFieldType,
-                            fieldName
-                        )
-                        .addStatement(INDENT + "break");
-                }
             }
         }
 
-        traverseChildBuilder
-            .addStatement("default: throw new $T(key)", NoSuchElementException.class)
-            .endControlFlow();
-
-        constructBuilder
-            .addStatement("default: throw new $T(key)", NoSuchElementException.class)
-            .endControlFlow();
-
-        constructDefaultBuilder
-            .addStatement("default: throw new $T(key)", NoSuchElementException.class)
-            .endControlFlow();
-
-        nodeClsBuilder
-            .addMethod(traverseChildrenBuilder.build())
-            .addMethod(traverseChildBuilder.build())
-            .addMethod(constructBuilder.build())
-            .addMethod(constructDefaultBuilder.build())
-            .addMethod(schemaTypeBuilder.build());
-
         TypeSpec viewCls = viewClsBuilder.build();
         TypeSpec changeCls = changeClsBuilder.build();
-        TypeSpec nodeCls = nodeClsBuilder.build();
 
         buildClass(viewClsName.packageName(), viewCls);
         buildClass(changeClsName.packageName(), changeCls);
-        buildClass(nodeClsName.packageName(), nodeCls);
     }
 
     /** */
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
index aa221eb..d1869d8 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
@@ -16,37 +16,17 @@
  */
 package org.apache.ignite.configuration.processor.internal;
 
-import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.ParameterizedTypeName;
-import com.squareup.javapoet.TypeName;
-import org.apache.ignite.configuration.internal.NamedListConfiguration;
 
 /**
  * Annotation processing utilities.
  */
 public class Utils {
-    /** */
-    private static final ClassName NAMED_LIST_CFG_CLASSNAME = ClassName.get("org.apache.ignite.configuration.internal", "NamedListConfiguration");
-
     /** Private constructor. */
     private Utils() {
     }
 
     /**
-     * Get {@link ClassName} for configuration class.
-     *
-     * @param schemaClassName Configuration schema ClassName.
-     * @return Configuration ClassName.
-     */
-    public static ClassName getConfigurationName(ClassName schemaClassName) {
-        return ClassName.get(
-            schemaClassName.packageName(),
-            schemaClassName.simpleName().replaceAll("Schema$", "Impl")
-        );
-    }
-
-    /**
      * Get {@link ClassName} for configuration class' public interface.
      *
      * @param schemaClassName Configuration schema ClassName.
@@ -60,19 +40,6 @@ public class Utils {
     }
 
     /**
-     * Get {@link ClassName} for a configuration node object class.
-     *
-     * @param schemaClassName Configuration schema ClassName.
-     * @return Configuration node object ClassName.
-     */
-    public static ClassName getNodeName(ClassName schemaClassName) {
-        return ClassName.get(
-            schemaClassName.packageName(),
-            schemaClassName.simpleName().replace("ConfigurationSchema", "Node")
-        );
-    }
-
-    /**
      * Get {@link ClassName} for configuration VIEW object class.
      *
      * @param schemaClassName Configuration schema ClassName.
@@ -97,29 +64,4 @@ public class Utils {
             schemaClassName.simpleName().replace("ConfigurationSchema", "Change")
         );
     }
-
-    /**
-     * Check whether type is {@link NamedListConfiguration}.
-     *
-     * @param type Type.
-     * @return {@code true} if type is {@link NamedListConfiguration}.
-     */
-    public static boolean isNamedConfiguration(TypeName type) {
-        if (type instanceof ParameterizedTypeName) {
-            ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
-
-            if (parameterizedTypeName.rawType.equals(NAMED_LIST_CFG_CLASSNAME))
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return {@code @SuppressWarnings("unchecked")} annotation spec object.
-     */
-    public static AnnotationSpec suppressWarningsUnchecked() {
-        return AnnotationSpec.builder(SuppressWarnings.class)
-            .addMember("value", "$S", "unchecked")
-            .build();
-    }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
index 79cc097..7e22e2e 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/ConfigurationChangerTest.java
@@ -20,7 +20,6 @@ import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
@@ -29,17 +28,21 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.storage.ConfigurationType;
 import org.apache.ignite.configuration.storage.Data;
 import org.apache.ignite.configuration.storage.TestConfigurationStorage;
+import org.apache.ignite.configuration.tree.InnerNode;
 import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.configuration.validation.ValidationContext;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Test;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.Collections.singletonMap;
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.ignite.configuration.AConfiguration.KEY;
@@ -91,6 +94,13 @@ public class ConfigurationChangerTest {
         public String strCfg;
     }
 
+    private static ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
     /**
      * Test simple change of configuration.
      */
@@ -98,17 +108,17 @@ public class ConfigurationChangerTest {
     public void testSimpleConfigurationChange() throws Exception {
         final TestConfigurationStorage storage = new TestConfigurationStorage();
 
-        ANode data = new ANode()
-            .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
-            .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
-
-        ConfigurationChanger changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+        ConfigurationChanger changer = new TestConfigurationChanger();
         changer.addRootKey(KEY);
         changer.register(storage);
 
-        changer.change(Collections.singletonMap(KEY, data)).get(1, SECONDS);
+        AChange data = ((AChange)changer.createRootNode(KEY))
+            .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
+            .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
+
+        changer.change(singletonMap(KEY, (InnerNode)data)).get(1, SECONDS);
 
-        ANode newRoot = (ANode)changer.getRootNode(KEY);
+        AView newRoot = (AView)changer.getRootNode(KEY);
 
         assertEquals(1, newRoot.child().intCfg());
         assertEquals("1", newRoot.child().strCfg());
@@ -122,36 +132,37 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorage() throws Exception {
         final TestConfigurationStorage storage = new TestConfigurationStorage();
 
-        ANode data1 = new ANode()
+        ConfigurationChanger changer1 = new TestConfigurationChanger();
+        changer1.addRootKey(KEY);
+        changer1.register(storage);
+
+        ConfigurationChanger changer2 = new TestConfigurationChanger();
+        changer2.addRootKey(KEY);
+        changer2.register(storage);
+
+        AChange data1 = ((AChange)changer1.createRootNode(KEY))
             .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
             .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
 
-        ANode data2 = new ANode()
+        changer1.change(singletonMap(KEY, (InnerNode)data1)).get(1, SECONDS);
+
+        AChange data2 = ((AChange)changer2.createRootNode(KEY))
             .changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
             .changeElements(change -> change
                 .create("a", element -> element.changeStrCfg("2"))
                 .create("b", element -> element.changeStrCfg("2"))
             );
 
-        ConfigurationChanger changer1 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
-        changer1.addRootKey(KEY);
-        changer1.register(storage);
-
-        ConfigurationChanger changer2 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
-        changer2.addRootKey(KEY);
-        changer2.register(storage);
-
-        changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
-        changer2.change(Collections.singletonMap(KEY, data2)).get(1, SECONDS);
+        changer2.change(singletonMap(KEY, (InnerNode)data2)).get(1, SECONDS);
 
-        ANode newRoot1 = (ANode)changer1.getRootNode(KEY);
+        AView newRoot1 = (AView)changer1.getRootNode(KEY);
 
         assertEquals(2, newRoot1.child().intCfg());
         assertEquals("2", newRoot1.child().strCfg());
         assertEquals("2", newRoot1.elements().get("a").strCfg());
         assertEquals("2", newRoot1.elements().get("b").strCfg());
 
-        ANode newRoot2 = (ANode)changer2.getRootNode(KEY);
+        AView newRoot2 = (AView)changer2.getRootNode(KEY);
 
         assertEquals(2, newRoot2.child().intCfg());
         assertEquals("2", newRoot2.child().strCfg());
@@ -166,26 +177,19 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws Exception {
         final TestConfigurationStorage storage = new TestConfigurationStorage();
 
-        ANode data1 = new ANode()
-            .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
-            .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
-
-        ANode data2 = new ANode()
-            .changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
-            .changeElements(change -> change
-                .create("a", element -> element.changeStrCfg("2"))
-                .create("b", element -> element.changeStrCfg("2"))
-            );
-
-        ConfigurationChanger changer1 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+        ConfigurationChanger changer1 = new TestConfigurationChanger();
         changer1.addRootKey(KEY);
         changer1.register(storage);
 
-        ConfigurationChanger changer2 = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+        ConfigurationChanger changer2 = new TestConfigurationChanger();
         changer2.addRootKey(KEY);
         changer2.register(storage);
 
-        changer1.change(Collections.singletonMap(KEY, data1)).get(1, SECONDS);
+        AChange data1 = ((AChange)changer1.createRootNode(KEY))
+            .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
+            .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")));
+
+        changer1.change(singletonMap(KEY, (InnerNode)data1)).get(1, SECONDS);
 
         changer2.addValidator(MaybeInvalid.class, new Validator<MaybeInvalid, Object>() {
             @Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
@@ -193,9 +197,16 @@ public class ConfigurationChangerTest {
             }
         });
 
-        assertThrows(ExecutionException.class, () -> changer2.change(Collections.singletonMap(KEY, data2)).get(1, SECONDS));
+        AChange data2 = ((AChange)changer2.createRootNode(KEY))
+            .changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
+            .changeElements(change -> change
+                .create("a", element -> element.changeStrCfg("2"))
+                .create("b", element -> element.changeStrCfg("2"))
+            );
+
+        assertThrows(ExecutionException.class, () -> changer2.change(singletonMap(KEY, (InnerNode)data2)).get(1, SECONDS));
 
-        ANode newRoot = (ANode)changer2.getRootNode(KEY);
+        AView newRoot = (AView)changer2.getRootNode(KEY);
 
         assertEquals(1, newRoot.child().intCfg());
         assertEquals("1", newRoot.child().strCfg());
@@ -209,9 +220,7 @@ public class ConfigurationChangerTest {
     public void testFailedToWrite() {
         final TestConfigurationStorage storage = new TestConfigurationStorage();
 
-        ANode data = new ANode().changeChild(child -> child.changeIntCfg(1));
-
-        ConfigurationChanger changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+        ConfigurationChanger changer = new TestConfigurationChanger();
         changer.addRootKey(KEY);
 
         storage.fail(true);
@@ -224,7 +233,9 @@ public class ConfigurationChangerTest {
 
         storage.fail(true);
 
-        assertThrows(ExecutionException.class, () -> changer.change(Collections.singletonMap(KEY, data)).get(1, SECONDS));
+        AChange data = ((AChange)changer.createRootNode(KEY)).changeChild(child -> child.changeIntCfg(1));
+
+        assertThrows(ExecutionException.class, () -> changer.change(singletonMap(KEY, (InnerNode)data)).get(1, SECONDS));
 
         storage.fail(false);
 
@@ -233,7 +244,7 @@ public class ConfigurationChangerTest {
 
         assertEquals(0, dataMap.size());
 
-        ANode newRoot = (ANode)changer.getRootNode(KEY);
+        AView newRoot = (AView)changer.getRootNode(KEY);
         assertNull(newRoot.child());
     }
 
@@ -267,7 +278,7 @@ public class ConfigurationChangerTest {
 
     @Test
     public void defaultsOnInit() throws Exception {
-        var changer = new ConfigurationChanger((oldRoot, newRoot, revision) -> completedFuture(null));
+        var changer = new TestConfigurationChanger();
 
         changer.addRootKey(DefaultsConfiguration.KEY);
 
@@ -277,19 +288,38 @@ public class ConfigurationChangerTest {
 
         changer.initialize(storage.type());
 
-        DefaultsNode root = (DefaultsNode)changer.getRootNode(DefaultsConfiguration.KEY);
+        DefaultsView root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
 
         assertEquals("foo", root.defStr());
         assertEquals("bar", root.child().defStr());
         assertEquals(List.of("xyz"), Arrays.asList(root.child().arr()));
 
-        // This is not init, move it to another test =(
-        changer.change(Map.of(DefaultsConfiguration.KEY, new DefaultsNode().changeChildsList(childs ->
+        DefaultsChange change = ((DefaultsChange)changer.createRootNode(DefaultsConfiguration.KEY)).changeChildsList(childs ->
             childs.create("name", child -> {})
-        ))).get(1, SECONDS);
+        );
 
-        root = (DefaultsNode)changer.getRootNode(DefaultsConfiguration.KEY);
+        changer.change(Map.of(DefaultsConfiguration.KEY, (InnerNode)change)).get(1, SECONDS);
+
+        root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
 
         assertEquals("bar", root.childsList().get("name").defStr());
     }
+
+    private static class TestConfigurationChanger extends ConfigurationChanger {
+        TestConfigurationChanger() {
+            super((oldRoot, newRoot, revision) -> completedFuture(null));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void addRootKey(RootKey<?, ?> rootKey) {
+            super.addRootKey(rootKey);
+
+            cgen.compileRootSchema(rootKey.schemaClass());
+        }
+
+        /** {@inheritDoc} */
+        @Override public InnerNode createRootNode(RootKey<?, ?> rootKey) {
+            return cgen.instantiateNode(rootKey.schemaClass());
+        }
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
index 1255534..48051c3 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/util/ConfigurationUtilTest.java
@@ -25,7 +25,12 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.storage.ConfigurationType;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static java.util.Collections.emptyMap;
@@ -39,6 +44,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /** */
 public class ConfigurationUtilTest {
+    private static ConfigurationAsmGenerator cgen;
+
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+
+        cgen.compileRootSchema(ParentConfigurationSchema.class);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
+    public static <P extends InnerNode & ParentView & ParentChange> P newParentInstance() {
+        return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
+    }
+
     /** */
     @Test
     public void escape() {
@@ -102,7 +125,9 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void findSuccessfully() {
-        var parent = new ParentNode().changeElements(elements ->
+        var parent = newParentInstance();
+
+        parent.changeElements(elements ->
             elements.update("name", element ->
                 element.changeChild(child ->
                     child.changeStr("value")
@@ -139,7 +164,7 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void findNulls() {
-        var parent = new ParentNode();
+        var parent = newParentInstance();
 
         assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent));
 
@@ -147,7 +172,7 @@ public class ConfigurationUtilTest {
 
         assertNull(ConfigurationUtil.find(List.of("elements", "name", "child"), parent));
 
-        parent.elements().get("name").changeChild(child -> {});
+        ((NamedElementChange)parent.elements().get("name")).changeChild(child -> {});
 
         assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent));
     }
@@ -155,7 +180,7 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void findUnsuccessfully() {
-        var parent = new ParentNode();
+        var parent = newParentInstance();
 
         assertThrows(
             KeyNotFoundException.class,
@@ -169,7 +194,7 @@ public class ConfigurationUtilTest {
             () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent)
         );
 
-        parent.elements().get("name").changeChild(child -> child.changeStr("value"));
+        ((NamedElementChange)parent.elements().get("name")).changeChild(child -> child.changeStr("value"));
 
         assertThrows(
             KeyNotFoundException.class,
@@ -204,7 +229,7 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void fillFromPrefixMapSuccessfully() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
             "elements", Map.of(
@@ -224,7 +249,9 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void fillFromPrefixMapSuccessfullyWithRemove() {
-        var parentNode = new ParentNode().changeElements(elements ->
+        var parentNode = newParentInstance();
+
+        parentNode.changeElements(elements ->
             elements.update("name", element ->
                 element.changeChild(child -> {})
             )
@@ -240,9 +267,9 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void nodeToFlatMap() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
-        var parentSuperRoot = new SuperRoot(emptyMap(), Map.of(
+        var parentSuperRoot = new SuperRoot(key -> null, Map.of(
             ParentConfiguration.KEY,
             parentNode
         ));
@@ -268,9 +295,9 @@ public class ConfigurationUtilTest {
 
         assertEquals(
             emptyMap(),
-            ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(emptyMap(), singletonMap(
+            ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(key -> null, singletonMap(
                 ParentConfiguration.KEY,
-                new ParentNode().changeElements(elements ->
+                (InnerNode)newParentInstance().changeElements(elements ->
                     elements.delete("void")
                 )
             )))
@@ -278,9 +305,9 @@ public class ConfigurationUtilTest {
 
         assertEquals(
             singletonMap("root.elements.name.child.str", null),
-            ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(emptyMap(), singletonMap(
+            ConfigurationUtil.nodeToFlatMap(parentSuperRoot, new SuperRoot(key -> null, singletonMap(
                 ParentConfiguration.KEY,
-                new ParentNode().changeElements(elements ->
+                (InnerNode)newParentInstance().changeElements(elements ->
                     elements.delete("name")
                 )
             )))
@@ -290,14 +317,16 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void patch() {
-        var originalRoot = new ParentNode().changeElements(elements ->
+        var originalRoot = newParentInstance();
+
+        originalRoot.changeElements(elements ->
             elements.create("name1", element ->
                 element.changeChild(child -> child.changeStr("value1"))
             )
         );
 
         // Updating config.
-        ParentNode updatedRoot = ConfigurationUtil.patch(originalRoot, new ParentNode().changeElements(elements ->
+        ParentView updatedRoot = ConfigurationUtil.patch(originalRoot, (TraversableTreeNode)newParentInstance().changeElements(elements ->
             elements.update("name1", element ->
                 element.changeChild(child -> child.changeStr("value2"))
             )
@@ -312,7 +341,7 @@ public class ConfigurationUtilTest {
         assertEquals("value2", updatedRoot.elements().get("name1").child().str());
 
         // Expanding config.
-        ParentNode expandedRoot = ConfigurationUtil.patch(originalRoot, new ParentNode().changeElements(elements ->
+        ParentView expandedRoot = ConfigurationUtil.patch(originalRoot, (TraversableTreeNode)newParentInstance().changeElements(elements ->
             elements.update("name2", element ->
                 element.changeChild(child -> child.changeStr("value2"))
             )
@@ -328,7 +357,7 @@ public class ConfigurationUtilTest {
         assertEquals("value2", expandedRoot.elements().get("name2").child().str());
 
         // Shrinking config.
-        ParentNode shrinkedRoot = ConfigurationUtil.patch(expandedRoot, new ParentNode().changeElements(elements ->
+        ParentView shrinkedRoot = (ParentView)ConfigurationUtil.patch((InnerNode)expandedRoot, (TraversableTreeNode)newParentInstance().changeElements(elements ->
             elements.delete("name1")
         ));
 
@@ -343,13 +372,17 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     public void cleanupMatchingValues() {
-        var curParent = new ParentNode().changeElements(elements -> elements
+        var curParent = newParentInstance();
+
+        curParent.changeElements(elements -> elements
             .create("missing", element -> {})
             .create("match", element -> element.changeChild(child -> child.changeStr("match")))
             .create("mismatch", element -> element.changeChild(child -> child.changeStr("foo")))
         );
 
-        var newParent = new ParentNode().changeElements(elements -> elements
+        var newParent = newParentInstance();
+
+        newParent.changeElements(elements -> elements
             .create("extra", element -> {})
             .create("match", element -> element.changeChild(child -> child.changeStr("match")))
             .create("mismatch", element -> element.changeChild(child -> child.changeStr("bar")))
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
index b97a3cb..ef370c8 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/internal/validation/ValidationUtilTest.java
@@ -30,23 +30,40 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
 import org.apache.ignite.configuration.storage.ConfigurationType;
+import org.apache.ignite.configuration.tree.InnerNode;
 import org.apache.ignite.configuration.tree.NamedListView;
 import org.apache.ignite.configuration.validation.ValidationContext;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static java.util.Collections.emptyMap;
 import static java.util.Collections.emptySet;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /** */
 public class ValidationUtilTest {
+    private static ConfigurationAsmGenerator cgen;
+
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+
+        cgen.compileRootSchema(ValidatedRootConfigurationSchema.class);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
     /** */
     @Target(FIELD)
     @Retention(RUNTIME)
@@ -89,18 +106,20 @@ public class ValidationUtilTest {
     }
 
     /** */
-    private final ValidatedRootNode root = new ValidatedRootNode();
+    private InnerNode root;
 
     /** */
     @BeforeEach
     public void before() {
+        root = cgen.instantiateNode(ValidatedRootConfigurationSchema.class);
+
         ConfigurationUtil.addDefaults(root, root);
     }
 
     /** */
     @Test
     public void validateLeafNode() throws Exception {
-        var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+        var rootsNode = new SuperRoot(key -> null, Map.of(ValidatedRootConfiguration.KEY, root));
 
         Validator<LeafValidation, String> validator = new Validator<>() {
             @Override public void validate(LeafValidation annotation, ValidationContext<String> ctx) {
@@ -125,7 +144,7 @@ public class ValidationUtilTest {
     /** */
     @Test
     public void validateInnerNode() throws Exception {
-        var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+        var rootsNode = new SuperRoot(key -> null, Map.of(ValidatedRootConfiguration.KEY, root));
 
         Validator<InnerValidation, ValidatedChildView> validator = new Validator<>() {
             @Override public void validate(InnerValidation annotation, ValidationContext<ValidatedChildView> ctx) {
@@ -150,7 +169,7 @@ public class ValidationUtilTest {
     /** */
     @Test
     public void validateNamedListNode() throws Exception {
-        var rootsNode = new SuperRoot(emptyMap(), Map.of(ValidatedRootConfiguration.KEY, root));
+        var rootsNode = new SuperRoot(key -> null, Map.of(ValidatedRootConfiguration.KEY, root));
 
         Validator<NamedListValidation, NamedListView<?>> validator = new Validator<>() {
             @Override public void validate(NamedListValidation annotation, ValidationContext<NamedListView<?>> ctx) {
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
index 3e3a1b3..ad2e7c5 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConfigurationArrayTest.java
@@ -20,6 +20,10 @@ package org.apache.ignite.configuration.sample;
 import java.util.function.Supplier;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.leafNodeVisitor;
@@ -39,28 +43,42 @@ public class ConfigurationArrayTest {
         public String[] array;
     }
 
+    private static ConfigurationAsmGenerator cgen;
+
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+
+        cgen.compileRootSchema(TestArrayConfigurationSchema.class);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
     /**
      * Test array node init operation.
      */
     @Test
     public void testInit() {
-        var arrayNode = new TestArrayNode();
+        InnerNode arrayNode = cgen.instantiateNode(TestArrayConfigurationSchema.class);
 
         Supplier<String[]> initialSupplier = () -> {
             return new String[]{"test1", "test2"};
         };
 
         final String[] initialValue = initialSupplier.get();
-        arrayNode.changeArray(initialValue);
+        ((TestArrayChange)arrayNode).changeArray(initialValue);
 
         // test that field is not the same as initialValue
         assertNotSame(getArray(arrayNode), initialValue);
 
         // test that init method set values successfully
-        assertThat(arrayNode.array(), is(initialSupplier.get()));
+        assertThat(((TestArrayView)arrayNode).array(), is(initialSupplier.get()));
 
         // test that returned array is a copy of the field
-        assertNotSame(getArray(arrayNode), arrayNode.array());
+        assertNotSame(getArray(arrayNode), ((TestArrayView)arrayNode).array());
     }
 
     /**
@@ -68,23 +86,23 @@ public class ConfigurationArrayTest {
      */
     @Test
     public void testChange() {
-        var arrayNode = new TestArrayNode();
+        InnerNode arrayNode = cgen.instantiateNode(TestArrayConfigurationSchema.class);
 
         Supplier<String[]> changeSupplier = () -> {
             return new String[]{"foo", "bar"};
         };
 
         final String[] changeValue = changeSupplier.get();
-        arrayNode.changeArray(changeValue);
+        ((TestArrayChange)arrayNode).changeArray(changeValue);
 
         // test that field is not the same as initialValue
         assertNotSame(getArray(arrayNode), changeValue);
 
         // test that change method set values successfully
-        assertThat(arrayNode.array(), is(changeSupplier.get()));
+        assertThat(((TestArrayView)arrayNode).array(), is(changeSupplier.get()));
 
         // test that returned array is a copy of the field
-        assertNotSame(getArray(arrayNode), arrayNode.array());
+        assertNotSame(getArray(arrayNode), ((TestArrayView)arrayNode).array());
     }
 
     /**
@@ -92,7 +110,7 @@ public class ConfigurationArrayTest {
      * @param arrayNode ArrayNode.
      * @return Array field value.
      */
-    private String[] getArray(TestArrayNode arrayNode) {
+    private String[] getArray(InnerNode arrayNode) {
         return (String[])arrayNode.traverseChild("array", leafNodeVisitor());
     }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
index 2867397..0b50243 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ConstructableTreeNodeTest.java
@@ -19,25 +19,48 @@ package org.apache.ignite.configuration.sample;
 
 import java.util.Collections;
 import java.util.NoSuchElementException;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.tree.ConfigurationSource;
 import org.apache.ignite.configuration.tree.ConstructableTreeNode;
-import org.apache.ignite.configuration.tree.NamedListNode;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /** */
 public class ConstructableTreeNodeTest {
+    private static ConfigurationAsmGenerator cgen;
+
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+
+        cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
+    public static <P extends InnerNode & ParentView & ParentChange> P newParentInstance() {
+        return (P)cgen.instantiateNode(TraversableTreeNodeTest.ParentConfigurationSchema.class);
+    }
+
+    public static <C extends InnerNode & ChildView & ChildChange> C newChildInstance() {
+        return (C)cgen.instantiateNode(TraversableTreeNodeTest.ChildConfigurationSchema.class);
+    }
+
     /** */
     @Test
     public void noKey() {
-        var childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         assertThrows(NoSuchElementException.class, () -> childNode.construct("foo", null));
     }
@@ -45,7 +68,9 @@ public class ConstructableTreeNodeTest {
     /** */
     @Test
     public void nullSource() {
-        var parentNode = new ParentNode().changeChild(child ->
+        var parentNode = newParentInstance();
+
+        parentNode.changeChild(child ->
             child.changeStrCfg("value")
         )
         .changeElements(elements ->
@@ -53,7 +78,7 @@ public class ConstructableTreeNodeTest {
         );
 
         // Named list node.
-        NamedListNode<NamedElementNode> elements = parentNode.elements();
+        var elements = parentNode.elements();
 
         parentNode.construct("elements", null);
 
@@ -62,14 +87,14 @@ public class ConstructableTreeNodeTest {
         assertEquals(Collections.emptySet(), parentNode.elements().namedListKeys());
 
         // Inner node.
-        NamedElementNode element = elements.get("name");
+        NamedElementView element = elements.get("name");
 
-        elements.construct("name", null);
+        ((ConstructableTreeNode)elements).construct("name", null);
 
         assertNull(elements.get("name"));
 
         // Leaf.
-        element.construct("strCfg", null);
+        ((ConstructableTreeNode)element).construct("strCfg", null);
 
         assertNull(element.strCfg());
     }
@@ -95,7 +120,7 @@ public class ConstructableTreeNodeTest {
     /** */
     @Test
     public void unwrap() {
-        var childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         childNode.construct("strCfg", new ConstantConfigurationSource("value"));
 
@@ -114,7 +139,7 @@ public class ConstructableTreeNodeTest {
     @Test
     public void descend() {
         // Inner node.
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         parentNode.construct("child", new ConfigurationSource() {
             @Override public <T> T unwrap(Class<T> clazz) {
@@ -129,9 +154,9 @@ public class ConstructableTreeNodeTest {
         assertEquals("value", parentNode.child().strCfg());
 
         // Named list node.
-        NamedListNode<NamedElementNode> elementsNode = parentNode.elements();
+        var elementsNode = parentNode.elements();
 
-        elementsNode.construct("name", new ConfigurationSource() {
+        ((ConstructableTreeNode)elementsNode).construct("name", new ConfigurationSource() {
             @Override public <T> T unwrap(Class<T> clazz) {
                 throw new UnsupportedOperationException("unwrap");
             }
@@ -148,7 +173,7 @@ public class ConstructableTreeNodeTest {
     @Test
     public void constructDefault() {
         // Inner node with no leaves.
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         assertThrows(NoSuchElementException.class, () -> parentNode.constructDefault("child"));
         assertThrows(NoSuchElementException.class, () -> parentNode.constructDefault("elements"));
@@ -156,20 +181,22 @@ public class ConstructableTreeNodeTest {
         // Inner node with a leaf.
         parentNode.changeElements(elements -> elements.create("name", element -> {}));
 
-        NamedElementNode elementNode = parentNode.elements().get("name");
+        var elementNode = parentNode.elements().get("name");
+
+        ((InnerNode)elementNode).constructDefault("strCfg");
 
-        assertFalse(elementNode.constructDefault("strCfg"));
+        assertNull(elementNode.strCfg());
 
         // Another inner node with leaves.
         parentNode.changeChild(child -> {});
 
-        ChildNode child = parentNode.child();
+        var child = parentNode.child();
 
-        assertFalse(child.constructDefault("strCfg"));
+        ((InnerNode)child).constructDefault("strCfg");
 
         assertThrows(NullPointerException.class, () -> child.intCfg());
 
-        assertTrue(child.constructDefault("intCfg"));
+        ((InnerNode)child).constructDefault("intCfg");
 
         assertEquals(99, child.intCfg());
     }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
index 30cce72..fafcaf8 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/TraversableTreeNodeTest.java
@@ -25,10 +25,16 @@ import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListChange;
 import org.apache.ignite.configuration.tree.NamedListNode;
+import org.apache.ignite.configuration.tree.NamedListView;
+import org.apache.ignite.configuration.tree.TraversableTreeNode;
 import org.apache.ignite.configuration.validation.Immutable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static java.util.Collections.emptySet;
@@ -43,6 +49,28 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /** */
 public class TraversableTreeNodeTest {
+    private static ConfigurationAsmGenerator cgen;
+
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+
+        cgen.compileRootSchema(ParentConfigurationSchema.class);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
+    public static <P extends InnerNode & ParentView & ParentChange> P newParentInstance() {
+        return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
+    }
+
+    public static <C extends InnerNode & ChildView & ChildChange> C newChildInstance() {
+        return (C)cgen.instantiateNode(ChildConfigurationSchema.class);
+    }
+
     /** */
     @Config
     public static class ParentConfigurationSchema {
@@ -83,21 +111,21 @@ public class TraversableTreeNodeTest {
     }
 
     /**
-     * Test that generated node classes implement generated VIEW, CHANGE and INIT interfaces.
+     * Test that generated node classes implement generated VIEW and CHANGE interfaces.
      */
     @Test
     public void nodeClassesImplementRequiredInterfaces() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         assertThat(parentNode, instanceOf(ParentView.class));
         assertThat(parentNode, instanceOf(ParentChange.class));
 
-        var namedElementNode = new NamedElementNode();
+        var namedElementNode = cgen.instantiateNode(NamedElementConfigurationSchema.class);
 
         assertThat(namedElementNode, instanceOf(NamedElementView.class));
         assertThat(namedElementNode, instanceOf(NamedElementChange.class));
 
-        var childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         assertThat(childNode, instanceOf(ChildView.class));
         assertThat(childNode, instanceOf(ChildChange.class));
@@ -108,7 +136,7 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void changeLeaf() {
-        var childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         assertNull(childNode.strCfg());
 
@@ -122,13 +150,13 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void changeInnerChild() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         assertNull(parentNode.child());
 
         parentNode.changeChild(child -> {});
 
-        ChildNode childNode = parentNode.child();
+        ChildView childNode = parentNode.child();
 
         assertNotNull(childNode);
 
@@ -143,9 +171,9 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void changeNamedChild() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
-        NamedListNode<NamedElementNode> elementsNode = parentNode.elements();
+        NamedListView<? extends NamedElementView> elementsNode = parentNode.elements();
 
         // Named list node must always be instantiated.
         assertNotNull(elementsNode);
@@ -161,21 +189,21 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void putRemoveNamedConfiguration() {
-        var elementsNode = new NamedListNode<>(NamedElementNode::new);
+        var elementsNode = newParentInstance().elements();
 
         assertEquals(emptySet(), elementsNode.namedListKeys());
 
-        elementsNode.update("keyPut", element -> {});
+        ((NamedListChange<?>)elementsNode).update("keyPut", element -> {});
 
         assertThat(elementsNode.namedListKeys(), hasItem("keyPut"));
 
-        NamedElementNode elementNode = elementsNode.get("keyPut");
+        NamedElementView elementNode = elementsNode.get("keyPut");
 
         assertNotNull(elementNode);
 
         assertNull(elementNode.strCfg());
 
-        elementsNode.update("keyPut", element -> element.changeStrCfg("val"));
+        ((NamedListChange<NamedElementChange>)elementsNode).update("keyPut", element -> element.changeStrCfg("val"));
 
         // Assert that consecutive put methods don't create new object every time.
         assertSame(elementNode, elementsNode.get("keyPut"));
@@ -183,9 +211,9 @@ public class TraversableTreeNodeTest {
         assertEquals("val", elementNode.strCfg());
 
         // Assert that once you put something into list, removing it makes no sense and hence prohibited.
-        assertThrows(IllegalStateException.class, () -> elementsNode.delete("keyPut"));
+        assertThrows(IllegalStateException.class, () -> ((NamedListChange<?>)elementsNode).delete("keyPut"));
 
-        elementsNode.delete("keyRemove");
+        ((NamedListChange<?>)elementsNode).delete("keyRemove");
 
         // Assert that "remove" method creates null element inside of the node.
         assertThat(elementsNode.namedListKeys(), hasItem("keyRemove"));
@@ -193,7 +221,7 @@ public class TraversableTreeNodeTest {
         assertNull(elementsNode.get("keyRemove"));
 
         // Assert that once you remove something from list, you can't put it back again with different set of fields.
-        assertThrows(IllegalStateException.class, () -> elementsNode.update("keyRemove", element -> {}));
+        assertThrows(IllegalStateException.class, () -> ((NamedListChange<?>)elementsNode).update("keyRemove", element -> {}));
     }
 
     /**
@@ -201,7 +229,7 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void innerNodeAcceptVisitor() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         assertThrows(VisitException.class, () ->
             parentNode.accept("root", new ConfigurationVisitor<Void>() {
@@ -217,7 +245,7 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void namedListNodeAcceptVisitor() {
-        var elementsNode = new NamedListNode<>(NamedElementNode::new);
+        var elementsNode = (TraversableTreeNode)newParentInstance().elements();
 
         assertThrows(VisitException.class, () ->
             elementsNode.accept("root", new ConfigurationVisitor<Void>() {
@@ -233,7 +261,7 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void traverseChildren() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         List<String> keys = new ArrayList<>(2);
 
@@ -258,7 +286,7 @@ public class TraversableTreeNodeTest {
 
         keys.clear();
 
-        ChildNode childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         childNode.traverseChildren(new ConfigurationVisitor<Object>() {
             @Override public Object visitLeafNode(String key, Serializable val) {
@@ -275,7 +303,7 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void traverseSingleChild() {
-        var parentNode = new ParentNode();
+        var parentNode = newParentInstance();
 
         // Assert that proper method has been invoked.
         assertThrows(VisitException.class, () ->
@@ -300,7 +328,7 @@ public class TraversableTreeNodeTest {
             })
         );
 
-        var childNode = new ChildNode();
+        var childNode = newChildInstance();
 
         // Assert that proper method has been invoked.
         assertThrows(VisitException.class, () ->
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
index 55dff07..bab8049 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
@@ -128,12 +128,12 @@ public class UsageTest {
 
         assertEquals(
             failureDetectionTimeout,
-            registry.getConfiguration(NetworkConfigurationImpl.KEY).discovery().failureDetectionTimeout().value()
+            registry.getConfiguration(NetworkConfiguration.KEY).discovery().failureDetectionTimeout().value()
         );
 
         assertEquals(
             autoAdjustTimeout,
-            registry.getConfiguration(LocalConfigurationImpl.KEY).baseline().autoAdjust().timeout().value()
+            registry.getConfiguration(LocalConfiguration.KEY).baseline().autoAdjust().timeout().value()
         );
     }
 }
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index c4e0b6f..6b3fc61 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -35,6 +35,11 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-bytecode</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-core</artifactId>
         </dependency>
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
index 2bd1094..344a9ab 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationChanger.java
@@ -28,6 +28,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.apache.ignite.configuration.internal.SuperRoot;
 import org.apache.ignite.configuration.internal.validation.MemberKey;
@@ -57,7 +58,7 @@ import static org.apache.ignite.configuration.internal.util.ConfigurationUtil.to
 /**
  * Class that handles configuration changes, by validating them, passing to storage and listening to storage updates.
  */
-public final class ConfigurationChanger {
+public abstract class ConfigurationChanger {
     /** */
     private final ForkJoinPool pool = new ForkJoinPool(2);
 
@@ -154,6 +155,25 @@ public final class ConfigurationChanger {
     }
 
     /**
+     * Created new {@code Node} object that corresponds to passed root keys root configuration node.
+     * @param rootKey Root key.
+     * @return New {@link InnerNode} instance that represents root.
+     */
+    public abstract InnerNode createRootNode(RootKey<?, ?> rootKey);
+
+    /**
+     * Utility method to create {@link SuperRoot} parameter value.
+     * @return Function that creates root node by root name or returns {@code null} if root name is not found.
+     */
+    @NotNull private Function<String, InnerNode> rootCreator() {
+        return key -> {
+            RootKey<?, ?> rootKey = rootKeys.get(key);
+
+            return rootKey == null ? null : createRootNode(rootKey);
+        };
+    }
+
+    /**
      * Registers a storage.
      * @param configurationStorage Configuration storage instance.
      */
@@ -174,14 +194,14 @@ public final class ConfigurationChanger {
             throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
         }
 
-        SuperRoot superRoot = new SuperRoot(rootKeys);
+        SuperRoot superRoot = new SuperRoot(rootCreator());
 
         Map<String, ?> dataValuesPrefixMap = toPrefixMap(data.values());
 
         for (RootKey<?, ?> rootKey : storageRootKeys) {
             Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
 
-            InnerNode rootNode = rootKey.createRootNode();
+            InnerNode rootNode = createRootNode(rootKey);
 
             if (rootPrefixMap != null)
                 fillFromPrefixMap(rootNode, rootPrefixMap);
@@ -211,7 +231,7 @@ public final class ConfigurationChanger {
         StorageRoots storageRoots = storagesRootsMap.get(storageType);
 
         SuperRoot superRoot = storageRoots.roots;
-        SuperRoot defaultsNode = new SuperRoot(rootKeys);
+        SuperRoot defaultsNode = new SuperRoot(rootCreator());
 
         addDefaults(superRoot, defaultsNode);
 
@@ -247,7 +267,7 @@ public final class ConfigurationChanger {
         ConfigurationSource source,
         @Nullable ConfigurationStorage storage
     ) {
-        SuperRoot superRoot = new SuperRoot(rootKeys);
+        SuperRoot superRoot = new SuperRoot(rootCreator());
 
         source.descend(superRoot);
 
@@ -318,14 +338,14 @@ public final class ConfigurationChanger {
             );
         }
 
-        return changeInternally(new SuperRoot(rootKeys, changes), storageInstances.get(storagesTypes.iterator().next()));
+        return changeInternally(new SuperRoot(rootCreator(), changes), storageInstances.get(storagesTypes.iterator().next()));
     }
 
     /**
      * @return Super root chat contains roots belonging to all storages.
      */
     public SuperRoot mergedSuperRoot() {
-        return new SuperRoot(rootKeys, storagesRootsMap.values().stream().map(roots -> roots.roots).collect(toList()));
+        return new SuperRoot(rootCreator(), storagesRootsMap.values().stream().map(roots -> roots.roots).collect(toList()));
     }
 
     /**
@@ -349,7 +369,7 @@ public final class ConfigurationChanger {
                 // different set of values and different dynamic defaults at the same time.
                 SuperRoot patchedSuperRoot = patch(curRoots, changes);
 
-                SuperRoot defaultsNode = new SuperRoot(rootKeys);
+                SuperRoot defaultsNode = new SuperRoot(rootCreator());
 
                 addDefaults(patchedSuperRoot, defaultsNode);
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
index a1a913e..c79610e 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationRegistry.java
@@ -26,9 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.function.BiFunction;
 import java.util.function.Function;
-import java.util.function.Supplier;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
@@ -36,6 +34,7 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.internal.DynamicConfiguration;
 import org.apache.ignite.configuration.internal.RootKeyImpl;
 import org.apache.ignite.configuration.internal.SuperRoot;
+import org.apache.ignite.configuration.internal.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.configuration.internal.util.ConfigurationUtil;
 import org.apache.ignite.configuration.internal.util.KeyNotFoundException;
 import org.apache.ignite.configuration.internal.validation.ImmutableValidator;
@@ -64,7 +63,15 @@ public class ConfigurationRegistry {
     private final Map<String, DynamicConfiguration<?, ?>> configs = new HashMap<>();
 
     /** */
-    private final ConfigurationChanger changer = new ConfigurationChanger(this::notificator);
+    private final ConfigurationChanger changer = new ConfigurationChanger(this::notificator) {
+        /** {@inheritDoc} */
+        @Override public InnerNode createRootNode(RootKey<?, ?> rootKey) {
+            return cgen.instantiateNode(rootKey.schemaClass());
+        }
+    };
+
+    /** */
+    private final ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
 
     {
         // Default vaildators implemented in current module.
@@ -85,10 +92,14 @@ public class ConfigurationRegistry {
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
         Collection<ConfigurationStorage> configurationStorages
     ) {
-        rootKeys.forEach(rootKey ->
-        {
+        rootKeys.forEach(rootKey -> {
+            cgen.compileRootSchema(rootKey.schemaClass());
+
             changer.addRootKey(rootKey);
-            configs.put(rootKey.key(), (DynamicConfiguration<?, ?>)rootKey.createPublicRoot(changer));
+
+            DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, changer);
+
+            configs.put(rootKey.key(), cfg);
         });
 
         validators.forEach(changer::addValidators);
@@ -195,21 +206,12 @@ public class ConfigurationRegistry {
     /**
      * Method to instantiate a new {@link RootKey} for your configuration root. Invoked in generated code only.
      * Does not register this root anywhere, used for static object initialization only.
-     *
-     * @param rootName Name of the root as described in {@link ConfigurationRoot#rootName()}.
-     * @param storageType Storage class as described in {@link ConfigurationRoot#type()}.
-     * @param rootSupplier Closure to instantiate internal configuration tree roots.
-     * @param publicRootCreator Function to create public user-facing tree instance.
+     * @param schemaClass Class of Configuration Schema that was annotated with {@link ConfigurationRoot}.
      * @param <T> Type of public configuration tree.
      * @param <V> View type for the root.
      * @return Root key instance.
      */
-    public static <T extends ConfigurationTree<V, ?>, V> RootKey<T, V> newRootKey(
-        String rootName,
-        ConfigurationType storageType,
-        Supplier<InnerNode> rootSupplier,
-        BiFunction<RootKey<T, V>, ConfigurationChanger, T> publicRootCreator
-    ) {
-        return new RootKeyImpl<>(rootName, storageType, rootSupplier, publicRootCreator);
+    public static <T extends ConfigurationTree<V, ?>, V> RootKey<T, V> newRootKey(Class<?> schemaClass) {
+        return new RootKeyImpl<>(schemaClass);
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
index b4ff0cf..29157d4 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/RootKey.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.configuration;
 
 import org.apache.ignite.configuration.storage.ConfigurationType;
-import org.apache.ignite.configuration.tree.InnerNode;
 
 /** */
 public abstract class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
@@ -33,13 +32,7 @@ public abstract class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
     protected abstract ConfigurationType type();
 
     /**
-     * @return New instance of the inner node for the root.
+     * @return Schema class for the root.
      */
-    protected abstract InnerNode createRootNode();
-
-    /**
-     * @param changer Configuration changer instance.
-     * @return New instance of the public tree for the root.
-     */
-    protected abstract T createPublicRoot(ConfigurationChanger changer);
+    public abstract Class<?> schemaClass();
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
index 9ba0d80..79efb43 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
@@ -22,7 +22,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
  * This annotation marks configuration schema field as a configuration tree node.
@@ -37,7 +37,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
  * </code></pre>
  */
 @Target({ FIELD })
-@Retention(SOURCE)
+@Retention(RUNTIME)
 @Documented
 public @interface ConfigValue {
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
index e8a5e86..d5864c1 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
@@ -23,7 +23,7 @@ import java.lang.annotation.Target;
 import org.apache.ignite.configuration.internal.NamedListConfiguration;
 
 import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
  * This annotation denotes configuration schema fields that are dynamically created and mapped by name.
@@ -43,7 +43,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
  * </code></pre>
  */
 @Target({ FIELD })
-@Retention(SOURCE)
+@Retention(RUNTIME)
 @Documented
 public @interface NamedConfigValue {
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
index a1b04e7..c6f15fd 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
@@ -23,7 +23,7 @@ import java.lang.annotation.Target;
 import org.apache.ignite.configuration.internal.DynamicProperty;
 
 import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
  * This annotation marks configuration schema field as a configuration tree leaf.
@@ -40,7 +40,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
  * </ul>
  */
 @Target(FIELD)
-@Retention(SOURCE)
+@Retention(RUNTIME)
 @Documented
 public @interface Value {
     /**
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
index 96fbb16..9b4f39a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/ConfigurationNode.java
@@ -33,7 +33,7 @@ import org.apache.ignite.configuration.tree.TraversableTreeNode;
 /**
  * Super class for dynamic configuration tree nodes. Has all common data and value retrieving algorithm in it.
  */
-abstract class ConfigurationNode<VIEW, CHANGE> implements ConfigurationProperty<VIEW, CHANGE> {
+public abstract class ConfigurationNode<VIEW, CHANGE> implements ConfigurationProperty<VIEW, CHANGE> {
     /** Listeners of property update. */
     protected final List<ConfigurationListener<VIEW>> updateListeners = new CopyOnWriteArrayList<>();
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
index 3177d4a..03073ae 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
@@ -67,21 +67,11 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
         members.put(member.key(), member);
     }
 
-    /**
-     * Add new configuration member.
-     *
-     * @param member Configuration member (leaf or node).
-     * @param <M> Type of member.
-     */
-    protected final <M extends DynamicProperty<?>> void add(M member) {
-        members.put(member.key(), member);
-    }
-
     /** {@inheritDoc} */
     @Override public final Future<Void> change(Consumer<CHANGE> change) {
         Objects.requireNonNull(change, "Configuration consumer cannot be null.");
 
-        InnerNode rootNodeChange = ((RootKeyImpl)rootKey).createRootNode();
+        InnerNode rootNodeChange = changer.createRootNode(rootKey);
 
         if (keys.size() == 1) {
             // Current node is a root.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
index b163501..1b5bcb7 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
@@ -60,7 +60,7 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
     @Override public Future<Void> update(T newValue) throws ConfigurationValidationException {
         Objects.requireNonNull(newValue, "Configuration value cannot be null.");
 
-        InnerNode rootNodeChange = ((RootKeyImpl)rootKey).createRootNode();
+        InnerNode rootNodeChange = changer.createRootNode(rootKey);
 
         assert keys instanceof RandomAccess;
         assert !keys.isEmpty();
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
index f8e04f4..69a2ec4 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/RootKeyImpl.java
@@ -17,14 +17,10 @@
 
 package org.apache.ignite.configuration.internal;
 
-import java.util.function.BiFunction;
-import java.util.function.Supplier;
-import org.apache.ignite.configuration.ConfigurationChanger;
 import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.storage.ConfigurationType;
-import org.apache.ignite.configuration.tree.InnerNode;
 
 /** */
 public class RootKeyImpl<T extends ConfigurationTree<VIEW, ?>, VIEW> extends RootKey<T, VIEW> {
@@ -35,27 +31,20 @@ public class RootKeyImpl<T extends ConfigurationTree<VIEW, ?>, VIEW> extends Roo
     private final ConfigurationType storageType;
 
     /** */
-    private final Supplier<InnerNode> rootSupplier;
-
-    /** */
-    private final BiFunction<RootKey<T, VIEW>, ConfigurationChanger, T> publicRootCreator;
+    private final Class<?> schemaClass;
 
     /**
-     * @param rootName Name of the root as described in {@link ConfigurationRoot#rootName()}.
-     * @param storageType Storage class as described in {@link ConfigurationRoot#type()}.
-     * @param rootSupplier Closure to instantiate internal configuration tree roots.
-     * @param publicRootCreator Function to create a public user-facing tree instance.
+     * @param schemaClass Class of the configuration schema.
      */
-    public RootKeyImpl(
-        String rootName,
-        ConfigurationType storageType,
-        Supplier<InnerNode> rootSupplier,
-        BiFunction<RootKey<T, VIEW>, ConfigurationChanger, T> publicRootCreator
-    ) {
-        this.rootName = rootName;
-        this.storageType = storageType;
-        this.rootSupplier = rootSupplier;
-        this.publicRootCreator = publicRootCreator;
+    public RootKeyImpl(Class<?> schemaClass) {
+        this.schemaClass = schemaClass;
+
+        ConfigurationRoot rootAnnotation = schemaClass.getAnnotation(ConfigurationRoot.class);
+
+        assert rootAnnotation != null;
+
+        this.rootName = rootAnnotation.rootName();
+        this.storageType = rootAnnotation.type();
     }
 
     /** {@inheritDoc} */
@@ -69,12 +58,7 @@ public class RootKeyImpl<T extends ConfigurationTree<VIEW, ?>, VIEW> extends Roo
     }
 
     /** {@inheritDoc} */
-    @Override protected InnerNode createRootNode() {
-        return rootSupplier.get();
-    }
-
-    /** {@inheritDoc} */
-    @Override protected T createPublicRoot(ConfigurationChanger changer) {
-        return publicRootCreator.apply(this, changer);
+    @Override public Class<?> schemaClass() {
+        return schemaClass;
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
index 991cbd0..dbea55b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/SuperRoot.java
@@ -22,6 +22,7 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.function.Function;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.tree.ConfigurationSource;
 import org.apache.ignite.configuration.tree.ConfigurationVisitor;
@@ -33,39 +34,39 @@ public final class SuperRoot extends InnerNode {
     private final SortedMap<String, InnerNode> roots = new TreeMap<>();
 
     /** */
-    private final Map<String, RootKey<?, ?>> allRootKeys;
+    private final Function<String, InnerNode> nodeCreator;
 
     /** Copy constructor. */
     private SuperRoot(SuperRoot superRoot) {
         roots.putAll(superRoot.roots);
 
-        allRootKeys = superRoot.allRootKeys;
+        nodeCreator = superRoot.nodeCreator;
     }
 
     /**
-     * @param rootKeys Shared map of all available root keys.
+     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
      */
-    public SuperRoot(Map<String, RootKey<?, ?>> rootKeys) {
-        allRootKeys = rootKeys;
+    public SuperRoot(Function<String, InnerNode> nodeCreator) {
+        this.nodeCreator = nodeCreator;
     }
 
     /**
-     * @param rootKeys Shared map of all available root keys.
+     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
      * @param roots Map of roots belonging to this super root.
      */
-    public SuperRoot(Map<String, RootKey<?, ?>> rootKeys, Map<RootKey<?, ?>, InnerNode> roots) {
-        allRootKeys = rootKeys;
+    public SuperRoot(Function<String, InnerNode> nodeCreator, Map<RootKey<?, ?>, InnerNode> roots) {
+        this.nodeCreator = nodeCreator;
 
         for (Map.Entry<RootKey<?, ?>, InnerNode> entry : roots.entrySet())
             this.roots.put(entry.getKey().key(), entry.getValue());
     }
 
     /**
-     * @param rootKeys Shared map of all available root keys.
+     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
      * @param superRoots List of super roots to merge into even more superior root.
      */
-    public SuperRoot(Map<String, RootKey<?, ?>> rootKeys, List<SuperRoot> superRoots) {
-        this(rootKeys);
+    public SuperRoot(Function<String, InnerNode> nodeCreator, List<SuperRoot> superRoots) {
+        this(nodeCreator);
 
         for (SuperRoot superRoot : superRoots)
             roots.putAll(superRoot.roots);
@@ -78,7 +79,6 @@ public final class SuperRoot extends InnerNode {
      */
     public void addRoot(RootKey<?, ?> rootKey, InnerNode root) {
         assert !roots.containsKey(rootKey.key()) : rootKey.key() + " : " + roots;
-        assert allRootKeys.get(rootKey.key()) == rootKey : rootKey.key() + " : " + allRootKeys;
 
         roots.put(rootKey.key(), root);
     }
@@ -112,14 +112,16 @@ public final class SuperRoot extends InnerNode {
     @Override public void construct(String key, ConfigurationSource src) throws NoSuchElementException {
         assert src != null;
 
-        RootKeyImpl<?, ?> rootKey = (RootKeyImpl<?, ?>)allRootKeys.get(key);
-
-        if (rootKey == null)
-            throw new NoSuchElementException(key);
-
         InnerNode root = roots.get(key);
 
-        root = root == null ? rootKey.createRootNode() : root.copy();
+        if (root == null) {
+            root = nodeCreator.apply(key);
+
+            if (root == null)
+                throw new NoSuchElementException(key);
+        }
+        else
+            root = root.copy();
 
         roots.put(key, root);
 
@@ -127,7 +129,7 @@ public final class SuperRoot extends InnerNode {
     }
 
     /** {@inheritDoc} */
-    @Override public boolean constructDefault(String key) throws NoSuchElementException {
+    @Override public void constructDefault(String key) throws NoSuchElementException {
         throw new NoSuchElementException(key);
     }
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/ConfigurationAsmGenerator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/ConfigurationAsmGenerator.java
new file mode 100644
index 0000000..8854545
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/ConfigurationAsmGenerator.java
@@ -0,0 +1,1139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.asm;
+
+import java.io.Serializable;
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import com.facebook.presto.bytecode.BytecodeBlock;
+import com.facebook.presto.bytecode.ClassDefinition;
+import com.facebook.presto.bytecode.ClassGenerator;
+import com.facebook.presto.bytecode.FieldDefinition;
+import com.facebook.presto.bytecode.MethodDefinition;
+import com.facebook.presto.bytecode.ParameterizedType;
+import com.facebook.presto.bytecode.Variable;
+import com.facebook.presto.bytecode.control.IfStatement;
+import com.facebook.presto.bytecode.expression.BytecodeExpression;
+import org.apache.ignite.configuration.ConfigurationChanger;
+import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.NamedConfigurationTree;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.DynamicProperty;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+import org.apache.ignite.configuration.tree.ConfigurationSource;
+import org.apache.ignite.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.configuration.tree.InnerNode;
+import org.apache.ignite.configuration.tree.NamedListNode;
+import org.apache.ignite.configuration.tree.NamedListView;
+import org.jetbrains.annotations.NotNull;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import static com.facebook.presto.bytecode.Access.FINAL;
+import static com.facebook.presto.bytecode.Access.PRIVATE;
+import static com.facebook.presto.bytecode.Access.PUBLIC;
+import static com.facebook.presto.bytecode.Access.STATIC;
+import static com.facebook.presto.bytecode.Access.SYNTHETIC;
+import static com.facebook.presto.bytecode.Parameter.arg;
+import static com.facebook.presto.bytecode.ParameterizedType.type;
+import static com.facebook.presto.bytecode.ParameterizedType.typeFromJavaClassName;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantClass;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantNull;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantString;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.inlineIf;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeDynamic;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeStatic;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNull;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.newInstance;
+import static java.lang.invoke.MethodType.methodType;
+import static java.util.Collections.singleton;
+import static java.util.EnumSet.of;
+import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
+import static org.objectweb.asm.Type.getMethodDescriptor;
+import static org.objectweb.asm.Type.getMethodType;
+import static org.objectweb.asm.Type.getType;
+
+/**
+ * This class is responsible for generating internal implementation classes for configuration schemas. It uses classes
+ * from {@code bytecode} module to achieve this goal, like {@link ClassGenerator}, for examples.
+ */
+public class ConfigurationAsmGenerator {
+    /** {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)} */
+    private static final Method LAMBDA_METAFACTORY;
+
+    /** {@link Consumer#accept(Object)}*/
+    private static final Method ACCEPT;
+
+    /** {@link ConfigurationVisitor#visitLeafNode(String, Serializable)} */
+    private static final Method VISIT_LEAF;
+
+    /** {@link ConfigurationVisitor#visitInnerNode(String, InnerNode)} */
+    private static final Method VISIT_INNER;
+
+    /** {@link ConfigurationVisitor#visitNamedListNode(String, NamedListNode)} */
+    private static final Method VISIT_NAMED;
+
+    /** {@link ConfigurationSource#unwrap(Class)} */
+    private static final Method UNWRAP;
+
+    /** {@link ConfigurationSource#descend(ConstructableTreeNode)} */
+    private static final Method DESCEND;
+
+    /** {@link ConstructableTreeNode#copy()} */
+    private static final Method COPY;
+
+    /** {@link DynamicConfiguration#DynamicConfiguration(List, String, RootKey, ConfigurationChanger)} */
+    private static final Constructor DYNAMIC_CONFIGURATION_CTOR;
+
+    /** {@link DynamicConfiguration#add(ConfigurationProperty)} */
+    private static final Method DYNAMIC_CONFIGURATION_ADD;
+
+    static {
+        try {
+            LAMBDA_METAFACTORY = LambdaMetafactory.class.getDeclaredMethod(
+                "metafactory",
+                Lookup.class,
+                String.class,
+                MethodType.class,
+                MethodType.class,
+                MethodHandle.class,
+                MethodType.class
+            );
+
+            ACCEPT = Consumer.class.getDeclaredMethod("accept", Object.class);
+
+            VISIT_LEAF = ConfigurationVisitor.class.getDeclaredMethod("visitLeafNode", String.class, Serializable.class);
+
+            VISIT_INNER = ConfigurationVisitor.class.getDeclaredMethod("visitInnerNode", String.class, InnerNode.class);
+
+            VISIT_NAMED = ConfigurationVisitor.class.getDeclaredMethod("visitNamedListNode", String.class, NamedListNode.class);
+
+            UNWRAP = ConfigurationSource.class.getDeclaredMethod("unwrap", Class.class);
+
+            DESCEND = ConfigurationSource.class.getDeclaredMethod("descend", ConstructableTreeNode.class);
+
+            COPY = ConstructableTreeNode.class.getDeclaredMethod("copy");
+
+            DYNAMIC_CONFIGURATION_CTOR = DynamicConfiguration.class.getDeclaredConstructor(
+                List.class,
+                String.class,
+                RootKey.class,
+                ConfigurationChanger.class
+            );
+
+            DYNAMIC_CONFIGURATION_ADD = DynamicConfiguration.class.getDeclaredMethod(
+                "add",
+                ConfigurationProperty.class
+            );
+        }
+        catch (NoSuchMethodException nsme) {
+            throw new ExceptionInInitializerError(nsme);
+        }
+    }
+
+    /** Information about schema classes - bunch of names and dynamically compiled internal classes. */
+    private final Map<Class<?>, SchemaClassesInfo> schemasInfo = new HashMap<>();
+
+    /** Class generator instance. */
+    private final ClassGenerator generator = ClassGenerator.classGenerator(this.getClass().getClassLoader());
+
+    /**
+     * Creates new instance of {@code *Node} class corresponding to the given Configuration Schema.
+     * @param schemaClass Configuration Schema class.
+     * @return Node instance.
+     */
+    public synchronized InnerNode instantiateNode(Class<?> schemaClass) {
+        SchemaClassesInfo info = schemasInfo.get(schemaClass);
+
+        assert info != null && info.nodeClass != null : schemaClass;
+
+        try {
+            Constructor<? extends InnerNode> constructor = info.nodeClass.getConstructor();
+
+            assert constructor.canAccess(null);
+
+            return constructor.newInstance();
+        }
+        catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Creates new instance of {@code *Configuration} class corresponding to the given Configuration Schema.
+     * @param rootKey Root key of the configuration root.
+     * @param changer Configuration changer instance to pass into constructor.
+     * @return Configuration instance.
+     */
+    public synchronized DynamicConfiguration<?, ?> instantiateCfg(
+        RootKey<?, ?> rootKey,
+        ConfigurationChanger changer
+    ) {
+        SchemaClassesInfo info = schemasInfo.get(rootKey.schemaClass());
+
+        assert info != null && info.cfgImplClass != null;
+
+        try {
+            Constructor<? extends DynamicConfiguration<?, ?>> constructor = info.cfgImplClass.getConstructor(
+                List.class,
+                String.class,
+                RootKey.class,
+                ConfigurationChanger.class
+            );
+
+            assert constructor.canAccess(null);
+
+            return constructor.newInstance(Collections.emptyList(), rootKey.key(), rootKey, changer);
+        }
+        catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Generates, defines, loads and initializes all dynamic classes required for the given Configuration Schema.
+     * @param rootSchemaClass Class of the root Configuration Schema.
+     */
+    public synchronized void compileRootSchema(Class<?> rootSchemaClass) {
+        if (schemasInfo.containsKey(rootSchemaClass))
+            return; // Already compiled.
+
+        Queue<Class<?>> compileQueue = new LinkedList<>();
+        compileQueue.add(rootSchemaClass);
+
+        schemasInfo.put(rootSchemaClass, new SchemaClassesInfo(rootSchemaClass));
+
+        Set<Class<?>> schemas = new HashSet<>();
+        List<ClassDefinition> definitions = new ArrayList<>();
+
+        while (!compileQueue.isEmpty()) {
+            Class<?> schemaClass = compileQueue.poll();
+
+            assert schemaClass.isAnnotationPresent(ConfigurationRoot.class)
+                || schemaClass.isAnnotationPresent(Config.class)
+                : schemaClass + " is not properly annotated";
+
+            assert schemasInfo.containsKey(schemaClass);
+
+            for (Field field : schemaClass.getDeclaredFields()) {
+                if (isConfigValue(field) || isNamedConfigValue(field)) {
+                    Class<?> subSchemaClass = field.getType();
+
+                    if (!schemasInfo.containsKey(subSchemaClass)) {
+                        compileQueue.offer(subSchemaClass);
+                        schemasInfo.put(subSchemaClass, new SchemaClassesInfo(subSchemaClass));
+                    }
+                }
+            }
+
+            schemas.add(schemaClass);
+            definitions.add(createNodeClass(schemaClass));
+            definitions.add(createCfgImplClass(schemaClass));
+        }
+
+        Map<String, Class<?>> definedClasses = generator.defineClasses(definitions);
+
+        for (Class<?> schemaClass : schemas) {
+            SchemaClassesInfo info = schemasInfo.get(schemaClass);
+
+            info.nodeClass = (Class<? extends InnerNode>)definedClasses.get(info.nodeClassName);
+            info.cfgImplClass = (Class<? extends DynamicConfiguration<?, ?>>)definedClasses.get(info.cfgImplClassName);
+        }
+    }
+
+    private ClassDefinition createNodeClass(Class<?> schemaClass) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+        // Node class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(schemaClassInfo.nodeClassName),
+            type(InnerNode.class),
+            typeFromJavaClassName(schemaClassInfo.viewClassName),
+            typeFromJavaClassName(schemaClassInfo.changeClassName)
+        );
+
+        // Spec field.
+        FieldDefinition specField = classDef.declareField(of(PRIVATE, FINAL), "_spec", schemaClass);
+
+        // org.apache.ignite.configuration.tree.InnerNode#schemaType
+        addNodeSchemaTypeMethod(classDef, specField);
+
+        // Cached array of reflected fields. Helps to avoid unnecessary clonings.
+        Field[] schemaFields = schemaClass.getDeclaredFields();
+
+        // Define the rest of the fields.
+        Map<String, FieldDefinition> fieldDefs = new HashMap<>();
+
+        for (Field schemaField : schemaFields) {
+            assert isValue(schemaField) || isConfigValue(schemaField) || isNamedConfigValue(schemaField) : schemaField;
+
+            fieldDefs.put(schemaField.getName(), addNodeField(classDef, schemaField));
+        }
+
+        // Constructor.
+        addNodeConstructor(classDef, schemaClass, specField, schemaFields, fieldDefs);
+
+        // VIEW and CHANGE methods.
+        for (Field schemaField : schemaFields) {
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            addNodeViewMethod(classDef, schemaField, fieldDef);
+
+            addNodeChangeMethod(classDef, schemaField, fieldDef, schemaClassInfo);
+        }
+
+        // traverseChildren
+        addNodeTraverseChildrenMethod(classDef, schemaFields, fieldDefs);
+
+        // traverseChild
+        addNodeTraverseChildMethod(classDef, schemaFields, fieldDefs);
+
+        // construct
+        addNodeConstructMethod(classDef, schemaFields, fieldDefs);
+
+        // constructDefault
+        addNodeConstructDefaultMethod(classDef, specField, schemaFields, fieldDefs);
+
+        return classDef;
+    }
+
+    /**
+     * Checks whether configuration schema field represents primitive configuration value.
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents primitive configuration.
+     */
+    private static boolean isValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(Value.class);
+    }
+
+    /**
+     * Checks whether configuration schema field represents regular configuration value.
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents regular configuration.
+     */
+    private static boolean isConfigValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(ConfigValue.class);
+    }
+
+    /**
+     * Checks whether configuration schema field represents named list configuration value.
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents named list configuration.
+     */
+    private static boolean isNamedConfigValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(NamedConfigValue.class);
+    }
+
+    /**
+     * Add {@link InnerNode#schemaType()} method implementation to the class. It looks like the following code:
+     * <pre><code>
+     * public Class schemaType() {
+     *     return this._spec.getClass();
+     * }
+     * </code></pre>
+     * @param classDef Class definition.
+     * @param specField Field definition of the {@code _spec} field.
+     */
+    private void addNodeSchemaTypeMethod(ClassDefinition classDef, FieldDefinition specField) {
+        MethodDefinition schemaTypeMtd = classDef.declareMethod(of(PUBLIC), "schemaType", type(Class.class));
+
+        schemaTypeMtd.getBody().append(
+            schemaTypeMtd.getThis().getField(specField).invoke("getClass", Class.class)
+        ).retObject();
+    }
+
+    /**
+     * Declares field that corresponds to configuration value. Depending on the schema, 3 options possible:
+     * <ul>
+     *     <li>
+     *         {@code @Value public type fieldName}<br/>becomes<br/>
+     *         {@code private BoxedType fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
+     *         {@code private MyNode fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
+     *         {@code private NamedListNode fieldName}
+     *     </li>
+     * </ul>
+     * @param classDef Node class definition.
+     * @param schemaField Configuration Schema class field.
+     * @return Declared field definition.
+     */
+    private FieldDefinition addNodeField(ClassDefinition classDef, Field schemaField) {
+        Class<?> schemaFieldClass = schemaField.getType();
+
+        ParameterizedType nodeFieldType;
+
+        if (isValue(schemaField))
+            nodeFieldType = type(box(schemaFieldClass));
+        else if (isConfigValue(schemaField))
+            nodeFieldType = typeFromJavaClassName(schemasInfo.get(schemaFieldClass).nodeClassName);
+        else
+            nodeFieldType = type(NamedListNode.class);
+
+        return classDef.declareField(of(PRIVATE), schemaField.getName(), nodeFieldType);
+    }
+
+    /**
+     * Implements default constructor for the node class. It initializes {@code _spec} field and every other field
+     * that represents named list configuration.
+     * @param classDef Node class definition.
+     * @param schemaClass Configuration Schema class.
+     * @param specField Field definition for the {@code _spec} field of the node class.
+     * @param schemaFields Configuration Schema class fields.
+     * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
+     */
+    private void addNodeConstructor(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        FieldDefinition specField,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition ctor = classDef.declareConstructor(of(PUBLIC));
+
+        // super();
+        ctor.getBody().append(ctor.getThis()).invokeConstructor(InnerNode.class);
+
+        // this._spec = new MyConfigurationSchema();
+        ctor.getBody().append(ctor.getThis().setField(specField, newInstance(schemaClass)));
+
+        for (Field schemaField : schemaFields) {
+            if (!isNamedConfigValue(schemaField))
+                continue;
+
+            SchemaClassesInfo fieldClassNames = new SchemaClassesInfo(schemaField.getType());
+
+            // this.values = new NamedListNode<>(ValueNode::new);
+            ctor.getBody().append(ctor.getThis().setField(
+                fieldDefs.get(schemaField.getName()),
+                newInstance(NamedListNode.class, newNamedListElementLambda(fieldClassNames.nodeClassName))
+            ));
+        }
+
+        // return;
+        ctor.getBody().ret();
+    }
+
+    /**
+     * Implements getter method from {@code VIEW} interface. It returns field value, possibly unboxed or cloned,
+     * depending on type.
+     * @param classDef Node class definition.
+     * @param schemaField Configuration Schema class field.
+     * @param fieldDefs Field definition.
+     */
+    private void addNodeViewMethod(
+        ClassDefinition classDef,
+        Field schemaField,
+        FieldDefinition fieldDef
+    ) {
+        Class<?> schemaFieldType = schemaField.getType();
+
+        ParameterizedType returnType;
+
+        // Return type is either corresponding VIEW type or the same type as declared in schema.
+        if (isConfigValue(schemaField))
+            returnType = typeFromJavaClassName(schemasInfo.get(schemaFieldType).viewClassName);
+        else if (isNamedConfigValue(schemaField))
+            returnType = type(NamedListView.class);
+        else
+            returnType = type(schemaFieldType);
+
+        String fieldName = schemaField.getName();
+
+        MethodDefinition viewMtd = classDef.declareMethod(
+            of(PUBLIC),
+            fieldName,
+            returnType
+        );
+
+        // result = this.field;
+        viewMtd.getBody().append(viewMtd.getThis().getField(fieldDef));
+
+        // result = Box.boxValue(result); // Unboxing.
+        if (schemaFieldType.isPrimitive()) {
+            viewMtd.getBody().invokeVirtual(
+                box(schemaFieldType),
+                schemaFieldType.getSimpleName() + "Value",
+                schemaFieldType
+            );
+        }
+
+        // retuls = result.clone();
+        if (schemaFieldType.isArray())
+            viewMtd.getBody().invokeVirtual(schemaFieldType, "clone", Object.class).checkCast(schemaFieldType);
+
+        // return result;
+        viewMtd.getBody().ret(schemaFieldType);
+    }
+
+    /**
+     * Implements changer method from {@code CHANGE} interface.
+     * @param classDef Node class definition.
+     * @param schemaField Configuration Schema class field.
+     * @param fieldDef Field definition.
+     * @param schemaClassInfo {@link SchemaClassesInfo} for Configuration Schema that contains original field.
+     */
+    private void addNodeChangeMethod(
+        ClassDefinition classDef,
+        Field schemaField,
+        FieldDefinition fieldDef,
+        SchemaClassesInfo schemaClassInfo
+    ) {
+        Class<?> schemaFieldType = schemaField.getType();
+
+        MethodDefinition changeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "change" + capitalize(schemaField.getName()),
+            typeFromJavaClassName(schemaClassInfo.changeClassName),
+            // Change argument type is a Consumer for all inner or named fields.
+            arg("change", isValue(schemaField) ? type(schemaFieldType) : type(Consumer.class))
+        );
+
+        BytecodeBlock changeBody = changeMtd.getBody();
+
+        if (isValue(schemaField)) {
+            // newValue = change;
+            BytecodeExpression newValue = changeMtd.getScope().getVariable("change");
+
+            // newValue = Box.valueOf(newValue); // Boxing.
+            if (schemaFieldType.isPrimitive())
+                newValue = invokeStatic(fieldDef.getType(), "valueOf", fieldDef.getType(), singleton(newValue));
+
+            // newValue = newValue.clone();
+            if (schemaFieldType.isArray())
+                newValue = newValue.invoke("clone", Object.class).cast(schemaFieldType);
+
+            // this.field = newValue;
+            changeBody.append(changeMtd.getThis().setField(fieldDef, newValue));
+        }
+        else {
+            // @ConfigValue fields might be null and have to be initialized before passing into closure.
+            if (isConfigValue(schemaField)) {
+                // if (this.field == null) this.field = new ValueNode();
+                changeBody.append(new IfStatement()
+                    .condition(isNull(changeMtd.getThis().getField(fieldDef)))
+                    .ifTrue(changeMtd.getThis().setField(fieldDef, newInstance(fieldDef.getType())))
+                );
+            }
+
+            // change.accept(this.field);
+            changeBody.append(changeMtd.getScope().getVariable("change").invoke(
+                ACCEPT,
+                changeMtd.getThis().getField(fieldDef)
+            ));
+        }
+
+        // return this;
+        changeBody.append(changeMtd.getThis()).retObject();
+    }
+
+    /**
+     * Implements {@link InnerNode#traverseChildren(ConfigurationVisitor)} method.
+     * @param classDef Class definition.
+     * @param schemaFields Array of all schema fields that represent configurations.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     */
+    private void addNodeTraverseChildrenMethod(
+        ClassDefinition classDef,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition traverseChildrenMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "traverseChildren",
+            type(void.class),
+            arg("visitor", type(ConfigurationVisitor.class))
+        ).addException(NoSuchElementException.class);
+
+        BytecodeBlock mtdBody = traverseChildrenMtd.getBody();
+
+        for (Field schemaField : schemaFields) {
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            // Visit every field. Returned value isn't used so we pop it off the stack.
+            mtdBody.append(invokeVisit(traverseChildrenMtd, schemaField, fieldDef).pop());
+        }
+
+        mtdBody.ret();
+    }
+
+    /**
+     * Implements {@link InnerNode#traverseChild(String, ConfigurationVisitor)} method.
+     * @param classDef Class definition.
+     * @param schemaFields Array of all schema fields that represent configurations.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     */
+    private void addNodeTraverseChildMethod(
+        ClassDefinition classDef,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition traverseChildMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "traverseChild",
+            type(Object.class),
+            arg("key", type(String.class)),
+            arg("visitor", type(ConfigurationVisitor.class))
+        ).addException(NoSuchElementException.class);
+
+        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
+
+        // Here we have a switch by passed kay parameter.
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope())
+            .expression(keyVar);
+
+        for (Field schemaField : schemaFields) {
+            String fieldName = schemaField.getName();
+
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            // Visit result should be immediately returned.
+            switchBuilder.addCase(fieldName, invokeVisit(traverseChildMtd, schemaField, fieldDef).retObject());
+        }
+
+        // Default option is to throw "NoSuchElementException(key)".
+        switchBuilder.defaultCase(new BytecodeBlock()
+            .append(newInstance(NoSuchElementException.class, keyVar))
+            .throwObject()
+        );
+
+        // No default "return" statement required.
+        traverseChildMtd.getBody().append(switchBuilder.build());
+    }
+
+    /**
+     * Creates bytecode block that invokes one of {@link ConfigurationVisitor}'s methods.
+     * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor)} or
+     *      {@link InnerNode#traverseChild(String, ConfigurationVisitor)} defined in {@code *Node} class.
+     * @param schemaField Configuration Schema field to visit.
+     * @param fieldDef Field definition from current class.
+     * @return Bytecode block that invokes "visit*" method.
+     */
+    private BytecodeBlock invokeVisit(MethodDefinition mtd, Field schemaField, FieldDefinition fieldDef) {
+        Method visitMethod;
+
+        if (isValue(schemaField))
+            visitMethod = VISIT_LEAF;
+        else if (isConfigValue(schemaField))
+            visitMethod = VISIT_INNER;
+        else
+            visitMethod = VISIT_NAMED;
+
+        return new BytecodeBlock().append(mtd.getScope().getVariable("visitor").invoke(
+            visitMethod,
+            constantString(fieldDef.getName()),
+            mtd.getThis().getField(fieldDef)
+        ));
+    }
+
+    /**
+     * Implements {@link ConstructableTreeNode#construct(String, ConfigurationSource)} method.
+     * @param classDef Class definition.
+     * @param schemaFields Array of all schema fields that represent configurations.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     */
+    private void addNodeConstructMethod(
+        ClassDefinition classDef,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition constructMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "construct",
+            type(void.class),
+            arg("key", String.class),
+            arg("src", ConfigurationSource.class)
+        ).addException(NoSuchElementException.class);
+
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope())
+            .expression(keyVar);
+
+        for (Field schemaField : schemaFields) {
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            BytecodeBlock caseClause = new BytecodeBlock();
+
+            switchBuilder.addCase(schemaField.getName(), caseClause);
+
+            // this.field = src == null ? null : src.unwrap(FieldType.class);
+            if (isValue(schemaField)) {
+                caseClause.append(constructMtd.getThis().setField(fieldDef, inlineIf(
+                    isNull(srcVar),
+                    constantNull(fieldDef.getType()),
+                    srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
+                )));
+            }
+            // this.field = src == null ? null : src.descend(field = (field == null ? new FieldType() : field.copy()));
+            else if (isConfigValue(schemaField)) {
+                caseClause.append(new IfStatement()
+                    .condition(isNull(srcVar))
+                    .ifTrue(constructMtd.getThis().setField(fieldDef, constantNull(fieldDef.getType())))
+                    .ifFalse(new BytecodeBlock()
+                        .append(constructMtd.getThis().setField(fieldDef, inlineIf(
+                            isNull(constructMtd.getThis().getField(fieldDef)),
+                            newInstance(fieldDef.getType()),
+                            constructMtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
+                        )))
+                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
+                    )
+                );
+            }
+            // this.field = src == null ? new NamedListNode(ValueNode::new) : src.descend(field = field.copy()));
+            else {
+                String fieldNodeClassName = schemasInfo.get(schemaField.getType()).nodeClassName;
+
+                caseClause.append(new IfStatement()
+                    .condition(isNull(srcVar))
+                    .ifTrue(constructMtd.getThis().setField(
+                        fieldDef,
+                        newInstance(NamedListNode.class, newNamedListElementLambda(fieldNodeClassName))
+                    ))
+                    .ifFalse(new BytecodeBlock()
+                        .append(constructMtd.getThis().setField(
+                            fieldDef,
+                            constructMtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
+                        ))
+                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
+                    )
+                );
+            }
+        }
+
+        // Default option is to throw "NoSuchElementException(key)".
+        switchBuilder.defaultCase(new BytecodeBlock()
+            .append(newInstance(NoSuchElementException.class, keyVar))
+            .throwObject()
+        );
+
+        constructMtd.getBody().append(switchBuilder.build()).ret();
+    }
+
+    /**
+     * Implements {@link InnerNode#constructDefault(String)} method.
+     * @param classDef Node class definition.
+     * @param specField Field definition for the {@code _spec} field of the node class.
+     * @param schemaFields Configuration Schema class fields.
+     * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
+     */
+    private void addNodeConstructDefaultMethod(
+        ClassDefinition classDef,
+        FieldDefinition specField,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition constructDfltMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "constructDefault",
+            type(void.class),
+            arg("key", String.class)
+        ).addException(NoSuchElementException.class);
+
+        Variable keyVar = constructDfltMtd.getScope().getVariable("key");
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope())
+            .expression(keyVar);
+
+        for (Field schemaField : schemaFields) {
+            if (!isValue(schemaField))
+                continue;
+
+            if (!schemaField.getAnnotation(Value.class).hasDefault()) {
+                switchBuilder.addCase(schemaField.getName(), new BytecodeBlock());
+
+                continue;
+            }
+
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            Class<?> schemaFieldType = schemaField.getType();
+
+            // defaultValue = _spec.field;
+            BytecodeExpression defaultValue = constructDfltMtd.getThis().getField(specField).getField(schemaField);
+
+            // defaultValue = Box.valueOf(defaultValue); // Boxing.
+            if (schemaFieldType.isPrimitive()) {
+                defaultValue = invokeStatic(
+                    fieldDef.getType(),
+                    "valueOf",
+                    fieldDef.getType(),
+                    singleton(defaultValue)
+                );
+            }
+
+            // defaultValue = defaultValue.clone();
+            if (schemaFieldType.isArray())
+                defaultValue = defaultValue.invoke("clone", Object.class).cast(schemaFieldType);
+
+            // this.field = defaultValue;
+            BytecodeBlock caseClause = new BytecodeBlock()
+                .append(constructDfltMtd.getThis().setField(fieldDef, defaultValue));
+
+            switchBuilder.addCase(schemaField.getName(), caseClause);
+        }
+
+        // Default option is to throw "NoSuchElementException(key)".
+        switchBuilder.defaultCase(new BytecodeBlock()
+            .append(newInstance(NoSuchElementException.class, keyVar))
+            .throwObject()
+        );
+
+        constructDfltMtd.getBody().append(switchBuilder.build()).ret();
+    }
+
+    /**
+     * Creates {@code *Node::new} lambda expression with {@link Supplier} type.
+     * @param nodeClassName Name of the {@code *Node} class.
+     * @return InvokeDynamic bytecode expression.
+     */
+    @NotNull private BytecodeExpression newNamedListElementLambda(String nodeClassName) {
+        return invokeDynamic(
+            LAMBDA_METAFACTORY,
+            Arrays.asList(
+                getMethodType(getType(Object.class)),
+                new Handle(
+                    H_NEWINVOKESPECIAL,
+                    internalName(nodeClassName),
+                    "<init>",
+                    getMethodDescriptor(Type.VOID_TYPE),
+                    false
+                ),
+                getMethodType(typeFromJavaClassName(nodeClassName).getAsmType())
+            ),
+            "get",
+            methodType(Supplier.class)
+        );
+    }
+
+    private ClassDefinition createCfgImplClass(Class<?> schemaClass) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+        // Configuration impl class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(schemaClassInfo.cfgImplClassName),
+            type(DynamicConfiguration.class),
+            typeFromJavaClassName(schemaClassInfo.cfgClassName)
+        );
+
+        // Cached array of reflected fields. Helps to avoid unnecessary clonings.
+        Field[] schemaFields = schemaClass.getDeclaredFields();
+
+        // Fields.
+        Map<String, FieldDefinition> fieldDefs = new HashMap<>();
+
+        for (Field schemaField : schemaFields)
+            fieldDefs.put(schemaField.getName(), addConfigurationImplField(classDef, schemaField));
+
+        // Constructor
+        addConfigurationImplConstructor(classDef, schemaClassInfo, schemaFields, fieldDefs);
+
+        for (Field schemaField : schemaFields) {
+            addConfigurationImplGetMethod(classDef, schemaClass, fieldDefs, schemaField);
+        }
+
+        return classDef;
+    }
+
+    /**
+     * Declares field that corresponds to configuration value. Depending on the schema, 3 options possible:
+     * <ul>
+     *     <li>
+     *         {@code @Value public type fieldName}<br/>becomes<br/>
+     *         {@code private DynamicProperty fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
+     *         {@code private MyConfiguration fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
+     *         {@code private NamedListConfiguration fieldName}
+     *     </li>
+     * </ul>
+     * @param classDef Configuration impl class definition.
+     * @param schemaField Configuration Schema class field.
+     * @return Declared field definition.
+     */
+    private FieldDefinition addConfigurationImplField(ClassDefinition classDef, Field schemaField) {
+        ParameterizedType fieldType;
+
+        if (isConfigValue(schemaField))
+            fieldType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).cfgClassName);
+        else if (isNamedConfigValue(schemaField))
+            fieldType = type(NamedListConfiguration.class);
+        else
+            fieldType = type(DynamicProperty.class);
+
+        return classDef.declareField(of(PRIVATE), schemaField.getName(), fieldType);
+    }
+
+    /**
+     * Implements default constructor for the configuration class. It initializes all fields and adds them to members
+     * collection.
+     * @param classDef Configuration impl class definition.
+     * @param schemaClassInfo Configuration Schema class info.
+     * @param schemaFields Configuration Schema class fields.
+     * @param fieldDefs Field definitions for all fields of configuration impl class.
+     */
+    private void addConfigurationImplConstructor(
+        ClassDefinition classDef,
+        SchemaClassesInfo schemaClassInfo,
+        Field[] schemaFields,
+        Map<String, FieldDefinition> fieldDefs
+    ) {
+        MethodDefinition ctor = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("prefix", List.class),
+            arg("key", String.class),
+            arg("rootKey", RootKey.class),
+            arg("changer", ConfigurationChanger.class)
+        );
+
+        BytecodeBlock ctorBody = ctor.getBody()
+            .append(ctor.getThis())
+            .append(ctor.getScope().getVariable("prefix"))
+            .append(ctor.getScope().getVariable("key"))
+            .append(ctor.getScope().getVariable("rootKey"))
+            .append(ctor.getScope().getVariable("changer"))
+            .invokeConstructor(DYNAMIC_CONFIGURATION_CTOR);
+
+        int newIdx = 0;
+        for (Field schemaField : schemaFields) {
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            BytecodeExpression newValue;
+
+            if (isValue(schemaField)) {
+                // newValue = new DynamicProperty(super.keys, fieldName, rootKey, changer);
+                newValue = newInstance(
+                    DynamicProperty.class,
+                    ctor.getThis().getField("keys", List.class),
+                    constantString(schemaField.getName()),
+                    ctor.getScope().getVariable("rootKey"),
+                    ctor.getScope().getVariable("changer")
+                );
+            }
+            else {
+                SchemaClassesInfo fieldInfo = schemasInfo.get(schemaField.getType());
+
+                ParameterizedType cfgImplParameterizedType = typeFromJavaClassName(fieldInfo.cfgImplClassName);
+
+                if (isConfigValue(schemaField)) {
+                    // newValue = new MyConfigurationImpl(super.keys, fieldName, rootKey, changer);
+                    newValue = newInstance(
+                        cfgImplParameterizedType,
+                        ctor.getThis().getField("keys", List.class),
+                        constantString(schemaField.getName()),
+                        ctor.getScope().getVariable("rootKey"),
+                        ctor.getScope().getVariable("changer")
+                    );
+                }
+                else {
+                    // We have to create method "$new$<idx>" to reference it in lambda expression. That's the way it
+                    // works, it'll invoke constructor with all 4 arguments, not just 2 as in BiFunction.
+                    MethodDefinition newMtd = classDef.declareMethod(
+                        of(PRIVATE, STATIC, SYNTHETIC),
+                        "$new$" + newIdx++,
+                        typeFromJavaClassName(fieldInfo.cfgClassName),
+                        arg("rootKey", RootKey.class),
+                        arg("changer", ConfigurationChanger.class),
+                        arg("prefix", List.class),
+                        arg("key", String.class)
+                    );
+
+                    // newValue = new NamedListConfiguration(super.keys, fieldName, rootKey, changer, (p, k) ->
+                    //     new ValueConfigurationImpl(p, k, rootKey, changer)
+                    // );
+                    newValue = newInstance(
+                        NamedListConfiguration.class,
+                        ctor.getThis().getField("keys", List.class),
+                        constantString(schemaField.getName()),
+                        ctor.getScope().getVariable("rootKey"),
+                        ctor.getScope().getVariable("changer"),
+                        invokeDynamic(
+                            LAMBDA_METAFACTORY,
+                            Arrays.asList(
+                                getMethodType(getType(Object.class), getType(Object.class), getType(Object.class)),
+                                new Handle(
+                                    Opcodes.H_INVOKESTATIC,
+                                    internalName(schemaClassInfo.cfgImplClassName),
+                                    newMtd.getName(),
+                                    newMtd.getMethodDescriptor(),
+                                    false
+                                ),
+                                getMethodType(
+                                    typeFromJavaClassName(fieldInfo.cfgClassName).getAsmType(),
+                                    getType(List.class),
+                                    getType(String.class)
+                                )
+                            ),
+                            "apply",
+                            BiFunction.class,
+                            ctor.getScope().getVariable("rootKey"),
+                            ctor.getScope().getVariable("changer")
+                        )
+                    );
+
+                    newMtd.getBody()
+                        .append(newInstance(
+                            cfgImplParameterizedType,
+                            newMtd.getScope().getVariable("prefix"),
+                            newMtd.getScope().getVariable("key"),
+                            newMtd.getScope().getVariable("rootKey"),
+                            newMtd.getScope().getVariable("changer")
+                        ))
+                        .retObject();
+                }
+            }
+
+            // this.field = newValue;
+            ctorBody.append(ctor.getThis().setField(fieldDef, newValue));
+
+            // add(this.field);
+            ctorBody.append(ctor.getThis().invoke(DYNAMIC_CONFIGURATION_ADD, ctor.getThis().getField(fieldDef)));
+        }
+
+        ctorBody.ret();
+    }
+
+    /**
+     * Implements accessor method in configuration impl class.
+     * @param classDef Configuration impl class definition.
+     * @param schemaClass Configuration Schema class.
+     * @param fieldDefs Field definitions for all fields of configuration impl class.
+     * @param schemaField Configuration Schema class field.
+     */
+    private void addConfigurationImplGetMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Map<String, FieldDefinition> fieldDefs,
+        Field schemaField
+    ) {
+        Class<?> schemaFieldType = schemaField.getType();
+
+        String fieldName = schemaField.getName();
+        FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+        ParameterizedType returnType;
+
+        if (isConfigValue(schemaField))
+            returnType = typeFromJavaClassName(new SchemaClassesInfo(schemaFieldType).cfgClassName);
+        else if (isNamedConfigValue(schemaField))
+            returnType = type(NamedConfigurationTree.class);
+        else {
+            assert isValue(schemaField) : schemaClass;
+
+            returnType = type(ConfigurationValue.class);
+        }
+
+        MethodDefinition viewMtd = classDef.declareMethod(
+            of(PUBLIC),
+            fieldName,
+            returnType
+        );
+
+        BytecodeBlock viewBody = viewMtd.getBody();
+
+        viewBody
+            .append(viewMtd.getThis())
+            .getField(fieldDef)
+            .retObject();
+    }
+
+    /**
+     * Replaces first letter in string with its upper-cased variant.
+     * @param name Some string.
+     * @return Capitalized version of passed string.
+     */
+    private static String capitalize(String name) {
+        return name.substring(0, 1).toUpperCase() + name.substring(1);
+    }
+
+    /**
+     * Returns internalized version of class name, replacing dots with slashes.
+     * @param className Class name (with package).
+     * @return Internal class name.
+     * @see Type#getInternalName(java.lang.Class)
+     */
+    @NotNull private static String internalName(String className) {
+        return className.replace('.', '/');
+    }
+
+    /**
+     * Creates boxed version of the class. Types that it can box: {@code boolean}, {@code int}, {@code long} and
+     *      {@code double}. Other primitive types are not supported by configuration framework.
+     * @param clazz Maybe primitive class.
+     * @return Not primitive class that represents parameter class.
+     */
+    private static Class<?> box(Class<?> clazz) {
+        if (!clazz.isPrimitive())
+            return clazz;
+
+        switch (clazz.getName()) {
+            case "boolean":
+                return Boolean.class;
+
+            case "int":
+                return Integer.class;
+
+            case "long":
+                return Long.class;
+
+            default:
+                assert clazz == double.class;
+
+                return Double.class;
+        }
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/SchemaClassesInfo.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/SchemaClassesInfo.java
new file mode 100644
index 0000000..1edb423
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/SchemaClassesInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.asm;
+
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.tree.InnerNode;
+
+/**
+ * Class to cache compiled classes and hold precalculated names to reference other existing classes.
+ */
+class SchemaClassesInfo {
+    /** Configuration Schema class. */
+    public final Class<?> schemaClass;
+
+    /** Class name for the VIEW class. */
+    public final String viewClassName;
+
+    /** Class name for the CHANGE class. */
+    public final String changeClassName;
+
+    /** Class name for the Configuration class. */
+    public final String cfgClassName;
+
+    /** Class name for the Node class. */
+    public final String nodeClassName;
+
+    /** Class name for the Configuration Impl class. */
+    public final String cfgImplClassName;
+
+    /** Node class instance. */
+    public Class<? extends InnerNode> nodeClass;
+
+    /** Configuration Impl class instance. */
+    public Class<? extends DynamicConfiguration<?, ?>> cfgImplClass;
+
+    /**
+     * @param schemaClass Configuration Schema class instance.
+     */
+    SchemaClassesInfo(Class<?> schemaClass) {
+        this.schemaClass = schemaClass;
+        String schemaClassName = schemaClass.getPackageName() + "." + schemaClass.getSimpleName(); // Support inner classes.
+
+        String prefix = schemaClassName.replaceAll("ConfigurationSchema$", "");
+
+        viewClassName = prefix + "View";
+        changeClassName = prefix + "Change";
+        cfgClassName = prefix + "Configuration";
+
+        nodeClassName = prefix + "Node";
+        cfgImplClassName = prefix + "ConfigurationImpl";
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/StringSwitchBuilder.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/StringSwitchBuilder.java
new file mode 100644
index 0000000..67cab54
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/asm/StringSwitchBuilder.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.asm;
+
+import com.facebook.presto.bytecode.BytecodeBlock;
+import com.facebook.presto.bytecode.BytecodeNode;
+import com.facebook.presto.bytecode.Scope;
+import com.facebook.presto.bytecode.Variable;
+import com.facebook.presto.bytecode.control.IfStatement;
+import com.facebook.presto.bytecode.control.SwitchStatement.SwitchBuilder;
+import com.facebook.presto.bytecode.expression.BytecodeExpression;
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.facebook.presto.bytecode.BytecodeUtils.checkState;
+import static com.facebook.presto.bytecode.control.SwitchStatement.switchBuilder;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantInt;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.constantString;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.IntStream.range;
+
+/**
+ * Class similar to {@link SwitchBuilder} but it allows to use {@link String}s as switch keys.
+ */
+class StringSwitchBuilder {
+    /** {@link Object#equals(Object)} */
+    private static final Method EQUALS;
+
+    /** {@link Object#hashCode()} */
+    private static final Method HASH_CODE;
+
+    static {
+        try {
+            EQUALS = Object.class.getDeclaredMethod("equals", Object.class);
+
+            HASH_CODE = Object.class.getDeclaredMethod("hashCode");
+        }
+        catch (NoSuchMethodException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    /** Expression to be used for matching. */
+    private BytecodeExpression expression;
+
+    /** Set of case statements in order of appearance. */
+    private final Set<CaseStatement> cases = new LinkedHashSet<>();
+
+    /** Default section for the switch. */
+    private BytecodeNode defaultBody;
+
+    /** Scope to create temp variables. */
+    private final Scope scope;
+
+    /**
+     * @param scope Scope to create temp variables.
+     */
+    StringSwitchBuilder(Scope scope) {
+        this.scope = scope;
+    }
+
+    /**
+     * @param expression Expression to be used for matching.
+     * @return {@code this} for chaining.
+     */
+    public StringSwitchBuilder expression(BytecodeExpression expression) {
+        this.expression = expression;
+        return this;
+    }
+
+    /**
+     * @param key Key for matching.
+     * @param body Case statement.
+     * @return {@code this} for chaining.
+     */
+    public StringSwitchBuilder addCase(String key, BytecodeNode body) {
+        CaseStatement statement = new CaseStatement(key, body);
+
+        checkState(cases.add(statement), "case already exists for value [%s]", key);
+
+        return this;
+    }
+
+    /**
+     * @param body Default statement.
+     * @return {@code this} for chaining.
+     */
+    public StringSwitchBuilder defaultCase(BytecodeNode body) {
+        checkState(defaultBody == null, "default case already set");
+        this.defaultBody = requireNonNull(body, "body is null");
+        return this;
+    }
+
+    /**
+     * Produces "compiled" switch statement, ready to be inserted into method.
+     * @return Bytecode block.
+     */
+    public BytecodeBlock build() {
+        checkState(expression != null, "expression is not set");
+
+        // Variable to cache the expression string.
+        Variable expVar = scope.createTempVariable(String.class);
+
+        // Variable to store integer index corresponding to the matched string.
+        Variable idxVar = scope.createTempVariable(int.class);
+
+        BytecodeBlock res = new BytecodeBlock()
+            .append(expVar.set(expression)) // expVar = evaluate(expression);
+            .append(idxVar.set(constantInt(-1))); // idxVar = -1;
+
+        BytecodeNode[] caseBodies = new BytecodeNode[cases.size()];
+
+        // Here we are preparing case statements for the first switch. It'll look like this:
+        // switch (expVar.hashCode()) {
+        //     case <hash1>:
+        //         if (expVar.equals("a")) idxVar = 0;
+        //     case <hash2>:
+        //         if (expVar.equals("b")) idxVar = 1;
+        //         if (expVar.equals("c")) idxVar = 2;
+        //     ...
+        // }
+        SwitchBuilder hashSwitch = switchBuilder()
+            .expression(expVar.invoke(HASH_CODE));
+
+        // Case for each hash value may have multiple matching strings due to collisions.
+        Map<Integer, List<CaseStatement>> groupedCases = cases.stream().collect(
+            groupingBy(statement -> statement.key.hashCode())
+        );
+
+        int idx = 0;
+        for (Map.Entry<Integer, List<CaseStatement>> entry : groupedCases.entrySet()) {
+            BytecodeBlock caseBody = new BytecodeBlock();
+
+            for (CaseStatement caseStmt : entry.getValue()) {
+                caseBody.append(new IfStatement()
+                    .condition(expVar.invoke(EQUALS, constantString(caseStmt.key)))
+                    .ifTrue(idxVar.set(constantInt(idx)))
+                );
+
+                caseBodies[idx++] = caseStmt.body;
+            }
+
+            hashSwitch.addCase(entry.getKey(), caseBody);
+        }
+
+        // No default statement required because "idxVar" was preemptively assigned to -1.
+        res.append(hashSwitch.build());
+
+        // Here's the actual switch by "idxVar" variable that uses user-defined "case" and "default" clauses.
+        res.append(range(0, caseBodies.length).boxed()
+            .reduce(
+                switchBuilder().expression(idxVar),
+                (builder, i) -> builder.addCase(i, caseBodies[i]),
+                (b0, b1) -> b0 // Won't be used by sequential stream but required to be non-null.
+            )
+            .defaultCase(defaultBody)
+            .build()
+        );
+
+        return res;
+    }
+
+    /**
+     * Case statement class for the builder.
+     */
+    private static class CaseStatement {
+        /** String key of the case statement. */
+        private final String key;
+
+        /** Body of the case statement. */
+        private final BytecodeNode body;
+
+        /**
+         * @param key String key of the case statement.
+         * @param body Body of the case statement.
+         */
+        CaseStatement(String key, BytecodeNode body) {
+            this.key = key;
+            this.body = requireNonNull(body, "body is null");
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return key.hashCode();
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+
+            CaseStatement other = (CaseStatement)obj;
+            return Objects.equals(this.key, other.key);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return getClass().getSimpleName() + "[key=" + key + ']';
+        }
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
index 833b84c..b199b43 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/util/ConfigurationUtil.java
@@ -595,7 +595,7 @@ public class ConfigurationUtil {
 
         /** {@inheritDoc} */
         @Override public void descend(ConstructableTreeNode dstNode) {
-            assert srcNode.getClass() == dstNode.getClass();
+            assert srcNode.getClass() == dstNode.getClass() : srcNode.getClass() + " : " + dstNode.getClass();
 
             srcNode.traverseChildren(new ConfigurationVisitor<>() {
                 @Override public Void visitLeafNode(String key, Serializable val) {
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
index 44b77d8..5cad648 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/ConstructableTreeNode.java
@@ -29,7 +29,7 @@ public interface ConstructableTreeNode {
      * @param src Source that provides data for construction.
      * @throws NoSuchElementException If {@code key} cannot be constructed.
      */
-    void construct(String key,/* boolean canMutate, */ ConfigurationSource src) throws NoSuchElementException;
+    void construct(String key, ConfigurationSource src) throws NoSuchElementException;
 
     /**
      * Public equivalent of {@link Object#clone()} method. Creates a copy with effectively the same content.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
index b21df65..605ac4b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/tree/InnerNode.java
@@ -117,10 +117,9 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
      * Assigns default value to the corresponding leaf. Defaults are gathered from configuration schema class.
      *
      * @param fieldName Field name to be initialized.
-     * @return {@code true} if default value has been assigned, {@code false} otherwise.
      * @throws NoSuchElementException If there's no such field or it is not a leaf value.
      */
-    public abstract boolean constructDefault(String fieldName) throws NoSuchElementException;
+    public abstract void constructDefault(String fieldName) throws NoSuchElementException;
 
     /**
      * @return Class of corresponding configuration schema.
diff --git a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ITMetaStorageServiceTest.java b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ITMetaStorageServiceTest.java
index 3689696..8125a18 100644
--- a/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ITMetaStorageServiceTest.java
+++ b/modules/metastorage-client/src/integrationTest/java/org/apache/ignite/internal/metastorage/client/ITMetaStorageServiceTest.java
@@ -43,7 +43,7 @@ import org.apache.ignite.network.internal.recovery.message.HandshakeStartMessage
 import org.apache.ignite.network.internal.recovery.message.HandshakeStartMessageSerializationFactory;
 import org.apache.ignite.network.internal.recovery.message.HandshakeStartResponseMessage;
 import org.apache.ignite.network.internal.recovery.message.HandshakeStartResponseMessageSerializationFactory;
-import org.apache.ignite.network.scalecube.ScaleCubeClusterServiceFactory;
+import org.apache.ignite.network.scalecube.TestScaleCubeClusterServiceFactory;
 import org.apache.ignite.network.scalecube.message.ScaleCubeMessage;
 import org.apache.ignite.network.scalecube.message.ScaleCubeMessageSerializationFactory;
 import org.apache.ignite.network.serialization.MessageSerializationRegistry;
@@ -94,7 +94,7 @@ public class ITMetaStorageServiceTest {
     private static final RaftClientMessageFactory FACTORY = new RaftClientMessageFactoryImpl();
 
     /** Network factory. */
-    private static final ClusterServiceFactory NETWORK_FACTORY = new ScaleCubeClusterServiceFactory();
+    private static final ClusterServiceFactory NETWORK_FACTORY = new TestScaleCubeClusterServiceFactory();
 
     /** */
     private static final MessageSerializationRegistry SERIALIZATION_REGISTRY = new MessageSerializationRegistry()
diff --git a/modules/vault/src/test/java/org/apache/ignite/internal/vault/impl/VaultBaseContractsTest.java b/modules/vault/src/test/java/org/apache/ignite/internal/vault/impl/VaultBaseContractsTest.java
index 3a11a4d..3d5aee8 100644
--- a/modules/vault/src/test/java/org/apache/ignite/internal/vault/impl/VaultBaseContractsTest.java
+++ b/modules/vault/src/test/java/org/apache/ignite/internal/vault/impl/VaultBaseContractsTest.java
@@ -185,7 +185,7 @@ public class VaultBaseContractsTest {
         for (int i = 3; i < 7; i++)
             vaultManager.put(getKey(i), ("new" + i).getBytes());
 
-        assertTrue(counter.await(10, TimeUnit.MILLISECONDS));
+        assertTrue(counter.await(1, TimeUnit.SECONDS));
     }
 
     /**