JAVA Tutorials

Spring AOP

Aspect-Oriented Programming is a paradigm that allows adding additional behavior to existing code without its modification. That additional code usually is needed for multiple types and objects. It can be a logic for transaction management, logging, or security. It is calling cross-cutting concerns. Spring framework has its own library to make aspects possible (Spring AOP). We can call aspect-oriented programming as an evolution of the decorator design pattern. But AOP provides you the possibility to enhance classes with much greater flexibility than the decorator. You can even do your manipulations with code from third-party sources.

You don’t need a special compilation process for Spring AOP because it is implemented in pure Java. The approach of Spring AOP differs from that of most other AOP frameworks. It provides close integration between AOP implementation and Spring IoC, that help solve common problems in enterprise applications.

Spring AOP Aspects are creating using normal bean definition syntax.

But there are some things you cannot do easily with Spring AOP, AspectJ is the best choice in such cases. For example,  advise domain objects. In general, Spring AOP provides a great solution to a lot of enterprise Java application issues. Spring AOP and AspectJ are complementary.

Terminology

One of the most important terms of AOP is advice. An action by an aspect at a particular join-point.

Join-point is a point of an application, like a method or an exception handling. In Spring AOP a joinpoint is a method execution.

Pointcut is an expression that corresponds to join-points. Advice runs when the join-point matched by the pointcut. Spring by default is using the AspectJ pointcut expression language

Types of AOP Advices

Spring AOP has five types of advice.

Before advice: this type of advice executes before a join point. It cannot prevent execution flow proceeding to the join point (if it does not throw an exception).

After returning advice: this advice executes after a normal completion of a join point (if no exception occurred).

After throwing advice: this advice is executed if a method throws an exception.

After advice: this advice is executed normal or exceptional return from a join point.

Around advice: this kind of advice is the most powerful. It surrounds a join point such as a method invocation. This advice can invoke custom behavior before and after the method. It also decides whether to proceed to the join point or to return its own value or throwing an exception.

All types of advices are implemented as interfaces in AOP and they have a hierarchy. The parent interface is Advice. Interfaces BeforeAdvice, AfterAdvice, and Interceptor extending Advice interface. MethodBeforeAdvice extends the BeforeAdvice interface, AfterReturningAdvice extends the AfterAdvice, ThrowsAdvice interface extends the AfterAdvice, MethodInterceptor interface extends the Interceptor interface and used in around advice.

Let’s create an example with BeforeAdvisor by implementing MethodBeforeAdvice.

package edu.example.aop;

import java.lang.reflect.Method;  
import org.springframework.aop.MethodBeforeAdvice;  

public class BeforeAdviceExecutor implements MethodBeforeAdvice {

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("We are going to run this code before an actual logic");  
}
}

The class where the advice is used will be simple:
package edu.example.aop;

public class AdviceExample {

public void method() {
System.out.println("Here is our main logic");  
}
}

In the XML configuration file, we will create 3 beans:  for AdviceExample class, for Advisor class, and for ProxyFactoryBean class. The class ProxyFactoryBean is provided by Spring Framework. It contains a target and an interceptor name. The instance of the AdviceExample class will be our target object and the instance of advisor class will be an interceptor. We can have several interceptors. Here is our configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<bean id="obj" class="edu.example.aop.AdviceExample"></bean>
<bean id="adviceExecutor"
class="edu.example.aop.BeforeAdviceExecutor"></bean>

<bean id="proxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="obj"></property>
<property name="interceptorNames">
<list>
<value>adviceExecutor</value>
</list>
</property>
</bean>
</beans>

The output will be:

We are going to run this code before an actual logic

Here is our main logic

Let’s also create a ThrowsAdvice Example. Here is class with logic:

package edu.example.aop;
public class Validator {  
    public void validate(int age)throws Exception{  
        if(age<18){  
            throw new ArithmeticException("Not Valid Age");  
        }  
        else{  
            System.out.println("Age is good");  
        }  
    }  
}

As we already said, we need to implement ThrowsAdvice for this type of interceptor:

package edu.example.aop;

import org.springframework.aop.ThrowsAdvice;  

public class ThrowAdviceExecutor implements ThrowsAdvice {  

    public void afterThrowing(Exception ex){  
        System.out.println("Exception occured, let's do something");  
    }  
}

The configuration file will look very similar to the previous:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<bean id="obj" class="edu.example.aop.Validator"></bean>
<bean id="adviceExecutor"
class="edu.example.aop.ThrowAdviceExecutor"></bean>

