Skip to content

Commit de81dfc

Browse files
SentryManrbygrave
andauthored
Feature: Add a MethodHandles.Lookup SPI (#3535)
* ServiceLoad lookups * remove more reflection * address comments * Update DefaultTypeManager.java * Javadoc and format changes --------- Co-authored-by: Rob Bygrave <[email protected]>
1 parent 2bd53cb commit de81dfc

File tree

12 files changed

+107
-43
lines changed

12 files changed

+107
-43
lines changed

ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.ebean.config;
22

3+
import io.ebean.plugin.Lookups;
4+
35
/**
46
* Helper to find classes taking into account the context class loader.
57
*/
@@ -73,9 +75,8 @@ public boolean isJacksonObjectMapperPresent() {
7375
*/
7476
public Object newInstance(String className) {
7577
try {
76-
Class<?> cls = forName(className);
77-
return cls.getDeclaredConstructor().newInstance();
78-
} catch (Exception e) {
78+
return Lookups.newDefaultInstance(forName(className));
79+
} catch (Throwable e) {
7980
throw new IllegalArgumentException("Error constructing " + className, e);
8081
}
8182
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.ebean.config;
2+
3+
import java.lang.invoke.MethodHandles.Lookup;
4+
5+
/**
6+
* Provides a Lookup instance for accessing entity/dto fields.
7+
*/
8+
public interface LookupProvider {
9+
10+
/**
11+
* Return the Lookup.
12+
*/
13+
Lookup provideLookup();
14+
15+
}

ebean-api/src/main/java/io/ebean/event/ClassUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ebean.event;
22

3+
import io.ebean.plugin.Lookups;
34

45
/**
56
* Helper to find classes taking into account the context class loader.
@@ -12,8 +13,8 @@ class ClassUtil {
1213
static Object newInstance(String className) {
1314
try {
1415
Class<?> cls = forName(className);
15-
return cls.getDeclaredConstructor().newInstance();
16-
} catch (Exception e) {
16+
return Lookups.newDefaultInstance(cls);
17+
} catch (Throwable e) {
1718
String msg = "Error constructing " + className;
1819
throw new IllegalArgumentException(msg, e);
1920
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.ebean.plugin;
2+
3+
import static java.util.stream.Collectors.toMap;
4+
5+
import java.lang.invoke.MethodHandles;
6+
import java.lang.invoke.MethodHandles.Lookup;
7+
import java.lang.invoke.MethodType;
8+
import java.util.Map;
9+
import java.util.ServiceLoader;
10+
11+
import io.ebean.config.LookupProvider;
12+
13+
/**
14+
* Provides Lookup instances using potentially module specific Lookups.
15+
*/
16+
public final class Lookups {
17+
18+
private static final Map<String, Lookup> LOOKUP_MAP =
19+
ServiceLoader.load(LookupProvider.class).stream()
20+
.collect(toMap(p -> p.type().getModule().getName(), p -> p.get().provideLookup()));
21+
22+
private static final Lookup DEFAULT_LOOKUP = MethodHandles.publicLookup();
23+
24+
private static final MethodType VOID = MethodType.methodType(void.class);
25+
26+
/**
27+
* Return a Lookup ideally for the module associated with the given type.
28+
*/
29+
public static Lookup getLookup(Class<?> type) {
30+
return LOOKUP_MAP.getOrDefault(type.getModule().getName(), DEFAULT_LOOKUP);
31+
}
32+
33+
/**
34+
* Find the default constructor and return a new instance for the given type
35+
* potentially using a module specific Lookup instance.
36+
*/
37+
@SuppressWarnings("unchecked")
38+
public static <T> T newDefaultInstance(Class<?> type) throws Throwable {
39+
return (T) getLookup(type).findConstructor(type, VOID).invoke();
40+
}
41+
}

ebean-api/src/main/java/module-info.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module io.ebean.api {
22

33
uses io.ebean.config.AutoConfigure;
4+
uses io.ebean.config.LookupProvider;
45
uses io.ebean.datasource.DataSourceAlertFactory;
56
uses io.ebean.service.BootstrapService;
67
uses io.ebean.service.SpiJsonService;
@@ -21,15 +22,15 @@
2122
exports io.ebean;
2223
exports io.ebean.bean;
2324
exports io.ebean.cache;
24-
exports io.ebean.meta;
25+
exports io.ebean.common;
2526
exports io.ebean.config;
2627
exports io.ebean.config.dbplatform;
28+
exports io.ebean.docstore;
2729
exports io.ebean.event;
2830
exports io.ebean.event.readaudit;
2931
exports io.ebean.event.changelog;
30-
exports io.ebean.common;
31-
exports io.ebean.docstore;
3232
exports io.ebean.plugin;
33+
exports io.ebean.meta;
3334
exports io.ebean.metric;
3435
exports io.ebean.search;
3536
exports io.ebean.service;

ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
import io.ebean.event.changelog.ChangeLogRegister;
1212
import io.ebean.event.readaudit.ReadAuditLogger;
1313
import io.ebean.event.readaudit.ReadAuditPrepare;
14+
import io.ebean.plugin.Lookups;
1415
import io.ebean.util.AnnotationUtil;
1516
import io.ebeaninternal.api.CoreLog;
16-
1717
import jakarta.persistence.AttributeConverter;
1818
import jakarta.persistence.Embeddable;
1919
import jakarta.persistence.Entity;
2020
import jakarta.persistence.Table;
2121
import java.lang.annotation.Annotation;
22+
import java.lang.invoke.MethodType;
2223
import java.lang.reflect.Modifier;
2324
import java.util.ArrayList;
2425
import java.util.List;
@@ -94,9 +95,9 @@ public BootupClasses(Set<Class<?>> classes) {
9495
public void runServerConfigStartup(DatabaseBuilder config) {
9596
for (Class<?> cls : serverConfigStartupCandidates) {
9697
try {
97-
ServerConfigStartup newInstance = (ServerConfigStartup) cls.getDeclaredConstructor().newInstance();
98+
ServerConfigStartup newInstance = Lookups.newDefaultInstance(cls);
9899
newInstance.onStart(config);
99-
} catch (Exception e) {
100+
} catch (Throwable e) {
100101
// assume that the desired behavior is to fail - add your own try catch if needed
101102
throw new IllegalStateException("Error running ServerConfigStartup " + cls, e);
102103
}
@@ -206,12 +207,12 @@ public void addChangeLogInstances(DatabaseBuilder.Settings config) {
206207
*/
207208
private <T> T create(Class<T> cls, boolean logOnException) {
208209
try {
209-
return cls.getConstructor().newInstance();
210+
return Lookups.newDefaultInstance(cls);
210211
} catch (NoSuchMethodException e) {
211212
log.log(DEBUG, "Ignore/expected - no default constructor: {0}", e.getMessage());
212213
return null;
213214

214-
} catch (Exception e) {
215+
} catch (Throwable e) {
215216
if (logOnException) {
216217
// not expected but we log and carry on
217218
log.log(ERROR, "Error creating " + cls, e);

ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.ebean.plugin.BeanDocType;
2424
import io.ebean.plugin.BeanType;
2525
import io.ebean.plugin.ExpressionPath;
26+
import io.ebean.plugin.Lookups;
2627
import io.ebean.plugin.Property;
2728
import io.ebean.util.SplitName;
2829
import io.ebeaninternal.api.*;
@@ -414,8 +415,8 @@ EntityBean createPrototypeEntityBean(Class<T> beanType) {
414415
return null;
415416
}
416417
try {
417-
return (EntityBean) beanType.getDeclaredConstructor().newInstance();
418-
} catch (Exception e) {
418+
return Lookups.newDefaultInstance(beanType);
419+
} catch (Throwable e) {
419420
throw new IllegalStateException("Error trying to create the prototypeEntityBean for " + beanType, e);
420421
}
421422
}

ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElementEmbedded.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.ebean.PersistenceIOException;
44
import io.ebean.SqlUpdate;
55
import io.ebean.bean.EntityBean;
6+
import io.ebean.plugin.Lookups;
67
import io.ebeaninternal.api.json.SpiJsonReader;
78
import io.ebeaninternal.api.json.SpiJsonWriter;
89
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
@@ -22,8 +23,8 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
2223
BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
2324
super(owner, deploy, elementHelp);
2425
try {
25-
this.prototype = (EntityBean) beanType.getDeclaredConstructor().newInstance();
26-
} catch (Exception e) {
26+
this.prototype = Lookups.newDefaultInstance(beanType);
27+
} catch (Throwable e) {
2728
throw new IllegalStateException("Unable to create entity bean prototype for "+beanType);
2829
}
2930
BeanPropertyAssocOne<?>[] embedded = propertiesEmbedded();

ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.ebean.config.dbplatform.PlatformIdGenerator;
1111
import io.ebean.event.*;
1212
import io.ebean.event.changelog.ChangeLogFilter;
13+
import io.ebean.plugin.Lookups;
1314
import io.ebean.text.PathProperties;
1415
import io.ebean.util.SplitName;
1516
import io.ebeaninternal.api.ConcurrencyMode;
@@ -24,7 +25,7 @@
2425
import jakarta.persistence.Entity;
2526
import jakarta.persistence.MappedSuperclass;
2627

27-
import java.lang.reflect.Field;
28+
import java.lang.invoke.MethodHandles.Lookup;
2829
import java.util.*;
2930

3031
/**
@@ -48,6 +49,7 @@ public int compare(DeployBeanProperty o1, DeployBeanProperty o2) {
4849

4950
private final DatabaseBuilder.Settings config;
5051
private final BeanDescriptorManager manager;
52+
5153
/**
5254
* Map of BeanProperty Linked so as to preserve order.
5355
*/
@@ -144,8 +146,8 @@ public BindMaxLength bindMaxLength() {
144146

145147
private String[] readPropertyNames() {
146148
try {
147-
Field field = beanType.getField("_ebean_props");
148-
return (String[]) field.get(null);
149+
final Lookup lookup = Lookups.getLookup(beanType);
150+
return (String[]) lookup.findStaticVarHandle(beanType, "_ebean_props", String[].class).get();
149151
} catch (Exception e) {
150152
throw new IllegalStateException("Error getting _ebean_props field on type " + beanType, e);
151153
}

ebean-core/src/main/java/io/ebeaninternal/server/dto/DtoMetaConstructor.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
package io.ebeaninternal.server.dto;
22

3-
import io.ebean.core.type.DataReader;
4-
import io.ebean.core.type.ScalarType;
5-
import io.ebeaninternal.server.type.TypeManager;
6-
73
import java.lang.invoke.MethodHandle;
8-
import java.lang.invoke.MethodHandles;
94
import java.lang.invoke.MethodType;
105
import java.lang.reflect.Constructor;
116
import java.sql.SQLException;
127

8+
import io.ebean.core.type.DataReader;
9+
import io.ebean.core.type.ScalarType;
10+
import io.ebean.plugin.Lookups;
11+
import io.ebeaninternal.server.type.TypeManager;
12+
1313
final class DtoMetaConstructor {
1414

1515
private final Class<?>[] types;
1616
private final MethodHandle handle;
17-
private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup();
1817
private final ScalarType<?>[] scalarTypes;
1918

2019
DtoMetaConstructor(TypeManager typeManager, Constructor<?> constructor, Class<?> someClass) throws NoSuchMethodException, IllegalAccessException {
@@ -23,7 +22,7 @@ final class DtoMetaConstructor {
2322
for (int i = 0; i < types.length; i++) {
2423
scalarTypes[i] = typeManager.type(types[i]);
2524
}
26-
this.handle = LOOKUP.findConstructor(someClass, typeFor(types));
25+
this.handle = Lookups.getLookup(someClass).findConstructor(someClass, typeFor(types));
2726
}
2827

2928
private MethodType typeFor(Class<?>[] types) {

0 commit comments

Comments
 (0)