diff --git a/core/includes/config.inc b/core/includes/config.inc
index 00115335d1986c733e9ae505fe63bd4fa3cd803a..1453adeb8a005ddca63c4b56bc9a06e856aa642a 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -253,12 +253,10 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
   // handle dependencies correctly.
   foreach (array('delete', 'create', 'change') as $op) {
     foreach ($config_changes[$op] as $key => $name) {
-      // Extract owner from configuration object name.
-      $module = strtok($name, '.');
-      // Check whether the module implements hook_config_import() and ask it to
-      // handle the configuration change.
+      // Call to the configuration entity's storage controller to handle the
+      // configuration change.
       $handled_by_module = FALSE;
-      if (module_exists($module) && module_hook($module, 'config_import_' . $op)) {
+      if ($entity_type = config_get_entity_type_by_name($name)) {
         $old_config = new Config($name, $target_storage);
         $old_config->load();
 
@@ -268,7 +266,8 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
           $new_config->setData($data);
         }
 
-        $handled_by_module = module_invoke($module, 'config_import_' . $op, $name, $new_config, $old_config);
+        $method = 'import' . ucfirst($op);
+        $handled_by_module = entity_get_controller($entity_type)->$method($name, $new_config, $old_config);
       }
       if (!empty($handled_by_module)) {
         unset($config_changes[$op][$key]);
@@ -297,3 +296,19 @@ function config_get_module_config_entities($module) {
     return ($entity_info['module'] == $module) && is_subclass_of($entity_info['class'], 'Drupal\Core\Config\Entity\ConfigEntityInterface');
   });
 }
+
+/**
+ * Returns the entity type of a configuration object.
+ *
+ * @param string $name
+ *   The configuration object name.
+ *
+ * @return string|null
+ *   Either the entity type name, or NULL if none match.
+ */
+function config_get_entity_type_by_name($name) {
+  $entities = array_filter(entity_get_info(), function($entity_info) use ($name) {
+    return (isset($entity_info['config_prefix']) && strpos($name, $entity_info['config_prefix'] . '.') === 0);
+  });
+  return key($entities);
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 7f60f9d6d6b899d7e912aafcb2de06c2b849c561..b472b8f525efa886ffaf8f6996b81286d3ec9b9e 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Config\Config;
 
 /**
  * Defines the storage controller class for configuration entities.
@@ -425,4 +426,76 @@ protected function invokeHook($hook, EntityInterface $entity) {
   public function getQueryServicename() {
     throw new \LogicException('Querying configuration entities is not supported.');
   }
+
+  /**
+   * Create configuration upon synchronizing configuration changes.
+   *
+   * This callback is invoked when configuration is synchronized between storages
+   * and allows a module to take over the synchronization of configuration data.
+   *
+   * @param string $name
+   *   The name of the configuration object.
+   * @param \Drupal\Core\Config\Config $new_config
+   *   A configuration object containing the new configuration data.
+   * @param \Drupal\Core\Config\Config $old_config
+   *   A configuration object containing the old configuration data.
+   */
+  public function importCreate($name, Config $new_config, Config $old_config) {
+    $entity = $this->create($new_config->get());
+    $entity->save();
+    return TRUE;
+  }
+
+  /**
+   * Update configuration upon synchronizing configuration changes.
+   *
+   * This callback is invoked when configuration is synchronized between storages
+   * and allows a module to take over the synchronization of configuration data.
+   *
+   * @param string $name
+   *   The name of the configuration object.
+   * @param \Drupal\Core\Config\Config $new_config
+   *   A configuration object containing the new configuration data.
+   * @param \Drupal\Core\Config\Config $old_config
+   *   A configuration object containing the old configuration data.
+   */
+  public function importChange($name, Config $new_config, Config $old_config) {
+    list(, , $id) = explode('.', $name);
+    $entities = $this->load(array($id));
+    $entity = $entities[$id];
+    $entity->original = clone $entity;
+
+    foreach ($old_config->get() as $property => $value) {
+      $entity->original->$property = $value;
+    }
+
+    foreach ($new_config->get() as $property => $value) {
+      $entity->$property = $value;
+    }
+
+    $entity->save();
+    return TRUE;
+  }
+
+  /**
+   * Delete configuration upon synchronizing configuration changes.
+   *
+   * This callback is invoked when configuration is synchronized between storages
+   * and allows a module to take over the synchronization of configuration data.
+   *
+   * @param string $name
+   *   The name of the configuration object.
+   * @param \Drupal\Core\Config\Config $new_config
+   *   A configuration object containing the new configuration data.
+   * @param \Drupal\Core\Config\Config $old_config
+   *   A configuration object containing the old configuration data.
+   */
+  public function importDelete($name, Config $new_config, Config $old_config) {
+    list(, , $id) = explode('.', $name);
+    $entities = $this->load(array($id));
+    $entity = $entities[$id];
+    $entity->delete();
+    return TRUE;
+  }
+
 }
diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php
index d84566712a9600531960e166de672e8884b8c96f..7b06336bf3923220fdff6e7c85a97de647930247 100644
--- a/core/modules/config/config.api.php
+++ b/core/modules/config/config.api.php
@@ -11,116 +11,3 @@
  * @todo Overall description of the configuration system.
  * @}
  */
