-
Notifications
You must be signed in to change notification settings - Fork 683
Add support for factory methods to instantiate entities #2534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Reuse existing utility methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of inline comments. I am not entirely sure about the naming of @Factory
as that collides with the concept of a factory type (as in: the pattern).
if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) { | ||
var entityCreator = entity.getEntityCreator(); | ||
|
||
if (entityCreator == null) { | ||
return true; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be a method on EntityCreator
with the different ways of looking this up living in the implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered the future arrangements where we might have no reflection access to constructors/methods (e.g. AOT/Graal Native that come with a different creator mechanism for entities). So I'd leave the reflective bits in the calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be a different implementation of EntityInstantiator
that could simply not call these methods? Or a decorator in between EntityCreator
and the actual implementation instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely it would be a different EntityInstantiator
that is implemented using generated code (AOT) per entity type. In our AOT Build-time POC a single class was implementing TypeInformation
, EntityInstantiator
and a few other interfaces to keep all concerns regarding a single class in a single place.
|
||
if (constructor != null && constructor.isEnclosingClassParameter(parameter)) { | ||
return (T) parent; | ||
if (creator instanceof PreferredConstructor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be moved to a callback on EntityCreator
with the default just returning false
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum, enclosing class is only applicable in the context of constructors and non-static inner classes. We have a single call to that method so it doesn't seem to be a good fit for the general API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about broadening the name of the method to something like isParentParameter()
and have that notion of "enclosing class parameter" move into the PersistenceConstructor
implementation. Methods actually have something similar to that synthetic parameter, called "receiver parameters" that don't appear on the call side of the invocation: https://www.logicbig.com/tutorials/core-java-tutorial/java-8-enhancements/explicit_receiver_parameters.html. Not saying we should support those, but it's not as conceptually odd as it seems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought about broadening the name of the method to something like isParentParameter()
That makes sense. Let me handle that as a separate commit.
params[i++] = provider.getParameterValue(parameter); | ||
} | ||
|
||
if (creator instanceof FactoryMethod method) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think this should be on the EntityCreator
interface to avoid the instance of checks and pulling out implementation details.
src/main/java/org/springframework/data/mapping/EntityCreatorSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/org/springframework/data/mapping/EntityCreatorSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/org/springframework/data/mapping/EntityCreatorSupport.java
Outdated
Show resolved
Hide resolved
* @author Mark Paluch | ||
* @since 3.0 | ||
*/ | ||
public final class FactoryMethod<T, P extends PersistentProperty<P>> extends EntityCreatorSupport<T, P> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if we could get as far as avoiding having to expose these types (just as PreferredConstructor
) publically, now that EntityCreator
is available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depends on the design really. A FactoryMethod
/PreferredConstructor
makes only sense when there's reflective access. With our separation where we externalize EntityInstantiator
s we always leak implementation details. EntityCreator
reduces the concept of entity creation to the actual description of input parameters without actually defining the approach (reflection, constructor/factory method, a provided lambda) how entities are being created. Exposing Executable
would already assume that we use reflection information in some sense to create entities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that, but most places in which the reflection API is used are not for the actual (reflective) invocation of those methods, but the inspection and decisions triggered by the outcome of that. Even in an AOT scenario, we would need that, wouldn't we? It feels pretty brittle to see all those instance of checks in different places, as they stop working as soon as you wrap the original instance in some decorator. The entire detection process leading to the creation of FactoryMethod
/PreferredConstructor
is reflection-based. If we avoid reflection happening in front of those instances, it's much easier to potentially replace the entire detection process and instances. For example, by an instance of EntityCreator
that will just obtain that metadata from a different place without using any runtime reflection at all.
@@ -64,11 +67,21 @@ | |||
|
|||
private volatile Map<TypeInformation<?>, EntityInstantiator> entityInstantiators = new HashMap<>(32); | |||
|
|||
private final boolean fallbackToReflectionOnError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introducing this flag seems to be related but not strictly required, is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for testing purposes only. Tests were green during development although class-generated entity creation was broken and the creation fell back to reflective instance creation.
src/main/java/org/springframework/data/mapping/model/EntityCreatorDiscoverer.java
Outdated
Show resolved
Hide resolved
src/main/java/org/springframework/data/mapping/model/CommonEntityInstantiator.java
Outdated
Show resolved
Hide resolved
|
Add support for parent/enclosing class/receiver parameters in entity creation.
Thanks for all the work implementing this, Mark. As a final step, I suggest a bit of polishing in the annotation model. We should expose a canonical annotation to declare both constructors and factory methods as "creator". Long term, it doesn't really make sense to go with Proposals:
Feel free to chime in if you'd like to veto those steps. Unless I get any until Monday, I'll proceed with the changes as proposed. |
Sounds good to have |
Move to @PersistenceCreator as canonical annotation to explicitly express constructors and methods to be used to create domain object instances from persistence operations. Removed @factorymethod as it's not needed anymore. @PersistenceConstructor is now deprecated. Renamed EntityCreatorMetadata(Support|Discoverer) to InstanceCreatorMetadata(Support|Discoverer) to avoid further manifestation of the notion of an entity in the metamodel as it's not used to only handle entities.
6b77868
to
14c150f
Compare
That's applied to 2.7 and the 3.0 branch. |
We now support static factory methods to instantiate entities. Factory methods can be disambiguated by annotating these with
@Factory
.Closes #2476