clarify TYPE_USE for qualifier and interceptor binding type annotations
The specification does not clearly say whether I can usefully declare a @Qualfier type like this:
@Qualifier
@Retention(RUNTIME)
@Target({TYPE, TYPE_USE})
public @interface Special {}
To be clear, it doesn't disallow this, but it's unclear if an implementation is required to recognize the qualifier when it occurs as follows:
@Inject @Special Thing thing;
The reason being, of course, that here @Special is an annotation of the type use Thing, and not of the field thing.
In fact, it's extremely natural to view qualifier annotations as qualifying the type use rather than the injection point itself, and I guess the reason we didn't define it that was in CDI 1.0 was that we didn't have type annotations at that time.
Some implementation probably already allow this. I think the spec should be changed to clarify that this is a required feature.
All of the above also applies to interceptor binding types.
The specification states that:
A qualifier type is a Java annotation defined as
@Retention(RUNTIME). Typically a qualifier type is defined as@Target({METHOD, FIELD, PARAMETER, TYPE}).
Furthermore, there is also:
The qualifiers of a bean are declared by annotating the bean class or producer method or field with the qualifier types.
And following sections specifically talk about qualifiers for injected fields and method parameters, not the type they hold.
While the former does not explicitly say those are the only valid @Target values, with the latter parts of spec you could argue that TYPE_USE is actually not supported.
That said, nowhere in the spec or TCKs do we use any different target values I am also fairly confident neither Weld not ArC expect TYPE_USE to be present.
The main issue is that enabling this will potentially break existing extensions that process annotations as those are bound to also expect just the basic four target values. So while this is definitely doable, the gain from doing so is in my view questionable as it doesn't enable anything you cannot already achieve. I am -1 on this proposal but let's see what others think :)
with the latter parts of spec you could argue that
TYPE_USEis actually not supported.
I think that's the most natural interpretation, yes. (And it's also certainly the intended interpretation, since we didn't have TYPE_USE annotations when we wrote the spec.) But I don't think it's sufficiently explicit.
The main issue is that enabling this will potentially break existing extensions that process annotations as those are bound to also expect just the basic four target values.
By that logic, any new feature added to CDI can "potentially break existing extensions". But of course that's not what we normally mean by a "breaking change". Something is a breaking change if it breaks programs which have not changed, and that's not the case with this proposal.
the gain from doing so is in my view questionable as it doesn't enable anything you cannot already achieve.
That's not the point. The point is not to add new capabilities. The point is to not get stuck in the past like Spring, and instead stay up to date with "new" features of the Java language (which are actually not very new at all in this case). This change is nice because TYPE_USE is arguably the most natural way to think of what a qualifier annotation actually is.
To illustrate that point, consider:
-
TYPE_USEis less verbose thanMETHOD, FIELD, PARAMETER, suggesting that it captures the semantics more cleanly, and - a
TYPE_USEannotation cannot be used to annotate avoidmethod, whereas asMETHODannotation can. So by using the more natural constraint, we actually gain some tiny amount of additional type safety.
If we were starting from scratch, I'm quite sure we would design it this way, and that's actually good enough reason to move toward this approach.
Indeed, I would say that the fact that qualifiers can't go on void methods is reason enough to prefer this approach.
A counter-argument to that would be that TYPE_USE annotations can occur on type arguments, where qualifiers are not currrently supported.
But on further examination, that turns out to be a pretty weak counterpoint, since qualifiers on type arguments totally make sense, at least in principle.
Hell, it would be awesome to be able to write:
@Inject Instance<@Special Thing> thingy;
That’s beautiful.
I faced a similar limitation with Guice recently (basically, I was not able to write Provider<@Named("foo") String>), so I created https://github.com/jakartaee/inject/issues/40 to lean the things towards TYPE_USE. I fully agree going for the type-based qualifiers would be great.
I agree with @manovotn that the spec seems fairly clear about qualifiers being declaration annotations. It might not be intentional, but it is what it is. So this
it's unclear if an implementation is required to recognize the qualifier when it occurs [as a type annotation]
doesn't seem right to me. Implementations certainly are not required to recognize type annotation qualifiers.
Now, type annotation qualifiers make sense. Whether the work to add support for it (in the spec and in implementations) also makes sense, I don't know. It seems like a small gain for a modest price. Probably best if someone prototyped this and we'll see.
I'm not sure what you mean by interceptor bindings.
To me, the important distinction is that qualifiers are associated with beans and not with types. (I'm fairly sure on this. Although 2.3 says "A qualifier type represents some client-visible semantic associated with a type", the rest of the spec talks about declaring the qualifiers of a bean and specifying qualifiers for an injection point.)
If they're associated with beans, then they only make sense when beans are declared or when they're resolved.
Beans can be declared using:
- a class (managed bean)
- a method (producer method)
- a field (producer field)
We need to resolve a bean for an injection point. An injection point is one of:
- a field of a managed bean
- a method parameter of
- a producer method
- an observer method
- an initializer method of a managed bean
- a constructor of a managed bean
You can also define a synthetic bean, but in that case the qualifier is passed as an argument or returned from a method so the @Target is not relevant. E.g. SyntheticBeanBuiilder.qualifier, BeanAttributes.getQualifiers
Similarly, beans are resolved at places other than injection points, but as far as I'm aware, the qualifiers are then also passed as arguments to a method. E.g. BeanContainer.getBeans, Instance.select
So, if qualifiers are associated with beans and injection points rather than types, I think it would be misleading to allow the qualifier to be associated with the type use rather than the injection point itself.
Qualifiers are also associated with Events, observers and decorators, but I think the same logic can be applied - they're all declared with either a class or method parameter.
To me, the important distinction is that qualifiers are associated with beans and not with types.
I view it the same way.
Associating qualifiers with types shifts my personal perception to assume that the given type always has the qualifier, but that it not how CDI handles them. A type Foo can simultaneously exist as many different beans within the CDI container, so the qualifier doesn't really "belong" to the type, but instead to the bean declaration or given injection point.
assume that the given type always has the qualifier, but that it not how CDI handles them
The spec https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#builtin_qualifiers reads:
If a bean does not explicitly declare a qualifier other than
@Namedor@Any, the bean has exactly one additional qualifier, of type@Default. This is called the default qualifier.
That looks like "given type always has the qualifier" to me.
@manovotn , imagine the bean is declared as follows:
@LDAP
class LdapAuthenticator
implements Authenticator {
How could I inject List<@LDAP Authenticator> instances then? (see https://github.com/google/guice/wiki/Multibindings )
How could I express MyService<@LDAP Authenticator, @PayBy(CHEQUE) PaymentProcessor> then?
If they're associated with beans, then they only make sense when beans are declared or when they're resolved.
@Azquelt , do you mean @Inject Instance<@Special Thing> thingy; should be completely unsupported by CDI?
Imagine there's a bean of type Thing with qualifier @Special. Imagine I have a producer method that takes such a @Special-qualified Thing and returns a new bean of type Instance<Thing>.
How could one distinguish between @Special Instance<Thing> (Instance is special, Thing is regular) vs Instance<@Special Thing> (Instance is regular, Thing is special) if the qualifier belongs to an injection point?
Do you mean CDI should reject such use cases?
Imagine there's a bean of type
Thingwith qualifier@Special. Imagine I have a producer method that takes such a@Special-qualifiedThingand returns a new bean of typeInstance<Thing>.
This would conflict with the built-in Instance bean so let's forget about Instance and ask whether we should allow @Inject List<@Special Thing> thingyList.
Do you mean CDI should reject such use cases?
It's not so much that I think CDI should reject such use cases, it's that:
- I'm yet to see a proper explanation of what these use cases are. Why are they valuable? What things would this allow that can't be done with the current system (or could be done better in the new system)?
- Why do you want to inject
List<@Special Thing>? - Why is it important to differentiate between
@Special List<Thing>andList<@Special Thing>?
- Why do you want to inject
- This would be changing one of the core parts of CDI and I think the scope of these changes would be very large, require a lot of additional tests, would require a lot change in every implementation, would require changes to lots of places in the API that currently assume qualifiers are associated with beans not types and would not be backwards compatible.
At the moment, qualifiers in CDI are relatively simple. Beans can declare qualifiers, injection points can declare qualifiers, the rules of typesafe resolution define how the types and qualifiers are compared to resolve an injection point to a bean.
If you're going to suggest changes to that, you'd need to explain how you'd alter those rules and have considered the consequences of those changes for the rest of the spec. For example, at the moment the idea that "beans have qualifiers" is baked into many places in the API (see the linked methods in my previous comment). How are you proposing those should change and what should happen to the existing APIs?
It's ok to suggest changes to these core rules of CDI, but you need but you need to explain the value of doing so (what use cases can't be done with the current system, how would your proposed changes help, how much would it break backwards compatibility) and that argument has to overcome the effort needed to make the changes, add the tests and maintain and implement the functionality and force end users to update their applications if the changes are not backward compatible.
At the moment it's not clear to me what use cases you're asking for or why your proposal is worth implementing and maintaining or how you think it could be done in a backwards compatible way.
I gotta be honest: this is one of the most depressing threads I've ever seen.
Really, really, depressing to see everyone looking for excuses to not do work, instead of trying to imagine possibilities.
I hope CDI is not really so moribund.
How could I inject
List<@LDAP Authenticator>instances then? (see https://github.com/google/guice/wiki/Multibindings) How could I expressMyService<@LDAP Authenticator, @PayBy(CHEQUE) PaymentProcessor>then?
Multibindings or map bindings do not exist in CDI, so regardless of this issue, you just don't.
(For the record, ArC has a solution for the most common case of multibindings, where you can @Inject @All List<MyBean>. That's something we might wanna put into the spec, but if someone feels strongly about it, I suggest opening a new issue.)
I'm not sure what you mean by interceptor bindings.
Yes, that was a a silly statement; this idea doesn't apply to interceptor binding types because they really do apply to the method itself, not to the type of the method.
@Inject @All List<MyBean>
Unclear that this is really better than just injecting Instance<MyBean>.
But, OTOH, this really is legitimately a nice example of how and where TYPE_USE qualifiers work better.
- We need the special
@Allqualifier to distinguish this sort ofListfrom any other bean typeList, but -
MyBeanmight have its own qualifiers, in which case they would get mixed in with@Allin a pathological way.
Hence:
@Inject @All List<@Special MyBean>
which "disentangles" the qualifiers on MyBean from the qualifier on List.
So, thanks, folks, for our first good example of how this is useful.
Presumably, you could define the bean to be injected something like this?
@Produces @Dependent
<T> @All List<T> produceAllList(Instance<T> items) {
List<T> result = new ArrayList<>();
for (T i : items) {
result.add(i);
}
return result;
}
Likewise to create something injectable into MyService<@LDAP Authenticator, @PayBy(CHEQUE) PaymentProcessor> you'd write this?
@Dependent
public class MyService<A extends Authenticator, P extends PaymentProcessor> {
@Inject A authenticator;
@Inject P paymentProcessor;
....
}
Talked about this on the CDI call today and the opinions were mixed :-) Few concerns arose:
- For producer methods and fields, there's an obvious (explicitly denoted) type to which type annotations are attached, but what about managed beans (class-based beans)?
- If we have a bean type with type annotations, we should propagate the type annotations to its supertypes when discovering bean types. This would need specification.
-
TypeLiteralcurrently only providesjava.lang.reflect.Type, but I think it should be able to providejava.lang.reflect.AnnotatedType. The simple name ofjava.lang.reflect.AnnotatedTypeunfortunately collides withjakarta.enterprise.inject.spi.AnnotatedType:-/ - Certain methods, like
BeanAttributes.getTypes()andgetQualifiers(), orBeanConfigurator.types()andaddQualifier[s](), would likely have to be deprecated (suggested replacement beinggetAnnotatedTypes(), which would returnSet<j.l.r.AnnotatedType>, oraddAnnotatedType()acceptingj.l.r.AnnotatedType).
We were able to identify a single obvious use case: instead of @Inject @Any Instance<MyBean>, which is what CDI currently requires, it would be more natural (for people acquainted with type annotations) to write @Inject Instance<@Any MyBean>. (Similarly with any other qualifier, not just @Any.)
I said I'll prototype this when I get some time, but please don't count on it happening in the short term.