<bean id="proxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="obj"></property>
<property name="interceptorNames">
<list>
<value>adviceExecutor</value>
</list>
</property>
</bean>
</beans>

Here are Spring AOP dependencies for your pom.xml in the case of maven project:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE<version>
</parent>
  
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

Now it is recommended to use Spring AspectJ AOP implementation with the Spring Framework. The old-style Spring 1.2 DTD-based AOP implementation is not so comfortable, because Spring AspectJ AOP provides for developers more control. It is easier to use.

We can use  Spring AOP AspectJ implementation by one of the two ways. The first way is by annotation. The second way is by XML configuration (schema based).

Implementation of the AspectJ AOP provides this list of annotations:

@Aspect is for the declaratio of the class as an aspect.

@Pointcut is for the creation of the pointcut expression.

For advices we need to use the annotations given below:

@Before annotation is for the before advice. It will be executed before calling the actual method.

@After declares the after advice. It will be executed after calling the actual method and before returning the result.

@AfterReturning annotation is for after returning advice. It will be executed after calling the actual method and before returning the result. It is important that you can get the result value in this type of the advice.

@Around annotation is for the around advice. It will be executed before and after calling the actual method.

@AfterThrowing annotation is used to declare the throws advice. It will be executed if the actual method throws an exception.

Let’s look at an example with the annotations. We have a Spring boot application:

package edu.example.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopApplicationRunner {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("aop-config.xml");
        User user = (User) context.getBean("user");

        System.out.println(user);
        user.throwSomeMysticException();
    }
}

Configuration file aop-config.xml is here:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

    <aop:aspectj-autoproxy/>

    <!-- Definition for user bean -->
    <bean id="user" class="edu.example.aop.User">
        <property name="name"  value="Mary" />
        <property name="surname"  value="Luck" />
        <property name="age"  value="35" />
    </bean>

    <!-- Definition for logging aspect -->
    <bean id="logging" class="edu.example.aop.Logging"/>

</beans>

There is one class User that we initialize in this configuration file too. Let’s look at this class:

package edu.example.aop;

public class User {
    private String name;
    private String surname;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void throwSomeMysticException(){
        System.out.println("We have some mystic exception here:");
        throw new ClassCastException();
    }

    @Override
    public String toString() {
        return "User:\n" +
                "Name: " + name + '\n' +
                "Surname: " + surname + '\n' +
                "Age: " + age + "\n";
    }
}

We are going to create aspects for logging:

package edu.example.aop;

import org.aspectj.lang.annotation.*;

@Aspect
public class Logging {

    @Pointcut("execution(* edu.example.aop.*.*(..))")
    public void selectAllMethodsAvaliable() {

    }

    @Before("selectAllMethodsAvaliable()")
    public void beforeAdvice() {
        System.out.println("Now we are going to initiate user's profile.");
    }

    @After("selectAllMethodsAvaliable()")
    public void afterAdvice() {
        System.out.println("User's profile has been initiated.");
    }

    @AfterReturning(pointcut = "selectAllMethodsAvaliable()", returning = "someValue")
    public void afterReturningAdvice(Object someValue) {
        System.out.println("Value: " + someValue.toString());
    }

    @AfterThrowing(pointcut = "selectAllMethodsAvaliable()", throwing = "e")
    public void inCaseOfExceptionThrowAdvice(ClassCastException e) {
        System.out.println("We have an exception here: " + e.toString());
    }

}

The output will be:

Now we are going to initiate user’s profile.
User’s profile has been initiated.
Value: User:
Name: Mary
Surname: Luck
Age: 35

User:
Name: Mary
Surname: Luck
Age: 35

Now we are going to initiate user’s profile.
We have some mystic exception here:
User’s profile has been initiated.
We have an exception here: java.lang.ClassCastException

Let’s summarize why AOP is useful. First of all, it is quick code reuse. We can reuse the code of aspects across many classes without touching much of the existing code. We can enhance numerous classes without repeating code, just by using simple annotation.

Also, it helps to deal with third-party code. For example, we need to inject shared behavior into a function that is using in core components. Unfortunately, we can’t alter the third-party code’s behavior. Even in the case of open-source code we need to understand and adapt it to our needs. The AOP allows us to decorate the needed behavior without touching the third-party code.

If you apply AOP your code corresponds to the single-responsibility principle. You can use aspects for authentication, logging, tracing, error handling, and the like.

Facebook Comments
Tags

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Articles

Back to top button
Close
Close