diff --git a/feeds.api.php b/feeds.api.php
index c139da1..89188f2 100644
--- a/feeds.api.php
+++ b/feeds.api.php
@@ -283,8 +283,8 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) {
 
       // Specify both summary_callback and form_callback to add a per mapping
       // configuration form.
-      'summary_callback' => 'my_module_summary_callback',
-      'form_callback' => 'my_module_form_callback',
+      'summary_callback' => array('my_module_summary_callback'),
+      'form_callback' => array('my_module_form_callback'),
     );
     $targets['my_node_field2'] = array(
       'name' => t('My Second custom node field'),
diff --git a/feeds.module b/feeds.module
index 419e071..4418e9c 100644
--- a/feeds.module
+++ b/feeds.module
@@ -35,6 +35,7 @@ function feeds_hook_info() {
     'feeds_after_save',
     'feeds_after_import',
     'feeds_after_clear',
+    'feeds_processor_targets',
     'feeds_processor_targets_alter',
     'feeds_parser_sources_alter',
   );
diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc
index 45b423c..f8750f5 100644
--- a/feeds_ui/feeds_ui.admin.inc
+++ b/feeds_ui/feeds_ui.admin.inc
@@ -633,12 +633,10 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
   }
 
   if ($form_state['mapping_settings_edit'] === $i) {
+    $settings_form = array();
     // Build the form.
-    if (isset($target['form_callback'])) {
-      $settings_form = call_user_func($target['form_callback'], $mapping, $target, $form, $form_state);
-    }
-    else {
-      $settings_form = array();
+    foreach ((array) $target['form_callback'] as $callback) {
+      $settings_form += $callback($mapping, $target, $form, $form_state);
     }
 
     // Merge in the optional unique form.
@@ -663,12 +661,11 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
   }
   else {
     // Build the summary.
-    if (isset($target['summary_callback'])) {
-      $summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state);
-    }
-    else {
-      $summary = '';
+    $summary = array();
+    foreach ((array) $target['summary_callback'] as $callback) {
+      $summary[] = $callback($mapping, $target, $form, $form_state);
     }
+    $summary = implode('<br />', $summary);
 
     // Append the optional unique summary.
     if ($optional_unique_summary = feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
diff --git a/mappers/date.inc b/mappers/date.inc
index 061962d..165db5a 100644
--- a/mappers/date.inc
+++ b/mappers/date.inc
@@ -6,13 +6,12 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets().
+ * Implements hook_feeds_processor_targets().
  *
  * @todo Only provides "end date" target if field allows it.
  */
-function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function date_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
     $info = field_info_field($name);
     if (in_array($info['type'], array('date', 'datestamp', 'datetime'))) {
@@ -30,12 +29,13 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
       );
     }
   }
+  return $targets;
 }
 
 /**
  * Callback for setting target values.
  */
-function date_feeds_set_target($source, $entity, $target, array $values) {
+function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   list($field_name, $sub_field) = explode(':', $target, 2);
 
   $delta = 0;
diff --git a/mappers/file.inc b/mappers/file.inc
index e9cbc26..d761ba6 100644
--- a/mappers/file.inc
+++ b/mappers/file.inc
@@ -7,11 +7,10 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
  */
-function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function file_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
     $info = field_info_field($name);
 
@@ -39,16 +38,13 @@ function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
       }
     }
   }
+  return $targets;
 }
 
 /**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * When the callback is invoked, $target contains the name of the field the
- * user has decided to map to and $value contains the value of the feed item
- * element the user has picked as a source.
+ * Callback for mapping file fields.
  */
-function file_feeds_set_target($source, $entity, $target, array $values) {
+function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   // Add default of uri for backwards compatibility.
   list($field_name, $sub_field) = explode(':', $target . ':uri');
   $info = field_info_field($field_name);
diff --git a/mappers/link.inc b/mappers/link.inc
index 90b5268..4d3d0e9 100644
--- a/mappers/link.inc
+++ b/mappers/link.inc
@@ -6,11 +6,10 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
  */
-function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function link_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
     $info = field_info_field($name);
     if ($info['type'] == 'link_field') {
@@ -32,16 +31,13 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
       }
     }
   }
+  return $targets;
 }
 
 /**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * When the callback is invoked, $target contains the name of the field the
- * user has decided to map to and $value contains the value of the feed item
- * element the user has picked as a source.
+ * Callback for mapping link fields.
  */
-function link_feeds_set_target($source, $entity, $target, array $values) {
+function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   list($field_name, $column) = explode(':', $target);
 
   $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array());
