diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 0ce31c2..c684283 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -6,7 +6,6 @@
  */
 
 use Drupal\aggregator\Entity\Feed;
-use Drupal\Component\Plugin\Exception\PluginException;
 
 /**
  * Denotes that a feed's items should never expire.
@@ -212,85 +211,13 @@ function aggregator_queue_info() {
  *
  * @param \Drupal\aggregator\Entity\Feed $feed
  *   An object describing the feed to be refreshed.
+ *
+ * @deprecated
+ *   Use \Drupal::service('aggregator.items.importer')->refresh($feed) instead
+ *   or the refreshItems method on the feed entity.
  */
 function aggregator_refresh(Feed $feed) {
-  // Store feed URL to track changes.
-  $feed_url = $feed->getUrl();
-
-  $config = \Drupal::config('aggregator.settings');
-  // Fetch the feed.
-  $fetcher_manager = \Drupal::service('plugin.manager.aggregator.fetcher');
-  try {
-    $success = $fetcher_manager->createInstance($config->get('fetcher'))->fetch($feed);
-  }
-  catch (PluginException $e) {
-    $success = FALSE;
-    watchdog_exception('aggregator', $e);
-  }
-
-  // Retrieve processor manager now.
-  $processor_manager = \Drupal::service('plugin.manager.aggregator.processor');
-  // Store instances in an array so we dont have to instantiate new objects.
-  $processor_instances = array();
-  foreach ($config->get('processors') as $processor) {
-    try {
-      $processor_instances[$processor] = $processor_manager->createInstance($processor);
-    }
-    catch (PluginException $e) {
-      watchdog_exception('aggregator', $e);
-    }
-  }
-
-  // We store the hash of feed data in the database. When refreshing a
-  // feed we compare stored hash and new hash calculated from downloaded
-  // data. If both are equal we say that feed is not updated.
-  $hash = hash('sha256', $feed->source_string);
-
-  if ($success && ($feed->getHash() != $hash)) {
-    // Parse the feed.
-    $parser_manager = \Drupal::service('plugin.manager.aggregator.parser');
-    try {
-      if ($parser_manager->createInstance($config->get('parser'))->parse($feed)) {
-        if ($feed->getWebsiteUrl()) {
-          $feed->setWebsiteUrl($feed->getUrl());
-        }
-        $feed->setHash($hash);
-        // Update feed with parsed data.
-        $feed->save();
-
-        // Log if feed URL has changed.
-        if ($feed->getUrl() != $feed_url) {
-          watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed->label(), '%url' => $feed->getUrl()));
-        }
-
-        watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed->label()));
-        drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed->label())));
-
-        // If there are items on the feed, let enabled processors process them.
-        if (!empty($feed->items)) {
-          foreach ($processor_instances as $instance) {
-            $instance->process($feed);
-          }
-        }
-      }
-    }
-    catch (PluginException $e) {
-      watchdog_exception('aggregator', $e);
-    }
-  }
-  else {
-    drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->label())));
-  }
-
-  // Regardless of successful or not, indicate that this feed has been checked.
-  $feed->setLastCheckedTime(REQUEST_TIME);
-  $feed->setQueuedTime(0);
-  $feed->save();
-
-  // Processing is done, call postProcess on enabled processors.
-  foreach ($processor_instances as $instance) {
-    $instance->postProcess($feed);
-  }
+  \Drupal::service('aggregator.items.importer')->refresh($feed);
 }
 
 /**
diff --git a/core/modules/aggregator/aggregator.services.yml b/core/modules/aggregator/aggregator.services.yml
index c00b2ac..7b942d6 100644
--- a/core/modules/aggregator/aggregator.services.yml
+++ b/core/modules/aggregator/aggregator.services.yml
@@ -16,3 +16,6 @@ services:
   aggregator.category.storage:
     class: Drupal\aggregator\CategoryStorageController
     arguments: ['@database']
+  aggregator.items.importer:
+    class: Drupal\aggregator\ItemsImporter
+    arguments: ['@config.factory', '@plugin.manager.aggregator.fetcher', '@plugin.manager.aggregator.parser', '@plugin.manager.aggregator.processor']
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php b/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
index f9b25b9..fd07a40 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Controller/AggregatorController.php
@@ -123,8 +123,10 @@ protected function buildPageList(array $items, $feed_source = '') {
    *   If the query token is missing or invalid.
    */
   public function feedRefresh(FeedInterface $aggregator_feed) {
-    // @todo after https://drupal.org/node/1972246 find a new place for it.
-    aggregator_refresh($aggregator_feed);
+    $message = $aggregator_feed->refreshItems()
+      ? $this->t('There is new syndicated content from %site.', array('%site' => $aggregator_feed->label()))
+      : $this->t('There is no new syndicated content from %site.', array('%site' => $aggregator_feed->label()));
+    drupal_set_message($message);
     return $this->redirect('aggregator.admin_overview');
   }
 
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Entity/Feed.php b/core/modules/aggregator/lib/Drupal/aggregator/Entity/Feed.php
index 61eae79..a3bfb6c 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Entity/Feed.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Entity/Feed.php
@@ -63,10 +63,8 @@ public function label() {
    * {@inheritdoc}
    */
   public function removeItems() {
-    $manager = \Drupal::service('plugin.manager.aggregator.processor');
-    foreach ($manager->getDefinitions() as $id => $definition) {
-      $manager->createInstance($id)->remove($this);
-    }
+    \Drupal::service('aggregator.items.importer')->remove($this);
+
     // Reset feed.
     $this->setLastCheckedTime(0);
     $this->setHash('');
@@ -80,6 +78,13 @@ public function removeItems() {
   /**
    * {@inheritdoc}
    */
+  public function refreshItems() {
+    return \Drupal::service('aggregator.items.importer')->refresh($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
     $values += array(
       'link' => '',
@@ -94,10 +99,7 @@ public static function preCreate(EntityStorageControllerInterface $storage_contr
   public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) {
     foreach ($entities as $entity) {
       // Notify processors to remove stored items.
-      $manager = \Drupal::service('plugin.manager.aggregator.processor');
-      foreach ($manager->getDefinitions() as $id => $definition) {
-        $manager->createInstance($id)->remove($entity);
-      }
+      \Drupal::service('aggregator.items.importer')->remove($entity);
     }
   }
 
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedInterface.php
index adabe96..a44736b 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/FeedInterface.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedInterface.php
@@ -218,9 +218,23 @@ public function setLastModified($modified);
   /**
    * Removes all items from a feed.
    *
+   * This will also reset the last checked and modified time of the feed and
+   * save it.
+   *
    * @return \Drupal\aggregator\FeedInterface
    *   The class instance that this method is called on.
    */
   public function removeItems();
 
+  /**
+   * Updates the feed items by triggering the import process.
+   *
+   * This process can be slow and lengthy because it relies on network
+   * operations. Calling it on performance critical paths should be avoided.
+   *
+   * @return bool
+   *   TRUE if there is new content for the feed FALSE otherwise.
+   */
+  public function refreshItems();
+
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporter.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporter.php
new file mode 100644
index 0000000..63a7cae
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporter.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\Entity\ItemsImporter.
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\aggregator\Plugin\AggregatorPluginManager;
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Config\ConfigFactory;
+
+/**
+ * Defines an importer of aggregator items.
+ */
+class ItemsImporter implements ItemsImporterInterface {
+
+  /**
+   * The aggregator fetcher manager.
+   *
+   * @var \Drupal\aggregator\Plugin\AggregatorPluginManager
+   */
+  protected $fetcherManager;
+
+  /**
+   * The aggregator processor manager.
+   *
+   * @var \Drupal\aggregator\Plugin\AggregatorPluginManager
+   */
+  protected $processorManager;
+
+  /**
+   * The aggregator parser manager.
+   *
+   * @var \Drupal\aggregator\Plugin\AggregatorPluginManager
+   */
+  protected $parserManager;
+
+  /**
+   * The aggregator.settings config object.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * Constructs an Importer object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\aggregator\Plugin\AggregatorPluginManager $fetcher_manager
+   *   The aggregator fetcher plugin manager.
+   * @param \Drupal\aggregator\Plugin\AggregatorPluginManager $parser_manager
+   *   The aggregator parser plugin manager.
+   * @param \Drupal\aggregator\Plugin\AggregatorPluginManager $processor_manager
+   *   The aggregator processor plugin manager.
+   */
+  public function __construct(ConfigFactory $config_factory, AggregatorPluginManager $fetcher_manager, AggregatorPluginManager $parser_manager, AggregatorPluginManager $processor_manager) {
+    $this->fetcherManager = $fetcher_manager;
+    $this->processorManager = $processor_manager;
+    $this->parserManager = $parser_manager;
+    $this->config = $config_factory->get('aggregator.settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function remove(FeedInterface $feed) {
+    foreach ($this->processorManager->getDefinitions() as $id => $definition) {
+      $this->processorManager->createInstance($id)->remove($feed);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function refresh(FeedInterface $feed) {
+    // Store feed URL to track changes.
+    $feed_url = $feed->getUrl();
+
+    // Fetch the feed.
+    try {
+      $success = $this->fetcherManager->createInstance($this->config->get('fetcher'))->fetch($feed);
+    }
+    catch (PluginException $e) {
+      $success = FALSE;
+      watchdog_exception('aggregator', $e);
+    }
+
+    // Store instances in an array so we dont have to instantiate new objects.
+    $processor_instances = array();
+    foreach ($this->config->get('processors') as $processor) {
+      try {
+        $processor_instances[$processor] = $this->processorManager->createInstance($processor);
+      }
+      catch (PluginException $e) {
+        watchdog_exception('aggregator', $e);
+      }
+    }
+
+    // We store the hash of feed data in the database. When refreshing a
+    // feed we compare stored hash and new hash calculated from downloaded
+    // data. If both are equal we say that feed is not updated.
+    $hash = hash('sha256', $feed->source_string);
+    $has_new_content = $success && ($feed->hash->value != $hash);
+
+    if ($has_new_content) {
+      // Parse the feed.
+      try {
+        if ($this->parserManager->createInstance($this->config->get('parser'))->parse($feed)) {
+          if ($feed->getWebsiteUrl()) {
+            $feed->setWebsiteUrl($feed->getUrl());
+          }
+          $feed->setHash($hash);
+          // Update feed with parsed data.
+          $feed->save();
+
+          // Log if feed URL has changed.
+          if ($feed->getUrl() != $feed_url) {
+            watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed->label(), '%url' => $feed->getUrl()));
+          }
+
+          watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed->label()));
+
+          // If there are items on the feed, let enabled processors process them.
+          if (!empty($feed->items)) {
+            foreach ($processor_instances as $instance) {
+              $instance->process($feed);
+            }
+          }
+        }
+      }
+      catch (PluginException $e) {
+        watchdog_exception('aggregator', $e);
+      }
+    }
+
+    // Regardless of successful or not, indicate that this feed has been checked.
+    $feed->setLastCheckedTime(REQUEST_TIME);
+    $feed->setQueuedTime(0);
+    $feed->save();
+
+    // Processing is done, call postProcess on enabled processors.
+    foreach ($processor_instances as $instance) {
+      $instance->postProcess($feed);
+    }
+
+    return $has_new_content;
+  }
+
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporterInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporterInterface.php
new file mode 100644
index 0000000..4572a9f
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemsImporterInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\ItemsImporterInterface.
+ */
+
+namespace Drupal\aggregator;
+
+/**
+ * Provides an interface defining an aggregator items importer.
+ */
+interface ItemsImporterInterface {
+
+  /**
+   * Updates the feed items by triggering the import process.
+   *
+   * This process can be slow and lengthy because it relies on network
+   * operations. Calling it on performance critical paths should be avoided.
+   *
+   * @param \Drupal\aggregator\FeedInterface $feed
+   *   The feed which items should be refreshed.
+   *
+   * @return bool
+   *   TRUE if there is new content for the feed FALSE otherwise.
+   */
+  public function refresh(FeedInterface $feed);
+
+  /**
+   * Removes all imported items from a feed.
+   *
+   * @param \Drupal\aggregator\FeedInterface $feed
+   *   The feed that associated items should be removed from.
+   */
+  public function remove(FeedInterface $feed);
+
+}
