« Aspect library discussion at AOSD 2005 | Main | AspectJ 5 M2 released today »
March 22, 2005
Hacking with Harrop...
(Rob Harrop that is, of Spring fame...)
Off the back of the AOSD 2005 library meeting, Rob Harrop and I managed to spend a happy few hours pair programming - made all the more interesting for Rob by the fact that I use a dvorak keyboard layout and occassionally forgot to switch it back to qwerty before giving him the keyboard....
By the time we were done, we could configure any aspect type (whatever the instantiation model) from Spring simply by using the @Bean annotation and defining a beans.xml file. In fact, we could configure any type with the @Bean annotation. We made it so that in the simplest case there is no need for the programmer to explicitly initialise spring - aspects do that for you when first detecting a type with the @Bean annotation, all you have to do is have one or more beans.xml files on the classpath. Oh, and did I mention that any type (including aspects) configured this way can be auto-exported as MBeans and managed via JMX? (no user code involved). This represents a pretty neat solution for configuring and managing aspects.
As ever, I'll sketch out the basics of the implementation for you here...
I'll use as an example an abstraction of the common session factory / session / worker pattern that crops up again and again. A worker class performs work that takes place in a session. Sessions are created by the session factory. Here's my Worker class:
public class Worker { public static void main(String[] args) { Worker worker1 = new Worker(); Worker worker2 = new Worker(); worker1.doWork(); worker2.doWork(); worker1.doWork(); } public void doWork() { Session session = SessionManager.aspectOf().getSession(); System.out.println("Doing work in " + session); doWorkHelper1(); doWorkHelper2(); } private void doWorkHelper1() { Session session = SessionManager.aspectOf().getSession(); System.out.println("Doing work in helper 1 for " + session); } private void doWorkHelper2() { Session session = SessionManager.aspectOf().getSession(); System.out.println("Doing work in helper 2 for " + session); } }
Worker has a convenience main method that creates two workers and asks them to do work. Note that the doWorkHelper1() and doWorkHelper2() methods are called within the cflow of the doWork method. In each method we print out the current session (from the SessionManager aspect). The SessionManager aspect is a percflow aspect that creates a new aspect for each control flow through doWork....
@Bean("SessionManager") public aspect SessionManager percflow(session()) { private Session session; private SessionFactory factory; /** * SessionFactory to use is configured by DI */ public void setSessionFactory(SessionFactory factory) { this.factory = factory; } /** * Make the current session available to anyone who needs it */ public Session getSession() { return session; } /** * A new SessionManager (and session) begins whenever * we execute doWork */ pointcut session() : execution(* doWork(..)); /** * Create a session using the factory that was passed to * us when we were configured */ before() : session() { session = factory.beginSession(); } /** * Close the session at the end of the session() control * flow. */ after() : session() { session.close(); } }
Note that the SessionManager aspect is annotated with @Bean, and has a setSessionFactory(..) method that is to be called by an IoC container to pass it the session factory to use.
Here's the Spring beans.xml file for the project. Note that this is 100% standard Spring, no funny stuff.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean name="SessionManager" class="org.aspectprogrammer.test.SessionManager"> <property name="sessionFactory"> <ref bean="SessionFactory"/> </property> </bean> <bean name="SessionFactory" class="org.aspectprogrammer.test.SessionFactory"> </bean> </beans>
Just compile this with the Spring AspectJ library on your aspectpath and run main:
Doing work in session #1001 Doing work in helper 1 for session #1001 Doing work in helper 2 for session #1001 Doing work in session #1002 Doing work in helper 1 for session #1002 Doing work in helper 2 for session #1002 Doing work in session #1003 Doing work in helper 1 for session #1003 Doing work in helper 2 for session #1003
We created 3 SessionManager aspect instances, one per control flow, and each was correctly configured by Spring. In true Spring style, note that we did this without using one single Spring API in our program
To get this to work, we used a couple of library aspects as follows. The abstract BeanConfigurator aspect will configure (and validate) any object whose type is annotated with @Bean:
/** * Configure and validate any object whose type is annotated with @Bean. * Validation occurs for any type implementing the ValidateConfiguration * interface. To be subclassed for each IoC mechanism you want to * support (eg. Spring, HiveMind, Pico, JDK, ...). */ public abstract aspect BeanConfigurator { /** * We only validate @Beans... */ declare warning : staticinitialization(ValidateConfiguration+) && !staticinitialization(@Bean *) && !staticinitialization(ValidateConfiguration) : "Implementors of ValidateConfiguration must have the @Bean annotation"; /** * The creation of a new bean (an object with the @Bean annotation) */ pointcut beanCreation(Bean beanAnnotation,Object beanInstance) : initialization((@Bean *).new(..)) && @this(beanAnnotation) && this(beanInstance); /** * All beans should be configured after construction. */ after(Bean beanAnnotation, Object beanInstance) returning : beanCreation(beanAnnotation,beanInstance) { configureBean(beanInstance,getBeanName(beanAnnotation,beanInstance)); } /** * If a bean implements the ValidateConfiguration interface, then call * validate() on it once it has been configured (this advice runs after the * configuration advice). */ after(Bean beanAnnotation, ValidateConfiguration beanInstance) returning : beanCreation(beanAnnotation,Object) && this(beanInstance) { beanInstance.validate(); } /** * The bean name is either the value given in the annotation (@Bean("MyBean") ), * or the name of the type if no value is given (@Bean ). */ private String getBeanName(Bean beanAnnotation, Object beanInstance) { String beanName = beanAnnotation.value(); if (beanName.equals("")) beanName = beanInstance.getClass().getName(); return beanName; } /** * To be overriden by sub-aspects. Configure the bean instance using the given * bean name. */ protected abstract void configureBean(Object bean,String beanName); }
This abstract aspect has been designed to allow you to plug-in any IoC container you choose. We chose Spring ;). So here's the concrete Spring sub-aspect:
/** * Configuration of any @Bean using Spring */ public aspect SpringBeanConfigurator extends BeanConfigurator implements ApplicationContextAware { private ConfigurableListableBeanFactory beanFactory; /** * DI the Spring application context in which this aspect should configure beans. */ public void setApplicationContext(ApplicationContext ctx) { if(!(ctx instanceof ConfigurableApplicationContext)) { throw new AspectConfigurationException( "ApplicationContext [" + ctx + "] does not implement ConfigurableApplicationContext."); } beanFactory = ((ConfigurableApplicationContext)ctx).getBeanFactory(); } /** * Implementation of configureBean from the super-aspect */ protected void configureBean(Object bean,String beanName) { if (beanFactory != null && beanFactory.containsBeanDefinition(beanName)) { beanFactory.applyBeanPropertyValues(bean,beanName); } } }
The final piece of the puzzle is how Spring gets bootstrapped. We use an aspect for that too. Here's the abstract Spring initialization aspect - it's abstract because we don't know what strategy you want to use for the creation of an application context.
/** * Implements lazy initialization of Spring based on first use of an * @Bean type. */ public abstract aspect AbstractSpringInitializer { /** * we only want to create the application context once */ static boolean initialized = false; /** * the first time we load a type with the @Bean annotation */ pointcut firstTimeLoadingofBeanAnnotation() : staticinitialization(@Bean *) && if(!initialized); /** * Create the Spring application context, delegate creation strategy to * concrete sub-aspects. */ after() returning : firstTimeLoadingofBeanAnnotation() { ApplicationContext context = createApplicationContext(); SpringBeanConfigurator.aspectOf().setApplicationContext(context); } /** * Sub-aspects override this method to provide a concrete strategy for * application context creation. */ protected abstract ApplicationContext createApplicationContext(); }
For my program above, I used a DefaultSpringInitializer that configures the application context based on one or more beans.xml files available on the classpath
/** * A default implementation of the AbstractSpringInitializer aspect. This will configure * the Spring application context using the set of beans.xml files available in the classpath * (what you want for many applications). Configuration files can be overriden by specifying * the org.aspectj.ajlib.configuration.spring.configLocations property (useful for testing etc.). */ public aspect DefaultSpringInitializer extends AbstractSpringInitializer { public static final String CONFIG_LOCATIONS = "org.aspectj.ajlib.configuration.spring.configLocations"; private static final String DEFAULT_LOCATIONS_PATTERN = "classpath*:/**/beans.xml"; protected ApplicationContext createApplicationContext() { String configFilePattern = null; try { configFilePattern = System.getProperty(CONFIG_LOCATIONS); if (configFilePattern == null) configFilePattern = DEFAULT_LOCATIONS_PATTERN; } catch(SecurityException secEx) { configFilePattern = DEFAULT_LOCATIONS_PATTERN; } return new ClassPathXmlApplicationContext( StringUtils.commaDelimitedListToStringArray(configFilePattern)); } }
The result of all this, is that with a few very simple building blocks you can configure aspects (and any other type) simply by writing a standard beans.xml file and using the @Bean annotation - no Spring APIs required in your code at all. There's more coming out of the joint Spring / AspectJ development effort, but I hope this whets your appetite for now.
Posted by adrian at March 22, 2005 03:47 PM [permalink]
Comments
Very cool. Now, when you say any type, does that refer to plain java beans?
Posted by: Peter Morelli at March 22, 2005 04:01 PM
Yes, although bear in mind this solution works best in situations where you are programmatically creating instances (or in the case of AspectJ, the AspectJ runtime is creating instances of aspects for you) that you subsequently want to be configured. For the "normal" pre-instantiated and wired-together singletons model that Spring supports you wouldn't need to use @Bean since Spring handles instantiation and configuration in one go.
Posted by: Adrian at March 22, 2005 06:34 PM
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)