|
Chapter 1 introduces the ideas that led to Aspect-Oriented Programming. An overview of
main concepts of AOP is used to describe components and features provided by Spring
AOP, while a set of concise yet clear examples lets the reader discover what can actually
be done with AOP.
Chapter 2 describes in detail the fundamentals of AOP in Spring, presenting interfaces
and classes introduced in early 1.x versions of the framework. This chapter shows how to
use AOP programmatically, to let the reader discover the basis of Spring AOP and the
components that implement Aspect-Oriented Programming in Spring.
Chapter 3 explains how the weaving of AOP components is done using the proxy pattern
and JDK or CGLIB implementations. It describes the purpose of proxies and how to use
them effectively. Some practical examples show how to use the proxies
programmatically, with annotations and with XML; they explain the ProxyFactoryBean
and how to make the programmer's work easier with AutoProxy. The chapter describes
also some smart techniques on target sources.
Chapter 4 explains how Spring AOP is supported by AspectJ. Configuration activity is
made simpler, more flexible and more powerful, thanks to annotations and the syntax of
AspectJ on pointcuts (without which those costructs would not be available). All
examples show how to use AspectJ with both annotations and XML. The chapter
contains practical recipes for specific cases, such as the injection of dependencies on
domain objects, the management of aspects' priority, the use of different life cycles for
Aspects and how to use Load Time Weaving. The chapter ends with some strategies on
how to choose different AOP approaches to fulfil specific requirements.
Chapter 5 describes the design alternatives that can be implemented using AOP. These
alternatives are solutions for common requirements: concurrency, caching, and security.
Using AOP, they can be achieved in a very elegant and easy way, being at the same time
totally transparent for the system where they are applied.
Chapter 6 introduces Domain-Driven Development as a alternative way to design
applications. The prototype example presented in this chapter is a typical Three-Layer
application, where DDD is used for design and AOP is used to inject the dependencies on
domain objects. iBatis is used for persistence to the database.
Chapter 7 completes the prototype application started in Chapter 6, showing the
application layer and the user interface. The latter is implemented with Spring
MVC using annotations. Integration and unit tests are used to verify the correctness
of the classes; DBUnit is used to test persistence classes, while some Mock classes
are used to test the UI. The chapter contains the configurations for the prototype
infrastructure, including autentication and authorization with Spring Security and the
JUnit 4.5 test suite.
Chapter 8 describes the development tools needed to include Spring AOP and AspectJ in
the Eclipse IDE. The reader can find here detailed istructions on how to configure Eclipse
with the plug-ins for Spring and for the AspectJ Development Tool, and how to install the
PostgreSQL database and the Apache Tomcat servlet engine. All installation procedures
are described for the three main operating systems: Ubuntu Linux, Apple Mac OS X, and
Microsoft Windows XP.
Design with AOP in Spring 2.5
In this chapter, we're going to examine some design decisions that are important for
building better applications. In these design decisions, the AOP plays a signifi cant
role because it provides smart solutions to common crosscutting problems.
We will look at the following AOP design solutions:
- Concurrency with AOP
- Transparent caching with AOP
- Security with AOP
Designing and implementing an enterprise Java application means not only dealing
with the application core business and architecture, but also with some typical
enterprise requirements.
We have to defi ne how the application manages concurrency so that the application
is robust and does not suffer too badly from an increase in the number of requests.
We have to defi ne the caching strategies for the application because we don't want
CPU- or data-intensive operations to be executed over and over.
We have to defi ne roles and profi les, applying security policies and restricting access
to application parts, because different kinds of users will probably have different
rights and permissions. All these issues require writing additional code that clutters
our application business code and reduces its modularity and maintainability.
But we have a choice. We can design our enterprise Java application keeping AOP in
mind. This will help us to concentrate on our actual business code, taking away all
the infrastructure issues that can otherwise be expressed as crosscutting concerns.
This chapter will introduce such issues, and will show how to design and implement
solutions to them with Spring 2.5 AOP support.
Concurrency with AOP
Concurrency is the system's ability to act with several requests simultaneously, such
a way that threads don't corrupt the state of objects when they gain access at the
same time.
A number of good books have been written on this subject, such as Concurrent
Programming in Java and Java Concurrency in Practice. They deserve much attention,
since concurrency is an aspect that's hard to understand, and not immediately visible
to developers. Problems in the area of concurrency are hard to reproduce. However,
it's important to keep concurrency in mind to assure that the application is robust
regardless of the number of users it will serve.
If we don't take into account concurrency and document when and how the
problems of concurrency are considered, we will build an application taking some
risks by supposing that the CPU will never simultaneously schedule processes on
parts of our application that are not thread-safe.
To ensure the building of robust and scalable systems, we use proper patterns. There
are JDK packages just for concurrency. They are in the java.util.concurrent
package, a result of JSR-166.
One of these patterns is the read-write lock pattern, which consists of is the interface
java.util.concurrent.locks.ReadWriteLock and some implementations, one of
which is ReentrantReadWriteLock.
The goal of ReadWriteLock is to allow the reading of an object from a virtually
endless number of threads, while only one thread at a time can modify it. In this
way, the state of the object can never be corrupted because threads reading the
object's state will always read up-to-date data, and the thread modifying the state
of the object in question will be able to act without the possibility of the object's
state being corrupted. Another necessary feature is that the result of a thread's
action can be visible to the other threads. The behavior is the same as we could have
achieved using synchronized, but when using a read-write lock we are explicitly
synchronizing the actions, whereas with synchronized synchronization is implicit.
Now let's see an example of ReadWriteLock on the BankAccountThreadSafe object.
Before the read operation that needs to be safe, we set the read lock. After the read
operation, we release the read lock.
Before the write operation that needs to be safe, we set the write lock. After a state
modifi cation, we release the write lock.
package org.springAOP.chapter.five.concurrent;
import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class BankAccountThreadSafe {
public BankAccountThreadSafe(Integer id) {
this.id = id;
balance = new Float(0);
startDate = new Date();
}
public BankAccountThreadSafe(Integer id, Float balance) {
this.id = id;
this.balance = balance;
startDate = new Date();
}
public BankAccountThreadSafe(Integer id, Float balance, Date start)
{
this.id = id;
this.balance = balance;
this.startDate = start;
}
public boolean debitOperation(Float debit) {
wLock.lock();
try {
float balance = getBalance();
if (balance < debit) {
return false;
} else {
setBalance(balance - debit);
return true;
}
} finally {
wLock.unlock();
}
}
public void creditOperation(Float credit) {
wLock.lock();
try {
setBalance(getBalance() + credit);
} finally {
wLock.unlock();
}
}
private void setBalance(Float balance) {
wLock.lock();
try {
balance = balance;
} finally {
wLock.unlock();
}
}
public Float getBalance() {
rLock.lock();
try {
return balance;
} finally {
rLock.unlock();
}
}
public Integer getId() {
return id;
}
public Date getStartDate() {
return (Date) startDate.clone();
}
…
private Float balance;
private final Integer id;
private final Date startDate;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock rLock = lock.readLock();
private final Lock wLock = lock.writeLock();
}
BankAccountThreadSafe is a class that doesn't allow a bank account to be
overdrawn (that is, have a negative balance), and it's an example of a thread-safe
class. The final fi elds are set in the constructors, hence implicitly thread-safe.
The balance fi eld, on the other hand, is managed in a thread-safe way by the
setBalance, getBalance, creditOperation, and debitOperation methods.
In other words, this class is correctly programmed, concurrency-wise. The problem is
that wherever we would like to have those characteristics, we have to write the same
code (especially the finally block containing the lock's release).
We can solve that by writing an aspect that carries out that task for us.
A state modifi cation is execution(void
com.mycompany.BankAccount.set*(*))
A safe read is execution(* com.mycompany.BankAccount.getBalance())
package org.springAOP.chapter.five.concurrent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.AspectJ.lang.annotation.After;
import org.AspectJ.lang.annotation.Aspect;
import org.AspectJ.lang.annotation.Before;
import org.AspectJ.lang.annotation.Pointcut;
@Aspect
public class BankAccountAspect {
/*pointcuts*/
@Pointcut(
"execution(* org.springAOP.chapter.five.concurrent.BankAccount.
getBalance())")
public void safeRead(){}
@Pointcut(
"execution(* org.springAOP.chapter.five.concurrent.BankAccount.
set*(*))")
public void stateModification(){}
@Pointcut(
"execution(* org.springAOP.chapter.five.concurrent.BankAccount.
getId())")
public void getId(){}
@Pointcut("execution(* org.springAOP.chapter.five.concurrent.
BankAccount.getStartDate()))
public void getStartDate(){}
/*advices*/
@Before("safeRead()")
public void beforeSafeRead() {
rLock.lock();
}
@After("safeRead()")
public void afterSafeRead() {
rLock.unlock();
}
@Before("stateModification()")
public void beforeSafeWrite() {
wLock.lock();
}
@After("stateModification()")
public void afterSafeWrite() {
wLock.unlock();
}
private final ReadWriteLock lock = new
ReentrantReadWriteLock();
private final Lock rLock = lock.readLock();
private final Lock wLock = lock.writeLock();
}
The BankAccountAspect class applies the crosscutting functionality. In this case,
the functionality is calling the lock and unlock methods on the ReadLock and the
WriteLock. The before methods apply the locks with the @Before annotation,
while the after methods release the locks as if they were in the fi nal block, with
the @After annotation that is always executed (an after-fi nally advice).
In this way the BankAccount class can become much easier, clearer, and briefer.
It doesn't need any indication that it can be executed in a thread-safe manner.
package org.springAOP.chapter.five.concurrent;
import java.util.Date;
public class BankAccount {
public BankAccount(Integer id) {
this.id = id;
this.balance = new Float(0);
this.startDate = new Date();
}
public BankAccount(Integer id, Float balance) {
this.id = id;
this.balance = balance;
this.startDate = new Date();
}
public BankAccount(Integer id, Float balance, Date start) {
this.id = id;
this.balance = balance;
this.startDate = start;
}
public boolean debitOperation(Float debit) {
float balance = getBalance();
if (balance < debit) {
return false;
} else {
setBalance(balance - debit);
return true;
}
}
public void creditOperation(Float credit) {
setBalance(getBalance() + credit);
}
private void setBalance(Float balance) {
this.balance = balance;
}
public Float getBalance() {
return balance;
}
public Integer getId() {
return id;
}
public Date getStartDate() {
return (Date) startDate.clone();
}
private Float balance;
private final Integer id;
private final Date startDate;
}
Another good design choice, together with the use of ReadWriteLock when
necessary, is using objects that once built are immutable, and therefore, not
corruptible and can be easily shared between threads. |