Thursday 3 May 2012

Using Spring @Profile with @EnableAspectJAutoProxy

A new feature introduced in Spring 3.1 is the ability to mark components as being ready for registration if one or more 'profiles' (logical groupings) have been activated. The activation of the profiles is done at runtime either through a system property (-Dspring.profiles.active=xxx) or programmatically by calling the setActiveProfiles method on the ConfigurableEnvironment interface.

A common example of using profiles is to have different datasource beans for different environments eg a local jdbc datasource for a profile of dev but a jndi datasource for a profile of prod. This post though will describe a simple example of using @Profile to switch between different aspects when in different profiles.

Also as this example will use annotations and no XML, it will briefly cover the ability to enable AspectJ auto proxying using the new annotation @EnableAspectJAutoProxy.

The below CustomerApplication class has one simple task and that is to process a Customer object. In order to do that, the CustomerService bean needs to be obtained from the context and the context in this example is an AnnotationConfigApplicationContext.

This type of context will register annotated classes with the package prefix of com.city81. Classes marked with @Profile but not dev will be ignored. (More than one profile can be specified as setActiveProfiles takes an array of Strings.)


public class CustomerApplication {

    public static void main(String[] args) {
  
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();  
        ctx.getEnvironment().setActiveProfiles("dev");  
        ctx.scan("com.city81");
        ctx.refresh(); 

        Customer address = new Customer();
        address.setId("idOne");
        address.setName("custOne");
        address.setAge(21);
        address.setAddress("addressOne");
  
        CustomerService customerService = ctx.getBean(CustomerService.class);
        customerService.processCustomer(address);    
    }
 
}

As we're using Spring configuration programmatically, the getBean method on the context is able find the CustomerService bean as we have a CustomerServiceConfig class which is annotated with @Configuration. This allows the container to generate bean definitions at runtime.

@Configuration
@EnableAspectJAutoProxy
public class CustomerServiceConfig {

    @Bean
    public CustomerService customerService() {
        return new CustomerServiceImpl();
    }

}

This is the class that also includes @EnableAspectJAutoProxy. Prior to Spring 3.1, auto proxying AspectJ could be done using  <aop:aspectj-autoproxy>  but now @Configuration classes can include the @EnableAspectJAutoProxy annotation thereby enabling support for classes annotated with @Aspect.

In this example, there are two classes marked with @Aspect. Both extend an abstract class which defines the pointcut.

public abstract class LoggingAspect {

    @Pointcut("execution(* com.city81.service.CustomerService.processCustomer(..))")
    public void processCustomer() {
    }

}


@Configuration 
@Profile("prod")  
@Aspect
public class ConciseLoggingAspect extends LoggingAspect {

    @Before("processCustomer()")
    public void logCustomerId(JoinPoint jp) {
        Customer customer = (Customer) jp.getArgs()[0];
        System.out.println("id = " + customer.getId());
    }

}


@Configuration 
@Profile("dev")  
@Aspect
public class VerboseLoggingAspect extends LoggingAspect {

    @Before("processCustomer()")
    public void logCustomerAll(JoinPoint jp) {
        Customer customer = (Customer) jp.getArgs()[0];
        System.out.println("id = " + customer.getId());
        System.out.println("name = " + customer.getName());
        System.out.println("age = " + customer.getAge());
        System.out.println("address = " + customer.getAddress());
    }

}

In this example, there are two profiles, dev and prod, and depending on the profile activated there will be a different level of (contrived) 'logging'.

Each concrete LoggingAspect class contains an @Profile annotation. This can specify one or more profiles. Therefore in the case of using  ctx.getEnvironment().setActiveProfiles("dev") only VerboseLoggingAspect will be registered.

This post shows how easy it is to create profiles and activate them programmatically although it can be done just as easily using XML config.