فهرست منبع

New injector (#111722)

* Initial new injector

* Allow createComponents to return classes

* Downsample injection

* Remove more vestiges of subtype handling

* Lowercase logger

* Respond to code review comments

* Only one object per class

* Some additional cleanup incl spotless

* PR feedback

* Missed one

* Rename workQueue

* Remove Injector.addRecordContents

* TelemetryProvider requires us to inject an object using a supertype

* Address Simon's comments

* Clarify the reason for SuppressForbidden

* Make log indentation code less intrusive
Patrick Doyle 1 سال پیش
والد
کامیت
50871a3d28
19فایلهای تغییر یافته به همراه1025 افزوده شده و 25 حذف شده
  1. 1 0
      server/src/main/java/module-info.java
  2. 314 0
      server/src/main/java/org/elasticsearch/injection/Injector.java
  3. 108 0
      server/src/main/java/org/elasticsearch/injection/PlanInterpreter.java
  4. 128 0
      server/src/main/java/org/elasticsearch/injection/Planner.java
  5. 23 0
      server/src/main/java/org/elasticsearch/injection/api/Inject.java
  6. 41 0
      server/src/main/java/org/elasticsearch/injection/package-info.java
  7. 17 0
      server/src/main/java/org/elasticsearch/injection/spec/ExistingInstanceSpec.java
  8. 13 0
      server/src/main/java/org/elasticsearch/injection/spec/InjectionSpec.java
  9. 30 0
      server/src/main/java/org/elasticsearch/injection/spec/MethodHandleSpec.java
  10. 24 0
      server/src/main/java/org/elasticsearch/injection/spec/ParameterSpec.java
  11. 25 0
      server/src/main/java/org/elasticsearch/injection/spec/package-info.java
  12. 11 0
      server/src/main/java/org/elasticsearch/injection/step/InjectionStep.java
  13. 17 0
      server/src/main/java/org/elasticsearch/injection/step/InstantiateStep.java
  14. 15 0
      server/src/main/java/org/elasticsearch/injection/step/package-info.java
  15. 48 22
      server/src/main/java/org/elasticsearch/node/NodeConstruction.java
  16. 52 0
      server/src/main/java/org/elasticsearch/node/PluginServiceInstances.java
  17. 154 0
      server/src/test/java/org/elasticsearch/injection/InjectorTests.java
  18. 1 1
      x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/Downsample.java
  19. 3 2
      x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleMetrics.java

+ 1 - 0
server/src/main/java/module-info.java

@@ -190,6 +190,7 @@ module org.elasticsearch.server {
     exports org.elasticsearch.common.file;
     exports org.elasticsearch.common.geo;
     exports org.elasticsearch.common.hash;
+    exports org.elasticsearch.injection.api;
     exports org.elasticsearch.injection.guice;
     exports org.elasticsearch.injection.guice.binder;
     exports org.elasticsearch.injection.guice.internal;

+ 314 - 0
server/src/main/java/org/elasticsearch/injection/Injector.java

@@ -0,0 +1,314 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection;
+
+import org.elasticsearch.injection.api.Inject;
+import org.elasticsearch.injection.spec.ExistingInstanceSpec;
+import org.elasticsearch.injection.spec.InjectionSpec;
+import org.elasticsearch.injection.spec.MethodHandleSpec;
+import org.elasticsearch.injection.spec.ParameterSpec;
+import org.elasticsearch.injection.step.InjectionStep;
+import org.elasticsearch.logging.LogManager;
+import org.elasticsearch.logging.Logger;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static java.util.function.Predicate.not;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * The main object for dependency injection.
+ * <p>
+ * Allows the user to specify the requirements, then call {@link #inject} to create an object plus all its dependencies.
+ * <p>
+ * <em>Implementation note</em>: this class itself contains logic for <em>specifying</em> the injection requirements;
+ * the actual injection operations are performed in other classes like {@link Planner} and {@link PlanInterpreter},
+ */
+public final class Injector {
+    private static final Logger logger = LogManager.getLogger(Injector.class);
+
+    /**
+     * The specifications supplied by the user, as opposed to those inferred by the injector.
+     */
+    private final Map<Class<?>, InjectionSpec> seedSpecs;
+
+    Injector(Map<Class<?>, InjectionSpec> seedSpecs) {
+        this.seedSpecs = seedSpecs;
+    }
+
+    public static Injector create() {
+        return new Injector(new LinkedHashMap<>());
+    }
+
+    /**
+     * Instructs the injector to instantiate <code>classToProcess</code>
+     * in accordance with whatever annotations may be present on that class.
+     * <p>
+     * There are only three ways the injector can find out that it must instantiate some class:
+     * <ol>
+     *     <li>
+     *         This method
+     *     </li>
+     *     <li>
+     *         The parameter passed to {@link #inject}
+     *     </li>
+     *     <li>
+     *         A constructor parameter of some other class being instantiated,
+     *         having exactly the right class (not a supertype)
+     *     </li>
+     * </ol>
+     *
+     * @return <code>this</code>
+     */
+    public Injector addClass(Class<?> classToProcess) {
+        MethodHandleSpec methodHandleSpec = methodHandleSpecFor(classToProcess);
+        var existing = seedSpecs.put(classToProcess, methodHandleSpec);
+        if (existing != null) {
+            throw new IllegalArgumentException("class " + classToProcess.getSimpleName() + " has already been added");
+        }
+        return this;
+    }
+
+    /**
+     * Equivalent to multiple chained calls to {@link #addClass}.
+     */
+    public Injector addClasses(Collection<Class<?>> classesToProcess) {
+        classesToProcess.forEach(this::addClass);
+        return this;
+    }
+
+    /**
+     * Equivalent to {@link #addInstance addInstance(object.getClass(), object)}.
+     */
+    public <T> Injector addInstance(Object object) {
+        @SuppressWarnings("unchecked")
+        Class<T> actualClass = (Class<T>) object.getClass(); // Whatever the runtime type is, it's represented by T
+        return addInstance(actualClass, actualClass.cast(object));
+    }
+
+    /**
+     * Equivalent to multiple calls to {@link #addInstance(Object)}.
+     */
+    public Injector addInstances(Collection<?> objects) {
+        for (var x : objects) {
+            addInstance(x);
+        }
+        return this;
+    }
+
+    /**
+     * Indicates that <code>object</code> is to be injected for parameters of type <code>type</code>.
+     * The given object is treated as though it had been instantiated by the injector.
+     */
+    public <T> Injector addInstance(Class<? super T> type, T object) {
+        assert type.isInstance(object); // No unchecked casting shenanigans allowed
+        var existing = seedSpecs.put(type, new ExistingInstanceSpec(type, object));
+        if (existing != null) {
+            throw new IllegalStateException("There's already an object for " + type);
+        }
+        return this;
+    }
+
+    /**
+     * Main entry point. Causes objects to be constructed.
+     * @return {@link Map} whose keys are all the requested <code>resultTypes</code> and whose values are all the instances of those types.
+     */
+    public Map<Class<?>, Object> inject(Collection<? extends Class<?>> resultTypes) {
+        resultTypes.forEach(this::ensureClassIsSpecified);
+        PlanInterpreter i = doInjection();
+        return resultTypes.stream().collect(toMap(c -> c, i::theInstanceOf));
+    }
+
+    private <T> void ensureClassIsSpecified(Class<T> resultType) {
+        if (seedSpecs.containsKey(resultType) == false) {
+            addClass(resultType);
+        }
+    }
+
+    private PlanInterpreter doInjection() {
+        logger.debug("Starting injection");
+        Map<Class<?>, InjectionSpec> specMap = specClosure(seedSpecs);
+        Map<Class<?>, Object> existingInstances = new LinkedHashMap<>();
+        specMap.values().forEach((spec) -> {
+            if (spec instanceof ExistingInstanceSpec e) {
+                existingInstances.put(e.requestedType(), e.instance());
+            }
+        });
+        PlanInterpreter interpreter = new PlanInterpreter(existingInstances);
+        interpreter.executePlan(injectionPlan(seedSpecs.keySet(), specMap));
+        logger.debug("Done injection");
+        return interpreter;
+    }
+
+    /**
+     * Finds an {@link InjectionSpec} for every class the injector is capable of injecting.
+     * <p>
+     * We do this once the injector is fully configured, with all calls to {@link #addClass} and {@link #addInstance} finished,
+     * so that we can easily build the complete picture of how injection should occur.
+     * <p>
+     * This is not part of the planning process; it's just discovering all the things
+     * the injector needs to know about. This logic isn't concerned with ordering or dependency cycles.
+     *
+     * @param seedMap the injections the user explicitly asked for
+     * @return an {@link InjectionSpec} for every class the injector is capable of injecting.
+     */
+    private static Map<Class<?>, InjectionSpec> specClosure(Map<Class<?>, InjectionSpec> seedMap) {
+        assert seedMapIsValid(seedMap);
+
+        // For convenience, we pretend there's a gigantic method out there that takes
+        // all the seed types as parameters.
+        Queue<ParameterSpec> workQueue = seedMap.values()
+            .stream()
+            .map(InjectionSpec::requestedType)
+            .map(Injector::syntheticParameterSpec)
+            .collect(toCollection(ArrayDeque::new));
+
+        // This map doubles as a checklist of classes we're already finished processing
+        Map<Class<?>, InjectionSpec> result = new LinkedHashMap<>();
+
+        ParameterSpec p;
+        while ((p = workQueue.poll()) != null) {
+            Class<?> c = p.injectableType();
+            InjectionSpec existingResult = result.get(c);
+            if (existingResult != null) {
+                logger.trace("Spec for {} already exists", c.getSimpleName());
+                continue;
+            }
+
+            InjectionSpec spec = seedMap.get(c);
+            if (spec instanceof ExistingInstanceSpec) {
+                // simple!
+                result.put(c, spec);
+                continue;
+            }
+
+            // At this point, we know we'll need a MethodHandleSpec
+            MethodHandleSpec methodHandleSpec;
+            if (spec == null) {
+                // The user didn't specify this class; we must infer it now
+                spec = methodHandleSpec = methodHandleSpecFor(c);
+            } else if (spec instanceof MethodHandleSpec m) {
+                methodHandleSpec = m;
+            } else {
+                throw new AssertionError("Unexpected spec: " + spec);
+            }
+
+            logger.trace("Inspecting parameters for constructor of {}", c);
+            for (var ps : methodHandleSpec.parameters()) {
+                logger.trace("Enqueue {}", ps);
+                workQueue.add(ps);
+            }
+
+            registerSpec(spec, result);
+        }
+
+        if (logger.isTraceEnabled()) {
+            logger.trace("Specs: {}", result.values().stream().map(Object::toString).collect(joining("\n\t", "\n\t", "")));
+        }
+        return result;
+    }
+
+    private static MethodHandleSpec methodHandleSpecFor(Class<?> c) {
+        Constructor<?> constructor = getSuitableConstructorIfAny(c);
+        if (constructor == null) {
+            throw new IllegalStateException("No suitable constructor for " + c);
+        }
+
+        MethodHandle ctorHandle;
+        try {
+            ctorHandle = lookup().unreflectConstructor(constructor);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException(e);
+        }
+
+        List<ParameterSpec> parameters = Stream.of(constructor.getParameters()).map(ParameterSpec::from).toList();
+
+        return new MethodHandleSpec(c, ctorHandle, parameters);
+    }
+
+    /**
+     * @return true (unless an assertion fails). Never returns false.
+     */
+    private static boolean seedMapIsValid(Map<Class<?>, InjectionSpec> seed) {
+        seed.forEach(
+            (c, s) -> { assert s.requestedType().equals(c) : "Spec must be associated with its requestedType, not " + c + ": " + s; }
+        );
+        return true;
+    }
+
+    /**
+     * For the classes we've been explicitly asked to inject,
+     * pretend there's some massive method taking all of them as parameters
+     */
+    private static ParameterSpec syntheticParameterSpec(Class<?> c) {
+        return new ParameterSpec("synthetic_" + c.getSimpleName(), c, c);
+    }
+
+    private static Constructor<?> getSuitableConstructorIfAny(Class<?> type) {
+        var constructors = Stream.of(type.getConstructors()).filter(not(Constructor::isSynthetic)).toList();
+        if (constructors.size() == 1) {
+            return constructors.get(0);
+        }
+        var injectConstructors = constructors.stream().filter(c -> c.isAnnotationPresent(Inject.class)).toList();
+        if (injectConstructors.size() == 1) {
+            return injectConstructors.get(0);
+        }
+        logger.trace("No suitable constructor for {}", type);
+        return null;
+    }
+
+    private static void registerSpec(InjectionSpec spec, Map<Class<?>, InjectionSpec> specsByClass) {
+        Class<?> requestedType = spec.requestedType();
+        var existing = specsByClass.put(requestedType, spec);
+        if (existing == null || existing.equals(spec)) {
+            logger.trace("Register spec: {}", spec);
+        } else {
+            throw new IllegalStateException("Ambiguous specifications for " + requestedType + ": " + existing + " and " + spec);
+        }
+    }
+
+    private List<InjectionStep> injectionPlan(Set<Class<?>> requiredClasses, Map<Class<?>, InjectionSpec> specsByClass) {
+        logger.trace("Constructing instantiation plan");
+        Set<Class<?>> allParameterTypes = new HashSet<>();
+        specsByClass.values().forEach(spec -> {
+            if (spec instanceof MethodHandleSpec m) {
+                m.parameters().stream().map(ParameterSpec::injectableType).forEachOrdered(allParameterTypes::add);
+            }
+        });
+
+        var plan = new Planner(specsByClass, requiredClasses, allParameterTypes).injectionPlan();
+        if (logger.isDebugEnabled()) {
+            logger.debug("Injection plan: {}", plan.stream().map(Object::toString).collect(joining("\n\t", "\n\t", "")));
+        }
+        return plan;
+    }
+
+    /**
+     * <em>Evolution note</em>: there may be cases in the where we allow the user to
+     * supply a {@link java.lang.invoke.MethodHandles.Lookup} for convenience,
+     * so that they aren't required to make things public just to participate in injection.
+     */
+    private static MethodHandles.Lookup lookup() {
+        return MethodHandles.publicLookup();
+    }
+
+}

+ 108 - 0
server/src/main/java/org/elasticsearch/injection/PlanInterpreter.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection;
+
+import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.injection.spec.MethodHandleSpec;
+import org.elasticsearch.injection.spec.ParameterSpec;
+import org.elasticsearch.injection.step.InjectionStep;
+import org.elasticsearch.injection.step.InstantiateStep;
+import org.elasticsearch.logging.LogManager;
+import org.elasticsearch.logging.Logger;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs the actual injection operations by running the {@link InjectionStep}s.
+ * <p>
+ * The intent is that this logic is as simple as possible so that we don't run complex injection
+ * logic alongside the user-supplied constructor logic. All the injector complexity is already
+ * supposed to have happened in the planning phase. In particular, no injection-related errors
+ * are supposed to be detected during execution; they should be detected during planning and validation.
+ * All exceptions thrown during execution are supposed to be caused by user-supplied code.
+ *
+ * <p>
+ * <strong>Execution model</strong>:
+ * The state of the injector during injection comprises a map from classes to objects.
+ * Before any steps execute, the map is pre-populated by object instances added via
+ * {@link Injector#addInstance(Object)}  Injector.addInstance},
+ * and then the steps begin to execute, reading and writing from this map.
+ * Some steps create objects and add them to this map; others manipulate the map itself.
+ */
+final class PlanInterpreter {
+    private static final Logger logger = LogManager.getLogger(PlanInterpreter.class);
+    private final Map<Class<?>, Object> instances = new LinkedHashMap<>();
+
+    PlanInterpreter(Map<Class<?>, Object> existingInstances) {
+        existingInstances.forEach(this::addInstance);
+    }
+
+    /**
+     * Main entry point. Contains the implementation logic for each {@link InjectionStep}.
+     */
+    void executePlan(List<InjectionStep> plan) {
+        int numConstructorCalls = 0;
+        for (InjectionStep step : plan) {
+            if (step instanceof InstantiateStep i) {
+                MethodHandleSpec spec = i.spec();
+                logger.trace("Instantiating {}", spec.requestedType().getSimpleName());
+                addInstance(spec.requestedType(), instantiate(spec));
+                ++numConstructorCalls;
+            } else {
+                // TODO: switch patterns would make this unnecessary
+                assert false : "Unexpected step type: " + step.getClass().getSimpleName();
+                throw new IllegalStateException("Unexpected step type: " + step.getClass().getSimpleName());
+            }
+        }
+        logger.debug("Instantiated {} objects", numConstructorCalls);
+    }
+
+    /**
+     * @return the list element corresponding to instances.get(type).get(0),
+     * assuming that instances.get(type) has exactly one element.
+     * @throws IllegalStateException if instances.get(type) does not have exactly one element
+     */
+    public <T> T theInstanceOf(Class<T> type) {
+        Object instance = instances.get(type);
+        if (instance == null) {
+            throw new IllegalStateException("No object of type " + type.getSimpleName());
+        }
+        return type.cast(instance);
+    }
+
+    private void addInstance(Class<?> requestedType, Object instance) {
+        Object old = instances.put(requestedType, instance);
+        if (old != null) {
+            throw new IllegalStateException("Multiple objects for " + requestedType);
+        }
+    }
+
+    /**
+     * @throws IllegalStateException if the <code>MethodHandle</code> throws.
+     */
+    @SuppressForbidden(
+        reason = "Can't call invokeExact because we don't know the method argument types statically, "
+            + "since each constructor has a different signature"
+    )
+    private Object instantiate(MethodHandleSpec spec) {
+        Object[] args = spec.parameters().stream().map(this::parameterValue).toArray();
+        try {
+            return spec.methodHandle().invokeWithArguments(args);
+        } catch (Throwable e) {
+            throw new IllegalStateException("Unexpected exception while instantiating {}" + spec, e);
+        }
+    }
+
+    private Object parameterValue(ParameterSpec parameterSpec) {
+        return theInstanceOf(parameterSpec.formalType());
+    }
+
+}

+ 128 - 0
server/src/main/java/org/elasticsearch/injection/Planner.java

@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection;
+
+import org.elasticsearch.injection.spec.ExistingInstanceSpec;
+import org.elasticsearch.injection.spec.InjectionSpec;
+import org.elasticsearch.injection.spec.MethodHandleSpec;
+import org.elasticsearch.injection.step.InjectionStep;
+import org.elasticsearch.injection.step.InstantiateStep;
+import org.elasticsearch.logging.LogManager;
+import org.elasticsearch.logging.Logger;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * <em>Evolution note</em>: the intent is to plan one domain/subsystem at a time.
+ */
+final class Planner {
+    private static final Logger logger = LogManager.getLogger(Planner.class);
+
+    final List<InjectionStep> plan;
+    final Map<Class<?>, InjectionSpec> specsByClass;
+    final Set<Class<?>> requiredTypes; // The injector's job is to ensure there is an instance of these; this is like the "root set"
+    final Set<Class<?>> allParameterTypes; // All the injectable types in all dependencies (recursively) of all required types
+    final Set<InjectionSpec> startedPlanning;
+    final Set<InjectionSpec> finishedPlanning;
+    final Set<Class<?>> alreadyProxied;
+
+    /**
+     * @param specsByClass an {@link InjectionSpec} indicating how each class should be injected
+     * @param requiredTypes the classes of which we need instances
+     * @param allParameterTypes the classes that appear as the type of any parameter of any constructor we might call
+     */
+    Planner(Map<Class<?>, InjectionSpec> specsByClass, Set<Class<?>> requiredTypes, Set<Class<?>> allParameterTypes) {
+        this.requiredTypes = requiredTypes;
+        this.plan = new ArrayList<>();
+        this.specsByClass = unmodifiableMap(specsByClass);
+        this.allParameterTypes = unmodifiableSet(allParameterTypes);
+        this.startedPlanning = new HashSet<>();
+        this.finishedPlanning = new HashSet<>();
+        this.alreadyProxied = new HashSet<>();
+    }
+
+    /**
+     * Intended to be called once.
+     * <p>
+     * Note that not all proxies are resolved once this plan has been executed.
+     * <p>
+     *
+     * <em>Evolution note</em>: in a world with multiple domains/subsystems,
+     * it will become necessary to defer proxy resolution until after other plans
+     * have been executed, because they could create additional objects that ought
+     * to be included in the proxies created by this plan.
+     *
+     * @return the {@link InjectionStep} objects listed in execution order.
+     */
+    List<InjectionStep> injectionPlan() {
+        for (Class<?> c : requiredTypes) {
+            planForClass(c, 0);
+        }
+        return plan;
+    }
+
+    /**
+     * Recursive procedure that determines what effect <code>requestedClass</code>
+     * should have on the plan under construction.
+     *
+     * @param depth is used just for indenting the logs
+     */
+    private void planForClass(Class<?> requestedClass, int depth) {
+        InjectionSpec spec = specsByClass.get(requestedClass);
+        if (spec == null) {
+            throw new IllegalStateException("Cannot instantiate " + requestedClass + ": no specification provided");
+        }
+        planForSpec(spec, depth);
+    }
+
+    private void planForSpec(InjectionSpec spec, int depth) {
+        if (finishedPlanning.contains(spec)) {
+            logger.trace("{}Already planned {}", indent(depth), spec);
+            return;
+        }
+
+        logger.trace("{}Planning for {}", indent(depth), spec);
+        if (startedPlanning.add(spec) == false) {
+            // TODO: Better cycle detection and reporting. Use SCCs
+            throw new IllegalStateException("Cyclic dependency involving " + spec);
+        }
+
+        if (spec instanceof MethodHandleSpec m) {
+            for (var p : m.parameters()) {
+                logger.trace("{}- Recursing into {} for actual parameter {}", indent(depth), p.injectableType(), p);
+                planForClass(p.injectableType(), depth + 1);
+            }
+            addStep(new InstantiateStep(m), depth);
+        } else if (spec instanceof ExistingInstanceSpec e) {
+            logger.trace("{}- Plan {}", indent(depth), e);
+            // Nothing to do. The injector will already have the required object.
+        } else {
+            throw new AssertionError("Unexpected injection spec: " + spec);
+        }
+
+        finishedPlanning.add(spec);
+    }
+
+    private void addStep(InjectionStep newStep, int depth) {
+        logger.trace("{}- Add step {}", indent(depth), newStep);
+        plan.add(newStep);
+    }
+
+    private static Supplier<String> indent(int depth) {
+        return () -> "\t".repeat(depth);
+    }
+}

+ 23 - 0
server/src/main/java/org/elasticsearch/injection/api/Inject.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.api;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Designates a constructor to be called by the injector.
+ */
+@Target(CONSTRUCTOR)
+@Retention(RUNTIME)
+public @interface Inject {
+}

+ 41 - 0
server/src/main/java/org/elasticsearch/injection/package-info.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * Our dependency injection technologies: our bespoke injector, plus our legacy vendored version of Google Guice.
+ * <h2>Usage</h2>
+ * The new injector is {@link org.elasticsearch.injection.Injector}.
+ * You create an instance using {@link org.elasticsearch.injection.Injector#create()},
+ * call various methods like {@link org.elasticsearch.injection.Injector#addClass} to configure it,
+ * then call {@link org.elasticsearch.injection.Injector#inject} to cause the constructors to be called.
+ *
+ * <h2>Operation</h2>
+ * Injection proceeds in three phases:
+ * <ol>
+ *     <li>
+ *         <em>Configuration</em>: the {@link org.elasticsearch.injection.Injector} captures the user's
+ *         intent in the form of {@link org.elasticsearch.injection.spec.InjectionSpec} objects,
+ *         one for each class.
+ *     </li>
+ *     <li>
+ *         <em>Planning</em>: the {@link org.elasticsearch.injection.Planner} analyzes the
+ *         {@link org.elasticsearch.injection.spec.InjectionSpec} objects, validates them,
+ *         and generates a <em>plan</em> in the form of a list of {@link org.elasticsearch.injection.step.InjectionStep} objects.
+ *     </li>
+ *     <li>
+ *         <em>Execution</em>: the {@link org.elasticsearch.injection.PlanInterpreter} runs
+ *         the steps in the plan, in sequence, to actually instantiate the objects and pass them
+ *         to each others' constructors.
+ *     </li>
+ * </ol>
+ *
+ * <h2>Google Guice</h2>
+ * The older injector, based on Google Guice, is in the {@code guice} package.
+ * The new injector is unrelated to Guice, and is intended to replace Guice eventually.
+ */
+package org.elasticsearch.injection;

+ 17 - 0
server/src/main/java/org/elasticsearch/injection/spec/ExistingInstanceSpec.java

@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.spec;
+
+public record ExistingInstanceSpec(Class<?> requestedType, Object instance) implements InjectionSpec {
+    @Override
+    public String toString() {
+        // Don't call instance.toString; who knows what that will return
+        return "ExistingInstanceSpec[" + "requestedType=" + requestedType + ']';
+    }
+}

+ 13 - 0
server/src/main/java/org/elasticsearch/injection/spec/InjectionSpec.java

@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.spec;
+
+public sealed interface InjectionSpec permits MethodHandleSpec, ExistingInstanceSpec {
+    Class<?> requestedType();
+}

+ 30 - 0
server/src/main/java/org/elasticsearch/injection/spec/MethodHandleSpec.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.spec;
+
+import java.lang.invoke.MethodHandle;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Indicates that a type should be instantiated by calling the given {@link java.lang.invoke.MethodHandle}.
+ * <p>
+ * <em>Design note</em>: the intent is that the semantics are fully specified by this record,
+ * and no additional reflection logic is required to determine how the object should be injected.
+ * Roughly speaking: all the reflection should be finished, and the results should be stored in this object.
+ */
+public record MethodHandleSpec(Class<?> requestedType, MethodHandle methodHandle, List<ParameterSpec> parameters) implements InjectionSpec {
+    public MethodHandleSpec {
+        assert Objects.equals(methodHandle.type().parameterList(), parameters.stream().map(ParameterSpec::formalType).toList())
+            : "MethodHandle parameter types must match the supplied parameter info; "
+                + methodHandle.type().parameterList()
+                + " vs "
+                + parameters;
+    }
+}

+ 24 - 0
server/src/main/java/org/elasticsearch/injection/spec/ParameterSpec.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.spec;
+
+import java.lang.reflect.Parameter;
+
+/**
+ * Captures the pertinent info required to inject one of the arguments of a constructor.
+ * @param name is for troubleshooting; it's not strictly needed
+ * @param formalType is the declared class of the parameter
+ * @param injectableType is the target type of the injection dependency
+ */
+public record ParameterSpec(String name, Class<?> formalType, Class<?> injectableType) {
+    public static ParameterSpec from(Parameter parameter) {
+        // We currently have no cases where the formal and injectable types are different.
+        return new ParameterSpec(parameter.getName(), parameter.getType(), parameter.getType());
+    }
+}

+ 25 - 0
server/src/main/java/org/elasticsearch/injection/spec/package-info.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * Objects that describe the means by which an object instance is created for (or associated with) some given type.
+ * <p>
+ * The hierarchy is rooted at {@link org.elasticsearch.injection.spec.InjectionSpec}.
+ * <p>
+ * Differs from {@link org.elasticsearch.injection.step.InjectionStep InjectionStep} in that:
+ *
+ * <ul>
+ *     <li>
+ *         this describes the requirements, while <code>InjectionStep</code> describes the solution
+ *     </li>
+ *     <li>
+ *         this is declarative, while <code>InjectionStep</code> is imperative
+ *     </li>
+ * </ul>
+ */
+package org.elasticsearch.injection.spec;

+ 11 - 0
server/src/main/java/org/elasticsearch/injection/step/InjectionStep.java

@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.step;
+
+public sealed interface InjectionStep permits InstantiateStep {}

+ 17 - 0
server/src/main/java/org/elasticsearch/injection/step/InstantiateStep.java

@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection.step;
+
+import org.elasticsearch.injection.spec.MethodHandleSpec;
+
+/**
+ * Constructs a new object by invoking a {@link java.lang.invoke.MethodHandle}
+ * as specified by a given {@link MethodHandleSpec}.
+ */
+public record InstantiateStep(MethodHandleSpec spec) implements InjectionStep {}

+ 15 - 0
server/src/main/java/org/elasticsearch/injection/step/package-info.java

@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * Objects that describe one operation to be performed by the <code>PlanInterpreter</code>.
+ * Injection is achieved by executing the steps in order.
+ * <p>
+ * See <code>PlanInterpreter</code> for more details on the execution model.
+ */
+package org.elasticsearch.injection.step;

+ 48 - 22
server/src/main/java/org/elasticsearch/node/NodeConstruction.java

@@ -80,6 +80,7 @@ import org.elasticsearch.common.settings.SettingsModule;
 import org.elasticsearch.common.util.BigArrays;
 import org.elasticsearch.common.util.PageCacheRecycler;
 import org.elasticsearch.core.IOUtils;
+import org.elasticsearch.core.SuppressForbidden;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.core.Tuple;
 import org.elasticsearch.discovery.DiscoveryModule;
@@ -216,6 +217,7 @@ import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
@@ -228,6 +230,9 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.util.Collections.newSetFromMap;
+import static java.util.function.Predicate.not;
 import static org.elasticsearch.core.Types.forciblyCast;
 
 /**
@@ -831,27 +836,6 @@ class NodeConstruction {
             metadataCreateIndexService
         );
 
-        record PluginServiceInstances(
-            Client client,
-            ClusterService clusterService,
-            RerouteService rerouteService,
-            ThreadPool threadPool,
-            ResourceWatcherService resourceWatcherService,
-            ScriptService scriptService,
-            NamedXContentRegistry xContentRegistry,
-            Environment environment,
-            NodeEnvironment nodeEnvironment,
-            NamedWriteableRegistry namedWriteableRegistry,
-            IndexNameExpressionResolver indexNameExpressionResolver,
-            RepositoriesService repositoriesService,
-            TelemetryProvider telemetryProvider,
-            AllocationService allocationService,
-            IndicesService indicesService,
-            FeatureService featureService,
-            SystemIndices systemIndices,
-            DataStreamGlobalRetentionSettings dataStreamGlobalRetentionSettings,
-            DocumentParsingProvider documentParsingProvider
-        ) implements Plugin.PluginServices {}
         PluginServiceInstances pluginServices = new PluginServiceInstances(
             client,
             clusterService,
@@ -874,7 +858,30 @@ class NodeConstruction {
             documentParsingProvider
         );
 
-        Collection<?> pluginComponents = pluginsService.flatMap(p -> p.createComponents(pluginServices)).toList();
+        Collection<?> pluginComponents = pluginsService.flatMap(plugin -> {
+            Collection<?> allItems = plugin.createComponents(pluginServices);
+            List<?> componentObjects = allItems.stream().filter(not(x -> x instanceof Class<?>)).toList();
+            List<? extends Class<?>> classes = allItems.stream().filter(x -> x instanceof Class<?>).map(x -> (Class<?>) x).toList();
+
+            // Then, injection
+            Collection<?> componentsFromInjector;
+            if (classes.isEmpty()) {
+                componentsFromInjector = Set.of();
+            } else {
+                logger.debug("Using injector to instantiate classes for {}: {}", plugin.getClass().getSimpleName(), classes);
+                var injector = org.elasticsearch.injection.Injector.create();
+                injector.addInstances(componentObjects);
+                addRecordContents(injector, pluginServices);
+                var resultMap = injector.inject(classes);
+                // For now, assume we want all components added to the Guice injector
+                var distinctObjects = newSetFromMap(new IdentityHashMap<>());
+                distinctObjects.addAll(resultMap.values());
+                componentsFromInjector = distinctObjects;
+            }
+
+            // Return both
+            return Stream.of(componentObjects, componentsFromInjector).flatMap(Collection::stream).toList();
+        }).toList();
 
         var terminationHandlers = pluginsService.loadServiceProviders(TerminationHandlerProvider.class)
             .stream()
@@ -1175,6 +1182,24 @@ class NodeConstruction {
         postInjection(clusterModule, actionModule, clusterService, transportService, featureService);
     }
 
+    /**
+     * For each "component" (getter) <em>c</em> of a {@link Record},
+     * calls {@link org.elasticsearch.injection.Injector#addInstance(Object) Injector.addInstance}
+     * to register the value with the component's declared type.
+     */
+    @SuppressForbidden(reason = "Can't call invokeExact because we don't know the exact Record subtype statically")
+    private static <T> void addRecordContents(org.elasticsearch.injection.Injector injector, Record r) {
+        for (var c : r.getClass().getRecordComponents()) {
+            try {
+                @SuppressWarnings("unchecked")
+                Class<T> type = (Class<T>) c.getType(); // T represents the declared type of the record component, whatever it is
+                injector.addInstance(type, type.cast(lookup().unreflect(c.getAccessor()).invoke(r)));
+            } catch (Throwable e) {
+                throw new IllegalStateException("Unable to read record component " + c, e);
+            }
+        }
+    }
+
     private ClusterService createClusterService(SettingsModule settingsModule, ThreadPool threadPool, TaskManager taskManager) {
         ClusterService clusterService = new ClusterService(
             settingsModule.getSettings(),
@@ -1595,4 +1620,5 @@ class NodeConstruction {
             b.bind(PersistentTasksClusterService.class).toInstance(persistentTasksClusterService);
         };
     }
+
 }

+ 52 - 0
server/src/main/java/org/elasticsearch/node/PluginServiceInstances.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.node;
+
+import org.elasticsearch.client.internal.Client;
+import org.elasticsearch.cluster.metadata.DataStreamGlobalRetentionSettings;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.routing.RerouteService;
+import org.elasticsearch.cluster.routing.allocation.AllocationService;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.env.Environment;
+import org.elasticsearch.env.NodeEnvironment;
+import org.elasticsearch.features.FeatureService;
+import org.elasticsearch.indices.IndicesService;
+import org.elasticsearch.indices.SystemIndices;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.plugins.internal.DocumentParsingProvider;
+import org.elasticsearch.repositories.RepositoriesService;
+import org.elasticsearch.script.ScriptService;
+import org.elasticsearch.telemetry.TelemetryProvider;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.watcher.ResourceWatcherService;
+import org.elasticsearch.xcontent.NamedXContentRegistry;
+
+public record PluginServiceInstances(
+    Client client,
+    ClusterService clusterService,
+    RerouteService rerouteService,
+    ThreadPool threadPool,
+    ResourceWatcherService resourceWatcherService,
+    ScriptService scriptService,
+    NamedXContentRegistry xContentRegistry,
+    Environment environment,
+    NodeEnvironment nodeEnvironment,
+    NamedWriteableRegistry namedWriteableRegistry,
+    IndexNameExpressionResolver indexNameExpressionResolver,
+    RepositoriesService repositoriesService,
+    TelemetryProvider telemetryProvider,
+    AllocationService allocationService,
+    IndicesService indicesService,
+    FeatureService featureService,
+    SystemIndices systemIndices,
+    DataStreamGlobalRetentionSettings dataStreamGlobalRetentionSettings,
+    DocumentParsingProvider documentParsingProvider
+) implements Plugin.PluginServices {}

+ 154 - 0
server/src/test/java/org/elasticsearch/injection/InjectorTests.java

@@ -0,0 +1,154 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.injection;
+
+import org.elasticsearch.test.ESTestCase;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.Set;
+
+public class InjectorTests extends ESTestCase {
+
+    public record First() {}
+
+    public record Second(First first) {}
+
+    public record Third(First first, Second second) {}
+
+    public record ExistingInstances(First first, Second second) {}
+
+    public void testMultipleResultsMap() {
+        Injector injector = Injector.create().addClasses(List.of(Service1.class, Component3.class));
+        var resultMap = injector.inject(List.of(Service1.class, Component3.class));
+        assertEquals(Set.of(Service1.class, Component3.class), resultMap.keySet());
+        Service1 service1 = (Service1) resultMap.get(Service1.class);
+        Component3 component3 = (Component3) resultMap.get(Component3.class);
+        assertSame(service1, component3.service1());
+    }
+
+    /**
+     * In most cases, if there are two objects that are instances of a class, that's ambiguous.
+     * However, if a concrete (non-abstract) superclass is configured directly, that is not ambiguous:
+     * the instance of that superclass takes precedence over any instances of any subclasses.
+     */
+    public void testConcreteSubclass() {
+        MethodHandles.lookup();
+        assertEquals(
+            Superclass.class,
+            Injector.create()
+                .addClasses(List.of(Superclass.class, Subclass.class)) // Superclass first
+                .inject(List.of(Superclass.class))
+                .get(Superclass.class)
+                .getClass()
+        );
+        MethodHandles.lookup();
+        assertEquals(
+            Superclass.class,
+            Injector.create()
+                .addClasses(List.of(Subclass.class, Superclass.class)) // Subclass first
+                .inject(List.of(Superclass.class))
+                .get(Superclass.class)
+                .getClass()
+        );
+        MethodHandles.lookup();
+        assertEquals(
+            Superclass.class,
+            Injector.create()
+                .addClasses(List.of(Subclass.class))
+                .inject(List.of(Superclass.class)) // Superclass is not mentioned until here
+                .get(Superclass.class)
+                .getClass()
+        );
+    }
+
+    //
+    // Sad paths
+    //
+
+    public void testBadInterfaceClass() {
+        assertThrows(IllegalStateException.class, () -> {
+            MethodHandles.lookup();
+            Injector.create().addClass(Listener.class).inject(List.of());
+        });
+    }
+
+    public void testBadUnknownType() {
+        // Injector knows only about Component4, discovers Listener, but can't find any subtypes
+        MethodHandles.lookup();
+        Injector injector = Injector.create().addClass(Component4.class);
+
+        assertThrows(IllegalStateException.class, () -> injector.inject(List.of()));
+    }
+
+    public void testBadCircularDependency() {
+        assertThrows(IllegalStateException.class, () -> {
+            MethodHandles.lookup();
+            Injector injector = Injector.create();
+            injector.addClasses(List.of(Circular1.class, Circular2.class)).inject(List.of());
+        });
+    }
+
+    /**
+     * For this one, we don't explicitly tell the injector about the classes involved in the cycle;
+     * it finds them on its own.
+     */
+    public void testBadCircularDependencyViaParameter() {
+        record UsesCircular1(Circular1 circular1) {}
+        assertThrows(IllegalStateException.class, () -> {
+            MethodHandles.lookup();
+            Injector.create().addClass(UsesCircular1.class).inject(List.of());
+        });
+    }
+
+    public void testBadCircularDependencyViaSupertype() {
+        interface Service1 {}
+        record Service2(Service1 service1) {}
+        record Service3(Service2 service2) implements Service1 {}
+        assertThrows(IllegalStateException.class, () -> {
+            MethodHandles.lookup();
+            Injector injector = Injector.create();
+            injector.addClasses(List.of(Service2.class, Service3.class)).inject(List.of());
+        });
+    }
+
+    // Common injectable things
+
+    public record Service1() {}
+
+    public interface Listener {}
+
+    public record Component1() implements Listener {}
+
+    public record Component2(Component1 component1) {}
+
+    public record Component3(Service1 service1) {}
+
+    public record Component4(Listener listener) {}
+
+    public record GoodService(List<Component1> components) {}
+
+    public record BadService(List<Component1> components) {
+        public BadService {
+            // Shouldn't be using the component list here!
+            assert components.isEmpty() == false;
+        }
+    }
+
+    public record MultiService(List<Component1> component1s, List<Component2> component2s) {}
+
+    public record Circular1(Circular2 service2) {}
+
+    public record Circular2(Circular1 service2) {}
+
+    public static class Superclass {}
+
+    public static class Subclass extends Superclass {}
+
+}

+ 1 - 1
x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/Downsample.java

@@ -137,6 +137,6 @@ public class Downsample extends Plugin implements ActionPlugin, PersistentTaskPl
 
     @Override
     public Collection<?> createComponents(PluginServices services) {
-        return List.of(new DownsampleMetrics(services.telemetryProvider().getMeterRegistry()));
+        return List.of(DownsampleMetrics.class);
     }
 }

+ 3 - 2
x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleMetrics.java

@@ -8,6 +8,7 @@
 package org.elasticsearch.xpack.downsample;
 
 import org.elasticsearch.common.component.AbstractLifecycleComponent;
+import org.elasticsearch.telemetry.TelemetryProvider;
 import org.elasticsearch.telemetry.metric.MeterRegistry;
 
 import java.io.IOException;
@@ -36,8 +37,8 @@ public class DownsampleMetrics extends AbstractLifecycleComponent {
 
     private final MeterRegistry meterRegistry;
 
-    public DownsampleMetrics(MeterRegistry meterRegistry) {
-        this.meterRegistry = meterRegistry;
+    public DownsampleMetrics(TelemetryProvider telemetryProvider) {
+        this.meterRegistry = telemetryProvider.getMeterRegistry();
     }
 
     @Override