-
-/**
- * Create configuration upon synchronizing configuration changes.
- *
- * This callback is invoked when configuration is synchronized between storages
- * and allows a module to take over the synchronization of configuration data.
- *
- * Modules should implement this callback if they manage configuration data
- * (such as image styles, node types, or fields) which needs to be
- * prepared and passed through module API functions to properly handle a
- * configuration change.
- *
- * @param string $name
- *   The name of the configuration object.
- * @param Drupal\Core\Config\Config $new_config
- *   A configuration object containing the new configuration data.
- * @param Drupal\Core\Config\Config $old_config
- *   A configuration object containing the old configuration data.
- */
-function hook_config_import_create($name, $new_config, $old_config) {
-  // Only configuration entities require custom handling. Any other module
-  // settings can be synchronized directly.
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-  $config_test = entity_create('config_test', $new_config->get());
-  $config_test->save();
-  return TRUE;
-}
-
-/**
- * Update configuration upon synchronizing configuration changes.
- *
- * This callback is invoked when configuration is synchronized between storages
- * and allows a module to take over the synchronization of configuration data.
- *
- * Modules should implement this callback if they manage configuration data
- * (such as image styles, node types, or fields) which needs to be
- * prepared and passed through module API functions to properly handle a
- * configuration change.
- *
- * @param string $name
- *   The name of the configuration object.
- * @param Drupal\Core\Config\Config $new_config
- *   A configuration object containing the new configuration data.
- * @param Drupal\Core\Config\Config $old_config
- *   A configuration object containing the old configuration data.
- */
-function hook_config_import_change($name, $new_config, $old_config) {
-  // Only configuration entities require custom handling. Any other module
-  // settings can be synchronized directly.
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-
-  // @todo Make this less ugly.
-  list($entity_type) = explode('.', $name);
-  $entity_info = entity_get_info($entity_type);
-  $id = substr($name, strlen($entity_info['config_prefix']) + 1);
-  $config_test = entity_load('config_test', $id);
-
-  // Store the original config, and iterate through each property to store it.
-  $config_test->original = clone $config_test;
-  foreach ($old_config->get() as $property => $value) {
-    $config_test->original->$property = $value;
-  }
-
-  // Iterate through each property of the new config, copying it to the test
-  // object.
-  foreach ($new_config->get() as $property => $value) {
-    $config_test->$property = $value;
-  }
-
-  $config_test->save();
-  return TRUE;
-}
-
-/**
- * Delete configuration upon synchronizing configuration changes.
- *
- * This callback is invoked when configuration is synchronized between storages
- * and allows a module to take over the synchronization of configuration data.
- *
- * Modules should implement this callback if they manage configuration data
- * (such as image styles, node types, or fields) which needs to be
- * prepared and passed through module API functions to properly handle a
- * configuration change.
- *
- * @param string $name
- *   The name of the configuration object.
- * @param Drupal\Core\Config\Config $new_config
- *   A configuration object containing the new configuration data.
- * @param Drupal\Core\Config\Config $old_config
- *   A configuration object containing the old configuration data.
- */
-function hook_config_import_delete($name, $new_config, $old_config) {
-  // Only configuration entities require custom handling. Any other module
-  // settings can be synchronized directly.
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-  // @todo image_style_delete() supports the notion of a "replacement style"
-  //   to be used by other modules instead of the deleted style. Essential!
-  //   But that is impossible currently, since the config system only knows
-  //   about deleted and added changes. Introduce an 'old_ID' key within
-  //   config objects as a standard?
-  list($entity_type) = explode('.', $name);
-  $entity_info = entity_get_info($entity_type);
-  $id = substr($name, strlen($entity_info['config_prefix']) + 1);
-  config_test_delete($id);
-  return TRUE;
-}
-
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 96d4aa0288ae203f37a8e63430c849fc18e02894..5dbcb92a3f0ef4d4abc991df630f4d4f21cbfeb9 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -9,68 +9,6 @@
 
 require_once dirname(__FILE__) . '/config_test.hooks.inc';
 
