Reputation: 94499
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
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
Reputation: 280141
The injection process goes like this
Class
instance for the class to create a bean forConstructor
or use Class#newInstance()
to create the bean@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