Introduction to Aspect-Oriented Programming
Compiling the Sample Application
If you want to play with this demo application yourself, extract the contents of the sample application's WAR file. You'll find the aspectwerkz.xml file in the root of the directory, which gets copied to the WEB-INF/classes directory when the application is built. The source files of the servlet and advice classes are in WEB-INF/src; I've included an Ant script that will build these classes for you.
Before you can see the demo in action, you will have to complete the post-compilation phase, too, and here's how:
- Navigate on the command line to the directory where you extracted the WAR file.
- Type the following command to invoke the AW compiler (all on one line):
aspectwerkz -offline aspectwerkz.xml WEB-INF/classes
-cp %TOMCAT_HOME%\common\lib\servlet.jar
You should see the following if the post-compilation completes successfully:
( 1 s )
SUCCESS: WEB-INF\classes
There is an Ant task in the build file called war
that you can use
to recreate the WAR file.
Running the Sample Application
- (Re)start Tomcat.
- Open http://localhost:8080/demo/ in a browser.
When you open your browser, you will see a standard HTML form with two fields: one for the name and one for email address of the contact. Enter some details and press submit, and you will see the contacts details displayed and a link to a file that contains a simple contact list. OK, so the demo isn't going to set the world on fire, but let's take a look under the hood and see what has really happened.
Code Walkthrough
Let's ignore the JSP files, as they there are of very little interest to us right now. Instead, have a look at the code of AOPServlet.
package example;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AOPServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
Person person = new Person();
if (request.getParameter("name") != null) {
person.setName(
request.getParameter("name"));
}
if (request.getParameter("email") != null) {
person.setEmail(
request.getParameter("email"));
}
request.setAttribute("person", person);
RequestDispatcher rd =
request.getRequestDispatcher("/view.jsp");
rd.forward(request, response);
}
}
In this example, you'll notice that the servlet code is very minimal; it only contains enough code to create an object to bind the request parameters to, and then it passes the object along in the request. There is no persistence code, no additional imports; it simply does what it meant to do.
But our design document stipulates that we must persist all objects
of type Person
in our application, so we will need to add an aspect to
the application. To create an aspect, we first have to create a file
called aspectwerkz.xml and place that file in the
classpath. I've include a simple example in the sample application that
you can open in your favorite editor; I'll explain what it is doing.
The first section defines the different advices that are available to us. We can add as many different advice definitions as we like:
<advice-def name="persist" class="example.PersistenceAdvice"
deployment-model="perJVM"/>
In this snippet, we define a piece of advice called persist
, which is of type
example.PersistenceAdvice
. The last attribute defines the exclusivity
of this advice; in this case, it's set to perJVM
, which
means that only one instance of this advice will be created in each JVM.
(See the Aspectwerkz documentation for more information about deployment models.)
The next section defines our aspects. This is where we map our advice to a point-cut to create an aspect.
<aspect name="servlet">
<pointcut-def name="all" type="method"
pattern="* example.*Servlet.doGet(..)"/>
<bind-advice pointcut="all">
<advice-ref name="persist"/>
</bind-advice>
</aspect>
Let's step through this section line by line:
We are creating an aspect called
servlet
; we can create as many aspects as we require.On the second line, we are creating a point-cut called
all
that will only be applied to methods (type="method"
).The third line is where we define, using a regular expression, where we want the advice to be given. In this example, we are stating that we want to apply the advice, regardless of the return type (the first
*
), to any class in theexample
package whose name ends with "Servlet
" (*Servlet
) that contains a method calleddoGet
with any parameter list (doGet(..)
).On the fourth line, we tell the Aspectwerkz compiler that we want to apply the following advice to the
all
point-cut.Here we are saying that we want to use the
persist
advice.
Now that we know how to map point-cuts and advice to create
aspects, let's look at an example of a class that provides advice. In
our mapping file, we registered an advice of type
example.PersistenceAdvice
. Here is the source of that
file:
package example;
import javax.servlet.http.*;
import org.codehaus.aspectwerkz.advice.*;
import org.codehaus.aspectwerkz.joinpoint.*;
public class PersistenceAdvice extends AroundAdvice {
public PersistenceAdvice() {
super();
}
public Object execute(final JoinPoint joinPoint)
throws Throwable {
MethodJoinPoint jp =
(MethodJoinPoint) joinPoint;
final Object result = joinPoint.proceed();
Object[] parameters = jp.getParameters();
if (parameters[0] instanceof HttpServletRequest) {
HttpServletRequest request =
(HttpServletRequest) parameters[0];
if (request.getAttribute("person") != null) {
Person contact =
(Person) request.getAttribute("person");
ContactManager persistent =
new ContactManager();
String fileName =
(request.getRealPath("/")+
"contacts.txt");
persistent.save(contact, fileName);
}
}
return result;
}
}
The first line of this method is self-explanatory: we simply cast to
the most specific type we can. The second line is probably the most
important: since we want to fire the method and then look at the
result, we must call proceed()
, which allows the method to complete. In
the next section, we are capturing the HttpServletRequest
and retrieving the object that is placed in request scope by the
servlet (remember, the doGet()
method has finished at this point).
Finally, we create a class called ContactManager
that persists
the person's details to a text file. This class could just as easily
save the details to an XML file, a database, or another persistent
store. The point to take away is that the servlet has no idea what
will happen to the bean when you design/prototype the application;
the secondary functionality can be added at any point in the future.
This is what I mean when I say that your basic application can learn new
behavior as it grows, and adding additional functionality at a later date is trivial.
Where Next?
In the example in the previous section we took a very basic application, deployed it to Tomcat, and ran it in a browser to test the functionality. While the application as it stands isn't very useful, the principles that it demonstrates are very powerful indeed. Imagine being able to quickly prototype functionality and then apply the cross-cutting concerns such as security, logging, persistence, and caching when you've finished. You could feasibly add logging to a whole application, regardless of the number of lines of code, in less than ten minutes!
I hope this you can see beyond the simplicity of this application and see where you can use AOP in your projects. While there might be a reasonably steep curve to overcome to familiarize yourself with the concepts behind AOP, it will definitely pay off, cutting weeks and probably thousands of lines of repetitive code from the average project.
Graham O'Regan is a senior developer with European Technology Consultants (ETC) in London.
'Software Engineering > AOP - Aspect Oriented Programming' 카테고리의 다른 글
Introduction to Aspect-Oriented Programming - Part 1/2 (0) | 2016.07.28 |
---|