1. Introduction

This tutorial is a quick and practical introduction to AspectJ.

First, we’ll demonstrate how to enable aspect-oriented programming, and then we’ll focus on the difference between compile-time, post-compile, and load-time weaving.

Let’s start with a short introduction of aspect-oriented programming (AOP) and AspectJ’s basics.

2. Overview

AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code without modification of the code itself. Instead, we declare separately which code to modify.

AspectJ implements both concerns and the weaving of crosscutting concerns using extensions of Java programming language.

3. Maven Dependencies

AspectJ offers different libraries depending on its usage. We can find Maven dependencies under group org.aspectj in the Maven Central repository.

In this article, we’ll focus on the dependencies needed to create aspects and Weaver using the compile-time, post-compile, and load-time Weavers.

3.1. AspectJ Runtime

When running an AspectJ program, the classpath should contain the classes and aspects, along with the AspectJ runtime library aspectjrt.jar:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.20.1</version>
</dependency>

This dependency is available over on Maven Central.

3.2. AspectJWeaver

In addition to the AspectJ runtime dependency, we’ll also need to include the aspectjweaver.jar to introduce advice to the Java class at load time:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.20.1</version>
</dependency>

This dependency is also available on Maven Central.

4. Aspect Creation

AspectJ provides an implementation of AOP and has three core concepts:

  • Join Point
  • Pointcut
  • Advice

We’ll demonstrate these concepts by creating a simple program to validate a user account balance.

First, let’s create an Account class with a given balance, and a method to withdraw:

public class Account {
    int balance = 20;

    public boolean withdraw(int amount) {
        if (balance < amount) {
            return false;
        } 
        balance = balance - amount;
        return true;
    }
}

We’ll create an AccountAspect.aj file to log account information, and to validate the account balance (note that AspectJ files end with an “*.aj*” file extension):

public aspect AccountAspect {
    final int MIN_BALANCE = 10;

    pointcut callWithDraw(int amount, Account acc) : 
     call(boolean Account.withdraw(int)) && args(amount) && target(acc);

    before(int amount, Account acc) : callWithDraw(amount, acc) {
    }

    boolean around(int amount, Account acc) : 
      callWithDraw(amount, acc) {
        if (acc.balance < amount) {
            return false;
        }
        return proceed(amount, acc);
    }

    after(int amount, Account balance) : callWithDraw(amount, balance) {
    }
}

As we can see, we’ve added a pointcut to the withdraw method, and created three advises that refer to the defined pointcut.

In order to understand the following, we’ll introduce the following definitions:

  • Aspect: A modularization of a concern that cuts across multiple objects. Each aspect focuses on a specific crosscutting functionality.
  • Join point: A point during the execution of a script, such as the execution of a method or property access.
  • Advice: Action taken by an aspect at a particular join point
  • Pointcut: A regular expression that matches join points. An advice is associated with a pointcut expression, and runs at any join point that matches the pointcut.

For more details on these concepts and their specific semantics, we may want to check out the following link.

Next, we’ll need to weave the aspects into our code. The sections below address three different types of weaving: compile-time weaving, post-compile weaving, and load-time weaving in AspectJ.

5. Compile-Time Weaving

The simplest approach of weaving is compile-time weaving. When we have both the source code of the aspect and the code that we’re using aspects in, the AspectJ compiler will compile from source, and produce a woven class files as output. Afterward, upon execution of our code, the weaving process output class is loaded into JVM as a normal Java class.

We can download the AspectJ Development Tools, since it includes a bundled AspectJ compiler. One of AJDT’s most important features is a tool for the visualization of crosscutting concerns, which is helpful for debugging a pointcut specification. We may visualize the combined effect even before the code is deployed.

We’ll use Mojo’s AspectJ Maven Plugin to weave AspectJ aspects into our classes using the AspectJ compiler:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
        <Xlint>ignore</Xlint>
        <encoding>UTF-8 </encoding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <!-- use this goal to weave all your main classes -->
                <goal>compile</goal>
                <!-- use this goal to weave all your test classes -->
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

For more details on the option reference of the AspectJ compiler, we may want to check out the following link.

Let’s add some test cases for our Account class:

public class AccountUnitTest {
    private Account account;

    @Before
    public void before() {
        account = new Account();
    }

    @Test
    public void givenBalance20AndMinBalance10_whenWithdraw5_thenSuccess() {
        assertTrue(account.withdraw(5));
    }

    @Test
    public void givenBalance20AndMinBalance10_whenWithdraw100_thenFail() {
        assertFalse(account.withdraw(100));
    }
}

When we run the test cases, the below text that’s shown in the console means that we successfully weaved the source code:

[INFO] Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20)
advised by around advice from 'com.baeldung.aspectj.AccountAspect'
(AccountAspect.class:18(from AccountAspect.aj))

[INFO] Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type 
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20) 
advised by before advice from 'com.baeldung.aspectj.AccountAspect' 
(AccountAspect.class:13(from AccountAspect.aj))

[INFO] Join point 'method-call
(boolean com.baeldung.aspectj.Account.withdraw(int))' in Type 
'com.baeldung.aspectj.test.AccountTest' (AccountTest.java:20) 
advised by after advice from 'com.baeldung.aspectj.AccountAspect'
(AccountAspect.class:26(from AccountAspect.aj))

2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
-  Balance before withdrawal: 20
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
-  Withdraw ammout: 5
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
- Balance after withdrawal : 15
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
-  Balance before withdrawal: 20
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
-  Withdraw ammout: 100
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
- Withdrawal Rejected!
2016-11-15 22:53:51 [main] INFO  com.baeldung.aspectj.AccountAspect 
- Balance after withdrawal : 20

