Last updated on September 4th, 2023
In this entry of my series about the Spring fundamental concepts, I will explain one essential concept underlying Spring framework. Our focus shall be on comprehending the fundamental of AOP, its underlying principles, and its application within the Spring framework.
AOP Fundamentals
Consider an application where multiple components required logging, security checks, and transaction management. Implementing these features within each component would lead to tightly coupled code, which is challenging to maintain and update.
For example, you have a method that logs the start and end of every method for debugging purposes. You will probably refactor the logging code into a special class. However, you still have to call methods on that class twice per method in your application to perform the logging that made the code duplication, violating the DRY mixing of concerns.
That’s why AOP was born to tackle this issue by allowing separate cross-cutting concerns without affecting the core business logic. It achieves this by introducing a new programming paradigm known as the separation of concerns.
Separation of concerns (SoC)
In computer science, separation of concerns is a design principle for separating a computer program into distinct sections. Each section addresses a separate concern, a set of information that affects the code of a computer program. Separation of concerns
A concern is a term that refers to a part of the system divided on the basis of functionality. Concerns can be categorized into 2 types:
- Core concerns: These are the business logic of the software components.
- Cross-cutting concerns: These logic affects multiple components and cannot be cleanly encapsulated in any single module using traditional programming techniques. Example of cross-cutting concern:
- Transaction management: Putting try catch with commit and rollback calls in all your service methods would be cumbersome. Annotating the methods @Transactional to encapsulate them with the desired transactional behaviour can help.
- Authorization: Annotating a service method such as @Preauthorized letting decide whether to allow the method call, is preferable to handling that in method code.
By implementing AOP, cross-cutting concerns can be extracted into separate modules called aspects, allowing developers to focus on the core concerns within the primary business logic. This approach helps to reduce code duplication, increasing the modularity of programs.
OOP or AOP
Object-Oriented Programming (OOP) enables the encapsulation of related data and methods within a single unit, promoting code reusability and modularization through inheritance, composition, and polymorphism. It excels at managing the core concerns of an application by establishing a clear structure and hierarchy between different components. However, it falls short when dealing with cross-cutting concerns that span across multiple components and modules. Implementing cross-cutting concerns within OOP can lead to code duplication, tightly-coupled code, and reduced maintainability.
Aspect-Oriented Programming (AOP) complements OOP by addressing cross-cutting concerns that cannot be managed effectively through OOP alone. AOP introduces the concept of “aspects” to separate cross-cutting concerns from the core business logic. Basically, aspects are modular units that encapsulate the code related to a specific cross-cutting concern, which can be woven into the main codebase at specified points, called join points. As a result, AOP promotes better separation of concerns, reduces code duplication, and enhances code maintainability by isolating cross-cutting functionalities from core business logic.
AOP Terminology
Before jumping into Spring AOP, it’s essential to explore the AOP core concept, including aspects, advice, Pointcut, and JoinPoint. We will go into details in the Spring AOP parts as below:
- Aspect
An aspect is a module that encapsulates the cross-cutting concern and brings together pointcut and advice.
- Join Points
Join points represent an execution flow where an aspect’s behaviour needs to be applied. In Spring AOP, join points are typically method executions, and the aspect’s behaviour is injected before, after, or around these method executions.
- Pointcut
A pointcut is an expression that defines which join points should be intercepted. (Imagine that they are conditions that define where the join points need to be executed). In Spring AOP, pointcuts can be defined using expressions or annotations to specify the methods that will be affected by the aspect.
- Advice
Advice is a piece of code that contains the logic to be applied at specific join points. This is where the cross-cutting concern is implemented. There are five types of advice: Before, After Returning, After Throwing, After, and Around in Spring AOP. Each of them is executed at a different stage in the method execution flow, enabling different behaviours to be applied depending on the scenario.
- Weaving
Weaving is the process of linking the aspect with the target objects at the specified join points. It ensures that the advice code is executed during the application’s runtime.
Compile-time weaving: Aspects are woven into the target classes during compilation. This approach usually requires a special AOP-aware compiler, such as the AspectJ compiler.
Runtime weaving: Aspects are woven into the target classes during runtime using runtime-generated proxies. This is the default weaving method in Spring AOP.
Proxy Pattern
What it is
One of the most common ways to implement AOP is through the Proxy design pattern. There are other patterns that can be used to achieve AOP such as Singleton, Observer, Command, and Chain of Responsibility. However, in this session, we will explain the Proxy patterns, since it is applied in Spring AOP.
In the context of AOP, proxies are used to inject the aspects’ behaviour into the target objects at specified join points. There are two types of proxies:
- Static Proxy: it can be achieved by modifying the actual bytecode of the application
- Dynamic Proxy: it is created during runtime through reflection such as Spring AOP.
Basically, a ProxyObject wraps the RealObject and optionally provides additional features. For a better understanding of how to implement it, you can refer to this link.
Proxy in action
Let’s see a proxy in action: Source code
I have a class SecureBean
that serves as a target object where actions need to be added.
public class SecureBean {
// method to secure
public void writeSecureMessage() {
System.out.println("I'm the secured method");
}
}
When debugging the SecureBean the output is shown as below.
In the above example, we use org.springframework.aop.framework.ProxyFactory
to programmatically create a proxy (ProxyFactory
class to take advantage of declarative proxy creation). However, it’s uncommon to use this one directly as the proxies are generated under the hood.
When a method of proxy is called, the call is delegated to the implementation class. The proxy can either directly delegate to the implementing class or perform additional actions before, after, and around the delegation. This is precisely how AOP works in Spring. Our implementation object is proxied, and the AOP code is executed by the proxy before, after, or around the invocation of the implementing object.
Let’s see how we can add an action before
object SecureBean
: Source code
public class SecurityAdvice implements MethodBeforeAdvice {
private SecurityManager securityManager;
public SecurityAdvice() {
this.securityManager = new SecurityManager();
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
UserInfo user = securityManager.getLoggedOnUser();
if (user == null) {
System.out.println("No user authenticated");
throw new SecurityException("Must login before invoking the method " + method.getName());
}
if (user.getUserName().equals("test")) {
System.out.println("Logged in user is test - OK");
} else {
throw new SecurityException("User " + user.getUserName() + " is not allowed to access the method " + method.getName());
}
}
}
Then, weaving SecurityAdvice
to our proxy and test:
public class SecurityExample {
public static void main(String[] args) {
SecurityManager securityManager = new SecurityManager();
SecureBean bean = getSecureBean();
// valid user
securityManager.login("test", "any");
bean.writeSecureMessage();
securityManager.logout();
private static SecureBean getSecureBean () {
// Weaving process
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new SecureBean());
proxyFactory.addAdvice(new SecurityAdvice());
return (SecureBean) proxyFactory.getProxy();
}
}
}
The output is as below:
Logged in user is Chris - OK Hello world. I'm the secured method
What happens when call proxy can be summarized as below:
- The framework makes use of ProxyFactory to create a proxy class for
SecureBean
and weavingSecurityAdvice
- A call to the
writeSecureMessage
method hits the proxy class - The proxy class execute logic
before
hitting the actual classSecureBean
to check access control by invoking logic fromSecurityAdvice
. - The proxy class returns the result to the original calling class.
Spring AOP
Spring AOP Architecture
As the above example, we understand that Spring AOP revolves around the usage of proxies. The AOP proxy is a crucial component of the Spring AOP architecture, responsible for intercepting method calls and applying the advice associated with an aspect. There are two types of AOP proxies in Spring AOP:
JDK Dynamic Proxies are created based on Java’s reflection package java.lang.reflect.Proxy
. When a target object implements one or more interfaces, Spring AOP employs JDK Dynamic Proxies to generate a proxy instance. This instance shares the same interfaces as the target object, allowing clients to seamlessly interact with the proxy.
However, JDK Dynamic Proxies have limitations, as they can only proxy method calls for the interfaces implemented by the target object.
- CGLIB Proxy
CGLIB (Code Generation Library) proxies use a third-party library to generate proxies. It’s a byte code generation library that exposes a high-level API to generate JDK byte code. CGLIB proxies generate a subclass of the target object and can proxy both interface and non-interface method calls, enhancing the flexibility of Spring AOP for different use cases. It can be used by AOP, testing Mockito, data access Hibernate,…
Spring boot proxy configuration:
Spring AOP In Action
Let’s implement Spring AOP AspectJ style to secure access to specific methods using custom annotations. Source code
In this example, defining a @CustomPreAuthorize
Annotation that will be used to mark and secure methods in our application that require specific user authorities.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CustomPreAuthorize {}
Next, create the Aspect AuthorityAspect that implements our method-level security logic using Spring AOP:
@Aspect
public class AuthorityAspect {
@Before("@annotation(com.example.demo.methodSecureAspectJStyle.annotation.CustomPreAuthorize)")
public void handleCustomAuth(final JoinPoint joinPoint) {
// Security logic implementation
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new RuntimeException("Un-authenticate");
}
String methodName = joinPoint.getSignature().getName();
final Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
if (userAuthorities.stream().noneMatch(
grantedAuthority -> grantedAuthority.getAuthority().equals(methodName))) {
throw new AccessDeniedException("Access denied");
}
}
}
We start by marking the class with the @Aspect
annotation, which informs Spring that this class contains aspects that need to be woven into the application. In the handleCustomAuth
method, we define a “Before” advice using the @Before
annotation. This means that the advice code will be executed before the join point, which in our case is any method annotated with the CustomPreAuthorize
annotation.
Let’s test the implementation below cases:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
SecureAspectConfig.class, AuthorityAspect.class, DummyServiceTest.class
})
class AuthorityAspectTest {
@Autowired
DummyServiceTest mockServiceTest;
@Mock
private SecurityContext securityContext;
@Test
void givenMethodWithNonPreAuthorizeAnnotation_whenIntercept_thenNothingHappen() {
Assertions.assertDoesNotThrow(() -> mockServiceTest.methodWithoutAnnotation());
}
@Test
void givenMethodWithPreAuthorizeAnnotation_whenIntercept_thenThrowException() {
setupSecurityContext(List.of(new SimpleGrantedAuthority("any")), securityContext);
Assertions.assertThrows(RuntimeException.class,
() -> mockServiceTest.methodTest());
}
@Test
void givenValidGrantedAuthority_whenIntercept_thenByPass() {
setupSecurityContext(List.of(new SimpleGrantedAuthority("methodTest")), securityContext);
Assertions.assertDoesNotThrow(() -> mockServiceTest.methodTest());
}
private static void setupSecurityContext(
Collection<GrantedAuthority> authorities, SecurityContext securityContext
) {
Jwt jwt = Jwt.withTokenValue("token").header("alg", "none")
.claim("ISS", "")
.build();
Mockito.when(securityContext.getAuthentication()).thenReturn(
new JwtAuthenticationToken(jwt, authorities)
);
SecurityContextHolder.setContext(securityContext);
}
}
When a method marked with the CustomPreAuthorize annotation is executed, Spring AOP will intercept the method execution, call the handleCustomAuth method in the AuthorityAspect, and apply the security logic.
Conclusion
This article has provided an in-depth exploration of the AOP concept on how to effectively manage cross-cutting concerns in the applications and its implementation using Spring AOP. To further enhance your understanding of Spring AOP, you can practice more using various resources and delve into real-world scenarios where AOP can be employed to improve software design. You can refer to more Dependency Injection which is the core of Spring Frameworks via this post.
The source code for these examples can be found on GitHub repo