Rick has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

CDI Dependency Injection - Tutorial II - Annotation Processing and Plugins - Java EE

04.05.2011
| 94948 views |
  • submit to reddit

Now we just change the producer to grab the new annotation and configure the transport as follows: (For clarity I took out all of the Sysout.prinltns.)



Code Listing: TransportFactory using the annotation configuration to configure a new instance of the transport
package org.cdi.advocacy;

...
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;

public class TransportFactory {
    @Produces ATMTransport createTransport(InjectionPoint injectionPoint) {
        
        Annotated annotated = injectionPoint.getAnnotated();
        
        TransportConfig transportConfig = annotated.getAnnotation(TransportConfig.class);



        StandardAtmTransport transport = new StandardAtmTransport();

        transport.setRetries(transportConfig.retries());
        return transport;
    }

}

(Side Note: we are missing a null pointer check. The annotation configuration could be null if the user did not set it, you may want to handle this. The example is kept deliberately short.)

The code just gets the annotation and shoves in the retires into the transport, and then just returns the transport.

We now have a producers that can use an annotation to configure an injection.

Here is our new output:

Output
...
deposit called
communicating with bank via Standard transport retries=2

You can see our retries are there as we configured them in the annotation. Wonderful! Annotation processing for the masses!

Ok we are done with this example. What remains is a victory lap. Let's say we had multiple transports in a single ATM and you wanted to configure all of the outputs at once.

Let's configure the transport based on an annotation in the parent class of the injection target, namely, AutomatedTellerMachine.



