Martijn Dashorst's blog on Wicket/Spring/Hibernate configurationwas the starting point for my setup, and as I hate "oodles of XML" as much as he does, I replaced all the XML bean declarations by a @Configuration class, a new feature in Spring 3.0.x.
Let us start with the
web.xml
deployment descriptor, for hooking both Wicket and Spring into our servlet container: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>WicketSpring</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/app-context.xml</param-value> </context-param> <filter> <filter-name>WicketSpring</filter-name> <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> <init-param> <param-name>applicationFactoryClassName</param-name> <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value> </init-param> </filter> <filter-mapping> <filter-name>WicketSpring</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
The ContextLoaderListener will create the Spring application context as soon as the servlet context is available. The contextConfigLocation parameter defines the location of the XML configuration file for the Spring context - unfortunately, we cannot fully drop XML configuration in Spring 3.0.
Then we set up the usual WicketFilter and map all requests to this filter. Instead of providing the applicationClassName as in a vanilla Wicket setup, we configure a SpringWebApplicationFactory which will look up a Wicket WebApplication bean from the Spring context.
The Spring application context in WEB-INF/app-context.xml is rather short:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <context:property-placeholder location="file:${wicketspring.config}"/> <tx:annotation-driven/> <bean class="com.blogspot.hwellmann.wicketspring.beans.WicketSpringConfig"/> </beans>
First, we enable configuration by annotations so we can do the rest of the setup in Java. The property-placeholder defines the location of a properties file outside of the WAR which we use to provide the JDBC settings. tx:annotation-driven enables declarative transactions by annotations. Finally, we define just one bean WicketSpringConfig which does the rest of the work in Java:
@Configuration public class WicketSpringConfig { @Value("${wicketspring.jdbc.url}") private String jdbcUrl; @Bean public WicketSpringApplication wicketSpringApplication() { return new WicketSpringApplication(); } @Bean public BookDao bookDao() { return new BookDao(); } @Bean public DataSource dataSource() { return new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), jdbcUrl); } @Bean public PlatformTransactionManager transactionManager() { return new JpaTransactionManager(entityManagerFactory()); } @Bean public EntityManagerFactory entityManagerFactory() { LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean(); bean.setDataSource(dataSource()); bean.setPersistenceProvider(new HibernatePersistence()); bean.afterPropertiesSet(); return bean.getObject(); } }
There are some points to note about the entityManagerFactory bean: We use a Spring factory bean to construct a JPA EntityManagerFactory, so the Spring class should really be called LocalContainerEntityManagerFactoryFactoryBean (yuck) or LocalContainerEmfFactoryBean for short.
The Spring Reference Manual does not explain how to use factory beans in Java configurations. For factory beans in XML configurations, bean.getObject() is called automatically by the container to produce the real bean from the factory. Apparently, you have to do this explicitly in Java configurations, and afterPropertiesSet() is a prerequisite for calling getObject().
The @Value annotation on the jdbcUrl field is used to access a value from the properties file referenced in the XML context.
In our Wicket application class, we define a SpringComponentInjector which will inject Spring beans into our Wicket components:
public class WicketSpringApplication extends WebApplication { @Autowired private BookDao bookDao; @Override protected void init() { super.init(); addComponentInstantiationListener(new SpringComponentInjector(this)); bookDao.createBooks(); } @Override public Class getHomePage() { return BooksPage.class; } }
WicketSpringApplication itself is not a Wicket component, but it is a Spring bean, so the required bookDao is injected automatically by the Spring container. The Wicket components like BooksPage, on the other hand, are instantiated by Wicket outside of the Spring container, so we need to configure a ComponentInstantiationListener to inject Spring beans into our Wicket components.
The SpringComponentInjector is defined in the wicket-spring extension library. It will inject all Wicket component fields annotated by @SpringBean with beans from the Spring context. This is an example of a Wicket page with an injected Spring DAO:
public class BooksPage extends WebPage { @SpringBean private BookDao bookDao; public BooksPage() { add(new PropertyListView<Book>("book", bookDao.findBooks()) { @Override protected void populateItem(ListItem<Book> item) { item.add(new Label("author")); item.add(new Label("title")); } }); } }
The DAO itself is nothing special. It has an injected
EntityManager
and automatic transactions by means of the @Transactional
annotation:public class BookDao { @PersistenceContext private EntityManager em; private static String[][] parameters = { {"Thomas Mann", "Buddenbrooks"}, {"Heinrich Mann", "Der Untertan"}, {"Günter Grass", "Die Blechtrommel"} }; @Transactional public void createBooks() { for (String[] param : parameters) { Book book = new Book(param[0], param[1]); em.persist(book); } } public List<Book> findBooks() { TypedQuery<Book> query = em.createQuery("select b from Book b", Book.class); return query.getResultList(); } }
Now we're ready to launch our simple Web applications which will display a list of books from the database. I'm leaving out the Book entity and the BooksPage.html markup, which are completely trivial.
This is how the wicket-spring integration works out of the box, but I'm not quite satisified with that. There are two different annotations @Autowired and @SpringBean doing basically the same thing and tying my application classes to Spring.
However, I would like to preserve the freedom to run my application either on Spring or on Java EE, an approach I explained in another post.
More precisely, I would like to replace both @Autowired and @SpringBean by the standard @javax.inject.Inject annotation from JSR-330, and also replace Spring's @Transactional by @javax.ejb.TransactionAttribute.
Spring 3.0 honours the @Inject and @TransactionAttribute annotations, provided they are on the classpath, so all we need to do is add the corresponding Apache Geronimo Java EE specification JARs to our project.
Making wicket-spring recognize @Inject instead of @SpringBean requires a few more steps, which I'll just outline:
- Create the classes Jsr330SpringComponentInjector, Jsr330SpringInjector and Jsr330ProxyFieldValueFactory as copies of SpringComponentInjector, SpringInjector and AnnotProxyFieldValueFactory in wicket-spring.
- Replace references to SpringBean in Jsr330ProxyFieldValueFactory by references to Inject.
- In our modified copies, reference the modified copies instead of the originals.
With this approach, I've created a functional example project, using Eclipse, Maven, Tomcat, Hibernate and Derby as further ingredients. Here is my Maven POM:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.blogspot.hwellmann.wicketspring</groupId> <artifactId>wicketspring-web</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <properties> <spring.version>3.0.5.RELEASE</spring.version> <wicket.version>1.4.15</wicket.version> </properties> <dependencies> <dependency> <groupId>org.apache.wicket</groupId> <artifactId>wicket</artifactId> <version>1.4.15</version> </dependency> <dependency> <groupId>org.apache.wicket</groupId> <artifactId>wicket-spring</artifactId> <version>1.4.15</version> </dependency> <dependency> <groupId>org.apache.wicket</groupId> <artifactId>wicket-ioc</artifactId> <version>1.4.15</version> <exclusions> <exclusion> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.5.11</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.11</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>0.9.20</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>0.9.20</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <type>jar</type> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-atinject_1.0_spec</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-ejb_3.1_spec</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.7.1.1</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.5.6-Final</version> </dependency> </dependencies> </project>
As my maths books used to say, "the details are left as a trivial exercise to the reader", but I hope I've provided enough details to get anyone started...
6 comments:
Very nice post. Exactly what I am looking for. Is there any way to by-pass the XML definition for Spring totally with Wicket?
In Spring 3.0.x, you cannot do completely without XML configuration, at least not in most real-life situations, because there is no annotation equivalent for things like or .
Spring 3.1 will be the first release to let you work with Java-only configurations. Check out the Springsource blog and the latest milestone releases. (Haven't tried this myself yet...)
Just small remark on Wicket and CDI as in JEE6 http://www.mail-archive.com/users@wicket.apache.org/msg50151.html
It looks that without some extra glue code you cannot run Wicket app in environment which uses OpenWebBeans as CDI implementation (for example Geronimo). For Weld there is a library but limited to subclasses of WebPage.
@Janek: Yes, you do need some glue code, just the same as with Spring (wicket-spring).
Igor Vaynberg created a wicket-cdi lib:
https://www.42lines.net/2011/11/15/integrating-cdi-into-wicket/
I haven't tried it yet, I'm still working with a solution of my own I created a year ago, based on Erik Brakkee's approach: http://brakkee.org/site/2010/08/14/using-cdi-to-inject-dependencies-into-unmanaged-objects/
This approach has no dependencies on Weld and only uses the official CDI APIs so there is now reason why it should not work with OpenWebBeans, but I've never tried it.
I somehow never got round to polishing and publishing this stuff - before doing so, I'd like to take some time looking at Igor's and Erik's current solutions to decide whether or not the world needs yet another Wicket/CDI integration lib...
Is there somewhere the working project ready for download? That would be very helpful.
@Patrick: There's no such download at the moment, I'm afraid.
But you're right, there's no reason not to publish the code - except that I'd need some time to brush it up...
Post a Comment