diff --git a/mappers/number.inc b/mappers/number.inc
index 338d569..d2c2d76 100644
--- a/mappers/number.inc
+++ b/mappers/number.inc
@@ -6,11 +6,10 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
  */
-function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function number_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   $numeric_types = array(
     'list_integer',
     'list_float',
@@ -30,14 +29,13 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n
       );
     }
   }
+  return $targets;
 }
 
 /**
- * Callback for mapping numerics.
- *
- * Ensure that $value is a numeric to avoid database errors.
+ * Callback for mapping numeric fields.
  */
-function number_feeds_set_target($source, $entity, $target, array $values) {
+function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   // Iterate over all values.
   $field = isset($entity->$target) ? $entity->$target : array('und' => array());
 
diff --git a/mappers/path.inc b/mappers/path.inc
index cd39fb1..8d86391 100644
--- a/mappers/path.inc
+++ b/mappers/path.inc
@@ -6,11 +6,11 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets().
+ * Implements hook_feeds_processor_targets().
  */
-function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function path_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
+
   switch ($entity_type) {
     case 'node':
     case 'taxonomy_term':
@@ -19,11 +19,12 @@ function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
         'name' => t('Path alias'),
         'description' => t('URL path alias of the node.'),
         'callback' => 'path_feeds_set_target',
-        'summary_callback' => 'path_feeds_summary_callback',
-        'form_callback' => 'path_feeds_form_callback',
+        'summary_callback' => array('path_feeds_summary_callback'),
+        'form_callback' => array('path_feeds_form_callback'),
       );
       break;
   }
+  return $targets;
 }
 
 /**
@@ -73,7 +74,7 @@ function path_feeds_set_target($source, $entity, $target, array $values, $mappin
  *   Associative array of the mapping settings.
  * @param $target
  *   Array of target settings, as defined by the processor or
- *   hook_feeds_processor_targets_alter().
+ *   hook_feeds_processor_targets().
  * @param $form
  *   The whole mapping form.
  * @param $form_state
diff --git a/mappers/profile.inc b/mappers/profile.inc
index 00fdf3a..20f4843 100644
--- a/mappers/profile.inc
+++ b/mappers/profile.inc
@@ -6,10 +6,10 @@
  */
 
 /**
- * Implements hook_feeds_processor_target_alter().
+ * Implements hook_feeds_processor_targets().
  */
-function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
-
+function profile_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   if ($entity_type != 'user') {
     return;
   }
@@ -25,6 +25,7 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_
       );
     }
   }
+  return $targets;
 }
 
 /**
diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc
index 088a558..1e455da 100644
--- a/mappers/taxonomy.inc
+++ b/mappers/taxonomy.inc
@@ -55,9 +55,10 @@ function taxonomy_feeds_get_source(FeedsSource $source, FeedsParserResult $resul
 }
 
 /**
- * Implements hook_feeds_processor_targets_alter().
+ * Implements hook_feeds_processor_targets().
  */
-function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function taxonomy_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
     $info = field_info_field($name);
     if ($info['type'] == 'taxonomy_term_reference') {
@@ -65,8 +66,9 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle
         'name' => check_plain($instance['label']),
         'callback' => 'taxonomy_feeds_set_target',
         'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
-        'summary_callback' => 'taxonomy_feeds_summary_callback',
-        'form_callback' => 'taxonomy_feeds_form_callback',
+        'summary_callback' => array('taxonomy_feeds_summary_callback'),
+        'form_callback' => array('taxonomy_feeds_form_callback'),
+        'preprocess_callbacks' => array('taxonomy_feeds_preprocess_callback'),
       );
     }
   }
@@ -75,20 +77,24 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle
     $targets['tid']['description'] = t('The tid of the taxonomy term. NOTE: use this feature with care, node ids are usually assigned by Drupal.');
     unset($targets['vocabulary']);
   }
+  return $targets;
 }
 
 /**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * @todo Do not create new terms for non-autotag fields.
+ * Callback for processing the mapping array.
  */
