diff --git a/core/core.services.yml b/core/core.services.yml
index 9afeed255c9693178b88da049b1a759b20df117c..a8278352d9cf37089abf2141ac571b17121c4fb2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -425,6 +425,11 @@ services:
     class: Drupal\Core\Theme\ThemeAccessCheck
     tags:
       - { name: access_check }
+  access_check.custom:
+    class: Drupal\Core\Access\CustomAccessCheck
+    arguments: ['@controller_resolver']
+    tags:
+      - { name: access_check }
   maintenance_mode_subscriber:
     class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
     tags:
diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..159d9d52618f6da431141b89eef3bd7fe6b014b8
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Access\CustomAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines an access checker that allows specifying a custom method for access.
+ *
+ * You should only use it when you are sure that the access callback will not be
+ * reused. Good examples in core are Edit or Toolbar module.
+ *
+ * The method is called on another instance of the controller class, so you
+ * cannot reuse any stored property of your actual controller instance used
+ * to generate the output.
+ */
+class CustomAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * Constructs a CustomAccessCheck instance.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver) {
+    $this->controllerResolver = $controller_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_custom_access');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $access_controller = $route->getRequirement('_custom_access');
+
+    $controller = $this->controllerResolver->getControllerFromDefinition($access_controller);
+    $arguments = $this->controllerResolver->getArguments($request, $controller);
+
+    return call_user_func_array($controller, $arguments);
+  }
+
+}
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Access/SubtreeAccess.php b/core/modules/toolbar/lib/Drupal/toolbar/Access/SubtreeAccess.php
deleted file mode 100644
index 131bc6a027d276b229291b052ae8c51bd3705003..0000000000000000000000000000000000000000
--- a/core/modules/toolbar/lib/Drupal/toolbar/Access/SubtreeAccess.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\toolbar\Access\SubtreeAccess.
- */
-
-namespace Drupal\toolbar\Access;
-
-use Drupal\Core\Access\StaticAccessCheckInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
-
-/**
- * Defines a special access checker for the toolbar subtree route.
- */
-class SubtreeAccess implements StaticAccessCheckInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function appliesTo() {
-    return array('_access_toolbar_subtree');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access(Route $route, Request $request) {
-    $hash = $request->get('hash');
-    return (user_access('access toolbar') && ($hash == _toolbar_get_subtrees_hash())) ? static::ALLOW : static::DENY;
-  }
-
-}
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
index 740455599f9797a79a1524e151aaa9af57bf27d6..3a7eb3c0d833937fb69100aa94736cf2aea6958b 100644
--- a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
@@ -7,12 +7,15 @@
 
 namespace Drupal\toolbar\Routing;
 
+use Drupal\Core\Access\AccessInterface;
+use Drupal\Core\Controller\ControllerBase;
 use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Defines a controller for the toolbar module.
  */
