I have a service mapped from an interface to class.
I have a controller implementing ICommandTrigger that uses local injection of that service in one of it's methods.
The service implements IInjectorContainer, I'm trying to use constructor injection to pass a function dependency:
@Inject("getUrl")
public function new(getUrl:String->String)
Injector has a mapping for getUrl.
However, the dependency doesn't get injected. There are no exceptions thrown.
First investigation shows that Injector::instantiateUnmapped does run this line: instance = InjectionUtil.applyConstructorInjection( type, this, classDescription.c.a, type );
However, classDescription.c is {a: Array(0)} - which is wrong.
So far I've dug at the roots and found some problems:
1) A missed case in hex.reflect.ReflectionBuilder::_parseMetadata: the code that iterates over func.args does a switch on arg.type, but handles only TPath( p ) case, doing nothing in all other cases. In the case of a function argument, arg.type is TFunction( args, ret ).
I've added this case to the switch, _parseMetadata now works correctly.
P.S. I'm not an expert on Haxe macros, but looking at haxe.macro.ComplexType makes me think that maybe some other cases also should be handled, e.g. TAnonymous? It would be good to test this part of code with various types of arguments and check that all of them are parsed correctly.
Note: the switch on f.kind handles FVar and FFun, but not FProp - this probably needs to be added for getter/setter property injection.
2) FastAnnotationReader::reflect is being called twice for my class (I suppose once for the subclass, once for it's superclass). The fields argument on the first call include the constructor with @Inject annotation, however on the second call the constructor doesn't have the @Inject annotation - it has been removed during the first pass by hex.reflect.ReflectionBuilder::_getAnnotations (f.meta.remove( m );//remove metadata). Because of this, the newly reflected data doesn't contain the constructor. When the existing data and new data is merged together by FastAnnotationReader::mergeReflectionData, it just uses constructor from the new data (which is null), so the final class description doesn't contain the constructor arguments. Using this as workaround: constructor: data2.constructor != null ? data2.constructor : data1.constructor in mergeReflectionData and it starts to work, but I'm not sure this is the correct solution. Perhaps, the inheritance chain should be checked instead, or something else, to decide which constructor to use when merging.
3) Unrelated issue: in my case, the superclass defines a @PostConstruct function which is then overridden in the subclass (and also annotated with @PostConstruct) - as a result this same function ends up duplicated in classDescription.pc array. The function is then called twice. Perhaps the user code shouldn't annotate the overridden function with @PostConstruct, however I think the framework still should de-duplicate the functions in this case.
I have a service mapped from an interface to class. I have a controller implementing ICommandTrigger that uses local injection of that service in one of it's methods. The service implements IInjectorContainer, I'm trying to use constructor injection to pass a function dependency:
Injector has a mapping for getUrl. However, the dependency doesn't get injected. There are no exceptions thrown.
First investigation shows that Injector::instantiateUnmapped does run this line:
instance = InjectionUtil.applyConstructorInjection( type, this, classDescription.c.a, type );
However,classDescription.c
is{a: Array(0)}
- which is wrong.So far I've dug at the roots and found some problems:
1) A missed case in
hex.reflect.ReflectionBuilder::_parseMetadata
: the code that iterates overfunc.args
does a switch onarg.type
, but handles onlyTPath( p )
case, doing nothing in all other cases. In the case of a function argument,arg.type
isTFunction( args, ret )
. I've added thiscase
to theswitch
,_parseMetadata
now works correctly. P.S. I'm not an expert on Haxe macros, but looking at haxe.macro.ComplexType makes me think that maybe some other cases also should be handled, e.g.TAnonymous
? It would be good to test this part of code with various types of arguments and check that all of them are parsed correctly. Note: the switch onf.kind
handlesFVar
andFFun
, but notFProp
- this probably needs to be added for getter/setter property injection.2)
FastAnnotationReader::reflect
is being called twice for my class (I suppose once for the subclass, once for it's superclass). Thefields
argument on the first call include the constructor with@Inject
annotation, however on the second call the constructor doesn't have the@Inject
annotation - it has been removed during the first pass byhex.reflect.ReflectionBuilder::_getAnnotations
(f.meta.remove( m );//remove metadata
). Because of this, the newly reflected data doesn't contain the constructor. When the existing data and new data is merged together byFastAnnotationReader::mergeReflectionData
, it just uses constructor from the new data (which is null), so the final class description doesn't contain the constructor arguments. Using this as workaround:constructor: data2.constructor != null ? data2.constructor : data1.constructor
inmergeReflectionData
and it starts to work, but I'm not sure this is the correct solution. Perhaps, the inheritance chain should be checked instead, or something else, to decide which constructor to use when merging.3) Unrelated issue: in my case, the superclass defines a
@PostConstruct
function which is then overridden in the subclass (and also annotated with@PostConstruct
) - as a result this same function ends up duplicated inclassDescription.pc
array. The function is then called twice. Perhaps the user code shouldn't annotate the overridden function with@PostConstruct
, however I think the framework still should de-duplicate the functions in this case.