Archive for the ‘Aspects’ Category
Pitfalls with aspect technologies
Even though aspect technologies are very powerful. Especially to address cross-cutting concerns like transactions, it shouldn’t be used everywhere it’s possible.
The first part of this post consists of a simple example that uses AspectJ to return an unexpected value. The last part is about where aspects fits and where you should consider to avoid it.
A quote from the Spider-Man movie:
With great power comes great responsibility
This applies to some degree to aspect technologies too. Since it can change the behaviour of an application at compilation, startup or at runtime. Even without leaving a trace in the source code.
An aspect that changes the return value of String’s toLowerCase()
To demonstrate an unexpected result from a method, I have created a StringAspect with AspectJ that returns the opposite of what the method name says.
When some code calls on String‘s toLowerCase() method, the StringAspect below reroutes the call to String‘s toUpperCase() method instead:
@Aspect public class StringAspect { @Pointcut("call(* String.toLowerCase())") public void toLowerCasePointcut() {} @Around("toLowerCasePointcut()") public String toLowerCaseAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { // String text = (String) joinPoint.proceed(); String text = ((String) joinPoint.getTarget()).toUpperCase(); return text; } }
To get the expected result, you can remove the comment on the line with joinPoint.proceed() and remove the line below. Eventually remove the AspectJ compiler or the JVM agent to avoid weaving in the advice.
The test class that asserts that the String‘s toLowerCase() method works as expected:
public class StringTest { @Test public void testAroundAdvice() { String demoText = "tHiS iS a TeSt"; assertEquals("this is a test", demoText.toLowerCase()); } }
Without AspectJ weaving, the test finishes successfully, but with weaving the result is:
org.junit.ComparisonFailure: expected:<[this is a test]> but was:<[THIS IS A TEST]>
Best practice from enterprise frameworks
Features like transaction handling, security and concurrency support are good candidates to be done with aspects. Spring, Guice and EJB implementations all use aspects to provide such enterprise capabilities without cluttering the application’s source code with infrastructure logic.
You can see this post for a good example. Best practice with Java 5 and later is to add an annotation on methods that will be intercepted.
Annotations that says this method should be executed asynchronously in a transaction:
@Async @Transactional public void demoMethod() { // logic .. }
These annotations are excellent pointcuts for enterprise frameworks.
Cases where aspects should be avoided
To change the target method like the StringAspect above should be avoided. You can replace an instance with a mock/stub object with AspectJ. In situations where the called instance is an object of a final class that doesn’t implement any interfaces, then an aspect is your only choice. It mocks away the dependency, but refactoring the code and use a mocking framework should be the preferred choice. It makes the code much more readable.
Especially without a tool like AJDT that visualizes the joinpoints in the source code. Here’s an example with the AJDT Eclipse plugin.
You should be really careful about adding or changing any data inside an advice. Such modifications makes the code almost impossible to read and the chance for bugs are higher.
Else, if many of the developers in a team isn’t familiar with aspects, you should also limit the use since it adds “magic” to the application that only a few developers on the team can understand.
Declaratively add enterprise functionality
The goal with this tutorial is to explain how you can add enterprise functionality declaratively and how an application container does it.
Enterprise functionality like transaction handling and security are often handled by an application container. I will demonstrate how to add transactional handling with the Decorator pattern, an interceptor (AspectJ) and an interceptor configured with the Spring framework. All the alternatives works without adding boilerplate transaction logic to the business classes.
The decorator pattern can add behaviour to your business implementation without polluting the business implementation. The limitation with the decorator pattern compared to an interceptor is that the decorator must implement your business class’ interface. An interceptor implemented with the AspectJ technology modifies the class either at compile or load-time. A general practice is to add business functionality with decorators, while you add enterprise functionality like transaction handling and security logic with interceptors.
Example case for this tutorial
This tutorial consist of four examples all using the interfaces on the image below with transaction logic around the transferAmount(..) method. The sequence diagram illustrates a successful transaction that will be commited.
The other case demonstrated is when the credit method fails. Then the amount transferred from the debit account must be cancelled with a roll back. The roll back is triggered with a RuntimeException. This is the default way with EJB as well as the lightweight containers like Spring and Google Juice.
All the examples except the last one uses stub implementations for the repository and the transaction logic. The last example with Spring uses an embedded database and a JDBC transaction manager.
Transaction handling with the decorator pattern
This decorator example consist of the interfaces on the sequence diagram above as well as implementations of them and a transaction decorator that also implements the AccountService.
The business interface:
public interface AccountService { public boolean transferAmount(final Amount amount, final Account debetAccount, final Account creditAccount); }
The transferAmount() method in the business implementation:
@Transactional public boolean transferAmount(final Amount amount, final Account debetAccount, final Account creditAccount) { final boolean transferred; final Amount debetAccountBalance = accountRepository .getBalance(debetAccount); logger.debug("Balance on debet account before transfer: {}", debetAccountBalance.getAmount()); if (debetAccountBalance.isLargerThanOrEqualTo(amount)) { debetAmount(amount, debetAccount); creditAmount(amount, creditAccount); transferred = true; logger.debug("{} amount was transferred from {} to {}", new Object[] { amount, debetAccount, creditAccount }); } else { transferred = false; logger.debug("The transfer amount: {} is higher than the balance available on the " + "debet account", amount.getAmount()); } return transferred; }
The method doesn’t contain any transactional logic. Since this example doesn’t use any interceptors, the @Transactional annotation is ignored.
When I execute the method with sufficient balance on the debit account, the following line is printed to the log:
INFO [com.redpill.linpro.service.impl.AccountServiceImpl] -
With the transaction decorator the output is:
DEBUG [com.redpill.linpro.service.impl.AccountTransactionDecorator] - INFO [com.redpill.linpro.service.impl.AccountServiceImpl] - <Amount [amount=50] was transferred from Account [accountNumber=1] to Account [accountNumber=2]> DEBUG [com.redpill.linpro.service.impl.AccountTransactionDecorator] - <Commit transaction>
The transaction decorator stub implementation:
package com.redpill.linpro.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.redpill.linpro.integration.Account; import com.redpill.linpro.integration.Amount; import com.redpill.linpro.service.AccountService; public class AccountTransactionDecorator implements AccountService { private final AccountService accountService; private final Logger logger = LoggerFactory.getLogger(AccountTransactionDecorator.class); private int commitCounter = 0; private int rollbackCounter = 0; public AccountTransactionDecorator(AccountService accountService) { this.accountService = accountService; } @Override public boolean transferAmount(Amount amount, Account debetAccount, Account creditAccount) { final boolean transferred; logger.debug("Begin transaction"); try { transferred = accountService.transferAmount(amount, debetAccount, creditAccount); commitCounter++; logger.debug("Commit transaction"); } catch (RuntimeException e) { rollbackCounter++; logger.debug("Rollback transaction"); throw e; } return transferred; } public int getCommitCounter() { return commitCounter; } public int getRollbackCounter() { return rollbackCounter; } }
The decorator must implement the business interface like the business implementation above. Then it must have a reference to the interface. This enables the decorator to call the next decorator/service recursively. The decorator doesn’t know if the reference to the interface is to the implementation or to another decorator. Only the factory method knows that. This enables loose coupling and the ability to add behaviour to your service with only a configuration change. The decorator adds behaviour before and after the service is executed. And if a RuntimeException is throwed back, it will be catched and a roll back will be simulated. With normal execution, the decorator will simulate a commit like you can see in the log message above.
An implementation that uses the decorator pattern, should also use the factory pattern. In this example the factory logic is in the constructor of the test class. The important part in this factory method is that the accountService instance points to the transaction decorator object. And that the transaction decorator is instantiated with a reference to the business implementation.
private final AccountService accountService; public AccountServiceWithTransactionDecoratorTest() { accountRepositoryStub = new AccountRepositoryStub(); // Basic service AccountService basicAccountService = new AccountServiceImpl(accountRepositoryStub); // Decorator accountTransactionDecorator = new AccountTransactionDecorator(basicAccountService); // The decorated service accountService = accountTransactionDecorator; }
If the accountService instance had pointed to the business implementation instead of the decorator, then no transaction logic would have been executed.
The last part of this decorator example is the JUnit test. As you can see from the highlighted code, the test doesn’t know about the implementation or any decorators. It just executes the transferAmount() method on the accountService instance. And this instance is declared to be of the interface type.
@Test public void testTransferAmount() { Amount amount = new Amount(50); Account debetAccount = new Account("1"); Account creditAccount = new Account("2"); boolean transferred = accountService.transferAmount(amount, debetAccount, creditAccount); assertTrue(transferred); assertEquals(1, accountTransactionDecorator.getCommitCounter()); }
Transaction handling with aspect
Spring Aspects supports Spring AOP (Dynamic Objects and CGLIB) and AspectJ. Both alternatives support the AspectJ syntax. AspectJ is more powerful while Spring AOP is easier to use together with the Spring container. In this interceptor example I will use AspectJ. This is because it also works without the Spring container. I’m using compile-time weaving for simplicity, since STS will do the weaving for me with the AJDT plugin and an AspectJ nature on the project.
An aspect doesn’t need to know anything about the class it will modify. All that’s necessary is to know the joinpoint where the extra functionality should be added. In this example the pointcut will be the @Transactional annotation which is showed here:
@Transactional public boolean transferAmount(final Amount amount, final Account debetAccount, final Account creditAccount) { .. }
To enable AspectJ functionality with Java annotations, the @Aspect annotation must be above the class name with the AspectJ logic. Further, you have to declare the pointcut in a new annotation like this:
@Pointcut("execution(@org.springframework.transaction.annotation.Transactional * *(..))") public void transactionalMethod() {}
A pointcut doesn’t add anything without advices. The three advises below supports the transaction handling example:
@Before("transactionalMethod()") public void beforeTransactionalMethod(JoinPoint joinPoint) { transactionService.beginTransaction(); } @AfterReturning("transactionalMethod()") public void afterTransactionalMethod(JoinPoint joinPoint) { transactionService.commit(); } @AfterThrowing(pointcut = "transactionalMethod()") public void afterThrowingFromTransactionalMethod(JoinPoint joinPoint) { transactionService.rollback(); }
All the advises delegates to an instance of TransactionService. The last advice doesn’t swallow the exception, but throws it further after the rollback functionality is executed. The link below shows the TransactionAspect:
package com.redpill.linpro.transaction; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import com.redpill.linpro.transaction.impl.TransactionServiceNull; @Aspect public class TransactionAspect { private TransactionService transactionService = new TransactionServiceNull(); @Pointcut("execution(@org.springframework.transaction.annotation.Transactional * *(..))") public void transactionalMethod() {} @Before("transactionalMethod()") public void beforeTransactionalMethod(JoinPoint joinPoint) { transactionService.beginTransaction(); } @AfterReturning("transactionalMethod()") public void afterTransactionalMethod(JoinPoint joinPoint) { transactionService.commit(); } @AfterThrowing(pointcut = "transactionalMethod()", throwing = "e") public void afterThrowingFromTransactionalMethod(JoinPoint joinPoint, RuntimeException e) { transactionService.rollback(); } public void setTransactionService(final TransactionService transactionService) { this.transactionService = transactionService; } }
If you have enabled AspectJ nature on the Eclipse project, then you should see an orange arrow to the left of the transferAmount(..) method name.
In the factory method in the JUnit test, the difference between this method and the decorator test is that the accountService instance doesn’t know about the aspect and the aspect doesn’t know about the AccountService implementation. In this case, the transaction logic will be weaved in at compile-time with the AspectJ compiler.
public AccountServiceWithTransactionAspectTest() { accountRepositoryStub = new AccountRepositoryStub(); accountService = new AccountServiceImpl(accountRepositoryStub); transactionServiceStub = new TransactionServiceStub(); TransactionAspect transactionAspect = Aspects.aspectOf(TransactionAspect.class); transactionAspect.setTransactionService(transactionServiceStub); }
The test class consist of two test methods. The first test method asserts that the normal flow works and the output from the log can be seen here:
DEBUG [com.redpill.linpro.transaction.impl.TransactionServiceStub] - <Begin transaction> INFO [com.redpill.linpro.service.impl.AccountServiceImpl] - <Amount [amount=50] was transferred from Account [accountNumber=1] to Account [accountNumber=2]> DEBUG [com.redpill.linpro.transaction.impl.TransactionServiceStub] - <Commit transaction>
The other test try to transfer to an invalid account which should cause an exception. The after throwing advice should roll back the transaction which the log indicates:
DEBUG [com.redpill.linpro.transaction.impl.TransactionServiceStub] - <Begin transaction> DEBUG [com.redpill.linpro.transaction.impl.TransactionServiceStub] - <Rollback transaction>
Transaction handling with Spring
With Spring it’s possible to add declarative transaction handling with Spring AOP and AspectJ. This example will use the default Spring AOP, since it’s the simplest and most used alternative.
The default way to add transaction support with Spring and Java 5 or better is to annotate the transactional methods with @Transactional. This alternative requires the element in the configuration. It’s higlighted below. This element requires a transaction manager that implements Spring’s PlatformTransactionManager interface. The example uses just a stub implementation. The component-scan elements below finds all the Spring beans annotated with a specialization of the @Component annotation.
<context:component-scan base-package="com.redpill.linpro.service" /> <context:component-scan base-package="com.redpill.linpro.integration.stub" /> <tx:annotation-driven /> <bean id="transactionManager" class="com.redpill.linpro.transaction.impl.PlatformTransactionManagerStub" />
The most used implementations of the PlatformTransactionManager is DataSourceTransactionManager for JDBC, JpaTransactionManager, HibernateTransactionManager and JtaTransactionManager. The stub implementation used in this example just counts and logs every time a method in the interface are executed. Below are the overriden interface methods in the stub implementation:
@Repository public class PlatformTransactionManagerStub implements PlatformTransactionManager { @Override public void commit(TransactionStatus status) throws TransactionException { commitTransactionCounter++; logger.debug("Commit transaction"); } @Override public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { beginTransactionCounter++; logger.debug("Begin transaction"); return new SimpleTransactionStatus(); } @Override public void rollback(TransactionStatus status) throws TransactionException { rollbackTransactionCounter++; logger.debug("Rollback transaction"); } }
The test below demonstrates how to instantiate the Spring container with annotations and how to inject Spring beans into class variables. Further it executes the transferAmount(..) method on the accountService instance. This instance is of AccountServiceImpl type because the component-scan element in the configuration found the @Service annotation above that class and the class implements the AccountService interface. It doesn’t know about any aspects. And in this case with Spring AOP, the transaction logic will be added at runtime. No extra compiler or JVM agent is required.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("stub-config.xml") public class AccountServiceWithTransactionManagerStubTest { @Inject private AccountService accountService; @Inject private PlatformTransactionManagerStub platformTransactionManagerStub; @Test public void testTransferAmount() { Amount amount = new Amount(50); Account debitAccount = new Account("1"); Account creditAccount = new Account("2"); boolean transferred = accountService.transferAmount(amount, debitAccount, creditAccount); assertTrue(transferred); assertEquals(1, platformTransactionManagerStub.getBeginTransactionCounter()); assertEquals(0, platformTransactionManagerStub.getRollbackTransactionCounter()); assertEquals(1, platformTransactionManagerStub.getCommitTransactionCounter()); } @Test public void testTransferAmountThatShouldRollback() {..} }
The test prints the following to the log:
DEBUG [com.redpill.linpro.transaction.impl.PlatformTransactionManagerStub] - <Begin transaction> INFO [com.redpill.linpro.service.impl.AccountServiceImpl] - <Amount [amount=50] was transferred from Account [accountNumber=1] to Account [accountNumber=2]> DEBUG [com.redpill.linpro.transaction.impl.PlatformTransactionManagerStub] - <Commit transaction>
Transaction handling with Spring and database
In all the examples so far in this tutorial, I have used stub implementations for the transaction handling and only simulated a repository with another stub. In this last example, I will use plain JDBC against HSQL database and a DataSourceTransactionManager.
First, the respository stub is replaced with this:
@Repository public class AccountRepositoryImpl implements AccountRepository { @Inject private SimpleJdbcTemplate jdbcTemplate; private final String GET_BALANCE_SQL = "select BALANCE from ACCOUNT where ACCOUNT_NUMBER = ?"; private final String UPDATE_AMOUNT_SQL = "update ACCOUNT set BALANCE = BALANCE + ? where ACCOUNT_NUMBER = ?"; private final String VALIDATE_ACCOUNT_EXIST_SQL = "select count(*) from ACCOUNT where ACCOUNT_NUMBER = ?"; @Override public Amount getBalance(Account account) { int balance = jdbcTemplate.queryForInt(GET_BALANCE_SQL, account.getAccountNumber()); return new Amount(balance); } @Override public void updateBalance(Amount balance, Account account) { jdbcTemplate.update(UPDATE_AMOUNT_SQL, balance.getAmount(), account.getAccountNumber()); } public boolean isAccountExisting(final Account account) { int accountsWithAccountNumber = jdbcTemplate.queryForInt(VALIDATE_ACCOUNT_EXIST_SQL, account.getAccountNumber()); return (accountsWithAccountNumber == 1 ? true : false); } }
Second, the configuration must contain a DataSourceTransactionManager, a data source and the AccountRepository implementation above.
<context:component-scan base-package="com.redpill.linpro.service" /> <context:component-scan base-package="com.redpill.linpro.integration.db" /> <tx:annotation-driven /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> jdbc.core.simple.SimpleJdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> <jdbc:embedded-database id="dataSource" type="HSQL"> <jdbc:script location="classpath:com/redpill/linpro/integration/db/schema.sql"/> <jdbc:script location="classpath:com/redpill/linpro/integration/db/data.sql"/> </jdbc:embedded-database>
The test below asserts that the balance on the debit account is the same after the roll back as it was before the transaction
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"config.xml", "../integration/db/database-config.xml"}) public class AccountServiceWithSpringTest { @Inject private AccountService accountService; @Inject private AccountRepository accountRepository; @Test public void testTransfer() {..} @Test public void testTransferWithInvalidCreditAccount() { Amount amount = new Amount(100); Account debetAccount = new Account("789"); Account creditAccount = new Account("INVALID"); try { accountService.transferAmount(amount, debetAccount, creditAccount); fail("transferAmount() should have throwed exception."); } catch (IllegalArgumentException e) { Amount debetBalanceAfterTransfer = accountRepository.getBalance(debetAccount); assertEquals(new Amount(100), debetBalanceAfterTransfer); } } }
Summary
The Decorator pattern and aspect technologies enables enterprise functionality without an application container and boilerplate code in your business logic.The simplest solution if you’re already are familiar with an application container is to delegate the enterprise functionality to the container like the last two examples in this tutorial.
The coded used in this article was developed in my work time at Redpill Linpro. It can be downloaded from: http://samplecode-espenberntsen.googlecode.com/svn/enterprise.functionality/trunk/