-function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $mapping = array()) {
+function taxonomy_feeds_preprocess_callback(FeedsSource $source, $entity, $target, array &$mapping) {
   // Add in default values.
   $mapping += array(
     'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME,
     'autocreate' => FALSE,
   );
+}
 
+/**
+ * Callback for mapping taxonomy fields.
+ */
+function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) {
   $info = field_info_field($target);
 
   $cache = &drupal_static(__FUNCTION__);
@@ -244,7 +250,7 @@ function taxonomy_feeds_term_lookup_term_by_guid($guid) {
  *   Associative array of the mapping settings.
  * @param array $target
  *   Array of target settings, as defined by the processor or
- *   hook_feeds_processor_targets_alter().
+ *   hook_feeds_processor_targets().
  * @param array $form
  *   The whole mapping form.
  * @param array $form_state
diff --git a/mappers/text.inc b/mappers/text.inc
index 235aea3..21ee8f9 100644
--- a/mappers/text.inc
+++ b/mappers/text.inc
@@ -6,11 +6,10 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
  */
-function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function text_feeds_processor_targets($entity_type, $bundle_name) {
+  $targets = array();
   $text_types = array(
     'list_text',
     'text',
@@ -38,16 +37,17 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
     }
 
     if (!empty($instance['settings']['text_processing'])) {
-      $targets[$name]['summary_callback'] = 'text_feeds_summary_callback';
-      $targets[$name]['form_callback'] = 'text_feeds_form_callback';
+      $targets[$name]['summary_callback'] = array('text_feeds_summary_callback');
+      $targets[$name]['form_callback'] = array('text_feeds_form_callback');
     }
   }
+  return $targets;
 }
 
 /**
  * Callback for mapping text fields.
  */
-function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) {
+function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   list($field_name, $column) = explode(':', $target . ':value');
 
   if ($column === 'value' && isset($source->importer->processor->config['input_format'])) {
@@ -88,7 +88,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val
  *
  * Displays which text format will be used for the text field target.
  *
- * @see text_feeds_processor_targets_alter()
+ * @see text_feeds_processor_targets()
  * @see text_feeds_form_callback()
  */
 function text_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
@@ -110,7 +110,7 @@ function text_feeds_summary_callback(array $mapping, $target, array $form, array
  *
  * Allows to select a text format for the text field target.
  *
- * @see text_feeds_processor_targets_alter()
+ * @see text_feeds_processor_targets()
  * @see text_feeds_summary_callback()
  */
 function text_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
diff --git a/plugins/FeedsEntityProcessor.inc b/plugins/FeedsEntityProcessor.inc
index 5125c94..4e48fe9 100644
--- a/plugins/FeedsEntityProcessor.inc
+++ b/plugins/FeedsEntityProcessor.inc
@@ -260,10 +260,7 @@ class FeedsEntityProcessor extends FeedsProcessor {
       unset($targets[$entity_info['bundle keys']['bundle']]);
     }
 
-    // Let other modules expose mapping targets.
-    self::loadMappers();
-    $type = $this->entityType();
-    drupal_alter('feeds_processor_targets', $targets, $type, $info['bundle']);
+    $this->getHookTargets($targets);
 
     return $targets;
   }
diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc
index d3d3bb4..1f8e584 100644
--- a/plugins/FeedsNodeProcessor.inc
+++ b/plugins/FeedsNodeProcessor.inc
@@ -339,11 +339,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
       );
     }
 
