12 Replies Latest reply on Jan 13, 2014 6:15 AM by philipp91

    Weld Proxies

    philipp91

      Hi,

       

      I use Weld SE to provide Dependency Injection in a JavaFX application, similar to this approach:

      http://java.dzone.com/articles/fxml-javafx-powered-cdi-jboss

       

      For those who don't use JavaFX, it is only relevant to know that JavaFX uses controller objects (MVC-Pattern) and is able to inject references to UI components on fields annotated with @FXML. To provide DI in those controllers, the approach I mentioned above is to have them created by Weld and then let JavaFX continue using them.

       

      All of this works fine as long as the controllers are in dependent scope. As soon as you add a (custom) scope (or ApplicationScoped), Weld will return proxy objects and JavaFX will inject the components on those proxies => They will never arrive in the actual controller instance and hence are not available there.

      So my question is: Is there any way to have Weld provide proxies that allow for injection by reflection (and maybe copy the injected values to the actual bean instance)?

      Or an alternative solution: Is it possible to receive the bean instance behind the proxy? I could hand this one to JavaFX to perform the injection.

       

      Thank you for any help!

      Philipp

        • 1. Re: Weld Proxies
          philipp91

          Hi again,

           

          I think I found a solution:

          if (something instanceof TargetInstanceProxy) {

             something = ((TargetInstanceProxy<?>) something).getTargetInstance();

          }

           

          Philipp

          • 2. Re: Weld Proxies
            mkouba

            Hi Phillip,

            keep in mind that TargetInstanceProxy is a Weld-specific internal interface - not even part of the Weld API. So your app will not be portable and may even stop working with future versions of Weld.

            • 3. Re: Weld Proxies
              jharting

              The portable way of obtaining an unproxied instance of a Bean is to get the Context object from BeanManager (BeanManager.getContext) and then call Context.get(), passing the Bean object (and possibly CreationalContext) as a parameter.

              • 4. Re: Weld Proxies
                philipp91

                Jozef, thank you for pointing out this way (which is most likely the correct answer to my original question). However, I don't know where to get the Bean object (and the CreationalContext?) from, because all I have is the proxy object (TargetInstanceProxy) that was returned by CDI.current().select(someClass).get()

                • 5. Re: Weld Proxies
                  mkouba

                  Phillip, you can obtain a bean by type ("someClass" in your case) - see also 11.3.6. Obtaining a Bean by type. CreationalContext is not necessary to retrieve an existing instance. If you're not sure the instance already exists you'll have to provide a creational context as well - see 11.3.5. Obtaining a CreationalContext.

                  • 6. Re: Weld Proxies
                    stefan.grossmann

                    The following solution also works fine:

                     

                    beanManager.getContext(MyScope.class).get((Contextual<T>) bean, ctx);

                     

                    But the problem for me is that I get a MyJavaFXController$Proxy$_$$_WeldSubclass, if e. g. the Controller uses interceptors. In this case the JavaFX injection of a field annotated with @FXML does not work, if the field is private. If the field is package protected it works.

                     

                    For beans where I get the bean instance, instead of a ...$Proxy$_$$_WeldSubclass, I have no problem in with @FXML and private field.

                     

                    Any idea what I can do, to make the @FXML injection for private fields?

                    • 7. Re: Weld Proxies
                      jharting

                      What do you mean by "injection of private field does not work"? Are you getting an exception? Once you get to the target instance and are able to inject fields it should make no difference whether the field is private or not (assuming you set the accessible flag of the field).

                      • 8. Re: Weld Proxies
                        philipp91

                        @Josef: It looks like Stefan is not talking about CDI injection (which works as designed in this case) but about FXML injection. When loading an fxml file, JavaFX will inject the UI component objects to your controller bean if you annotated the corresponding fields with @FXML.

                        @Stefan: I just checked and I have a private field in a super-class that works with @FXML. Also from reading the code of FXMLLoader, I cannot see any reason why that wouldn't work. The only thing that is *not* possible is injection on a private field in a super-class when there exists an identically named field in the sub-class. So maybe that is the reason. You might just check with the debugger (or ask Jozef) whether the WeldSubclass defines the same private fields as the original class did.

                         

                        // EDIT:

                        Sorry, I was wrong. You seem to be using a newer version of JavaFX and there is definitely some code that restricts injection on private field to the class itself and excludes super-classes:

                        https://bitbucket.org/shemnon/openjfx-rt/src/34ae31eab2567eadbdf5e1970e57c61cec368612/javafx-fxml/src/javafx/fxml/FXMLLoader.java#cl-2561

                        And when looking at the placement of this code, it's very unlikely that you will be able to change that ...

                        • 9. Re: Weld Proxies
                          stefan.grossmann

                          Philipp Keck wrote:

                           

                          @Josef: It looks like Stefan is not talking about CDI injection (which works as designed in this case) but about FXML injection. When loading an fxml file, JavaFX will inject the UI component objects to your controller bean if you annotated the corresponding fields with @FXML.

                          Exactly. FXML injection simply does nothing, the fields are null.

                          Sorry, I was wrong. You seem to be using a newer version of JavaFX and there is definitely some code that restricts injection on private field to the class itself and excludes super-classes:

                          https://bitbucket.org/shemnon/openjfx-rt/src/34ae31eab2567eadbdf5e1970e57c61cec368612/javafx-fxml/src/javafx/fxml/FXMLLoader.java#cl-2561

                          And when looking at the placement of this code, it's very unlikely that you will be able to change that ...

                          philipp91: I think you are right!

                           

                          Weld subclasses my controller to make the CDI-Interceptors working:

                          contollerInstance.getClass()

                               (java.lang.Class<T>) class examples.helloworld.HelloWorldController$Proxy$_$$_WeldSubclass

                           

                          contollerInstance.getClass().getSuperclass()

                               (java.lang.Class<T>) class examples.helloworld.HelloWorldController

                           

                          So for the FXMLLoader the HelloWorldController is a superclass of the controller an JavaFX ignores the private fields.

                          • 10. Re: Weld Proxies
                            philipp91

                            A solution for you might be to make your fields protected.

                             

                            // EDIT: Sorry, my previous answer was partly wrong. JavaFX 8 is just as good as JavaFX 2 with regard to this issue. In order to make it work, you need to make the fields protected or package-private, both of which are reasonable options, I think.

                            • 11. Re: Weld Proxies
                              stefan.grossmann

                              I now use the following implementation to retrieve the controller:

                               

                              private <T> T getController(Class<T> clazz) {

                                  final List<Bean<T>> types = IteratorUtils.toList(beanManager.getBeans(clazz).iterator());

                                  if (types.size() > 1) {

                                      LOG.warn(...);

                                  } else if (types.size() == 0) {

                                      throw new ...;          

                                  }

                               

                                  final Bean<T> bean = types.get(0);      

                                  final CreationalContext<T> ctx = beanManager.createCreationalContext(null);

                                  T contollerInstance = beanManager.getContext(bean.getScope()).get((Contextual<T>) bean, ctx);

                                  return contollerInstance;

                              }

                               

                              Note: The getController method is executed in a callback which is passed to the FXMLLoader with the setControllerFactory method. (see: http://java.dzone.com/articles/fxml-javafx-powered-cdi-jboss).

                               

                              The call of beanManager.getContext(bean.getScope()).get((Contextual<T>) bean, ctx) returns the bean-implementation or a sub-class of the bean. A subclass is e. g. returned if you want to use interceptors for your controller. With JavaFX 2.2 and Weld 1.1 you have to make the attributes of your controller package protected, to make the @FXML-Injection working in that situation. In Java8 FXML-Injection it will not work for sub-classed beans any-more. See https://javafx-jira.kenai.com/browse/RT-30131 and the other entries of this discussion for more details.

                              • 12. Re: Weld Proxies
                                philipp91

                                In Java8 FXML-Injection it will not work for sub-classed beans any-more.

                                Actually, I think it does. Any field that is visible to the sub-class will be injected. And because in the case of Weld with interceptors, the sub-class is always in the same package, anything works except private fields. However, I didn't test it, because I don't need it.