0

I am creating a financial application using Swing with Spring. In one portion of my application I have a JList containing a JLabel for each Account in the application. When the JLabel is clicked, I want to display a JPanel of type AccountTab within a JTabbedPane. This is all performed in the following controller.

@Component
public class AccountListController extends MouseAdapter implements MouseListener {

    @Autowired
    private AccountService accountService;

    @Autowired
    private MainFrameView mainFrameView;

    @Autowired
    private ApplicationContext context;

    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.getSource() instanceof JList) {
            JList list = (JList) e.getSource();
            JTabbedPane tabbedPane = this.mainFrameView.getTabbedPane();

            SidebarItem item = (SidebarItem) list.getModel().getElementAt(list.getSelectedIndex());
            Account account = accountService.findById(item.getValue());

            if (tabbedPane.indexOfTab(account.getName()) == -1) {
                AccountTab panel = context.getBean(AccountTab.class);
                panel.addTransactions(account.getTransactions());
                panel.getSplitPane().setDividerLocation(500);
                tabbedPane.addTab(item.getTitle(), panel);
            }

            tabbedPane.setSelectedIndex(tabbedPane.indexOfTab(account.getName()));
        }
    }

}

The AccountTab changes for each JLabel clicked so I made AccountTab a prototype bean, so I receive a new instance for each account. In order to make the prototype scope work, I need to use context.getBean(AccountTab.class). Here is the AccountTab code:

@Component
@Scope("prototype")
public class AccountTab extends JPanel {

    @Autowired
    private AccountTransactionPane transactionPane;

    private static final long serialVersionUID = 1L;
    private JTable table = new JTable();
    private JSplitPane splitPane;

    public AccountTab() {
        this.setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(this.table);
        splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, transactionPane);
        splitPane.setEnabled(false);
        this.add(splitPane, BorderLayout.CENTER);
    }

    public void addTransactions(List<AccountTransaction> transactions) {
        this.table.setModel(new AccountTransactionTableModel(transactions));
    }

    //omitted rest of code
}

You will notice I attempt to autowire a bean of type AccountTransactionPane however, the bean is not properly autowired, it is null. Here is the code for AccountTransactionPane:

@Component
public class AccountTransactionPane extends JPanel {

    private static final long serialVersionUID = 1L;

    JTabbedPane tabbedPane = new JTabbedPane();
    private JPanel withdrawlTransactionPane = new DepositTransactionPane().build();
    private JPanel depositTransactionPane = new DepositTransactionPane().build();
    private JPanel transferTransactionPane = new DepositTransactionPane().build();

    public AccountTransactionPane() {
        this.setLayout(new BorderLayout());
        tabbedPane.addTab("Withdrawl", this.withdrawlTransactionPane);
        tabbedPane.addTab("Deposit", this.depositTransactionPane);
        tabbedPane.addTab("Transfer", this.transferTransactionPane);
        this.add(tabbedPane, BorderLayout.CENTER);
    }
    //rest of class omitted
}

I have one specific problem and one general. First, the AccountTransactionPane is not autowired within the AccountTab bean. I am not sure why, how can I get the AccountTransactionPane bean to be autowired within AccountTab?

My second problem is more general, I am autowiring everything. It seems like everything needs to be a bean. I have fallen into the pattern of create a controller(bean), inject the UI(bean), then inject another controller(bean) which is used as the listener, within that controller I inject a service(bean). Is this normal? I can't find any good examples of large Swing with Spring applications to guide me.

UPDATE: Spring Java Config

@Configuration
@EnableJpaRepositories(basePackages="rhcloud.blog.tothought.data.repositories")
@EnableTransactionManagement
@ComponentScan({"rhcloud.blog.tothought.data", "rhcloud.blog.tothought.controllers", "rhcloud.blog.tothought.view.ui"})
public class ApplicationConfiguration {

      private static final String H2_JDBC_URL_TEMPLATE = "jdbc:h2:file:~/MyFinances/data;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3";

      @Bean
      public DataSource dataSource(){
            JdbcDataSource ds = new JdbcDataSource();       
            ds.setURL(H2_JDBC_URL_TEMPLATE);
            ds.setUser("sa");
            ds.setPassword("sa");

            return ds;
      }

      @Bean
      public EntityManagerFactory entityManagerFactory() {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        vendorAdapter.setShowSql(true);
        vendorAdapter.setDatabase(Database.H2);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("rhcloud.blog.tothought.data.entities");
        factory.setDataSource(dataSource());
        factory.afterPropertiesSet();

        return factory.getObject();
      }