-    // Let other modules expose mapping targets.
-    self::loadMappers();
-    $entity_type = $this->entityType();
-    $bundle = $this->bundle();
-    drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
+    $this->getHookTargets($targets);
 
     return $targets;
   }
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index 1c56f62..e42928c 100755
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -584,6 +584,21 @@ abstract class FeedsProcessor extends FeedsPlugin {
   }
 
   /**
+   * Returns a statically cached version of the target mappings.
+   *
+   * @return array
+   *   The targets for this importer.
+   */
+  protected function getCachedTargets() {
+    $targets = &drupal_static('FeedsProcessor::getCachedTargets', array());
+    if (!isset($targets[$this->id])) {
+      $targets[$this->id] = $this->getMappingTargets();
+    }
+
+    return $targets[$this->id];
+  }
+
+  /**
    * Execute mapping on an item.
    *
    * This method encapsulates the central mapping functionality. When an item is
@@ -601,82 +616,133 @@ abstract class FeedsProcessor extends FeedsPlugin {
    * @ingroup mappingapi
    *
    * @see hook_feeds_parser_sources_alter()
-   * @see hook_feeds_data_processor_targets_alter()
-   * @see hook_feeds_node_processor_targets_alter()
-   * @see hook_feeds_term_processor_targets_alter()
-   * @see hook_feeds_user_processor_targets_alter()
+   * @see hook_feeds_processor_targets()
+   * @see hook_feeds_processor_targets_alter()
    */
   protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) {
-
-    // Static cache $targets as getMappingTargets() may be an expensive method.
-    static $sources;
-    if (!isset($sources[$this->id])) {
-      $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
-    }
-    static $targets;
-    if (!isset($targets[$this->id])) {
-      $targets[$this->id] = $this->getMappingTargets();
-    }
-    $parser = feeds_importer($this->id)->parser;
     if (empty($target_item)) {
       $target_item = array();
     }
+    $targets = $this->getCachedTargets();
 
     // Many mappers add to existing fields rather than replacing them. Hence we
     // need to clear target elements of each item before mapping in case we are
     // mapping on a prepopulated item such as an existing node.
     foreach ($this->config['mappings'] as $mapping) {
-      if (isset($targets[$this->id][$mapping['target']]['real_target'])) {
-        $target_item->{$targets[$this->id][$mapping['target']]['real_target']} = NULL;
+      if (isset($targets[$mapping['target']]['real_target'])) {
+        $target_item->{$targets[$mapping['target']]['real_target']} = NULL;
       }
       else {
         $target_item->{$mapping['target']} = NULL;
       }
     }
 
-    /*
-    This is where the actual mapping happens: For every mapping we envoke
-    the parser's getSourceElement() method to retrieve the value of the source
-    element and pass it to the processor's setTargetElement() to stick it
-    on the right place of the target item.
-
-    If the mapping specifies a callback method, use the callback instead of
-    setTargetElement().
-    */
+    // This is where the actual mapping happens: For every mapping we envoke
+    // the parser's getSourceElement() method to retrieve the value of the source
+    // element and pass it to the processor's setTargetElement() to stick it
+    // on the right place of the target item.
     self::loadMappers();
     foreach ($this->config['mappings'] as $mapping) {
-      // Retrieve source element's value from parser.
-      if (isset($sources[$this->id][$mapping['source']]) &&
-          is_array($sources[$this->id][$mapping['source']]) &&
-          isset($sources[$this->id][$mapping['source']]['callback']) &&
-          function_exists($sources[$this->id][$mapping['source']]['callback'])) {
-        $callback = $sources[$this->id][$mapping['source']]['callback'];
-        $value = $callback($source, $result, $mapping['source']);
-      }
-      else {
-        $value = $parser->getSourceElement($source, $result, $mapping['source']);
-      }
+      $value = $this->getSourceValue($source, $result, $mapping['source']);
+      $this->mapTarget($source, $target_item, $value, $mapping);
+    }
 
-      // Map the source element's value to the target.
-      if (isset($targets[$this->id][$mapping['target']]) &&
-          is_array($targets[$this->id][$mapping['target']]) &&
-          isset($targets[$this->id][$mapping['target']]['callback']) &&
-          function_exists($targets[$this->id][$mapping['target']]['callback'])) {
-        $callback = $targets[$this->id][$mapping['target']]['callback'];
+    return $target_item;
+  }
 
-        // All target callbacks expect an array.
-        if (!is_array($value)) {
-          $value = array($value);
-        }
+  /**
+   * Returns the values from the parser, or callback.
+   *
+   * @param FeedsSource $source
+   *   The feed source.
+   * @param FeedsParserResult $result
+   *   The parser result.
+   * @param string $source_key
+   *   The current key being processed.
+   *
+   * @return mixed
+   *   A value, or a list of values.
+   */
+  protected function getSourceValue(FeedsSource $source, FeedsParserResult $result, $source_key) {
+    $parser = feeds_importer($this->id)->parser;
+    static $sources = array();
+    if (!isset($sources[$this->id])) {
+      $sources[$this->id] = $parser->getMappingSources();
+    }
 
-        $callback($source, $target_item, $mapping['target'], $value, $mapping);
-      }
-      else {
-        $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping);
+    if (isset($sources[$this->id][$source_key]) &&
+        is_array($sources[$this->id][$source_key]) &&
+        isset($sources[$this->id][$source_key]['callback']) &&
+        function_exists($sources[$this->id][$source_key]['callback'])) {
+
+      $callback = $sources[$this->id][$source_key]['callback'];
+      return $callback($source, $result, $source_key);
+    }
+    else {
+      return $parser->getSourceElement($source, $result, $source_key);
+    }
+  }
+
+  /**
+   * Maps values onto the target item.
+   *
+   * @param FeedsSource $source
+   *   The feed source.
+   * @param mixed &$target_item
+   *   The target item to apply values into.
+   * @param mixed $value
+   *   A value, or a list of values.
+   * @param array $mapping
+   *   The mapping configuration.
+   */
+  protected function mapTarget(FeedsSource $source, &$target_item, $value, array $mapping) {
+    $targets = $this->getCachedTargets();
+    $target = $mapping['target'];
+
+    $callbacks = $targets[$target]['preprocess_callbacks'];
+    $this->invokePreprocessCallbacks($callbacks, $source, $target_item, $target, $mapping);
+
+    // Map the source element's value to the target.
+    // If the mapping specifies a callback method, use the callback instead of
+    // setTargetElement().
+    if (isset($targets[$target]) &&
+        is_array($targets[$target]) &&
+        isset($targets[$target]['callback']) &&
+        function_exists($targets[$target]['callback'])) {
+
+      $callback = $targets[$target]['callback'];
+      // All target callbacks expect an array.
+      if (!is_array($value)) {
+        $value = array($value);
       }
+      $callback($source, $target_item, $target, $value, $mapping);
     }
+    else {
+      $this->setTargetElement($source, $target_item, $target, $value, $mapping);
+    }
+  }
 
-    return $target_item;
+  /**
+   * Invokes the preprocess callbacks for a target.
+   *
+   * @param array $callbacks
+   *   A list of callables.
+   * @param FeedsSource $source
+   *   The feed source.
+   * @param object $target_item
+   *   The target item being created/updated.
+   * @param string $target
+   *   The target key.
+   * @param array &$mapping
+   *   The mapping configuration.
+   */
+  protected function invokePreprocessCallbacks(array $callbacks, FeedsSource $source, $target_item, $target, array &$mapping) {
+    if (!is_array($values)) {
+      $values = array($values);
+    }
+    foreach ($callbacks as $callback) {
+      $callback($source, $target_item, $target, $values, $mapping);
+    }
   }
 
   /**
@@ -820,6 +886,41 @@ abstract class FeedsProcessor extends FeedsPlugin {
   }
 
   /**
+   * Allows other modules to expose targets.
+   *
+   * @param array &$targets
+   *   The existing target array.
+   */
+  protected function getHookTargets(array &$targets) {
+    self::loadMappers();
+    $type = $this->entityType();
+    $bundle = $this->bundle();
+    $targets += module_invoke_all('feeds_processor_targets', $type, $bundle);
+    foreach ($targets as $target => $info) {
+      $targets[$target] += array(
+        'summary_callback' => array(),
+        'form_callback' => array(),
+        'preprocess_callbacks' => array(),
+        'unique_callbacks' => array(),
+      );
+    }
+
+    drupal_alter('feeds_processor_targets', $targets, $type, $bundle);
+
+    // Old implementations of hook_feeds_processor_targets_alter() set things
+    // directly, killing the defaults we added before.
+    // @todo Remove this at some point, like a stable release.
+    foreach ($targets as $target => $info) {
+      $targets[$target] += array(
+        'summary_callback' => array(),
+        'form_callback' => array(),
+        'preprocess_callbacks' => array(),
+        'unique_callbacks' => array(),
+      );
+    }
+  }
+
+  /**
    * Set a concrete target element. Invoked from FeedsProcessor::map().
    *
    * @ingroup mappingapi
@@ -850,11 +951,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
    *   The serial id of an entity if found, 0 otherwise.
    */
   protected function existingEntityId(FeedsSource $source, FeedsParserResult $result) {
-    $targets = &drupal_static('FeedsProcessor::existingEntityId', array());
-    if (!isset($targets[$this->id])) {
-      $targets[$this->id] = $this->getMappingTargets();
-    }
-
+    $targets = $this->getCachedTargets();
     $entity_id = 0;
 
     // Iterate through all unique targets and test whether they already exist in
@@ -871,12 +968,12 @@ abstract class FeedsProcessor extends FeedsPlugin {
           ->fetchField();
       }
 
-      if (!$entity_id && !empty($targets[$this->id][$target]['unique_callbacks'])) {
+      if (!$entity_id && !empty($targets[$target]['unique_callbacks'])) {
         if (!is_array($value)) {
           $value = array($value);
         }
 
-        foreach ($targets[$this->id][$target]['unique_callbacks'] as $callback) {
+        foreach ($targets[$target]['unique_callbacks'] as $callback) {
           if (is_callable($callback) && $entity_id = call_user_func_array($callback, array($source, $this->entityType(), $this->bundle(), $target, $value))) {
             // Stop at the first unique ID returned by a callback.
             break;
diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc
index 9b0024e..d4670d4 100644
--- a/plugins/FeedsTermProcessor.inc
+++ b/plugins/FeedsTermProcessor.inc
@@ -177,21 +177,13 @@ class FeedsTermProcessor extends FeedsProcessor {
       'description' => array(
         'name' => t('Term description'),
         'description' => t('Description of the taxonomy term.'),
-        'summary_callback' => 'text_feeds_summary_callback',
-        'form_callback' => 'text_feeds_form_callback',
+        'summary_callback' => array('text_feeds_summary_callback'),
+        'form_callback' => array('text_feeds_form_callback'),
       ),
     );
 
-    // Let implementers of hook_feeds_term_processor_targets() add their targets.
-    try {
-      self::loadMappers();
-      $entity_type = $this->entityType();
-      $bundle = $this->bundle();
-      drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
-    }
-    catch (Exception $e) {
-      // Do nothing.
-    }
+    $this->getHookTargets($targets);
+
     return $targets;
   }
 
diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc
index d01db43..02607f2 100644
--- a/plugins/FeedsUserProcessor.inc
+++ b/plugins/FeedsUserProcessor.inc
@@ -193,11 +193,7 @@ class FeedsUserProcessor extends FeedsProcessor {
        );
     }
 
-    // Let other modules expose mapping targets.
-    self::loadMappers();
-    $entity_type = $this->entityType();
-    $bundle = $this->bundle();
-    drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
+    $this->getHookTargets($targets);
 
     return $targets;
   }
diff --git a/tests/feeds_mapper_taxonomy.test b/tests/feeds_mapper_taxonomy.test
index 2951b58..14b1a16 100644
--- a/tests/feeds_mapper_taxonomy.test
+++ b/tests/feeds_mapper_taxonomy.test
@@ -114,7 +114,7 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase {
   /**
    * Tests inheriting taxonomy from the feed node.
    */
-  function testInheritTaxonomy() {
+  public function testInheritTaxonomy() {
 
     // Adjust importer settings
     $this->setSettings('syndication', NULL, array('import_period' => FEEDS_SCHEDULE_NEVER));
@@ -236,11 +236,13 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase {
       'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_ID,
     );
 
-    taxonomy_feeds_set_target(NULL, $entity, $target, $terms, $mapping);
+    $source = FeedsSource::instance('tmp', 0);
+
+    taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping);
     $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10);
 
     // Test a second mapping with a bogus term id.
-    taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping);
+    taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping);
     $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10);
   }
 
@@ -284,14 +286,15 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase {
       'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_GUID,
     );
 
-    taxonomy_feeds_set_target(NULL, $entity, $target, $guids, $mapping);
+    $source = FeedsSource::instance('tmp', 0);
+    taxonomy_feeds_set_target($source, $entity, $target, $guids, $mapping);
     $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10);
     foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) {
       $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.');
     }
 
     // Test a second mapping with a bogus term id.