Code Listing: TransportFactory using the annotation configuration from class not field to configure a new instance of the transport
public class TransportFactory {
    @Produces ATMTransport createTransport(InjectionPoint injectionPoint) {

        Bean<?> bean = injectionPoint.getBean();
        TransportConfig transportConfig = bean.getBeanClass().getAnnotation(TransportConfig.class);

        StandardAtmTransport transport = new StandardAtmTransport();

        transport.setRetries(transportConfig.retries());
        return transport;

It is an exercise for the reader to make the injection level annotation (from the last example) override the class level annotations. As always, if you are playing along in the home version of CDI hacker, send me your solution. Best solution gets my admiration.

Output
deposit called
communicating with bank via Standard transport retries=7

Exercise: Make the injection from the field override the injection from the class. It is a mere matter of Java code. Send me your solution on the CDI group mailing list. The first one to send gets put on the CDI wall of fame. (All others get honorable mentions.)



Advanced Using @Nonbinding to combine a configuration annotation and a qualifier annotation into one annotation


In the section titled "Using @Qualfiers with members to discriminate injection and stop the explosion of annotation creation" we covered adding additional members to a qualifier annotation and then in *"Advanced: Using @Produces and InjectionPoint to create configuration annotations"* we talked about how to write an annotation to configure an injection. Wouldn't be great if we could combine these two concepts into one annotation?

The problem is that qualifier members are used to do the discrimination. We need some qualifier members that are not used for configuration not discrimination.

To make an qualifier member just a configuration member use @Nonbinding annotation as follows:



Code Listing: Transport qualifier annotation using @Nonbinding to add configuration retries param
package org.cdi.advocacy;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;


@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Transport {
    TransportType type() default TransportType.STANDARD;
    int priorityLevel() default -1;
    String name() default "standard";

    @Nonbinding int retries() default 5;

}
Now let's add the setRetries to the Fast Transport:

Code Listing: Transport qualifier annotation using @Nonbinding to add configuration retries param
package org.cdi.advocacy;

@Transport(type=TransportType.STANDARD, priorityLevel=1, name="super")
public class SuperFastAtmTransport implements ATMTransport {
    private int retries=0;

	public void setRetries(int retries) {
        this.retries=retries;
    }


    public void communicateWithBank(byte[] datapacket) {
        System.out.println("communicating with bank via the Super Fast transport retries=" + retries);
    }

}

Then we use it as follows:
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {

    @Inject @Transport(type=TransportType.STANDARD, priorityLevel=1, name="super", retries=9)
    private ATMTransport transport;
        ...
Ouptut
deposit called
communicating with bank via Standard transport retries=9

The final result is we have one annotation that does both qualification and configuration. Booyah!

Exercise: There is an easter egg in this example. There is concept we talked about earlier (in the qualifier discrimination but never added. Please find it and describe it. What are some potential problems of using this approach? Send me your answers on the CDI group mailing list. The first one to send gets put on the CDI wall of fame. (All others get honorable mentions.)



Advanced: Using Instance to inject transports


The use of the class Instance allows you to dynamically look up instances of a certain type. This is the plugin architecture for the masses, built right into CDI. Grok this and you will not only understand CDI but have a powerful weapon in your arsenal of mass programming productivity.

These instances can be instances that are in a jar files. For example the AutomatedTellerMachine could work with transports that did not even exist when the AutomatedTellerMachine was created. If you don't grok that, read the last sentence again. You are tapping into the scanning capabilities of CDI. This power is there for the taking. The Instance class is one of the things that makes CDI so cool and flexible. In this section, I hope to give it some justice while still keeping the example small and understandable.

Let's say we wanted to work with multiple transports. But we don't know which transport is configured and on the classpath. It could be that the build was special for a certain type of transport, and it just does not exist on the classpath. Suspend disbelief for a moment and let's look at the code.



Code Listing: AutomatedTellerMachineImpl using Instance
package org.cdi.advocacy;

import java.math.BigDecimal;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Named;

@Named("atm")
public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {

    @Inject @Soap 
    private Instance<ATMTransport> soapTransport;

    @Inject @Json 
    private Instance<ATMTransport> jsonTransport;

    @Inject @Default 
    private Instance<ATMTransport> defaultTransport;
	
    private ATMTransport transport;

    @PostConstruct
    protected void init() {
        if (!defaultTransport.isUnsatisfied()) {
            System.out.println("picked Default");
            transport = defaultTransport.iterator().next();
        } else if (!jsonTransport.isUnsatisfied()) {
            System.out.println("picked JSON");
            transport = jsonTransport.iterator().next();
        } else if (!soapTransport.isUnsatisfied()) {
            System.out.println("picked SOAP");
            transport = soapTransport.iterator().next();
        }
    }

Notice we are using *`Instance`* as the field type instead of ATMTransport. Then we look up the actual transport. We can query a Instance with the Instance.isUnsatisfied to see it this transport actually exist. There is an Instance.get method to retrieve a single transport, but I used *`Instance.iterator().next()`* to highlight an important aspect of Instance, namely, it can return more than one. For example, there could be 20 @Default based transports in the system.

Imagine if you were implementing a chain of responsibility pattern or a command pattern, and you wanted an easy way to discover the actions or commands that were on the classpath. Instance would be that way. CDI makes this type of plugin development very easy.

If it could find a single @Default, the one we have been using since the start, on the classpath. The output from the above would be as follows:

Output
picked Default
deposit called
communicating with bank via Standard transport
Continue reading... Click on the navigation links below the author bio to read the other pages of this article.

Be sure to check out part I of this series as well: CDI DI Tutorial!



About the author
This article was written with CDI advocacy in mind by Rick Hightower with some collaboration from others. Rick Hightower has worked as a CTO, Director of Development and a Developer for the last 20 years. He has been involved with J2EE since its inception. He worked at an EJB container company in 1999. He has been working with Java since 1996, and writing code professionally since 1990. Rick was an early Spring enthusiast. Rick enjoys bouncing back and forth between C, Python, Groovy and Java development.

Although not a fan of EJB 3, Rick is a big fan of the potential of CDI and thinks that EJB 3.1 has come a lot closer to the mark.

Rick Hightower is CTO of Mammatus and is an expert on Java and Cloud Computing. Rick is invovled in Java CDI advocacy and Java EE. CDI Implementations - Resin Candi - Seam Weld - Apache OpenWebBeans

Published at DZone with permission of its author, Rick Hightower.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Rogerio Liesenfeld replied on Tue, 2011/04/05 - 11:18am

Great stuff!

Unless I missed something, though, CDI does not (yet?) support injection into non-managed beans, as provided through @Configurable in Spring. Is this planned? And if it is, with build-time or load-time bytecode modification, or with either mechanism as in Spring?

Frank Schwarz replied on Tue, 2011/04/05 - 12:36pm

The source code is missing some angle-bracketed stuff, isn't it?

Rick Hightower replied on Tue, 2011/04/05 - 2:58pm in response to: Rogerio Liesenfeld

If you have a beans.xml under META-INF then that resource (classpath location or jar file) will get processed. It will process all of the Java classes in that jar.
All of the examples were tested with Resin CANDI and WELD.
I reserve the right to be wrong, but I am pretty sure I am right.

Rick Hightower replied on Tue, 2011/04/05 - 2:59pm in response to: Frank Schwarz

Well there was some angle brackets in the first article to activate Alternatives, but nothing in this article. No XML. No XML is needed.

Rick Hightower replied on Tue, 2011/04/05 - 5:35pm in response to: Rick Hightower

Clarification: The AutomatedTellerMachineImpl from the first article is not marked with any @Service, @Managed, etc. and it uses @Inject. But, AutomatedTellerMachineImpl is being "managed" when we look it up through the container.

The example would not work if you "new"ed AutomatedTellerMachineImpl instead of looking it up.

Dan Allen replied on Tue, 2011/04/05 - 9:12pm

You can also add beans programmatically using a CDI extension. Observe the BeforeBeanDiscovery event and use the addAnnotatedType method:

event.addAnnotatedType(beanManager.createAnnotatedType(MyClass.class));

That's one idea. We were using this in Solder for a while, but we opted to switch back to using beans.xml.

Samuli Saarinen replied on Wed, 2011/04/06 - 12:24am

Hi,

 Is the something missing from the last example code snippet or how this can work with identical annotation literals?

standardTransport = allTransports.select(new AnnotationLiteral(){}).get();
jsonTransport = allTransports.select(new AnnotationLiteral(){}).get();
soapTransport = allTransports.select(new AnnotationLiteral(){}).get();

 

Rogerio Liesenfeld replied on Wed, 2011/04/06 - 10:18am in response to: Rick Hightower

Yes, of course, I could do that.

But suppose I don't want to depend on a "ServiceLocator" lookup in top-level components, ie, I don't want to have to make calls like

AutomatedTellerMachine atm = BeanContainerManager.getInstance().getBeanByType(AutomatedTellerMachine.class);

in AtmMain.java. Instead, I would prefer to simply write

AutomatedTellerMachine atm = new AutomatedTellerMachineImpl();

BTW, this "Impl" class would probably be the only implementation in practice, so in most cases it would make more sense to not have the separate interface.

Personally, I prefer my business service classes to have initial state passed through a constructor. With injection into non-managed beans, it would be possible to use custom constructors and final instance fields, while still having dependencies injected into instances created with "new".

Rick Hightower replied on Wed, 2011/04/06 - 12:52pm in response to: Samuli Saarinen

Samuli,

Good eye. It appears I have a slight error in my code listing. DOH! I will fix it and repost that last listing. I meant to pass the annotation to annotation literal and then to the select. It appears I misfired. :)

Pobody's Nerfect.

Rick Hightower replied on Wed, 2011/04/06 - 7:12pm in response to: Samuli Saarinen

http://code.google.com/p/jee6-cdi/wiki/DependencyInjectionAnIntroductoryTutorial_Part2#Code_Listing:_AutomatedTellerMachineImpl_using_selects_and_some Ok... the angle bracket stuff was in it when I wrote it. :) It seems the JavaLobby engine thinks it is a tag and then strips it from the post (on the way out... the tags are in the post).

Rick Hightower replied on Wed, 2011/04/06 - 7:19pm in response to: Frank Schwarz

Ok... Frank... It has all of the angle bracket stuff back.

Rick Hightower replied on Wed, 2011/04/06 - 8:02pm in response to: Rogerio Liesenfeld

(I got the ideas from this response from someone... I am not sure if they want credit, but please chime in if you do... you know who you are....)

"There is no standards-defined way of injecting into objects instantiated via 'new' in CDI as you would do in the case of Spring @Configurable."

"It is doubtful whether there will ever be. The issue is that it would basically require mandating that all CDI implementations use AspectJ style byte-code manipulation as opposed to code generation, proxying, etc."

"If this is something that is really needed, you could create an AspectJ based tool for CDI that takes a class definition and adds CDI run-time look-ups behind the scenes to mimic injection/other CDI functionality. If run-time weaving is necessary, you would then need to specify a weaver for class-loading. In my opinion, this is not really something that needs to be done (as part of the JSR) since it adds additional build/compile/class-loading complexity for a very rare, domain-driven-design purist case. For example, I know of no Spring shops that use @Configurable or compile time weaving and a very small handful that uses load-time weaving."

I could see us adding something like this to CDISource.

Mark Struberg replied on Thu, 2011/04/07 - 12:50am

Hi Rick!

 

Good series!  I just like to add a few oftern overlooked gems. Today it's the @Typed Annotation. It can be used to explicitely declare the type of a bean. There are 2 cases where this is reaaaly handy.

 

Use case 1: 'disabling' a java class as bean

 

Let's say we have a 


@Typed()
public class Menu {
private String name;
private String action;
private Menu parent;
}

and we have a producer method like

@ApplicationScoped
public class MenuProducer {
private @Inject menuSvc;
private @Inject User currentUser;

public @Produces @SessionScoped Menu menuForUser() {
Menu menu = menuSvc.getMenuForUser(currentUser);
return menu;
}
}

 

 A Menu is naturally a tree with a 1:n parent menu relationship. So this will just be created via an @ApplicationScoped MenuServiceImpl or such - but never directly as a bean! Annotate the Menu class as @Typed() effectively says: this bean has no Type at all, thus it will not satisfy any injection point. This has almost the same effect as veto() ing a bean in an Extension (via @Observes ProcessAnnotatedType) but without needing to write an Extension. If we wouldn't annotate our Menu class as @Typed(), it would get picked up as @Dependent scoped bean automatically (I personally don't like that, but thats what the spec defines) - leading to an AmbiguousResolutionException.

 

The 2nd case where @Typed is really handy is if you have subclasses which should not get picked up as the base type:

 

/** 
* This menu service will provide
* read operations only!
*/
@ApplicationScoped
public class MenuService {
public Menu getMenuOfUser(User user) {
..
}
}
/** 
* This will add Administration
* functionality to our base MenuService
*/
@ApplicationScoped
@Typed(MenuAdminService.class)
public class MenuAdminService extends MenuService {
public addMenuPermission(Menu menuTree, String role) {
... }
public storeMenuTree(Menu menu) {
... }
}

 If I would not use the @Typed annotation at the MenuAdminService, then it would automatically satisfy all the types in it's whole class hierarchy - including the MenuService it extends. So if you would try to

private @Inject MenuService menuSvc; 

 then you would get an AmbiguousResolutionException (remember: both MenuService and MenuAdminService implement the MenuService type!). By adding 

@Typed(MenuAdminService.class)

it will only act as the type in the annotation and not automatically use all the parent types too! 


 

Rick Hightower replied on Thu, 2011/04/07 - 10:25am in response to: Mark Struberg

Thanks for adding this. Great info. Thanks again for taking the time to expand on this.

Mark Struberg replied on Thu, 2011/04/07 - 11:47am

Might as well add another tip: Beware of the Constructor!

Writing CDI bean initialisation code in Constructors is not a good thingy as you will see weird effects.

Remember - all beans you get injected (except  @Dependent ones) are always 'contextual references' = proxies for their underlying contextual instances! But CDI implementations do not use Java's own reflect proxy (because this one is only able to proxy interfaces) but must use instance proxies.

How goes? The CDI implementations I know (Weld, CanDI and of course OpenWebBeans) create proxies by dynamically subclassing the class which should get proxied. So whenever a proxy gets created, it will naturally also invoke the constructors up in the parent-class chain.

Thus writing the classic reference counter for your bean will give you wrong numbers:

@SessionScoped
public class MyBean implements Serializable {
  private static int refCounter = 0;

  public MyBean() {
    refCounter++;  // <- this will be called too often since the ct will also get called for proxies
  }
  ...
}

 Instead use the @PostConstruct or @Inject annotations on any method:

@SessionScoped
public class MyBean implements Serializable {
  private static int refCounter = 0;

  @Inject
  public void init() {
    refCounter++;  // <- this will be called too often since the ct will also get called for proxies
  }

  @PostConstruct
  pubic void anotherInit() {
  }
  ...
  @PreDestroy // ofc this works as well btw!
  public void cleanup() {
  }
}

 

Mark Struberg replied on Thu, 2011/04/07 - 12:19pm

btw, in your example above:
@Produces ATMTransport createTransport(InjectionPoint injectionPoint) {

please note that this would only work for producing @Dependent scoped beans! (which is exactly what you get if you don't explicitely specify another scope).

 

The reason why this only works with @Dependent scoped beans is that for all NormalScoped beans (@RequestScoped, @SessionScoped, @ApplicationScoped,...), you just don't know where they get injected into.

In fact, your contextual instance (your one-and-only instance of that bean in the given scope) will never get @Inject-ed directly, but instead you will always 'only' get a contextal reference (the proxy) injected into your injection point!

Ergo, this trick only works for @Dependent scoped beans...

 

Overally the JSR-299 spec is one of the best specs I've ever read. But as I already expressed above: I don't like the automatic pickup of not-explicitely scoped beans as @Dependent.

Reason is that this information is not really obvious and often gets forgotten.  If you forget about adding the scope annotation or per accident import e.g. javax.faces.beans.SessionScoped instead of javax.enterprise.context.SessionScoped - IDEs are damn good in picking up the wrong import ;) - you will end up with @Dependent. This mean you will be creating a new instance for each and every injection point and EL invocation - and loosing lots of data in between...

Rogerio Liesenfeld replied on Thu, 2011/04/07 - 12:44pm in response to: Rick Hightower

Indeed. If I have the time, I could implement a CDI portable extension that installs a java.lang.instrument.ClassFileTransformer at startup. The transformer would then modify bytecode in the constructors of classes containing injection points (while ignoring classes without such points). Such a modified constructor would end with a call to the InjectionTarget#inject(...) method.

Use of java.lang.instrument is much better and simpler than using AspectJ or compile-time instrumentation. And by using the Attach API, there is no need to have users manually add a "-javaagent:instrumentation.jar" JVM parameter when running an app/container. There is some runtime overhead, obviously, but I believe it should be small enough (based on my experience with the JMockit tools).

I wish CDISource the best of luck. CDI is great, and I am sure it will only improve.

Mark Struberg replied on Thu, 2011/04/07 - 12:58pm

@Rogerio:

 

I fear using -javaagent has one BIG problem: you will always hit the SystemClassLoader!

This is really a no-go  in EE server scenarios and any other situations where you need classpath isolation by using a ClassLoaders hierarchy. The whole javaagent approach is imo only useful if you have a standalone application.In all other situations you will most probably either break the application - or even worse - open a fat security hole without even notizing it!

All existing CDI implementations (Dan and Reza can confirm this for Weld and CanDI) are using a ClassLoader hack. So we are not using compile time instrumentation - in fact we don't change the original bean classes at all! We just additionally create proxies for them at runtime by 'dynamically' creating subclasses via in memory byte-code engineering.So you can still write code like

private MyBean myBean = new MyBean();

 

Mark Struberg replied on Thu, 2011/04/07 - 1:12pm in response to: Rogerio Liesenfeld

> support injection into non-managed beans

Stu (Stuart Douglas) of JBoss fame wrote a portable CDI Extension for exactly that [1]. This Extension basically will dynamically apply annotations to your classes - without having to write them in your code. It will give you the kind of XML config you might know from Spring 2.

 In general: if you are looking for CDI Extensions, then check JBoss Seam3 [2] and  Apache MyFaces CODI [3] first

 LieGrue,
strub

 

[1] http://docs.jboss.org/seam/3/xml-config/latest/reference/en-US/html_single/

[2] http://docs.jboss.org/seam/3/3.0.0.Final/reference/en-US/html/

[3] http://myfaces.apache.org/extensions/cdi/

PS: <shamelessselfadvert>the Seam3 doc is much better, but CODI is in some areas much more powerful ;) </shamelessselfadvert>

Rick Hightower replied on Thu, 2011/04/07 - 1:53pm in response to: Mark Struberg

RE: the Seam3 doc is much better, but CODI is in some areas much more powerful ;)


When I think of successful OS projects, they usually have on thing in common: good docs.

Facelets was well documented and easy to learn. Hibernate had excellent documents (not everyone agrees with this). Spring has excellent documents. etc. etc.

Docs are a good sign of the overall quality of a project. That being said. I am horrible at writing docs (see crank).

Reza Rahman replied on Thu, 2011/04/07 - 2:29pm

Yep - we do the same thing. In the early EJB 3.1 time-frame there was actually some talk of making EJB/JPA/CDI "new"-able. It was tabled because it gets hideously complicated for everyone involved - especially when you get beyond simple injection and there is a possibility the modified byte-code can be run in environments/class-loaders that you cannot entirely predict (e.g. taking a war that runs fine on Resin and try to run it on GlassFish). Basically it would require standardizing not just the API but how the API is actually implemented.

Rogerio Liesenfeld replied on Thu, 2011/04/07 - 3:26pm in response to: Mark Struberg

Hello Mark,

Actually, a ClassFileTransformer (registered through the java.lang.instrument.Instrumentation object provided by a "Java agent") can transform classes loaded by *any* classloader. In fact, the first parameter to the "transform" method is the ClassLoader which defined the class to be transformed. Therefore, the transformer implementation can decide whether to transform or not each class based on its defining classloader.

Also, it's not actually necessary to use the "-javaagent" parameter at all. A Java agent can be loaded programmaticaly (at least from JDK 1.6 on).

I have actually implemented this kind of stuff in a tool which uses multiple classloaders; it works.

Mark Struberg replied on Fri, 2011/04/08 - 3:59am in response to: Rogerio Liesenfeld

Hi Rogerio!

What I meant is that by using -javaagent alll the classes you need for the transformation will end up getting loaded via the SystemClassLoader. I'm not 100% sure yet about the 'transformed classes' butI think they also get loaded that way.

 You will see this effect if you use those libs with tomcat - they will just refuse to work. The reason is that tomcat implements a security feature to prevent people from overloading classes which got loaded via the SystemClassLoader. I stumbled accross this problem in OpenJPA:

https://issues.apache.org/jira/browse/OPENJPA-1454
https://issues.apache.org/jira/browse/OPENJPA-1410

I haven't yet used the programmatical way to use the javaagent in Java6 of course

Jessie Mear replied on Wed, 2011/09/07 - 6:40am

The latest release of the technology, JSR 314: JavaServer Faces 2.0, makes UI development for Java EE applications even easier through support for annotations and the addition of new features such as Facelets and composite components. java programmers

Rick Siskey replied on Thu, 2011/10/27 - 12:06am

CDI or Computer Design Integration understands what your business is like, its application, structure, the whole nine yards. After it gets the necessary information, it then evaluates what you require and finally, it formulates recommendations. - Rick Siskey

Krishna Bheeman... replied on Sun, 2012/06/24 - 9:49pm

Hi Rick is it posible to have @Inject @Any private Instance allTransports; and @Produces ATMTransport createTransport(InjectionPoint injectionPoint) { in same application? I am getting null for allTransports Thank you for your help

Pradeep Kumar replied on Wed, 2012/09/19 - 12:57pm

Thanks for adding this, great explanation, very useful for me..!!!!!!!!!!!

Mark Srikanth replied on Tue, 2014/05/20 - 8:07am

 great work really thank you for the info about sap .a free live demo on all sap courses.by Exp trainers  SAP BO Online Training .http://sapbusinessobjectstraining.com/

Vladislav Naydenov replied on Mon, 2014/06/30 - 9:06am

Hi, Rick.

I'm following your tutorial step-by-step and I can't make

 createTransport(InjectionPoint injectioPoint) 

to work only with @Produces annotation. I'm getting 

org.jboss.weld.exceptions.DeploymentException: WELD-001409Ambiguous dependencies for type [IATMTransport] with qualifiers [@Default] at injection point [[field] @Inject private com.automatedtellermachine.AutomatedTellerMachine.transport]. Possible dependencies [[Producer Method [IATMTransport] with qualifiers [@Default @Any] declared as [[method] @Produces public com.automatedtellermachine.transport.factory.TransportFactory.createTransport(InjectionPoint)], Managed Bean [class com.automatedtellermachine.transport.StandartATMTransport] with qualifiers [@Default @Any]]]

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.