In this article, we are gonna see the basic concepts of Aspect Oriented Programming and develop a Spring Boot application in order to demonstrate some real time examples. If you already have knowledge about AOP and Spring AOP applications and just looking for the real time usages of Spring AOP, then you can skip to the next article.
What is AOP ?
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect.
Aspects enable the modularization of concerns (such as transaction management, logging, performance monitoring) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

In simple terms, it’s just an interceptor to intercept some processes, for example, when a method is executing, Spring AOP can hijack the executing method, and add extra functionality before or after the method execution.
What is Weaving ?
Weaving is the process of linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
Spring AOP does dynamic weaving of aspects by creating proxy of the target objects.
It uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created. You can learn more about Spring AOP in the official documentation.
What is Aspect, Advice, Join point and Pointcut ?
- Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the
@Aspect
annotation (the @AspectJ style). - Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
- Advice: Action taken by an aspect at a particular join point. Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
- Pointcut: A predicate or expression that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.
- Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an
IsModified
interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.) - Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.
- AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.
Note: The Spring AOP framework supports only limited types of AspectJ pointcuts and allows aspects to apply to beans declared in the IoC container. If you want to use additional pointcut types or apply your aspects “to objects created outside the Spring IoC container“, you have to use the AspectJ framework in your Spring application and use it’s weaving feature.
Types of AOP advices
- Before advice: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
- After returning advice: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception.
- After throwing advice: Advice to be executed if a method exits by throwing an exception.
- After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
- Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
Bootstrap your application
You can create your spring boot application with the spring boot starter aop, security, jpa & devtools dependencies and download it from here
Project Dependencies
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | <? xml version = "1.0" encoding = "UTF-8" ?> < project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.2.5.RELEASE</ version > < relativePath /> <!-- lookup parent from repository --> </ parent > < groupId >com.javachinna</ groupId > < artifactId >spring-boot-aop-demo</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >spring-boot-aop-demo</ name > < description >Demo project for Spring Boot AOP</ description > < properties > < java.version >11</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-aop</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-security</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-data-jpa</ artifactId > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < scope >runtime</ scope > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-devtools</ artifactId > < scope >runtime</ scope > < optional >true</ optional > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >org.junit.vintage</ groupId > < artifactId >junit-vintage-engine</ artifactId > </ exclusion > </ exclusions > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-devtools</ artifactId > <!-- <scope>runtime</scope> --> </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
Create model classes and entities
UserEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | package com.javachinna.model; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @Entity public class UserEntity { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) @Column (name = "USER_ID" ) private Long id; private String username; private String password; // bi-directional many-to-many association to Role @ManyToMany @JoinTable (name = "user_role" , joinColumns = { @JoinColumn (name = "USER_ID" ) }, inverseJoinColumns = { @JoinColumn (name = "ROLE_ID" ) }) private Set<Role> roles; public UserEntity() { // TODO Auto-generated constructor stub } public UserEntity(String username, String password) { this .username = username; this .password = password; } public Long getId() { return id; } public void setId(Long id) { this .id = id; } public String getUsername() { return username; } public void setUsername(String username) { this .username = username; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public Set<Role> getRoles() { return roles; } public void setRoles(Set<Role> roles) { this .roles = roles; } } |
Role.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | package com.javachinna.model; import java.io.Serializable; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.NamedQuery; /** * The persistent class for the role database table. */ @Entity @NamedQuery (name = "Role.findAll" , query = "SELECT r FROM Role r" ) public class Role implements Serializable { private static final long serialVersionUID = 1L; public static final String USER = "USER" ; public static final String ADMIN = "ADMIN" ; public static final String DBA = "DBA" ; public static final String ROLE_USER = "ROLE_USER" ; public static final String ROLE_ADMIN = "ROLE_ADMIN" ; public static final String ROLE_DBA = "ROLE_DBA" ; @Id @Column (name = "ROLE_ID" ) private int roleId; private String name; // bi-directional many-to-many association to User @ManyToMany (mappedBy = "roles" ) private Set<UserEntity> users; public Role() { } public int getRoleId() { return this .roleId; } public void setRoleId( int roleId) { this .roleId = roleId; } public String getName() { return this .name; } public void setName(String name) { this .name = name; } public Set<UserEntity> getUsers() { return this .users; } public void setUsers(Set<UserEntity> users) { this .users = users; } @Override public int hashCode() { final int prime = 31 ; int result = 1 ; result = prime * result + ((name == null ) ? 0 : name.hashCode()); return result; } @Override public boolean equals( final Object obj) { if ( this == obj) { return true ; } if (obj == null ) { return false ; } if (getClass() != obj.getClass()) { return false ; } final Role role = (Role) obj; if (!role.equals(role.name)) { return false ; } return true ; } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append( "Role [name=" ).append(name).append( "]" ).append( "[id=" ).append(roleId).append( "]" ); return builder.toString(); } } |
AbstractEntity.java
@MappedSuperclass
annotation is used to allow an entity to inherit properties from a base class.
This super class defines some common properties like id, createdBy, etc., which will be inherited by the child classes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.javachinna.model; import java.io.Serializable; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; import org.springframework.data.annotation.CreatedBy; @MappedSuperclass public abstract class AbstractEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Long id; @CreatedBy // bi-directional many-to-one association to User @ManyToOne (fetch = FetchType.EAGER) @JoinColumn (name = "created_by" ) protected UserEntity createdBy; public UserEntity getCreatedBy() { return createdBy; } public void setCreatedBy(UserEntity createdBy) { this .createdBy = createdBy; } public Long getId() { return id; } public void setId(Long id) { this .id = id; } } |
ExampleEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.javachinna.model; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table (name = "example" ) public class ExampleEntity extends AbstractEntity { private static final long serialVersionUID = 1L; private String text; public String getText() { return text; } public void setText(String text) { this .text = text; } } |
LocalUser.java
LocalUser
extends org.springframework.security.core.userdetails.User
which models core user information
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | package com.javachinna.model; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; public class LocalUser extends User { private static final long serialVersionUID = -2845160792248762779L; private UserEntity user; public LocalUser( final String userID, final String password, final boolean enabled, final boolean accountNonExpired, final boolean credentialsNonExpired, final boolean accountNonLocked, final Collection<? extends GrantedAuthority> authorities, final UserEntity user) { super (userID, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this .user = user; } public static LocalUser create(UserEntity user) { LocalUser localUser = new LocalUser(user.getUsername(), user.getPassword(), true , true , true , true , buildSimpleGrantedAuthorities(user.getRoles()), user); return localUser; } public UserEntity getUser() { return user; } public static List<SimpleGrantedAuthority> buildSimpleGrantedAuthorities( final Set<Role> roles) { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles) { authorities.add( new SimpleGrantedAuthority(role.getName())); } return authorities; } } |
Create repositories
UserRepository.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.javachinna.repo; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.javachinna.model.UserEntity; @Repository public interface UserRepository extends JpaRepository<UserEntity, Long> { @Transactional @Modifying @Query ( "delete from UserEntity where id in :users" ) public void deleteUsers( @Param ( "users" ) List<Long> users); } |
RoleRepository.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.javachinna.repo; import org.springframework.data.jpa.repository.JpaRepository; import com.javachinna.model.Role; public interface RoleRepository extends JpaRepository<Role, Long> { Role findByName(String name); @Override void delete(Role role); } |
ExampleRepository.java
1 2 3 4 5 6 7 8 9 10 11 | package com.javachinna.repo; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.javachinna.model.ExampleEntity; @Repository public interface ExampleRepository extends JpaRepository<ExampleEntity, Long> { } |
Create service layer
UserService.java
1 2 3 4 5 6 7 8 9 10 11 12 | package com.javachinna.service; import java.util.List; import com.javachinna.model.UserEntity; public interface UserService { public UserEntity save(UserEntity user); void deleteUsers(List<Long> userIds); } |
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.javachinna.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.javachinna.model.UserEntity; import com.javachinna.repo.UserRepository; import com.javachinna.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public UserEntity save(UserEntity user) { return userRepository.save(user); } @Override public void deleteUsers(List<Long> userIds) { userRepository.deleteUsers(userIds); } } |
Define Application Properties
application.properties
1 2 3 4 5 6 7 8 9 10 11 | # Database configuration props spring.datasource.url=jdbc:mysql://localhost:3306/aop?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password=secret # hibernate props spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect logging.level.com.javachinna=trace logging.level.org.springframework.aop.interceptor=trace |
Create Spring boot application class
SpringBootAopApplication.java
@SpringBootApplication
Indicates a configuration
class that declares one or more @Bean
methods and also triggers auto-configuration
and component scanning
. This is a convenience annotation that is equivalent to declaring @Configuration
, @EnableAutoConfiguration
and @ComponentScan
@EnableTransactionManagement
annotation which enables Spring’s annotation-driven transaction management capability, similar to the support found in Spring’s <tx:*> XML namespace. To be used on @Configuration
classes only.
@EnableAspectJAutoProxy
annotation enables support for handling components marked with AspectJ’s @Aspect
annotation, similar to functionality found in Spring’s <aop:aspectj-autoproxy>
XML element. To be used any one of the @Configuration
classes as follows:
CommandLineRunner
is an interface. It is used to execute the code after the Spring Boot application started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | package com.javachinna; import java.util.HashSet; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.javachinna.model.ExampleEntity; import com.javachinna.model.LocalUser; import com.javachinna.model.Role; import com.javachinna.model.UserEntity; import com.javachinna.repo.ExampleRepository; import com.javachinna.repo.RoleRepository; import com.javachinna.service.UserService; @SpringBootApplication @EnableTransactionManagement @EnableAspectJAutoProxy public class SpringBootAopApplication implements CommandLineRunner { private final Logger logger = LogManager.getLogger(getClass()); @Autowired private UserService userService; @Autowired private RoleRepository roleRepository; @Autowired private ExampleRepository exampleRepository; public static void main(String[] args) { SpringApplication.run(SpringBootAopApplication. class , args); } @Override public void run(String... args) throws Exception { // Create new user role and persist it Role role = new Role(); role.setRoleId( 1 ); role.setName(Role.ROLE_USER); var userRole = roleRepository.save(role); final HashSet<Role> roles = new HashSet<Role>(); roles.add(userRole); // Publishing events + logging + Performance Monitoring example // Create and persist user1 UserEntity user1 = new UserEntity( "JavaChinna" , "secret" ); user1.setRoles(roles); // Time taken for executing by the service method will be logged // Repository method entry and exit will be logged // Entity created event will be triggered userService.save(user1); // Set user1 as the logged in user in the Spring's Security Context authenticateUser(LocalUser.create(user1)); // Create and persist user2 UserEntity user2 = new UserEntity( "Chinna" , "secret" ); user2.setRoles(roles); userService.save(user2); // User access restriction example ExampleEntity example = new ExampleEntity(); example.setText( "some text" ); // Set user2 as the owner of this entity. So that currently logged in user // (user1) cannot modify this entity example.setCreatedBy(user2); exampleRepository.save(example); // Try to modify this entity example.setText( "some other text" ); try { exampleRepository.save(example); // Throws access denied exception } catch (Exception e) { } // Transaction Management userService.deleteUsers(List.of(1L)); } private void authenticateUser(UserDetails userDetails) { logger.debug( "Logging in principal: {}" , userDetails); Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null , userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); logger.info( "User: {} has been logged in." , userDetails); } } |
Conclusion
That’s all folks! We’ve developed a Spring Boot AOP application and in the next article we are gonna implement some real time examples like logging, performance monitoring, events publishing and security with Spring AOP.