-    taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping);
+    taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping);
     $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10);
     foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) {
       $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.');
diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module
index fe25704..5ce8961 100644
--- a/tests/feeds_tests.module
+++ b/tests/feeds_tests.module
@@ -102,17 +102,17 @@ function feeds_tests_files_remote() {
 }
 
 /**
- * Implements hook_feeds_processor_targets_alter().
+ * Implements hook_feeds_processor_targets().
  */
-function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) {
+function feeds_tests_feeds_processor_targets($entity_type, $bundle) {
+  $targets = array();
   $targets['test_target'] = array(
     'name' => t('Test Target'),
     'description' => t('This is a test target.'),
     'callback' => 'feeds_tests_mapper_set_target',
-    'summary_callback' => 'feeds_tests_mapper_summary',
-    'form_callback' => 'feeds_tests_mapper_form',
+    'summary_callback' => array('feeds_tests_mapper_summary'),
+    'form_callback' => array('feeds_tests_mapper_form'),
   );
-
   $targets['test_unique_target'] = array(
     'name' => t('Test unique target'),
     'description' => t('This is a unique test target.'),
@@ -120,6 +120,7 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun
     'optional_unique' => TRUE,
     'unique_callbacks' => array('feeds_tests_mapper_unique'),
   );
+  return $targets;
 }
 
 /**
@@ -217,7 +218,7 @@ function feeds_tests_mapper_form($mapping, $target, $form, $form_state) {
 /**
  * Callback for unique_callbacks for test_target mapper.
  *
- * @see feeds_tests_feeds_processor_targets_alter()
+ * @see feeds_tests_feeds_processor_targets()
  */
 function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) {
   $query = new EntityFieldQuery();
