Given the following technology stack:
– Java frontend with the Standard Web Toolkit (SWT), started via Java Web Start.
– Spring Remoting as the interface to the backend.
– Spring web application on a Tomcat as backend.
The backend is standard Spring, and who doesn’t know what Spring Remoting is can read it up here . Here I wanna talk about the combination of Spring and SWT in the frontend.
So, what are the goals? We want the UI components to be as dumb as possible, because they are a little harder to test. Everything that smells like business logic should be located in easy-to-test-POJOs. Components should be wired up via dependency injection, removing the need for service locators in the code.
I had no experience with SWT, so my first naive thought was, good, SWT UI components are becoming Spring beans, services on the client side are Spring Remoting proxies, and between the services and the UI components we add a layer of controllers containing any business logic.
It wasn’t that easy.
SWT UI components have their own lifecycle, they may be created and disposed at any time, and recreated again. The scope Singleton would definitely have been the wrong one. Okay, I thought, then the UI components would be prototype beans, and whenever we need one, we create it.
Even that’s easier said than done. Let’s take a look at a typical UI Component.
1public class SomeListView extends Composite
2{
3 private TabFolder tabFolder;
4 private SomeFilterComponent someFilterComponent;
5 private SomeSortableGrid grid;
6
7 public SomeListView(Composite parent)
8 {
9 super(parent, SWT.EMBEDDED);
10 setLayout(new GridLayout(1, false));
11 someFilterComponent = new SomeFilterComponent(this, SWT.NONE);
12 someFilterComponent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
13
14 tabFolder = new TabFolder(this, SWT.NONE);
15 tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
16
17 TabItem someListTab = new TabItem(tabFolder, SWT.NONE);
18 someListTab.setText("some list");
19
20 grid = new SomeSortableGrid(tabFolder, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
21 grid.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
22 grid.setHeaderVisible(true);
23 grid.setSize(this.getSize().x, 400);
24 grid.setAutoHeight(true);
25 grid.setAutoWidth(true);
26
27 campaignListTab.setControl(grid);
28 }
29}
This component is a view with a list that can be filtered via a filter component. The list is displayed in a tab. SomeFilterComponent and SomeSortableGrid are proprietary UI components as well. As you can see, the parent component is always a constructor argument to the child component. We have a bidirectional dependency here that makes dependency injection hard: if you want to inject SomeFilterComponent into SomeListView, you would have to create SomeListView before, so that it can be used in the constructor of SomeFilterComponent. That would still be possible with Singletons, but taking into account that both components need to be Prototypes it gets impossible.
Conclusion: SWT – UI components cannot become Spring beans.
What now? Good old service locators again?
No, there is an elegant solution with a little bit of AspectJ magic needing very little effort. Here the three steps involved:
1. Include the Maven-AspectJ-Plugin for compile-time-weaving in our pom
2. Use @Configurable and @Autowired in our UI component classes
3. Activate dependency injection in the application-context.xml
This way we can inject Spring beans in normal objects (our UI components) created via normal constructor invocation.
Let’s go into the details:
1. Include the Maven-AspectJ-Plugin for compile-time-weaving in our pom
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<complianceLevel>1.6</complianceLevel>
<Xlint>ignore</Xlint>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.6.11</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Having this configuration included in the pom the aspects from spring-aspects will be woven into the classes. We are interested in the AnnotationBeanConfigurerAspect, that will be woven into each class annotated with @Configurable. Now we can use the @Autowired annotation to inject dependencies in those classes, which leads us directly to the next point.
2. Use @Configurable and @Autowired in our UI component classes
1@Configurable(preConstruction = true)
2public class SomeListView extends Composite
3{
4 private TabFolder tabFolder;
5 private SomeFilterComponent someFilterComponent;
6 private SomeSortableGrid grid;
7 @Autowired
8 private SomeController someController;
9
10 public SomeListView(Composite parent)
11 {
12 super(parent, SWT.EMBEDDED);
13 setLayout(new GridLayout(1, false));
14 someFilterComponent = new SomeFilterComponent(this, SWT.NONE);
15 someFilterComponent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
16
17 tabFolder = new TabFolder(this, SWT.NONE);
18 tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
19
20 TabItem someListTab = new TabItem(tabFolder, SWT.NONE);
21 someListTab.setText("some list");
22
23 grid = new SomeSortableGrid(tabFolder, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
24 grid.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
25 grid.setHeaderVisible(true);
26 grid.setSize(this.getSize().x, 400);
27 grid.setAutoHeight(true);
28 grid.setAutoWidth(true);
29
30 campaignListTab.setControl(grid);
31 someController.doSomething();
32 }
33}
@Configurable is telling AspectJ to weave the AnnotationBeanConfigurerAspect into those classes. When you set ‘preConstruction = true’ as an annotation parameter, dependencies will even be injected before the constructor of the class is called. That’s why we can use SomeController in the constructor here. Dependencies you want to inject have to be annotated with @Autowired, like it is done here with the SomeController.
3. Activate dependency injection in the application-context.xml
The AnnotationBeanConfigurerAspect being woven in doesn’t mean that it can get active right away. Aspects are static usually, so we have to expose our ApplicationContext in a static place the aspect knows. Spring is doing this transparently when we add the following to the ApplicationContext xml file:
<context:spring-configured/>
For activating the autowiring via annotations we need the following entry:
<context:annotation-config/>
That’s it. Now we can create SomeListView via constructor and the dependencies get injected automatically. No service locator, no glue code. Everything smelling like business logic can be sourced out into controllers (or however you wanna name them) being injected into the UI component. The UI component itself stays as simple as possible.
One additional word regarding testability
If you take a look again at the code of SomeListView, you’ll notice a Nullpointer being thrown whenever you instantiate the class without having an application context configured in the right way. We accepted that you cannot test SWT – UI components easily in a standard unit test (that’s nothing about Spring – since the UI components are coupled tightly through the constructor calls, unit testing is impossible (to be clear: I’m talking about unit tests, not JUnit tests)). For testing an UI component we use the following base class:
1@ContextConfiguration("classpath:conf/some-config-test.xml")
2@RunWith(SpringJUnit4ClassRunner.class)
3@DirtiesContext
4public abstract class AbstractViewTest
5{
6 protected Shell shell;
7
8 @Autowired
9 protected SomeController someControllerMock;
10
11 @Before
12 public void setUp() throws Exception
13 {
14 assertNotNull(someControllerMock);
15
16 shell = new Shell(Display.getDefault(), SWT.NONE);
17 }
18}
some-config-test.xml looks like this:
<bean class="org.mockito.Mockito" factory-method="mock" >
<constructor-arg value="de.codecentric.client.controller.SomeController"/>
</bean>
<context:annotation-config/>
<context:spring-configured/>
A test class for SomeListView could look like this:
1public class SomeListViewTest extends AbstractViewTest
2{
3 private SomeListView someListView;
4
5 @Before
6 public void setUp() throws Exception
7 {
8 super.setUp();
9 SomeObject something = new SomeObject();
10
11 when(someControllerMock.doSomething()).thenReturn(something);
12 someListView = new SomeListView(shell);
13 }
14
15 @Test
16 public void testSomeListView()
17 {
18 Control[] children = someListView.getChildren();
19 assertEquals(2, children.length);
20
21 assertTrue(children[0] instanceof SomeFilterComponent);
22 assertTrue(children[1] instanceof TabFolder);
23
24 TabFolder tabFolder = (TabFolder) children[1];
25 Control[] tabFolderChildren = tabFolder.getChildren();
26 assertEquals(1, tabFolderChildren.length);
27 assertTrue(tabFolderChildren[0] instanceof SomeSortableGrid);
28
29 }
30}
The someControllerMock gets injected into someListView automatically, when someListView is created via constructor call. All necessary validations can be done on the mock.
More articles
fromTobias Flohre
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Tobias Flohre
Senior Software Developer
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.