You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/10/04 20:03:50 UTC

[isis] branch v2 updated: ISIS-2158: annotation scanning: minor performance tweaks

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new e88bb5a  ISIS-2158: annotation scanning: minor performance tweaks
e88bb5a is described below

commit e88bb5a8fe31db0059e51c65a2aee75a87137f3e
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Oct 4 22:03:41 2019 +0200

    ISIS-2158: annotation scanning: minor performance tweaks
    
    - also adding caches, but these are all disabled for now
    - we'll need a large sample domain to actually measure performance
---
 .../commons/internal/reflection/_Annotations.java  |  52 ++-
 .../internal/reflection/_AnnotationsLegacy.java    | 361 +++++++++++++++++++++
 .../reflection/_Annotations_SyntCache.java         |  78 +++++
 .../isis/metamodel/facets/DomainEventHelper.java   |   9 +-
 .../apache/isis/metamodel/facets/FacetFactory.java |  10 +-
 .../metamodel/facets/jaxb/JaxbFacetFactory.java    |   4 +-
 .../DisableForSessionFacetViaMethod.java           |   2 +-
 .../forsession/HideForSessionFacetViaMethod.java   |   2 +-
 .../method/ActionChoicesFacetViaMethod.java        |   8 +-
 .../method/ActionChoicesFacetViaMethodFactory.java |   3 +-
 .../ActionParameterDefaultsFacetViaMethod.java     |   2 +-
 ...tionParameterDefaultsFacetViaMethodFactory.java |   4 +-
 .../domainmodel/SpecloaderPerformanceTest.java     |  20 +-
 13 files changed, 509 insertions(+), 46 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
index 403e496..99bf6ac 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
@@ -62,6 +62,13 @@ public final class _Annotations {
         return synthesize(annotatedElement, annotationType);
     }
     
