Spring AOP (Aspect-Oriented Programming)

Along with DI, AOP is one of the core technologies of the Spring Framework. This article explains the structure of AOP, which inserts processing into a class from the outside, and covers its basic usage.

What Is AOP?

In the Spring Framework, there is an important foundational technology called AOP, alongside DI (Dependency Injection).

AOP stands for Aspect-Oriented Programming. An aspect generally means a cross-cutting concern. In other words, AOP is a programming technique based on the perspective from which you look at a problem.

  • It helps apply common modules easily across multiple pieces of code by separating the core concerns needed to solve a problem from common concerns applied across the whole application.
  • An important concept in AOP is the separation of cross-cutting concerns.
  • It makes OOP more object-oriented in practice.

Object-oriented programs are written around classes. Each class implements the features it needs as methods. This approach is sound as a way of thinking, but it can also become cumbersome because everything must be determined perfectly for each class.

For example, during development, it is common to write System.out.println statements in many places to check runtime behavior and print values. But when you think about it, this is a very tedious method. If there are many classes, you need to add println calls to each method in each class. After the program is complete, all those println statements must also be removed.

This kind of processing that is commonly required across many classes is a cross-cutting concern. If a feature could automatically insert println statements into methods across multiple classes, it would be very convenient. And if those statements could be removed automatically when no longer needed, that would be even better. This is the basic idea of AOP.

If DI is dependency or value injection, AOP can be described as processing injection. AOP makes it possible to insert or remove prepared processing at specific points in a class from the outside.

Prepare pom.xml
First, add the AOP-related library to the project. Open pom.xml and add the following content inside the <dependencies> tag.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.10.RELEASE</version>
</dependency>

This adds the Spring AOP library. Specify org.springframework as the groupId and spring-aop as the artifactId. The version is aligned with Spring Framework 4.3.10. If you use a different Spring Framework version, adjust the version accordingly.

Create a Bean Class That Uses AOP

Now let’s use AOP. AOP inserts specific processing into a class from the outside. To do that, you need to prepare the following.

  • The class targeted by AOP. Prepare a normal bean class.
  • The class that performs the processing inserted by AOP. Prepare the operation to insert here.
  • Configuration information for AOP. Prepare this with a bean configuration file or configuration class.

First, prepare the target class for AOP. This time, create a package named com.devkuma.spring.aop and place the required classes in it. Create a class named SampleAopBean as follows.

package com.devkuma.spring.aop;
 
public class SampleAopBean {
    private String message;
 
    public SampleAopBean() {
        super();
    }
    public SampleAopBean(String message) {
        this.message = message;
    }
     
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    public void printMessage() {
        System.out.println("message:[" + message + "]");
    }
}

This is almost the same as the SampleBean created earlier. It has a message property for storing a message, constructors, and a method named printMessage. A bean used in this way is just a simple POJO class, which is a characteristic of the Spring Framework.

Create the MethodBeforeAdvice Class

Next, prepare the processing that AOP inserts into SampleAopBean. This is also defined as a Java class.

Create a class named SampleMethodAdvice in the com.devkuma.spring.aop package. Then write the following code.

package com.devkuma.spring.aop;
 
import java.lang.reflect.Method;
 
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
 
public class SampleMethodAdvice 
        implements MethodBeforeAdvice, AfterReturningAdvice {
 
    @Override
    public void before(Method method, Object[] args, 
            Object target) throws Throwable {
        System.out.println("*before: " + method.getName() + "[" + target + "]");
    }
 
    @Override
    public void afterReturning(Object returnValue, Method method, 
            Object[] args, Object target) throws Throwable {
        System.out.println("*after: " + method.getName() + "[" + target + "]");
    }
 
}

The SampleMethodAdvice class created here implements two interfaces. These interfaces add methods related to inserting processing. They can be summarized as follows.

MethodBeforeAdvice
This interface inserts processing before a method is executed. It has one method named before, defined as follows.

public void before (Method method, Object [] args, Object target)
    throws Throwable

The target method is passed as method, the arguments passed to it as args, and the target object or instance as target. These arguments let you know which method of which instance is about to be called before this processing is executed.

AfterReturningAdvice
This interface inserts processing after a method finishes execution and returns to the caller. It provides a method named afterReturning, defined as follows.

public void afterReturning (Object returnValue, Method method,
    Object [] args, Object target) throws Throwable

The method return value, method, method arguments, and target instance are passed as arguments. Except for the return value, this is almost the same as before, so it can be handled in a similar way.

Here, the code simply prints each method and target with System.out.println. AOP was described as processing insertion, but it does not insert processing anywhere arbitrarily. Several predefined timings exist for when processing can be inserted.

For now, learning these two interfaces is enough to understand the basics: processing can be inserted before and after a method call.

Create bean.xml

The next step is to prepare the required bean configuration. First, use a bean configuration file.

