Hippo Spring Integration with AspectJ and Build Time Weaving
Patrick Kromeyer
2015-10-21
Introduction
One characteristic of good software design is the separation of concerns and the encapsulation of business logic into a service layer. In a large Hippo CMS project, this is achieved by creating independent services which will be injected into HST Component classes. A good instrument for achieving this is the Spring framework which is already packaged and internally used by the HST. Contained within the Hippo documentation is an article that explains dependency injection for HST Components via a HST-2 Spring Bean Bridge. Unfortunately, there are some drawbacks to the described approach. Apart from the overhead of the additional configuration required inside the HST, the Spring Bean Bridge cannot be used with HST Components which are designed to be dragged and dropped in the Channel Manager. In this article, we will illustrate an alternative approach using the Spring Aspects library and Build Time Weaving.
Step 1: Dependencies
Having started with a clean Hippo 10 project generated via the Maven Archetype, our first step is to define the required dependency on the Spring Aspects library. Hippo uses a Maven Multi Module Configuration, meaning we need to change both the Parent and Site Module POMs. First we add the required dependency inside the Dependency Management section of the Parent POM:
<dependencyManagement>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
...
</dependencyManagement>
Next in the Site module POM we add the defined dependency from the Parent POM as well:As Hippo CMS also uses Spring extensively it has already defined the "spring.version" property that we reference in our dependency definition - by using this property we can be sure we are using the correct version.
<dependencies>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
...
</dependencies>
Step 2: Infrastructure setup
The next step is the Build Time Weaving configuration, for this we will use the Aspectj Maven Plugin. First we add the following configuration to the Plugin Management section of the Parent POM:
<pluginManagement>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<complianceLevel>1.8</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<!-- the aspect libraries to use for build time weaving -->
<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>
</plugin>
...
</pluginManagement>
We then need to enable the AspectJ Maven Plugin for the Site project build by referencing this configuration from within the Site module POM:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
</plugin>
Our next step is to tell Spring to consider the @Configureable annotation. For that we need to extend the default Hippo Spring configuration which is achieved by creating the file component-integration.xml in the META-INF/hst-assembly/overrides folder of our Hippo Site project:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<!-- enable @Configurable Annotation -->
<context:spring-configured/>
<!-- enable annotation driven Dependency Injection and search for Spring
Beans in Package -->
<context:component-scan base-package="de.netpioneer.demo"/>
</beans>
If we now try to build the Site project we get the following AspectJ Compiler Error:
AJC compiler errors: can't determine superclass of missing type
org.springframework.transaction.interceptor.TransactionAspectSupport
This is because an aspect defined within the Spring Aspects Library extends this abstract class. To resolve this issue, we need to add the following Dependency inside our Parent POM:
<dependencyManagement>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
...
</dependencyManagement>
And of course inside our Site Project POM:
<dependencies>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
...
</dependencies>
Now if we try to build again we get a further AspectJ Compiler Error:
AJC compiler errors: can't determine annotations of missing type
javax.persistence.Entity
The reason for this error is that the Spring Aspect library defines a number of aspects which use the missing types in their pointcut definitions. Since we don't use these aspects, we add the missing dependency with the scope provided . We will also get similar errors for other types as well, so for berevity all the required dependencies to resolve these issues are listed:
<dependencyManagement>
...
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
...
</dependencyManagement>
And as usual we update our Site Project POM:
<dependencies>
...
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
...
</dependencies>
Step 3: A short demo
Building once again should result in no further problems and a successful build.
Now it's time to create a short demo to test our solution. Let's start with a simple Ping Service:
package de.netpioneer.demo.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class PingService {
private static final Logger LOG =
LoggerFactory.getLogger(PingService.class);
public String ping() {
return "pong";
}
}
We now need to create an HST Component that uses our Ping Service:
package de.netpioneer.demo.components;
import de.netpioneer.demo.services.PingService;
import org.hippoecm.hst.component.support.bean.BaseHstComponent;
import org.hippoecm.hst.core.component.HstComponentException;
import org.hippoecm.hst.core.component.HstRequest;
import org.hippoecm.hst.core.component.HstResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class DemoComponent extends BaseHstComponent {
private static final Logger LOG =
LoggerFactory.getLogger(DemoComponent.class);
@Autowired
private PingService pingService;
@Override
public void doBeforeRender(HstRequest request, HstResponse response)
throws HstComponentException {
LOG.info("PingService: " + pingService.ping());
}
}
With the @Configurable annotation, we tell Spring to wire all dependencies marked with @Autowire . Since we use a Logger to show the Ping Service output inside our HST Component, we have to set the log level inside the conf/log4j-dev.xml to Info:
<root>
<level value="info"/>
...
</root>
After another successful build and startup, we have to enable our HST Component in the HST Configuration as usual. Therefore we create a HST Sitemap item and a HST Page that uses our DemoComponent class and then link them to each other. Afterwards, we execute an HTTP Request against Hippo that requires our HST Component and we get the Ping Service output in our logs:
[INFO] [talledLocalContainer] 06.09.2015 19:42:31 INFO http-nio-8080-exec-2
[DemoComponent.doBeforeRender:23] PingService: pong
Conclusion
The solution described is characterized by simple configuration through annotations without additional configuration inside the HST. One restriction however, is that you can only use property injection, wiring dependencies via constructor will not work. The reason for this is that Hippo is still managing the actual object creation, which means a default constructor is still required.
Further reading
https://www.onehippo.org/library/concepts/web-application/spring-bridge.html
https://docs.spring.io/spring/docs/current/spring-framework-reference/#aop-using-aspectj