+    //final static _Probe probe1 = _Probe.unlimited().label("synthesizeInherited");
+    //final static _Probe probe2 = _Probe.unlimited().label("synthesizeInherited");
+    private final static _Annotations_SyntCache syntCache = new _Annotations_SyntCache();
+    public static void clearCache() {
+        syntCache.clear();
+    }
+    
     /**
      * Optionally create a type-safe synthesized version of this annotation based on presence.
      * <p>
@@ -77,6 +84,25 @@ public final class _Annotations {
             AnnotatedElement annotatedElement, 
             Class<A> annotationType) {
         
+        //probe1.println("lookup");
+        
+        //return _AnnotationsLegacy.nearest(annotatedElement, annotationType);
+        
+        return calc_synthesizeInherited(annotatedElement, annotationType);
+               
+        
+//        return syntCache.computeIfAbsent(
+//                annotatedElement, 
+//                annotationType, 
+//                _Annotations::calc_synthesizeInherited);
+    }
+    
+    private static <A extends Annotation> Optional<A> calc_synthesizeInherited(
+            AnnotatedElement annotatedElement, 
+            Class<A> annotationType) {
+        
+        //probe2.println("cache-miss");
+        
         val collected = _Annotations
                 .collect(annotatedElement);
         
@@ -111,7 +137,7 @@ public final class _Annotations {
      * @return non-null
      */
     public static <A extends Annotation> Optional<A> synthesize(
-            Class<?> annotatedElement, 
+            AnnotatedElement annotatedElement, 
             Class<A> annotationType) {
         
         val synthesized = _Annotations
@@ -128,10 +154,13 @@ public final class _Annotations {
     /**
      * @apiNote don't expose Spring's MergedAnnotations
      */
-    private static MergedAnnotations collect(AnnotatedElement annotatedElement) {
+    static MergedAnnotations collect(AnnotatedElement annotatedElement) {
         
-        //TODO use cache if not already
         val collected = MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS);
+        
+//        val collected = syntCache.computeIfAbsent(annotatedElement, __->
+//            MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS));
+
         return collected;
     }
     
@@ -139,23 +168,12 @@ public final class _Annotations {
         if(ReflectionUtils.isObjectMethod(getter)) {
             return null;
         }
-        val fieldNameCandidate1 = fieldNameForGetter(getter);
-        if(fieldNameCandidate1==null) {
+        val fieldNameCandidate = fieldNameForGetter(getter);
+        if(fieldNameCandidate==null) {
             return null;
         }
-        //val fieldNameCandidate2 = "_" + fieldNameCandidate1; //XXX legacy behavior
-        
         val declaringClass = getter.getDeclaringClass();
-        for(val field : declaringClass.getDeclaredFields()) { //TODO use cache if appropriate ... ReflectionUtils.findField(clazz, name)
-            val fieldName = field.getName(); 
-            if(fieldName.equals(fieldNameCandidate1)) {
-                return field;
-            }
-//            if(fieldName.equals(fieldNameCandidate2)) { //XXX legacy behavior
-//                return field;
-//            }
-        }
-        return null;
+        return ReflectionUtils.findField(declaringClass, fieldNameCandidate);
     }
     
     private static String fieldNameForGetter(Method getter) {
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_AnnotationsLegacy.java b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_AnnotationsLegacy.java
new file mode 100644
index 0000000..df88b6e
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_AnnotationsLegacy.java
@@ -0,0 +1,361 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.commons.internal.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.collections._Lists;
+
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+
+import lombok.val;
+
+public final class _AnnotationsLegacy  {
+
+    private _AnnotationsLegacy() {}
+    
+    public static <A extends Annotation> Optional<A> nearest(
+            AnnotatedElement annotatedElement, 
+            Class<A> annotationType) {
+        
+        if(annotatedElement instanceof Method) {
+            return _AnnotationsLegacy.getAnnotations((Method)annotatedElement, annotationType)
+                    .stream()
+                    .findFirst();
+        }
+        if(annotatedElement instanceof Parameter) {
+            val param = (Parameter)annotatedElement;
+            val method = (Method)param.getDeclaringExecutable();
+            val params = method.getParameters();
+            for(int i = 0 ; i<params.length; ++i) {
+                int paramIndex = i;
+                if(params[i] == param) {
+                    return _AnnotationsLegacy.getAnnotations(method, paramIndex, annotationType)
+                            .stream()
+                            .findFirst();        
+                }
+            }
+            throw new NoSuchElementException();
+            
+            
+        }
+        return _AnnotationsLegacy.getAnnotations((Class<?>)annotatedElement, annotationType)
+                .stream()
+                .findFirst();
+
+    }
+
+    private static class AnnotationAndDepth<T extends Annotation>
+    implements Comparable<AnnotationAndDepth<T>> {
+        AnnotationAndDepth(final T annotation, final int depth) {
+            this.annotation = annotation;
+            this.depth = depth;
+        }
+        T annotation;
+
+        private static <T extends Annotation> List<T> sorted(
+                final List<AnnotationAndDepth<T>> annotationAndDepths) {
+            Collections.sort(annotationAndDepths);
+            return annotationAndDepths.stream()
+                    .map(AnnotationAndDepth::getAnnotation)
+                    .collect(Collectors.toList());
+        }
+
+        T getAnnotation() {
+            return annotation;
+        }
+        int depth;
+
+        @Override
+        public int compareTo(final AnnotationAndDepth<T> o) {
+            return depth - o.depth;
+        }
+    }
+
+
+    /**
+     * Searches for annotation on provided class, and if not found for the
+     * superclass.
+     * @deprecated use {@link _Annotations} instead
+     */
+    public static <T extends Annotation> List<T> getAnnotations(
+            final Class<?> cls,
+            final Class<T> annotationClass) {
+
+        if (cls == null) {
+            return Collections.emptyList();
+        }
+        
+        final List<AnnotationAndDepth<T>> annotationAndDepths = _Lists.newArrayList();
+        for (final Annotation annotation : cls.getAnnotations()) {
+            append(annotation, annotationClass, annotationAndDepths);
+        }
+        if(!annotationAndDepths.isEmpty()) {
+            return AnnotationAndDepth.sorted(annotationAndDepths);
+        }
+
+        // search superclasses
+        final Class<?> superclass = cls.getSuperclass();
+        if (superclass != null) {
+            try {
+                final List<T> annotationsFromSuperclass = getAnnotations(superclass, annotationClass);
+                if (!annotationsFromSuperclass.isEmpty()) {
+                    return annotationsFromSuperclass;
+                }
+            } catch (final SecurityException e) {
+                // fall through
+            }
+        }
+
+        // search implemented interfaces
+        final Class<?>[] interfaces = cls.getInterfaces();
+        for (final Class<?> iface : interfaces) {
+            final List<T> annotationsFromInterface = getAnnotations(iface, annotationClass);
+            if (!annotationsFromInterface.isEmpty()) {
+                return annotationsFromInterface;
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    private static <T extends Annotation> void append(
+            final Annotation annotation,
+            final Class<T> annotationClass,
+            final List<AnnotationAndDepth<T>> annotationAndDepths) {
+        
+        appendWithDepth(annotation, annotationClass, annotationAndDepths, 0, _Lists.newArrayList());
+    }
+
+    private static <T extends Annotation> void appendWithDepth(
+            final Annotation annotation,
+            final Class<T> annotationClass,
+            final List<AnnotationAndDepth<T>> annotationAndDepths,
+            final int depth,
+            final List<Annotation> visited) {
+        if (visited.contains(annotation)) {
+            return;
+        } else {
+            // prevent infinite loop
+            visited.add(annotation);
+        }
+        final Class<? extends Annotation> annotationType = annotation.annotationType();
+
+        // directly annotated
+        if(annotationClass.isAssignableFrom(annotationType)) {
+            annotationAndDepths.add(new AnnotationAndDepth<>(_Casts.uncheckedCast(annotation), depth));
+        }
+
+        // if meta-annotation
+        //if(annotationType.getAnnotation(Meta.class) != null) {
+        final Annotation[] annotationsOnAnnotation = annotationType.getAnnotations();
+        for (final Annotation annotationOnAnnotation : annotationsOnAnnotation) {
+            appendWithDepth(annotationOnAnnotation, annotationClass, annotationAndDepths, depth+1, visited);
+        }
+        //}
+    }
+
+    /**
+     * Searches for annotation on provided method, and if not found for any
+     * inherited methods up from the superclass.
+     * @deprecated use {@link _Annotations} instead
+     */
+    public static <T extends Annotation> List<T> getAnnotations(
+            final Method method,
+            final Class<T> annotationClass) {
+        if (method == null) {
+            return Collections.emptyList();
+        }
+
+        final List<AnnotationAndDepth<T>> annotationAndDepths = _Lists.newArrayList();
+        for (final Annotation annotation : method.getAnnotations()) {
+            append(annotation, annotationClass, annotationAndDepths);
+        }
+        if(!annotationAndDepths.isEmpty()) {
+            return AnnotationAndDepth.sorted(annotationAndDepths);
+        }
+
+
+        // search for field
+        if ( shouldSearchForField(annotationClass) ) {
+            declaredFields_matching(method.getDeclaringClass(), isFieldForGetter(method), field->{
+                for(final Annotation annotation: field.getAnnotations()) {
+                    append(annotation, annotationClass, annotationAndDepths);
+                }
+            }); 
+        }
+        if(!annotationAndDepths.isEmpty()) {
+            return AnnotationAndDepth.sorted(annotationAndDepths);
+        }
+
+        // search superclasses
+        final Class<?> superclass = method.getDeclaringClass().getSuperclass();
+        if (superclass != null) {
+            final Method parentClassMethod = 
+                    firstDeclaredMethod_matching(method, superclass, isSuperMethodFor(method)); 
+
+            if(parentClassMethod!=null) {
+                final List<T> annotationsFromSuperclass = 
+                        getAnnotations(parentClassMethod, annotationClass);
+                if(!annotationsFromSuperclass.isEmpty()) {
+                    return annotationsFromSuperclass;
+                }
+            }
+        }
+
+        // search implemented interfaces
+        final Class<?>[] interfaces = method.getDeclaringClass().getInterfaces();
+        for (final Class<?> iface : interfaces) {
+            final Method ifaceMethod = 
+                    firstDeclaredMethod_matching(method, iface, isSuperMethodFor(method));
+
+            if(ifaceMethod!=null) {
+                final List<T> annotationsFromInterfaces = getAnnotations(ifaceMethod, annotationClass);
+                if(!annotationsFromInterfaces.isEmpty()) {
+                    return annotationsFromInterfaces;
+                }
+            }
+        }
+
+        return Collections.emptyList();
+    }
+
+
+//XXX optimization
+//    private static List<Class<?>> fieldAnnotationClasses = 
+//            _Lists.of(
+//                    Property.class,
+//                    PropertyLayout.class,
+//                    Collection.class,
+//                    CollectionLayout.class,
+//                    Programmatic.class,
+//                    MemberOrder.class,
+//                    Pattern.class,
+//                    javax.annotation.Nullable.class,
+//                    Title.class,
+//                    XmlJavaTypeAdapter.class,
+//                    XmlTransient.class,
+//                    javax.jdo.annotations.Column.class
+//                    );
+
+    private static boolean shouldSearchForField(final Class<?> annotationClass) {
+        return true; //fieldAnnotationClasses.contains(annotationClass);
+    }
+
+    /**
+     * Searches for parameter annotations on provided method, and if not found
+     * for any inherited methods up from the superclass.
+     *
+     * <p>
+     * Added to allow bytecode-mangling libraries such as CGLIB to be supported.
+     * @deprecated use {@link _Annotations} instead
+     */
+    private static <T extends Annotation> List<T> getAnnotations(
+            final Method method,
+            final int paramNum,
+            final Class<T> annotationClass) {
+
+        if(method == null || paramNum < 0 || paramNum >= method.getParameterCount()) {
+            return Collections.emptyList();
+        }
+
+        final List<AnnotationAndDepth<T>> annotationAndDepths = _Lists.newArrayList();
+        final Annotation[] parameterAnnotations = method.getParameterAnnotations()[paramNum];
+        for (Annotation annotation : parameterAnnotations) {
+            append(annotation, annotationClass, annotationAndDepths);
+        }
+        if(!annotationAndDepths.isEmpty()) {
+            return AnnotationAndDepth.sorted(annotationAndDepths);
+        }
+
+        return Collections.emptyList();
+    }
+
+    // -- HELPER
+
+    private static Method firstDeclaredMethod_matching(
+            Method method,
+            Class<?> type, 
+            Predicate<Method> filter) {
+
+        return stream(type.getDeclaredMethods())
+                .filter(filter)
+                .findFirst()
+                .orElse(null);
+    }
+
+
+    private static void declaredFields_matching(
+            Class<?> type, 
+            Predicate<Field> filter, 
+            Consumer<Field> onField) {
+
+        stream(type.getDeclaredFields())
+        .filter(filter)
+        .forEach(onField);
+
+    }
+
+    // -- HELPER - PREDICATES
+
+    private static Predicate<Method> isSuperMethodFor(final Method method) {
+        return m->_Reflect.same(method, m);
+    }
+
+    private static Predicate<Field> isFieldForGetter(final Method getter) {
+        return field->{
+            int beginIndex;
+            final String methodName = getter.getName();
+            if (methodName.startsWith("get")) {
+                beginIndex = 3;
+            } else if (methodName.startsWith("is")) {
+                beginIndex = 2;
+            } else {
+                return false;
+            }
+            if(methodName.length()==beginIndex) {
+                return false;
+            }
+            final String suffix = methodName.substring(beginIndex);
+            final char c = suffix.charAt(0);
+            final char lower = Character.toLowerCase(c);
+            final String candidate = "" + lower + suffix.substring(1);
+            if(field.getName().equals(candidate)) {
+                return true;
+            }
+            if(field.getName().equals("_" + candidate)) {
+                return true;
+            }
+            return false;
+        };
+    }
+
+}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SyntCache.java b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SyntCache.java
new file mode 100644
index 0000000..f208630
--- /dev/null
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations_SyntCache.java
@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.commons.internal.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.springframework.core.annotation.MergedAnnotations;
+
+import org.apache.isis.commons.internal.collections._Maps;
+
+import lombok.Value;
+import lombok.val;
+
+final class _Annotations_SyntCache {
+    
+    // -- L2 CACHE
+    
+    @Value(staticConstructor = "of")
+    private final static class Key {
+        AnnotatedElement annotatedElement; 
+        Class<? extends Annotation> annotationType;
+    }
+    
+    private Map<Key, Optional<?>> map = _Maps.newConcurrentHashMap();
+    
+    @SuppressWarnings("unchecked")
+    <A extends Annotation> Optional<A> computeIfAbsent(
+            AnnotatedElement annotatedElement,
+            Class<A> annotationType,
+            BiFunction<AnnotatedElement, Class<A>, Optional<A>> factory) {
+
+        val key = Key.of(annotatedElement, annotationType);
+
+        return (Optional<A>) map.computeIfAbsent(key, __->factory.apply(annotatedElement, annotationType));
+    }
+    
+    // -- L1 CACHE
+    
+    private Map<AnnotatedElement, MergedAnnotations> mergedByTarget = _Maps.newConcurrentHashMap();
+    
+    MergedAnnotations computeIfAbsent(
+            AnnotatedElement annotatedElement,
+            Function<AnnotatedElement, MergedAnnotations> factory) {
+
+        val key = annotatedElement;
+        return mergedByTarget.computeIfAbsent(key, factory);
+    }
+
+    // -- CLEANUP
+    
+    void clear() {
+        map.clear();
+        mergedByTarget.clear();
+    }
+
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
index 0cdecd1..26f8003 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
@@ -133,8 +133,7 @@ public class DomainEventHelper {
 
         // no-arg constructor
         for (final Constructor<?> constructor : constructors) {
-            final Class<?>[] parameterTypes = constructor.getParameterTypes();
-            if(parameterTypes.length == 0) {
+            if(constructor.getParameterCount() == 0) {
                 final Object event = constructor.newInstance();
                 final ActionDomainEvent<S> ade = uncheckedCast(event);
 
@@ -231,8 +230,7 @@ public class DomainEventHelper {
 
         // no-arg constructor
         for (final Constructor<?> constructor : constructors) {
-            final Class<?>[] parameterTypes = constructor.getParameterTypes();
-            if(parameterTypes.length == 0) {
+            if(constructor.getParameterCount() == 0) {
                 final Object event = constructor.newInstance();
                 final PropertyDomainEvent<S, T> pde = uncheckedCast(event);
                 pde.initSource(source);
@@ -320,8 +318,7 @@ public class DomainEventHelper {
 
         // no-arg constructor
         for (final Constructor<?> constructor : constructors) {
-            final Class<?>[] parameterTypes = constructor.getParameterTypes();
-            if(parameterTypes.length ==0) {
+            if(constructor.getParameterCount() == 0) {
                 final Object event = constructor.newInstance();
                 final CollectionDomainEvent<S, T> cde = uncheckedCast(event);
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
index ee92f1f..75e7b9f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
@@ -219,6 +219,8 @@ public interface FacetFactory {
 
     public static class ProcessParameterContext extends AbstractProcessWithMethodContext<FacetedMethodParameter> {
         private final int paramNum;
+        private final Class<?> paramType;
+        private final Parameter parameter; 
 
         public ProcessParameterContext(
                 final Class<?> cls,
@@ -232,6 +234,8 @@ public interface FacetFactory {
                 throw _Exceptions.unrecoverable("invalid ProcessParameterContext");
             }
             this.paramNum = paramNum;
+            this.paramType = super.method.getParameterTypes()[paramNum];
+            this.parameter = super.method.getParameters()[paramNum];
         }
 
         public int getParamNum() {
@@ -243,21 +247,21 @@ public interface FacetFactory {
          * @since 2.0
          */
         public <A extends Annotation> Optional<A> synthesizeOnParameter(Class<A> annotationType) {
-            return _Annotations.synthesizeInherited(getParameter(), annotationType);
+            return _Annotations.synthesizeInherited(parameter, annotationType);
         }
 
         /**
          * @since 2.0
          */
         public Class<?> getParameterType() {
-            return super.method.getParameterTypes()[paramNum];
+            return this.paramType;
         }
         
         /**
          * @since 2.0
          */
         public Parameter getParameter() {
-            return super.method.getParameters()[paramNum];
+            return parameter;
         }
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/jaxb/JaxbFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/jaxb/JaxbFacetFactory.java
index c771718..d5214c0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/jaxb/JaxbFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/jaxb/JaxbFacetFactory.java
@@ -33,8 +33,6 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.config.IsisConfiguration;
-import org.apache.isis.config.IsisConfigurationLegacy;
-import org.apache.isis.config.internal._Config;
 import org.apache.isis.metamodel.facetapi.FacetHolder;
 import org.apache.isis.metamodel.facetapi.FacetUtil;
 import org.apache.isis.metamodel.facetapi.FeatureType;
@@ -366,7 +364,7 @@ implements MetaModelValidatorRefiner {
             final Class<?> correspondingClass = objectSpec.getCorrespondingClass();
             final Constructor<?>[] constructors = correspondingClass.getDeclaredConstructors();
             for (Constructor<?> constructor : constructors) {
-                if(constructor.getParameterTypes().length == 0) {
+                if(constructor.getParameterCount() == 0) {
                     if (!Modifier.isPublic(constructor.getModifiers())) {
                         validationFailures
                         .add(objectSpec.getIdentifier(),
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/disabled/forsession/DisableForSessionFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/disabled/forsession/DisableForSessionFacetViaMethod.java
index 511652d..9457c9c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/disabled/forsession/DisableForSessionFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/disabled/forsession/DisableForSessionFacetViaMethod.java
@@ -63,7 +63,7 @@ public class DisableForSessionFacetViaMethod extends DisableForSessionFacetAbstr
         if (session == null) {
             return null;
         }
-        final int len = method.getParameterTypes().length;
+        final int len = method.getParameterCount();
         final Object[] parameters = new Object[len];
         parameters[0] = session.createUserMemento();
         // TODO: need to change to pick up as non-static rather than static
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/hidden/forsession/HideForSessionFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/hidden/forsession/HideForSessionFacetViaMethod.java
index 0af1d0b..2b51850 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/hidden/forsession/HideForSessionFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/members/hidden/forsession/HideForSessionFacetViaMethod.java
@@ -63,7 +63,7 @@ public class HideForSessionFacetViaMethod extends HideForSessionFacetAbstract im
         if (session == null) {
             return null;
         }
-        final int len = method.getParameterTypes().length;
+        final int len = method.getParameterCount();
         final Object[] parameters = new Object[len];
         parameters[0] = session.createUserMemento();
         // TODO: need to change to pick up as non-static rather than static
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethod.java
index 1011638..cfea71a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethod.java
@@ -34,6 +34,8 @@ import org.apache.isis.metamodel.facets.param.choices.ActionChoicesFacetAbstract
 import org.apache.isis.metamodel.spec.DomainModelException;
 import org.apache.isis.metamodel.spec.ObjectSpecification;
 
+import lombok.val;
+
 public class ActionChoicesFacetViaMethod extends ActionChoicesFacetAbstract implements ImperativeFacet {
 
     private final Method method;
@@ -77,10 +79,10 @@ public class ActionChoicesFacetViaMethod extends ActionChoicesFacetAbstract impl
         final Object[] options = (Object[]) objectOrCollection;
         final Object[][] results = new Object[options.length][];
 
+        val parameterTypes = method.getParameterTypes();
+        
         for (int i = 0; i < results.length; i++) {
-            final Class<?> parameterType = method.getParameterTypes()[i];
-            results[i] = handleResults(options[i], parameterType,
-                    interactionInitiatedBy);
+            results[i] = handleResults(options[i], parameterTypes[i], interactionInitiatedBy);
         }
         return results;
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethodFactory.java
index fb326b7..6282e84 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethodFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/choices/method/ActionChoicesFacetViaMethodFactory.java
@@ -58,9 +58,8 @@ public class ActionChoicesFacetViaMethodFactory extends MethodPrefixBasedFacetFa
     private void attachActionChoicesFacetIfParameterChoicesMethodIsFound(final ProcessMethodContext processMethodContext) {
 
         final Method actionMethod = processMethodContext.getMethod();
-        final Class<?>[] actionParamTypes = actionMethod.getParameterTypes();
 
-        if (actionParamTypes.length <= 0) {
+        if (actionMethod.getParameterCount() <= 0) {
             return;
         }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethod.java
index 515a134..da32a96 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethod.java
@@ -78,7 +78,7 @@ public class ActionParameterDefaultsFacetViaMethod extends ActionParameterDefaul
 
         // this could be a dependent defaults situation, but has a previous parameter been updated
         // that this parameter is dependent upon?
-        final int numParams = method.getParameterTypes().length;
+        final int numParams = method.getParameterCount();
         if (paramNumUpdated < numParams) {
             // in this case the parameter that was updated is previous
             //
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethodFactory.java
index 1f45c41..b731a50 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethodFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/param/defaults/methodnum/ActionParameterDefaultsFacetViaMethodFactory.java
@@ -73,9 +73,9 @@ public class ActionParameterDefaultsFacetViaMethodFactory extends MethodPrefixBa
         }
 
         final Method actionMethod = processMethodContext.getMethod();
-        final Class<?>[] paramTypes = actionMethod.getParameterTypes();
+        final int paramCount = actionMethod.getParameterCount();
 
-        for (int i = 0; i < paramTypes.length; i++) {
+        for (int i = 0; i < paramCount; i++) {
 
             // attempt to match method...
             Method defaultMethod = findDefaultNumMethod(processMethodContext, i);
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
index 9fd1e1b..3181ac6 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
@@ -18,8 +18,6 @@
  */
 package org.apache.isis.testdomain.domainmodel;
 
-import java.time.Duration;
-
 import javax.inject.Inject;
 
 import org.junit.jupiter.api.BeforeAll;
@@ -27,6 +25,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.TestPropertySource;
 
+import org.apache.isis.commons.internal.base._Timing;
+import org.apache.isis.commons.internal.reflection._Annotations;
 import org.apache.isis.config.IsisConfiguration;
 import org.apache.isis.config.IsisPresets;
 import org.apache.isis.config.registry.IsisBeanTypeRegistry;
@@ -36,7 +36,7 @@ import org.apache.isis.testdomain.Smoketest;
 import org.apache.isis.testdomain.conf.Configuration_headless;
 import org.apache.isis.testdomain.model.good.Configuration_usingValidDomain;
 
-import static org.junit.jupiter.api.Assertions.assertTimeout;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import lombok.val;
 
@@ -66,7 +66,7 @@ class SpecloaderPerformanceTest {
         IsisBeanTypeRegistry.repeatedTesting = true;
     }
     
-    static long ITERATIONS = 40; /* should typically run in ~10s */
+    static long ITERATIONS = 400; /* should typically run in ~10s */
     static long EXPECTED_MILLIS_PER_ITERATION = 500;
     
     @Test 
@@ -75,17 +75,23 @@ class SpecloaderPerformanceTest {
         config.getReflector().getIntrospector().setParallelize(true);
         
         val timeOutMillis = ITERATIONS * EXPECTED_MILLIS_PER_ITERATION;
+        val goodUntilMillis = System.currentTimeMillis() + timeOutMillis;
         
-        assertTimeout(Duration.ofMillis(timeOutMillis), ()->{
+        val repeatedRun = (Runnable)()->{
         
             for(int i=0; i<ITERATIONS; ++i) {
+                _Annotations.clearCache();
                 specificationLoader.shutdown();
                 specificationLoader.init();
+           
+                if(System.currentTimeMillis() > goodUntilMillis) {
+                    fail("timed out");
+                }
             }
             
-        });
-        
+        };
         
+        _Timing.runVerbose("Repeated Concurrent Specloading", repeatedRun);
     }