Kevin Bowersox
Kevin Bowersox

Reputation: 94499

Autowiring in Prototype Bean

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();
      }
}

Upvotes: 0

Views: 2719

Answers (2)

Jose Luis Martin
Jose Luis Martin

Reputation: 10709

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) {
     ...
}

Upvotes: 1

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280141

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.

Upvotes: 2

Related Questions