Showing posts with label aspectj. Show all posts
Showing posts with label aspectj. Show all posts

Monday, January 22, 2007

Using AspectJ with Annotations

Recently I've been exploring annotations and how to use them with AspectJ. I've ran into a few things that I found to be odd, and may be due to the fact that annotations are still new to Java. Consider this example:

/* Required.java @Required annotation */
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Required {
String[] names();
}


/*MyClass.java*/
import java.util.Map;
public class MyClass {

@Required(names = {"id","label"})
public static void myMethod(Map<String,String> args,Object obj)
{
//id and label should be guaranteed not null
}
}


The following aspect with the required pointcut will match against the method above like one would expect.

/*CheckRequired.aj*/
import java.util.Map;

public aspect CheckRequired {

protected pointcut required(Required required) :
execution(@Required * *(Map<String,String>,..))
&& @annotation(required);

before(Required required) : required(required)
{
Map<String,String> args = (Map)thisJoinPoint.getArguments()[0];
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

Instead of getting the arguments from thisJoinPoint we would like the arguments to be part of the pointcut like the following:

/*CheckRequired.aj*/
import java.util.Map;
public aspect CheckRequired {
protected pointcut required(Map<String,String> args, Required required) :
execution(@Required * *(Map<String,String>,*))
&& @annotation(required)
&& args(args);
before(Map<String,String> args, Required required) : required(args,required)
{
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

But when this is compiled we get the warning "advice defined in CheckRequired has not been applied". Why is this? Changing the method from static to non-static doesn't make a difference. You'll notice that MyClass.myMethod takes a second argument that is never used in this example. Removing the argument will cause the second example of CheckRequired.aj to advise that pointcut. I came across this problem with a method that actually does need a second argument so that is why I included it in this example.

Friday, December 1, 2006

Using AspectJ in web applications

AspectJ is a great tool, and can make your life a lot easier when used properly. I have found several great uses for it in web application programming. Most web applications use a database connection and share that connection in a http session. Often a programmer will need to get the connection object from the session and check to make sure it isn't null, if it is try to instantiate it and catch any errors. Instead of putting this code everywhere you use the connection just use an aspect. The following example works well with a portlet application.


public aspect SanityCheck {
protected pointcut render(RenderRequest req,RenderResponse res) :
execution(void GenericPortlet.do*(RenderRequest,RenderResponse)
&& args(req,res);

around(RenderRequest req, RenderResponse res) : render(req,res)
{
Object conn = req.getPortletSession().getAttribute("conn");
if (conn == null)
{
try{
conn = new MyConnection();
req.getPortletSession().setAttribute("conn",conn,PortletSession.APPLICATION_SCOPE);
}catch(Exception e){
//handle error
}
}
proceed(req,res);
}
}


Now you have all this logic in one place and there is no need to be concerned with this problem elsewhere in your code. You also have a reusable pointcut for all renders, notice it is protected so any aspects that extend this aspect can also advise this pointcut.

Managing sessions can also be a pain sometimes. You might have one class that relies on session variables, but you want those to be cleared when a user navigates away from that page. Luckily we have the negation operator in AspectJ.


public aspect ClearSession {

private List<String> varNames = new ArrayList<String>();

protected pointcut display(MyPortlet portlet, RenderRequest req) :
execution(void MyPortlet.do*(RenderRequest,RenderResponse))
&& target(portlet)
&& args(req,*);

private pointcut notDisplay(RenderRequest req) :
execution (void GenericPortlet.do*(RenderRequest,RenderResponse)
&& !execution(void MyPortlet.do*(RenderRequest,RenderResponse))
&& args(req,*);

//only do this after returning, if it threw an exception we probably don't want to bother
after(MyPortlet portlet, RenderRequest req) returning : display(portlet,req)
{
//assumes MyPortlet has this method to retrieve a list of variable names it uses
List<String> names = portlet.getVarNames();
for (String x:names)
{
if (!varNames.contains(x))
varNames.add(x);
}
}

before(RenderRequest req) : notDisplay(req)
{
for (String x:varNames)
req.getPortletSession().removeAttribute(x,PortletSession.APPLICATION_SCOPE);
varNames.clear();
}
}


This will get a list of variables to be cleared from the portlet, and then when anything other than this portlet executes, we clear those variables.