Best practice to instantinate parametrized components with Spring

Hi, this problem has been bugging me for a long time. What is the best practice to instantinate Vaadin components that have dependencies on Spring services and also runtime parameters? Just explaining. Let there be some Spring services

[code]
@Service
public class GreetingService {
public String getGreeting(String name) {
return "Hello " + name;
}
}

@Service
public class LuckyNumberService {
public int getLuckyNumber(String name) {
return name.hashCode() % 10;
}
}
[/code] and i would like to create Dialog window which displays data from the services for specific user.


Solution 1

Inject services to a managed UI/View and pass them to the dialog. A problem with this solution is that a parent container can end up with a large number of injected servies and every change to child dependencies causes change of the parent code.

public class SampleDialog extends Window {
   public SampleDialog(String name,
                       GreetingService greetingService,
                       LuckyNumberService luckyNumberService) {
      setContent(new Label(greetingService.getGreeting(name) + "Your lucky numer is " + luckyNumberService.getLuckyNumber(name)));
   }
}


@SpringUI
public class MainUI extends UI {

   @Autowired
   private GreetingService greetingService;

   @Autowired
   private LuckyNumberService luckyNumberService;
   
   @Override
   protected void init(VaadinRequest request) {
       // build some ui with a button and handle click with a function below
   }

   private void handleAction(String name){
       SampleDialog dialog = new SampleDialog(name,greetingService,luckyNumberService);
       addWindow(dialog);
   }

 


Solution 2

Create managed factory with injected servies. Downside is some boilerplate code and additional class for each component type, but this is probably the cleanest solution from api user perspective.

public class SampleDialog extends Window {
   public SampleDialog(String name,
                       GreetingService greetingService,
                       LuckyNumberService luckyNumberService) {
      setContent(new Label(greetingService.getGreeting(name) + "Your lucky numer is " + luckyNumberService.getLuckyNumber(name)));
   }
}

@Component
public class SampleDialogFactory {

       @Autowired
       private GreetingService greetingService;

       @Autowired
       private LuckyNumberService luckyNumberService;
       
       public SampleDialog createSampleDialogForUser(String name) {
           return new SampleDialog(name,greetingService,luckyNumberService);
       }
}

@SpringUI
public class MainUI extends UI {

   @Autowired
   private SampleDialogFactory sampleDialogFactory;

   @Override
   protected void init(VaadinRequest request) {
       // build some ui with a button and handle click with a function below
   }

   private void handleAction(String name) {
       SampleDialog dialog = sampleDialogFactory.createSampleDialogForUser(name);
       addWindow(dialog);
   }
}
 


Solution 3

Create managed dialog prototype and initalize it with runtime parameters after construcion. Api user must remember to call initialization function. What if he doesn’t? Can component be reused? Below is a code with two approaches to instantinate dialog component

@Component
@Scope("prototype")
public class SampleDialog extends Window {

   private GreetingService greetingService;
   private LuckyNumberService luckyNumberService
   
   public SampleDialog(GreetingService greetingService,
                       LuckyNumberService luckyNumberService) {
      this.greetingService = greetingService;
      this.luckyNumberService = luckyNumberService; 
   }

   public void init(String name) {
       setContent(new Label(greetingService.getGreeting(name) + "Your lucky numer is " + luckyNumberService.getLuckyNumber(name)));
   }
}

@SpringUI
public class MainUI extends UI {

   @Autowired
   private SampleDialog dialog;

   @Autowired
   private ApplicationContext applicationContext;

   @Override
   protected void init(VaadinRequest request) {
       // build some ui with a button and handle click with a function below
   }

   private void handleAction(String name) {
       dialog.init(name);
       addWindow(dialog);
   }

   private void alternativeHandleAction(String name) {
       // alternative to injecting protype into component      
       SampleDialog dialog = applicationContext.getBean(SampleDialog.class);
       dialog.init(name);
       addWindow(dialog);
   }
}
 


What do you think? Which one do you use? Or is there any other solution?

During writing of this post I nearly convinced myself that factory is the best solution, but still it seems like a lot of boilerplate code.

One alternative would be also not to inject class members with @Autowired, but use @Autowired with class constructor and have the things to be injected as parameters to constructor, and use @Postconstruct annotation for init(). You can find examples of this in our app starter.

https://vaadin.com/full-stack-starter

Thanks. Autowired constructor probably would be better than field injection but init() with @Postconstruct still doesn’t solve problem with passing runtime parameters. I see that you are using MVP in app starter - that’s a lot more code than I would want to write :wink: But I see a little improvment to Soultion 3 - naming initilizers in more semantic way, eg. dialog.showUserGreeting(name) or dialog.editOrder(orderId) - when it’s not called init() api user can suppose to call this method many times and component should support it properly.