      @Bean
      public PlatformTransactionManager transactionManager() {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory());
        return txManager;
      }

      /**
       * See: http://stackoverflow.com/questions/8434712/no-persistence-exception-translators-found-in-bean-factory-cannot-perform-excep
       * @return
       */
      @Bean 
        public HibernateExceptionTranslator hibernateExceptionTranslator(){ 
          return new HibernateExceptionTranslator(); 
        }

      @Bean
      public Application application(){
          return new Application();
      }
}
Kevin Bowersox
  • 93,289
  • 19
  • 159
  • 189
  • Spring will never inject `null` (not for beans anyway). Post your context configuration. – Sotirios Delimanolis Jan 04 '14 at 23:49
  • @SotiriosDelimanolis I have added the configuration as requested. If I put `System.out.println(transactionPane);` in the `AccountTab` constructor it prints `null` to the console. Appreciate the help. – Kevin Bowersox Jan 04 '14 at 23:52
  • please whats `I have a JList containing a JLabel for each Account`, I'm hope that isn't true, this is only wrong description – mKorbel Jan 04 '14 at 23:57
  • Account account = accountService.findById(item.getValue()); nobody told us, restrict that JList can be based on 2D array only – mKorbel Jan 04 '14 at 23:59
  • @mKorbel Can you elaborate? Very new to Swing or well anything beyond the basics. This is kind of a learn and explore project. – Kevin Bowersox Jan 05 '14 at 00:00
  • `//omitted rest of code` == for better help sooner post an [SSCCE](http://sscce.org/), short, runnable, compilable, with hardcode valuse for `JList`/`XxxListModel` – mKorbel Jan 05 '14 at 00:01
  • Can you elaborate? - Model implemented in Swing by default isn't designated to hold JComponent (excluding JEditorPane) only the value for view – mKorbel Jan 05 '14 at 00:03
  • @mKorbel I use a `DefaultListCellRenderer` to put the `JLabel` in the `JList`. Thoughts? – Kevin Bowersox Jan 05 '14 at 00:05
  • `I use a DefaultListCellRenderer to put the JLabel in the JList.` I'm hope that is joke, only. Nothing, any without your SSCCE – mKorbel Jan 05 '14 at 00:08
  • @mKorbel Not a joke. I was hoping to get some insights regarding why it is such a bad practice. As I said, I'm not familiar with Swing, I mainly work with J2EE. – Kevin Bowersox Jan 05 '14 at 00:10
  • I'm still hope that your missinterperted, otherwise you are in troubles, not joking much luck with ...., out of this thread – mKorbel Jan 05 '14 at 00:13

2 Answers2

2

The injection process goes like this

  • Get Class instance for the class to create a bean for
  • Either get a Constructor or use Class#newInstance() to create the bean
  • Inject the bean with all of its @Autowired targets.

Your class' constructor is like

@Autowired
private AccountTransactionPane transactionPane;

...

public AccountTab() {
    this.setLayout(new BorderLayout());
    JScrollPane scrollPane = new JScrollPane(this.table);
    splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, transactionPane);
    splitPane.setEnabled(false);
    this.add(splitPane, BorderLayout.CENTER);
}

So obviously transactionPane is going to be null in the constructor. Spring hasn't gotten around to injecting it. Consider using a @PostConstruct annotated method if there is some initialization you need to do with the injected beans.


My second problem is more general, I am autowiring everything. It seems like everything needs to be a bean. I have fallen into the pattern of create a controller(bean), inject the UI(bean), then inject another controller(bean) which is used as the listener, within that controller I inject a service(bean). Is this normal?

There should be one entry point to your application that loads the Spring ApplicationContext. Every other component (and its dependencies) should be managed by that ApplicationContext.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • So add the `PostConstruct` annotated method then, perform the ui operations such as creating the splitpane? – Kevin Bowersox Jan 04 '14 at 23:57
  • @kevin I don't know Swing very well, but it doesn't seem like there's anything you really need to do in the constructor. So yes. – Sotirios Delimanolis Jan 04 '14 at 23:58
  • Works like a charm! Thanks for bailing me out. – Kevin Bowersox Jan 05 '14 at 00:00
  • @Kevin You're welcome. And remember my first comment. If a field annotated with `@Autowired` or `@Inject` or `@Resource` is `null`, then Spring is not or has not managed your object. – Sotirios Delimanolis Jan 05 '14 at 00:01
  • Regarding your updated post about the general question. So creating all these beans for components, controllers and services is normal? Should I be autowiring everything or using `ApplicationContext#getBean`? – Kevin Bowersox Jan 05 '14 at 00:07
  • @KevinBowersox Again, don't know Swing very well, but IMO you should really only need to to `ApplicationContext#getBean` in one place, the `main` method. Get your first window and make it visible. The rest should be done and managed by the context and its beans. – Sotirios Delimanolis Jan 05 '14 at 00:10
  • Another big take away from your answer for me is that the constructor is called prior to autowiring the fields. This is different from creating a regular Java bean where fields are instantiated prior to constructor, but it makes sense that Spring needs an instance prior to autowiring. – Kevin Bowersox Jan 05 '14 at 00:16
  • @kevin You can also inject the bean directly into the constructor and assign it yourself. – Sotirios Delimanolis Jan 05 '14 at 00:18
  • You mean without Spring? I'm not following. – Kevin Bowersox Jan 05 '14 at 00:22
  • @Kevin You can annotate your constructor or constructor parameters with `@Autowired` as well. I'll let you look into that though. – Sotirios Delimanolis Jan 05 '14 at 00:23
  • I just noticed that option was provided in the other example. Thanks again! – Kevin Bowersox Jan 05 '14 at 00:23
1

Use a init method to build the panel instead Ctor. Autowiring is done after construction.

ie:

@PostConstruct
public void init() {
  this.setLayout(new BorderLayout());
  JScrollPane scrollPane = new JScrollPane(this.table);
  splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, transactionPane);
  splitPane.setEnabled(false);
  this.add(splitPane, BorderLayout.CENTER);
}

You can also inject the transactionPane in Ctor:

   @Autowired 
   public AccountTab(AccountTransactionPane transactionPane) {
     ...
}
Jose Luis Martin
  • 10,459
  • 1
  • 37
  • 38
  • Thanks for suggesting the `PostConstruct` method but I think this needs to be on the `AccountTab` instead of the `AccountTransactionPane`. – Kevin Bowersox Jan 05 '14 at 00:06