diff --git a/feeds.api.php b/feeds.api.php
index 58e1670..4698a90 100644
--- a/feeds.api.php
+++ b/feeds.api.php
@@ -285,8 +285,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'),
@@ -334,23 +334,23 @@ function my_module_set_target($source, $entity, $target, array $values, $mapping
  * Example of the summary_callback specified in
  * hook_feeds_processor_targets_alter().
  *
- * @param $mapping
+ * @param array $mapping
  *   Associative array of the mapping settings.
- * @param $target
+ * @param string $target
  *   Array of target settings, as defined by the processor or
  *   hook_feeds_processor_targets_alter().
- * @param $form
+ * @param array $form
  *   The whole mapping form.
- * @param $form_state
+ * @param array $form_state
  *   The form state of the mapping form.
  *
- * @return
+ * @return string
  *   Returns, as a string that may contain HTML, the summary to display while
  *   the full form isn't visible.
  *   If the return value is empty, no summary and no option to view the form
  *   will be displayed.
  */
-function my_module_summary_callback($mapping, $target, $form, $form_state) {
+function my_module_summary_callback(array $mapping, $target, array $form, array $form_state) {
   if (empty($mapping['my_setting'])) {
     return t('My setting <strong>not</strong> active');
   }
@@ -365,13 +365,13 @@ function my_module_summary_callback($mapping, $target, $form, $form_state) {
  *
  * The arguments are the same that my_module_summary_callback() gets.
  *
- * @return
+ * @return array
  *   The per mapping configuration form. Once the form is saved, $mapping will
  *   be populated with the form values.
  *
  * @see my_module_summary_callback()
  */
-function my_module_form_callback($mapping, $target, $form, $form_state) {
+function my_module_form_callback(array $mapping, $target, array $form, array $form_state) {
   return array(
     'my_setting' => array(
       '#type' => 'checkbox',
diff --git a/feeds.module b/feeds.module
index f536926..8d147b8 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 b174d75..85313e8 100644
--- a/feeds_ui/feeds_ui.admin.inc
+++ b/feeds_ui/feeds_ui.admin.inc
@@ -634,12 +634,13 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
   }
 
   if ($form_state['mapping_settings_edit'] === $i) {
-    // 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();
+    $settings_form = array();
+
+    foreach (array_filter((array) $target['form_callback']) as $callback) {
+      if (!is_callable($callback)) {
+        continue;
+      }
+      $settings_form += $callback($mapping, $target, $form, $form_state);
     }
 
     // Merge in the optional unique form.
@@ -664,13 +665,17 @@ 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_filter((array) $target['summary_callback']) as $callback) {
+      if (!is_callable($callback)) {
+        continue;
+      }
+      $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)) {
       $summary .= ' ' . $optional_unique_summary;
diff --git a/mappers/date.inc b/mappers/date.inc
index 061962d..d5ea81f 100644
--- a/mappers/date.inc
+++ b/mappers/date.inc
@@ -6,13 +6,13 @@
  */
 
 /**
- * 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 +30,14 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
       );
     }
   }
+
+  return $targets;
 }
 
 /**
- * Callback for setting target values.
+ * Callback for setting date values.
  */
-function date_feeds_set_target($source, $entity, $target, array $values) {
+function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values) {
   list($field_name, $sub_field) = explode(':', $target, 2);
 
   $delta = 0;
diff --git a/mappers/file.inc b/mappers/file.inc
index 2273590..0c40677 100644
--- a/mappers/file.inc
+++ b/mappers/file.inc
@@ -7,11 +7,11 @@
  */
 
 /**
- * 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);
 
@@ -47,16 +47,14 @@ 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) {
   // 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..e9c3632 100644
--- a/mappers/link.inc
+++ b/mappers/link.inc
@@ -6,11 +6,11 @@
  */
 
 /**
- * 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 +32,14 @@ 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) {
   list($field_name, $column) = explode(':', $target);
 
   $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array());
diff --git a/mappers/list.inc b/mappers/list.inc
index 6a8a168..8c5dc36 100644
--- a/mappers/list.inc
+++ b/mappers/list.inc
@@ -6,11 +6,11 @@
  */
 
 /**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
  */
-function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function list_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);
 
@@ -42,12 +42,14 @@ function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
         break;
     }
   }
+
+  return $targets;
 }
 
 /**
  * Callback for setting list_boolean fields.
  */
-function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) {
+function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values) {
   $field = isset($entity->$target) ? $entity->$target : array(LANGUAGE_NONE => array());
 
   foreach ($values as $value) {
diff --git a/mappers/number.inc b/mappers/number.inc
index 2b9c436..7f9a0d8 100644
--- a/mappers/number.inc
+++ b/mappers/number.inc
@@ -6,11 +6,11 @@
  */
 
 /**
- * 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(
     'number_integer',
     'number_decimal',
@@ -27,14 +27,14 @@ 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 number fields.
  */
-function number_feeds_set_target($source, $entity, $target, array $values) {
+function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values) {
   // 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..519db98 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,21 +19,19 @@ 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;
 }
 
 /**
- * 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 path aliases.
  */
-function path_feeds_set_target($source, $entity, $target, array $values, $mapping) {
+function path_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   $alias = FALSE;
   // Path alias cannot be multi-valued, so use the first non-empty value.
   foreach ($values as $value) {
@@ -68,24 +66,8 @@ function path_feeds_set_target($source, $entity, $target, array $values, $mappin
 
 /**
  * Mapping configuration summary for path.module.
- *
- * @param $mapping
- *   Associative array of the mapping settings.
- * @param $target
- *   Array of target settings, as defined by the processor or
- *   hook_feeds_processor_targets_alter().
- * @param $form
- *   The whole mapping form.
- * @param $form_state
- *   The form state of the mapping form.
- *
- * @return
- *   Returns, as a string that may contain HTML, the summary to display while
- *   the full form isn't visible.
- *   If the return value is empty, no summary and no option to view the form
- *   will be displayed.
  */
-function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
+function path_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
   if (!module_exists('pathauto')) {
     return;
   }
@@ -101,12 +83,8 @@ function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
 
 /**
  * Settings form callback.
- *
- * @return
- *   The per mapping configuration form. Once the form is saved, $mapping will
- *   be populated with the form values.
  */
-function path_feeds_form_callback($mapping, $target, $form, $form_state) {
+function path_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
   return array(
     'pathauto_override' => array(
       '#type' => 'checkbox',
diff --git a/mappers/profile.inc b/mappers/profile.inc
index 00fdf3a..33a4f2d 100644
--- a/mappers/profile.inc
+++ b/mappers/profile.inc
@@ -2,16 +2,17 @@
 
 /**
  * @file
- * On behalf implementation of Feeds mapping API for user profiles.
+ * On behalf implementation of Feeds mapping API for profile.module.
  */
 
 /**
- * Implements hook_feeds_processor_target_alter().
+ * Implements hook_feeds_processor_target().
  */
-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;
+    return $targets;
   }
 
   $categories = profile_user_categories();
@@ -25,11 +26,13 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_
       );
     }
   }
+
+  return $targets;
 }
 
 /**
  * Set the user profile target after import.
  */
-function profile_feeds_set_target($source, $entity, $target, array $values, $mapping) {
+function profile_feeds_set_target(FeedsSource $source, $entity, $target, array $values) {
   $entity->$target = reset($values);
 }
diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc
index 75f6541..c9da96b 100644
--- a/mappers/taxonomy.inc
+++ b/mappers/taxonomy.inc
@@ -23,7 +23,7 @@ define('FEEDS_TAXONOMY_SEARCH_TERM_GUID', 2);
 /**
  * Implements hook_feeds_parser_sources_alter().
  */
-function taxonomy_feeds_parser_sources_alter(&$sources, $content_type) {
+function taxonomy_feeds_parser_sources_alter(array &$sources, $content_type) {
   if (!empty($content_type)) {
     foreach (taxonomy_get_vocabularies($content_type) as $vocabulary) {
       $sources['parent:taxonomy:' . $vocabulary->machine_name] = array(
@@ -55,9 +55,11 @@ 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 +67,8 @@ 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'),
       );
     }
   }
@@ -75,14 +77,14 @@ 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 mapping taxonomy terms.
  */
-function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $mapping = array()) {
+function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) {
   // Add in default values.
   $mapping += array(
     'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME,
@@ -252,24 +254,8 @@ function taxonomy_feeds_term_lookup_term_by_guid($guid) {
 
 /**
  * Mapping configuration summary for taxonomy.module.
- *
- * @param array $mapping
- *   Associative array of the mapping settings.
- * @param array $target
- *   Array of target settings, as defined by the processor or
- *   hook_feeds_processor_targets_alter().
- * @param array $form
- *   The whole mapping form.
- * @param array $form_state
- *   The form state of the mapping form.
- *
- * @return string
- *   Returns, as a string that may contain HTML, the summary to display while
- *   the full form isn't visible.
- *   If the return value is empty, no summary and no option to view the form
- *   will be displayed.
  */
-function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state) {
+function taxonomy_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
   $options = _taxonomy_feeds_form_callback_options();
   if (empty($mapping['term_search'])) {
     return t('Search taxonomy terms by: <strong>@search</strong>', array('@search' => $options[FEEDS_TAXONOMY_SEARCH_TERM_NAME]));
@@ -279,12 +265,8 @@ function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state)
 
 /**
  * Settings form callback.
- *
- * @return array
- *   The per mapping configuration form. Once the form is saved, $mapping will
- *   be populated with the form values.
  */
-function taxonomy_feeds_form_callback($mapping, $target, $form, $form_state) {
+function taxonomy_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
   return array(
     'term_search' => array(
       '#type' => 'select',
diff --git a/mappers/text.inc b/mappers/text.inc
index 89a03ff..b690ee4 100644
--- a/mappers/text.inc
+++ b/mappers/text.inc
@@ -6,11 +6,11 @@
  */
 
 /**
- * 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_alter($entity_type, $bundle_name) {
+  $targets = array();
+
   $text_types = array(
     'text',
     'text_long',
@@ -37,16 +37,18 @@ 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'])) {
@@ -87,7 +89,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) {
@@ -109,7 +111,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/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc
index ec23016..2cfdf30 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 fe7c541..a0494bb 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -586,6 +586,38 @@ 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];
+  }
+
+  /**
+   * Returns a statically cached version of the source mappings.
+   *
+   * @return array
+   *   The sources for this importer.
+   */
+  protected function getCachedSources() {
+    $sources = &drupal_static('FeedsProcessor::getCachedSources', array());
+
+    if (!isset($sources[$this->id])) {
+      $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
+    }
+
+    return $sources[$this->id];
+  }
+
+  /**
    * Execute mapping on an item.
    *
    * This method encapsulates the central mapping functionality. When an item is
@@ -603,23 +635,12 @@ 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) {
+    $targets = $this->getCachedTargets();
 
-    // 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();
     }
@@ -628,57 +649,116 @@ abstract class FeedsProcessor extends FeedsPlugin {
     // 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 invoke
+    // 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) {
+    $sources = $this->getCachedSources();
 
-        $callback($source, $target_item, $mapping['target'], $value, $mapping);
-      }
-      else {
-        $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping);
+    if (isset($sources[$source_key]) &&
+        is_array($sources[$source_key]) &&
+        isset($sources[$source_key]['callback']) &&
+        is_callable($sources[$source_key]['callback'])) {
+
+      $callback = $sources[$source_key]['callback'];
+
+      return $callback($source, $result, $source_key);
+    }
+
+    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']) &&
+        is_callable($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);
     }
 
-    return $target_item;
+    else {
+      $this->setTargetElement($source, $target_item, $target, $value, $mapping);
+    }
+  }
+
+  /**
+   * 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) {
+    foreach ($callbacks as $callback) {
+      $callback($source, $target_item, $target, $mapping);
+    }
   }
 
   /**
@@ -822,6 +902,39 @@ 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);
+
+    $defaults = array(
+      'summary_callback' => array(),
+      'form_callback' => array(),
+      'preprocess_callbacks' => array(),
+      'unique_callbacks' => array(),
+    );
+
+    foreach ($targets as $target => $info) {
+      $targets[$target] += $defaults;
+    }
+
+    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.
+    foreach ($targets as $target => $info) {
+      $targets[$target] += $defaults;
+    }
+  }
+
+  /**
    * Set a concrete target element. Invoked from FeedsProcessor::map().
    *
    * @ingroup mappingapi
@@ -832,6 +945,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
       case 'guid':
         $target_item->feeds_item->$target_element = $value;
         break;
+
       default:
         $target_item->$target_element = $value;
         break;
@@ -852,10 +966,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;
 
@@ -873,12 +984,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 40b9207..2232602 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 4a2eb8a..231235e 100644
--- a/tests/feeds_mapper_taxonomy.test
+++ b/tests/feeds_mapper_taxonomy.test
@@ -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,16 @@ 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..c5955c2 100644
--- a/tests/feeds_tests.module
+++ b/tests/feeds_tests.module
@@ -102,15 +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_alter($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(
@@ -120,6 +122,8 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun
     'optional_unique' => TRUE,
     'unique_callbacks' => array('feeds_tests_mapper_unique'),
   );
+
+  return $targets;
 }
 
 /**
@@ -127,7 +131,7 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun
  *
  * @see my_module_set_target()
  */
-function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mapping) {
+function feeds_tests_mapper_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
   $entity->body['und'][0]['value'] = serialize($mapping);
 }
 
@@ -136,7 +140,7 @@ function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mappi
  *
  * @see my_module_summary_callback()
  */
-function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) {
+function feeds_tests_mapper_summary(array $mapping, $target, array $form, array $form_state) {
   $options = array(
     'option1' => t('Option 1'),
     'option2' => t('Another Option'),
@@ -174,7 +178,7 @@ function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) {
 /**
  * Provides the form with mapper settings.
  */
-function feeds_tests_mapper_form($mapping, $target, $form, $form_state) {
+function feeds_tests_mapper_form(array $mapping, $target, array $form, array $form_state) {
   $mapping += array(
     'checkbox' => FALSE,
     'textfield' => '',
@@ -217,7 +221,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();
