mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-11 19:16:42 +00:00
[JENKINS-52818] support @Postconstruct on extensions (#3570)
* add support for JSR250 @PostConstruct Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * with PostConstruct method no need to explicitely invoke load() anymore Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * introduce PersistentDescriptor to flag Descriptors which expect load() to be invoked. This prevent double execution for plugins which have been designed on previous version of code and don’t expect load to be automatically invoked. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * document PostConstruct method discovery logic Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * Force reflective access to non-public @PostConstruct methods Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * check @PostConstruct annotation both on method and on implemented interface with a matching method. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com> * typo Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
1 parent
8b6f3c2a06
commit
c3988aae8a
16 files changed
+126
-68
No files matched your search
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
package hudson;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -35,11 +36,13 @@ import com.google.inject.Module;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Scope;
|
||||
import com.google.inject.Scopes;
|
||||
import com.google.inject.matcher.Matchers;
|
||||
import com.google.inject.name.Names;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.spi.ProvisionListener;
|
||||
import hudson.init.InitMilestone;
|
||||
import hudson.model.Descriptor;
|
||||
import hudson.model.Hudson;
|
||||
import hudson.util.ReflectionUtils;
|
||||
import jenkins.ExtensionComponentSet;
|
||||
import jenkins.ExtensionFilter;
|
||||
import jenkins.ExtensionRefreshException;
|
||||
@@ -49,20 +52,25 @@ import net.java.sezpoz.Index;
|
||||
import net.java.sezpoz.IndexItem;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Discovers the implementations of an extension point.
|
||||
@@ -454,7 +462,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
|
||||
* Instead of using SezPoz to instantiate, we'll instantiate them by using Guice,
|
||||
* so that we can take advantage of dependency injection.
|
||||
*/
|
||||
private class SezpozModule extends AbstractModule {
|
||||
private class SezpozModule extends AbstractModule implements ProvisionListener {
|
||||
private final List<IndexItem<?,Object>> index;
|
||||
private final List<IndexItem<?,Object>> loadedIndex;
|
||||
|
||||
@@ -503,6 +511,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
|
||||
@SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"})
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
bindListener(Matchers.any(), this);
|
||||
|
||||
for (final IndexItem<?,Object> item : index) {
|
||||
boolean optional = isOptional(item.annotation());
|
||||
try {
|
||||
@@ -550,9 +561,64 @@ public abstract class ExtensionFinder implements ExtensionPoint {
|
||||
public List<IndexItem<?, Object>> getLoadedIndex() {
|
||||
return Collections.unmodifiableList(loadedIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void onProvision(ProvisionInvocation<T> provision) {
|
||||
final T instance = provision.provision();
|
||||
if (instance == null) return;
|
||||
List<Method> methods = new LinkedList<>();
|
||||
Class c = instance.getClass();
|
||||
|
||||
// find PostConstruct methods in class hierarchy, the one from parent class being first in list
|
||||
// so that we invoke them before derived class one. This isn't specified in JSR-250 but implemented
|
||||
// this way in Spring and what most developers would expect to happen.
|
||||
|
||||
final Set<Class> interfaces = ClassUtils.getAllInterfacesAsSet(instance);
|
||||
|
||||
while (c != Object.class) {
|
||||
Arrays.stream(c.getDeclaredMethods())
|
||||
.map(m -> getMethodAndInterfaceDeclarations(m, interfaces))
|
||||
.flatMap(Collection::stream)
|
||||
.filter(m -> m.getAnnotation(PostConstruct.class) != null)
|
||||
.findFirst()
|
||||
.ifPresent(method -> methods.add(0, method));
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
|
||||
for (Method postConstruct : methods) {
|
||||
try {
|
||||
postConstruct.setAccessible(true);
|
||||
postConstruct.invoke(instance);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException(String.format("@PostConstruct %s", postConstruct), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns initial {@link Method} as well as all matching ones found in interfaces.
|
||||
* This allows to introspect metadata for a method which is both declared in parent class and in implemented
|
||||
* interface(s). <code>interfaces</code> typically is obtained by {@link ClassUtils#getAllInterfacesAsSet}
|
||||
*/
|
||||
Collection<Method> getMethodAndInterfaceDeclarations(Method method, Collection<Class> interfaces) {
|
||||
final List<Method> methods = new ArrayList<>();
|
||||
methods.add(method);
|
||||
|
||||
// we search for matching method by iteration and comparison vs getMethod to avoid repeated NoSuchMethodException
|
||||
// being thrown, while interface typically only define a few set of methods to check.
|
||||
interfaces.stream()
|
||||
.map(Class::getMethods)
|
||||
.flatMap(Arrays::stream)
|
||||
.filter(m -> m.getName().equals(method.getName()) && Arrays.equals(m.getParameterTypes(), method.getParameterTypes()))
|
||||
.findFirst()
|
||||
.ifPresent(methods::add);
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The bootstrap implementation that looks for the {@link Extension} marker.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user