6. Post-Compile Weaving

Post-compile weaving (also sometimes called binary weaving) is used to weave existing class files and JAR files. As with compile-time weaving, the aspects used for weaving may be in source or binary form, and may themselves be woven by aspects.

In order to do this with Mojo’s AspectJ Maven Plugin, we’ll need to setup all the JAR files we would like to weave in the plugin configuration:

<configuration>
    <weaveDependencies>
        <weaveDependency>  
            <groupId>org.agroup</groupId>
            <artifactId>to-weave</artifactId>
        </weaveDependency>
        <weaveDependency>
            <groupId>org.anothergroup</groupId>
            <artifactId>gen</artifactId>
        </weaveDependency>
    </weaveDependencies>
</configuration>

The JAR files containing the classes to weave must be listed as <dependencies/> in the Maven project, and listed as <weaveDependencies/> in the <configuration> of the AspectJ Maven Plugin.

7. Load-Time Weaving

Load-time weaving is simply binary weaving deferred until the point that a class loader loads a class file and defines the class to the JVM.

To support this, one or more “weaving class loaders” are required. These are either provided explicitly by the run-time environment, or enabled using a “weaving agent.”

7.1. Enabling Load-Time Weaving

AspectJ load-time weaving can be enabled using an AspectJ agent that can get involved in the class loading process, and weave any types before they’re defined in the VM. We specify the javaagent option to the JVM -javaagent:pathto/aspectjweaver.jar or using a Maven plugin to configure the javaagent :

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <argLine>
            -javaagent:"${settings.localRepository}"/org/aspectj/
            aspectjweaver/${aspectj.version}/
            aspectjweaver-${aspectj.version}.jar
        </argLine>
        <useSystemClassLoader>true</useSystemClassLoader>
        <forkMode>always</forkMode>
    </configuration>
</plugin>

7.2. Configuration Weaver

AspectJ’s load-time weaving agent is configured by the use of aop.xml files. It looks for one or more aop.xml files on the classpath in the META-INF directory, and aggregates the contents to determine the weaver configuration.

An aop.xml file contains two key sections:

  • Aspects: defines one or more aspects to the weaver, and controls which aspects are to be used in the weaving process. The aspects element may optionally contain one or more include and exclude elements (by default, all defined aspects are used for weaving).
  • Weaver: defines weaver options to the weaver, and specifies the set of types that should be woven. If no include elements are specified, then all types visible to the weaver will be woven.

Let’s configure an aspect to the weaver:

<aspectj>
    <aspects>
        <aspect name="com.baeldung.aspectj.AccountAspect"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.baeldung.aspectj.*"/>
        </weaver>
    </aspects>
</aspectj>

As we can see, we configured an aspect that points to the AccountAspect, and only the source code in the com.baeldung.aspectj package will be woven by AspectJ.

8. Annotating Aspects

In addition to the familiar AspectJ code-based style of aspect declaration, AspectJ 5 also supports an annotation-based style of aspect declaration. We informally call the set of annotations that support this development style the “*@AspectJ*” annotations.

Let’s create an annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Secured {
    public boolean isLocked() default false; 
}

We’ll use the @Secured annotation to enable or disable a method:

public class SecuredMethod {

    @Secured(isLocked = true)
    public void lockedMethod() {
    }

    @Secured(isLocked = false)
    public void unlockedMethod() {
    }
}

Next, we’ll add an aspect using AspectJ annotation-style, and check the permission based on the attribute of the @Secured annotation:

@Aspect
public class SecuredMethodAspect {
    @Pointcut("@annotation(secured)")
    public void callAt(Secured secured) {
    }

    @Around("callAt(secured)")
    public Object around(ProceedingJoinPoint pjp, 
      Secured secured) throws Throwable {
        return secured.isLocked() ? null : pjp.proceed();
    }
}

For more detail on AspectJ annotation-style, we can check out the following link.

Next, we’ll weave our class and aspect using load-time weaver, and put aop.xml under META-INF folder:

<aspectj>
    <aspects>
        <aspect name="com.baeldung.aspectj.SecuredMethodAspect"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.baeldung.aspectj.*"/>
        </weaver>
    </aspects>
</aspectj>

Finally, we’ll add a unit test and check the result:

@Test
public void testMethod() throws Exception {
    SecuredMethod service = new SecuredMethod();
    service.unlockedMethod();
    service.lockedMethod();
}

When we run the test cases, we may check the console output to verify that we successfully weaved our aspect and class in the source code:

[INFO] Join point 'method-call
(void com.baeldung.aspectj.SecuredMethod.unlockedMethod())'
in Type 'com.baeldung.aspectj.test.SecuredMethodTest'
(SecuredMethodTest.java:11)
advised by around advice from 'com.baeldung.aspectj.SecuredMethodAspect'
(SecuredMethodAspect.class(from SecuredMethodAspect.java))

2016-11-15 22:53:51 [main] INFO com.baeldung.aspectj.SecuredMethod 
- unlockedMethod
2016-11-15 22:53:51 [main] INFO c.b.aspectj.SecuredMethodAspect - 
public void com.baeldung.aspectj.SecuredMethod.lockedMethod() is locked

9. Conclusion

In this article, we covered introductory concepts about AspectJ. For more details, take a look at the AspectJ home page.

As always, the source code for this article is available over on GitHub.


» 下一篇: Java 8 Optional介绍