-class ToolbarController {
+class ToolbarController extends ControllerBase {
 
   /**
    * Returns the rendered subtree of each top-level toolbar link.
@@ -27,4 +30,12 @@ public function subtreesJsonp() {
     return $response;
   }
 
+  /**
+   * Checks access for the subtree controller.
+   */
+  public function checkSubTreeAccess(Request $request) {
+    $hash = $request->get('hash');
+    return ($this->currentUser()->hasPermission('access toolbar') && ($hash == _toolbar_get_subtrees_hash())) ? AccessInterface::ALLOW : AccessInterface::DENY;
+  }
+
 }
diff --git a/core/modules/toolbar/toolbar.routing.yml b/core/modules/toolbar/toolbar.routing.yml
index 609d0c70b4cd16a69e55729de3042cd5651848e1..a4aabb1c05c3f98df0d4100ada96b16ee8e4d90c 100644
--- a/core/modules/toolbar/toolbar.routing.yml
+++ b/core/modules/toolbar/toolbar.routing.yml
@@ -3,4 +3,4 @@ toolbar.subtrees:
   defaults:
     _controller: '\Drupal\toolbar\Routing\ToolbarController::subtreesJsonp'
   requirements:
-    _access_toolbar_subtree: 'TRUE'
+    _custom_access: '\Drupal\toolbar\Routing\ToolbarController::checkSubTreeAccess'
diff --git a/core/modules/toolbar/toolbar.services.yml b/core/modules/toolbar/toolbar.services.yml
index c700b62941ad0fb2b9ce2939208b5a28125c35c0..7f269683c143d03c18f0440af7bced9a90be11a9 100644
--- a/core/modules/toolbar/toolbar.services.yml
+++ b/core/modules/toolbar/toolbar.services.yml
@@ -1,8 +1,4 @@
 services:
-  access_check.toolbar_subtree:
-    class: Drupal\toolbar\Access\SubtreeAccess
-    tags:
-      - { name: access_check }
   cache.toolbar:
     class: Drupal\Core\Cache\CacheBackendInterface
     tags:
diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5eb130571f5764bf1ed823055b8507a1e84c398a
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Access\CustomAccessCheckTest.
+ */
+
+namespace Drupal\Tests\Core\Access;
+
+use Drupal\Core\Access\AccessInterface;
+use Drupal\Core\Access\CustomAccessCheck;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the custom access checker.
+ *
+ * @see \Drupal\Core\Access\CustomAccessCheck
+ */
+class CustomAccessCheckTest extends UnitTestCase {
+
+  /**
+   * The access checker to test.
+   *
+   * @var \Drupal\Core\Access\CustomAccessCheck
+   */
+  protected $accessChecker;
+
+  /**
+   * The mocked controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $controllerResolver;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom access check',
+      'description' => 'Tests the custom access checker.',
+      'group' => 'Access'
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
+    $this->accessChecker = new CustomAccessCheck($this->controllerResolver);
+  }
+
+
+  /**
+   * Tests the appliesTo method.
+   */
+  public function testAppliesTo() {
+    $this->assertEquals($this->accessChecker->appliesTo(), array('_custom_access'));
+  }
+
+  /**
+   * Test the access method.
+   */
+  public function testAccess() {
+    $request = new Request(array());
+
+    $this->controllerResolver->expects($this->at(0))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessDeny')
+      ->will($this->returnValue(array(new TestController(), 'accessDeny')));
+
+    $this->controllerResolver->expects($this->at(1))
+      ->method('getArguments')
+      ->will($this->returnValue(array()));
+
+    $this->controllerResolver->expects($this->at(2))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessAllow')
+      ->will($this->returnValue(array(new TestController(), 'accessAllow')));
+
+    $this->controllerResolver->expects($this->at(3))
+      ->method('getArguments')
+      ->will($this->returnValue(array()));
+
+    $this->controllerResolver->expects($this->at(4))
+      ->method('getControllerFromDefinition')
+      ->with('\Drupal\Tests\Core\Access\TestController::accessParameter')
+      ->will($this->returnValue(array(new TestController(), 'accessParameter')));
+
+    $this->controllerResolver->expects($this->at(5))
+      ->method('getArguments')
+      ->will($this->returnValue(array('parameter' => 'TRUE')));
+
+    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessDeny'));
+    $this->assertNull($this->accessChecker->access($route, $request));
+
+    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessAllow'));
+    $this->assertTrue($this->accessChecker->access($route, $request));
+
+    $route = new Route('/test-route', array('parameter' => 'TRUE'), array('_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessParameter'));
+    $this->assertTrue($this->accessChecker->access($route, $request));
+  }
+
+}
+
+class TestController {
+
+  public function accessAllow() {
+    return AccessInterface::ALLOW;
+  }
+
+  public function accessDeny() {
+    return AccessInterface::DENY;
+  }
+
+  public function accessParameter($parameter) {
+    if ($parameter == 'TRUE') {
+      return AccessInterface::ALLOW;
+    }
+    else {
+      return AccessInterface::DENY;
+    }
+  }
+
+}