-/**
- * Implements hook_config_import_create().
- */
-function config_test_config_import_create($name, $new_config, $old_config) {
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-  // Set a global value we can check in test code.
-  $GLOBALS['hook_config_import'] = __FUNCTION__;
-
-  $config_test = entity_create('config_test', $new_config->get());
-  $config_test->save();
-  return TRUE;
-}
-
-/**
- * Implements hook_config_import_change().
- */
-function config_test_config_import_change($name, $new_config, $old_config) {
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-  // Set a global value we can check in test code.
-  $GLOBALS['hook_config_import'] = __FUNCTION__;
-
-  // @todo Make this less ugly.
-  list(, , $id) = explode('.', $name);
-  $config_test = entity_load('config_test', $id);
-
-  // Store the original config, and iterate through each property to store it.
-  $config_test->original = clone $config_test;
-  foreach ($old_config->get() as $property => $value) {
-    $config_test->original->$property = $value;
-  }
-
-  // Iterate through each property of the new config, copying it to the test
-  // object.
-  foreach ($new_config->get() as $property => $value) {
-    $config_test->$property = $value;
-  }
-
-  $config_test->save();
-  return TRUE;
-}
-
-/**
- * Implements hook_config_import_delete().
- */
-function config_test_config_import_delete($name, $new_config, $old_config) {
-  if (strpos($name, 'config_test.dynamic.') !== 0) {
-    return FALSE;
-  }
-  // Set a global value we can check in test code.
-  $GLOBALS['hook_config_import'] = __FUNCTION__;
-
-  // @todo Make this less ugly.
-  list(, , $id) = explode('.', $name);
-  $config_test = entity_load('config_test', $id);
-  $config_test->delete();
-  return TRUE;
-}
-
 /**
  * Entity URI callback.
  *
@@ -197,4 +135,4 @@ function config_test_cache_flush() {
   $GLOBALS['hook_cache_flush'] = __FUNCTION__;
 
   return array();
-}
\ No newline at end of file
+}
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..e1fe4fe025e00ed0aeb24d74c8b5a0c56cfeb768
--- /dev/null
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_test\ConfigTestStorageController.
+ */
+
+namespace Drupal\config_test;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+use Drupal\Core\Config\Config;
+
+/**
+ * @todo.
+ */
+class ConfigTestStorageController extends ConfigStorageController {
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importCreate().
+   */
+  public function importCreate($name, Config $new_config, Config $old_config) {
+    // Set a global value we can check in test code.
+    $GLOBALS['hook_config_import'] = __METHOD__;
+
+    return parent::importCreate($name, $new_config, $old_config);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importChange().
+   */
+  public function importChange($name, Config $new_config, Config $old_config) {
+    // Set a global value we can check in test code.
+    $GLOBALS['hook_config_import'] = __METHOD__;
+
+    return parent::importChange($name, $new_config, $old_config);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importDelete().
+   */
+  public function importDelete($name, Config $new_config, Config $old_config) {
+    // Set a global value we can check in test code.
+    $GLOBALS['hook_config_import'] = __METHOD__;
+
+    return parent::importDelete($name, $new_config, $old_config);
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php
index 8690b2aba55d143fe51e391d12eb043ecb2f98cc..52da7775529163ce183fa5a0847a398a79679c98 100644
--- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php
@@ -18,7 +18,7 @@
  *   id = "config_test",
  *   label = @Translation("Test configuration"),
  *   module = "config_test",
- *   controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   controller_class = "Drupal\config_test\ConfigTestStorageController",
  *   list_controller_class = "Drupal\Core\Config\Entity\ConfigEntityListController",
  *   form_controller_class = {
  *     "default" = "Drupal\config_test\ConfigTestFormController"
diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 00345e12372207f49a447c42fac68f7cfa468050..44a6f5af1abbbf6f2e3b710f2e1c4c0455569d29 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -173,56 +173,6 @@ function _contact_personal_tab_access($account) {
   return user_access('access user contact forms');
 }
 
-/**
- * Implements MODULE_config_import_create().
- */
-function contact_config_import_create($name, $new_config, $old_config) {
-  if (strpos($name, 'contact.category.') !== 0) {
-    return FALSE;
-  }
-
-  $category = entity_create('contact_category', $new_config->get());
-  $category->save();
-  return TRUE;
-}
-
-/**
- * Implements MODULE_config_import_change().
- */
-function contact_config_import_change($name, $new_config, $old_config) {
-  if (strpos($name, 'contact.category.') !== 0) {
-    return FALSE;
-  }
-
-  list(, , $id) = explode('.', $name);
-  $category = entity_load('contact_category', $id);
-
-  $category->original = clone $category;
-  foreach ($old_config->get() as $property => $value) {
-    $category->original->$property = $value;
-  }
-
-  foreach ($new_config->get() as $property => $value) {
-    $category->$property = $value;
-  }
-
-  $category->save();
-  return TRUE;
-}
-
-/**
- * Implements MODULE_config_import_delete().
- */
-function contact_config_import_delete($name, $new_config, $old_config) {
-  if (strpos($name, 'contact.category.') !== 0) {
-    return FALSE;
-  }
-
-  list(, , $id) = explode('.', $name);
-  entity_delete_multiple('contact_category', array($id));
-  return TRUE;
-}
-
 /**
  * Implements hook_entity_info().
  */
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 83124204f1290dfb804cc8962c682bafce0198ff..556467bf55c49e755c0347f01d54a9e20dca4ac8 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -502,65 +502,6 @@ function image_path_flush($path) {
   }
 }
 
-/**
- * Implements hook_config_import_create().
- */
-function image_config_import_create($name, $new_config, $old_config) {
-  // Only image styles require custom handling. Any other module settings can be
-  // synchronized directly.
-  if (strpos($name, 'image.style.') !== 0) {
-    return FALSE;
-  }
-  $style = entity_create('image_style', $new_config->get());
-  $style->save();
-  return TRUE;
-}
-
-/**
- * Implements hook_config_import_change().
- */
-function image_config_import_change($name, $new_config, $old_config) {
-  // Only image styles require custom handling. Any other module settings can be
-  // synchronized directly.
-  if (strpos($name, 'image.style.') !== 0) {
-    return FALSE;
-  }
-
-  list(, , $id) = explode('.', $name);
-  $style = entity_load('image_style', $id);
-
-  $style->original = clone $style;
-  foreach ($old_config->get() as $property => $value) {
-    $style->original->$property = $value;
-  }
-
-  foreach ($new_config->get() as $property => $value) {
-    $style->$property = $value;
-  }
-
-  $style->save();
-  return TRUE;
-}
-
-/**
- * Implements MODULE_config_import_delete().
- */
-function image_config_import_delete($name, $new_config, $old_config) {
-  // Only image styles require custom handling. Any other module settings can be
-  // synchronized directly.
-  if (strpos($name, 'image.style.') !== 0) {
-    return FALSE;
-  }
-  // @todo image_style_delete() supports the notion of a "replacement style"
-  //   to be used by other modules instead of the deleted style. Essential!
-  //   But that is impossible currently, since the config system only knows
-  //   about deleted and added changes. Introduce an 'old_ID' key within
-  //   config objects as a standard?
-  list(, , $id) = explode('.', $name);
-  $style = entity_load('image_style', $id);
-  return image_style_delete($style);
-}
-
 /**
  * Loads an ImageStyle object.
  *
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..ddeaac2f45eb4330cb75779acafd45fd3db98135
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\ImageStyleStorageController.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+use Drupal\Core\Config\Config;
+
+/**
+ * Defines a controller class for image styles.
+ */
+class ImageStyleStorageController extends ConfigStorageController {
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importDelete().
+   */
+  public function importDelete($name, Config $new_config, Config $old_config) {
+    list(, , $id) = explode('.', $name);
+    $entities = $this->load(array($id));
+    $entity = $entities[$id];
+
+    // @todo image_style_delete() supports the notion of a "replacement style"
+    //   to be used by other modules instead of the deleted style. Essential!
+    //   But that is impossible currently, since the config system only knows
+    //   about deleted and added changes. Introduce an 'old_ID' key within
+    //   config objects as a standard?
+    return image_style_delete($entity);
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
index 363fbd87d93e3156ba4db12abeda59eac45629a0..8110b77677389ca05f55d1cc4ea05c373768eee6 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
@@ -18,7 +18,7 @@
  *   id = "image_style",
  *   label = @Translation("Image style"),
  *   module = "image",
- *   controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   controller_class = "Drupal\image\ImageStyleStorageController",
  *   uri_callback = "image_style_uri",
  *   config_prefix = "image.style",
  *   entity_keys = {
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index ac4952bb1604f3550894745814989548395f9670..c3b450a659d6024cffe51e5f105d48f6b01799fd 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -78,19 +78,6 @@ function views_pre_render_view_element($element) {
   return $element;
 }
 
-/**
- * Implements hook_config_import_create().
- */
-function views_config_import_create($name, $new_config, $old_config) {
-  if (strpos($name, 'views.view.') !== 0) {
-    return FALSE;
-  }
-
-  $view = entity_create('view', $new_config->get());
-  $view->save();
-  return TRUE;
-}
-
 /**
  * Implement hook_theme(). Register views theming functions.
  */
@@ -2072,22 +2059,6 @@ function views_array_key_plus($array) {
   return $array;
 }
 
-/**
- * Implements hook_config_import_delete().
- */
-function views_config_import_delete($name, $new_config, $old_config) {
-  // Only image styles require custom handling. Any other module settings can be
-  // synchronized directly.
-  if (strpos($name, 'views.view.') !== 0) {
-    return FALSE;
-  }
-
-  list(, , $id) = explode('.', $name);
-  $view = entity_load('view', $id);
-  entity_delete($view);
-  return TRUE;
-}
-
 /**
  * Set a cached item in the views cache.
  *