Open the bean.xml file created in the resources folder and write it as follows. This completes the required library setup.

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd">
     
    <!-- aop bean... -->
    <bean id="sampleAopBean" class="com.devkuma.spring.aop.SampleAopBean">
        <property name="message" value="this is AOP bean!" />
    </bean>
    
    <bean id="sampleMethodAdvice"
        class="com.tuyano.libro.aop.SampleMethodAdvice" />
 
    <bean id="proxyFactoryBean"
            class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="sampleAopBean"/>
        <property name="interceptorNames">
            <list>
                <value>sampleMethodAdvice</value>
            </list>
        </property>
    </bean>
             
</beans>

This time, three beans are registered. They are as follows.

SampleAopBean
This is the bean created earlier. It is prepared with the name id="sampleAopBean".

SampleMethodAdvice
This is the AOP processing class created earlier. It is prepared with the name id="sampleMethodAdvice".

ProxyFactoryBean
This is the key point here. It is a Spring AOP class included in the org.springframework.aop.framework package. Classes included in libraries can also be instantiated automatically through a bean configuration file.

This ProxyFactoryBean adds two properties with the <property> tag. They are as follows.

target: Specifies the bean targeted by AOP. Here, it specifies sampleAopBean, which was prepared with <bean id="sampleAopBean">.

interceptorNames: Specifies the processing bean inserted by AOP. Since multiple values can be specified, a <list> tag is used and the bean name is specified inside it with a <value> tag. Here, it specifies the sampleMethodAdvice created earlier.

Therefore, three items are required: the bean targeted by AOP, the bean that performs AOP processing, and the ProxyFactoryBean that configures their relationship as properties.

Run AOP

Now the preparation is finally complete. Let’s actually use AOP. Create an App class in the com.devkuma.spring.aop package and write the following source code.

package com.devkuma.spring.aop;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
         
        SampleAopBean bean1 = (SampleAopBean) app.getBean("sampleAopBean");
        bean1.printMessage();
 
        System.out.println("--------------------");
 
        SampleAopBean bean2 = (SampleAopBean) app.getBean("proxyFactoryBean");
        bean2.printMessage();
    }
 
}

When this runs, it obtains SampleAopBean from bean.xml and executes printMessage. Looking closely, this is repeated twice.

The first SampleAopBean is obtained with getBean("sampleAopBean"). This is the same approach as before. The second is obtained with getBean("proxyFactoryBean"). The bean returned here may look like a ProxyFactoryBean, but it is cast correctly to SampleAopBean and works as a SampleBean.

This is a useful feature of ProxyFactoryBean. This bean can be obtained as the bean specified by its target property.

So what output does printMessage produce for the obtained SampleAopBean? It looks like this.

message : [this is AOP bean!]
--------------------
* before : printMessage [com.devkuma.spring.aop.SampleAopBean@de3a06f]
message : [this is AOP bean!]
* after : printMessage [com.devkuma.spring.aop.SampleAopBean@de3a06f]

The first SampleAopBean simply prints the output from printMessage. But for the second SampleAopBean, the execution results of before and afterReturning provided by SampleMethodAdvice are inserted before and after printMessage. Other processing has been added automatically around the method execution.

This is the power of AOP. By obtaining the bean as ProxyFactoryBean with getBean, automatic processing can be added in this way. If it is no longer needed, you can return the getBean argument to SampleAopBean.

Create an AOP Configuration Class with Annotations

Now that the basics are clear, let’s rewrite bean.xml as a class. In the Spring Framework, you can do the same thing with a configuration class instead of a bean configuration file.

Create a class named SampleAopConfig in the com.devkuma.aop package. Then write the following source code.

package com.devkuma.spring.aop;
 
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class SampleAopConfig {
    private SampleAopBean sampleAopBean = 
        new SampleAopBean("this is message bean.");
    private SampleMethodAdvice sampleMethodAdvice = 
        new SampleMethodAdvice();
     
    @Bean
    SampleAopBean sampleAopBean() {
        return sampleAopBean;
    }
     
    @Bean
    SampleMethodAdvice sampleMethodAdvice() {
        return sampleMethodAdvice;
    }
     
    @Bean
    ProxyFactoryBean proxyFactoryBean() {
        ProxyFactoryBean bean = new ProxyFactoryBean();
        bean.setTarget(sampleAopBean);
        bean.setInterceptorNames("sampleMethodAdvice");
        return bean;
    }
     
}

After writing this, modify the App class so that it uses the SampleAopConfig class instead of bean.xml. Change the following statement.

Modify App

ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");

    ↓

ApplicationContext app = new AnnotationConfigApplicationContext (SampleAopConfig.class);

When you run App, the output is the same as before. You can see that the same beans described in bean.xml can now be obtained from SampleAopConfig.

Here, the SampleAopConfig class is declared with the @Configuration annotation, and methods returning bean instances are annotated with @Bean so that the beans can be obtained. Pay attention to the sampleMethodAdvice method that creates the SampleMethodAdvice instance. After creating the instance, it sets the required properties as follows.

bean.setTarget(sampleAopBean);
bean.setInterceptorNames ("sampleMethodAdvice");

This means that setTarget and setInterceptorNames correspond to the <property name="target"> and <property name="interceptorNames"> elements previously written in bean.xml.