![]() | Implementing a Simple Aspect From Spring Start Here by Laurentiu Spilca In this article, we discuss implementing a simple aspect to solve our scenario. We’ll create a new project, and we’ll define a service class containing a method which we use to test our implementation and prove the aspect we define works as desired in the end. |
Take 40% off Spring Start Here by entering fccspilca2 into the discount code box at checkout at manning.com.
You find this example in the project named sq-ch6-ex1 in the book’s accompanying source code. Besides the spring-context dependency, for this example, we also need the spring-aspects dependency. Make sure to update your pom.xml file and add the needed dependencies, as presented in the next code snippet.
1 2 3 4 5 6 7 8 9 10 11 | <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> #A <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.8.RELEASE</version> </dependency> |
#A We need this dependency to implement the aspects.
To make our example shorter and allow you to focus on the syntax related to aspects, we’ll only consider one service object named CommentService and a use case it defines named publishComment(Comment comment). This method, defined in the CommentService class, receives a parameter of type Comment. Comment is a model class and is presented in the next code snippet.
1 2 3 4 5 6 7 | public class Comment { private String text; private String author; // Omitted getters and setters } |
NOTE Remember that a model class is a class that models the data processed by the app. In our case, the Comment class describes a comment with its attributes: text and author. A service class implements use cases of an app.
In listing 1, you find the definition of the CommentService class. We annotate the CommentService class with the @Service stereotype annotation to make it a bean in the Spring context. The CommentService class defines the publishComment(Comment comment) method, representing our scenario’s use case.
You also observe in this example, instead of using System.out, I used an object of type Logger to write messages in the console. In real-world apps, you don’t use System.out to write messages in the console. You’ll generally use a logging framework that offers you more flexibility in customizing the logging features and standardizing the logging messages. Some good options for a logging framework are
The logging frameworks are compatible with any Java app, whether it’s using Spring or not. Not being a subject directly related to Spring, up to now, I didn’t use them in our examples to avoid distracting your attention. But we are far enough now with Spring that we can start using these frameworks as well in our examples to familiarize you with syntaxes closer to production-ready apps.
1 2 3 4 5 6 7 8 9 10 11 | @Service #A public class CommentService { private Logger logger = #B Logger.getLogger(CommentService.class.getName()); public void publishComment(Comment comment) { #C logger.info("Publishing comment:" + comment.getText()); } } |
#A We use the stereotype annotation to make this a bean in the Spring context.
#B To log a message in the app’s console every time someone calls the use case, we use a logger object.
#C This method defines the use case for our demonstration.
In this example, I use the JDK logging capabilities to avoid adding other dependencies to our project. When declaring a logger object, you need to give it a name as a parameter. This name appears then in the logs and makes it easy for you to observe the log message source. Often, we use the class name, as you observe, I also did for our example by using CommentService.class.getName().
We also need to add a configuration class to tell Spring where to look for the classes annotated with stereotype annotations. In my case, I have added the service class in the package named services, and this is what I need to specify with the @ComponentScan annotation, as you observe from the next code snippet.
1 2 3 4 5 | @Configuration @ComponentScan(basePackages = "services") #A public class ProjectConfig { } |
#A We use @ComponentScan to tell Spring where to search for classes annotated with stereotype annotations.
Let’s write the Main class that calls the publishComment() method in the service class and observe the current behavior. Listing 2 presents the Main class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class Main { public static void main(String[] args) { var c = new AnnotationConfigApplicationContext(ProjectConfig.class); var service = c.getBean(CommentService.class); #A Comment comment = new Comment(); #B comment.setText("Demo comment"); comment.setAuthor("Natasha"); service.publishComment(comment); #C } } |
#A Getting the CommentService bean from the context.
#B Creating a Comment instance to give as a parameter to the publishComment() method.
#C Calling the publishComment() method.
If you now run the app, you’ll observe an output in the console similar to what you see in the next snippet.
1 2 3 | Sep 26, 2020 12:39:53 PM services.CommentService publishComment INFO: Publishing comment:Demo comment |
You will see the output generated by the publishComment() method. This is how the app looks like before we solve the example we discussed. Remember, we need to print messages in the console before and after the service method call. Let’s now enhance the project with an aspect class that intercepts the method call and adds an output before and after the call.
To create an aspect, you follow these steps (1):
For the first step, you need to tell Spring you’ll use aspects in your app. Whenever you use a specific mechanism provided by Spring, you have to explicitly enable it by annotating your configuration class with a particular annotation. In most cases, the names of these annotations start with “Enable”. In this example, we need to use the @EnableAspectJAutoProxy annotation to enable the aspect capabilities. The configuration class needs to look like the one presented in listing 3.
1 2 3 4 5 6 7 | @Configuration @ComponentScan(basePackages = "services") @EnableAspectJAutoProxy #A public class ProjectConfig { } |
#A Enabling the aspects mechanism in our Spring app
We need to create a new bean in the Spring context that defines the aspect. This object holds the methods which will intercept specific method calls and augment them with specific logic. In listing 4, you find the definition of this new class.
1 2 3 4 5 6 7 | @Aspect public class LoggingAspect { public void log() { // To implement later } } |
You can use any of the approaches you might know to add an instance of this class to the Spring context. If you decide to use the @Bean annotation, you have to change the configuration class as presented in the next code snippet. Of course, you can also go with using stereotype annotations if you’d like.
1 2 3 4 5 6 7 8 9 10 11 | @Configuration @ComponentScan(basePackages = "services") @EnableAspectJAutoProxy public class ProjectConfig { @Bean #A public LoggingAspect aspect() { return new LoggingAspect(); } } |
#A Adding an instance of the LoggingAspect class to the Spring context.
Remember, you need to make this object a bean in the Spring context because Spring needs to know about any object it needs to manage. Managing the Spring context is extremely important. You’ll use these skills almost everywhere when developing a Spring app.
Also, mind that the @Aspect annotation isn’t a stereotype annotation. Using @Aspect, you tell Spring that the class implements the definition of an aspect, but Spring won’t also create a bean for this class. You need to explicitly use one of the syntaxes you learned in chapter 2 to create a bean for your class and allow Spring to manage it this way. It’s a common mistake to forget that annotating the class with @Aspect doesn’t also add a bean to the context, and I’ve seen much frustration caused by forgetting this.
Now that we have defined the aspect class, we choose the advice and annotate the method accordingly. In listing 5, you see how I annotated the method with the @Around annotation.
1 2 3 4 5 6 7 8 | @Aspect public class LoggingAspect { @Around("execution(* services.*.*(..))") #A public void log(ProceedingJoinPoint joinPoint) { joinPoint.proceed(); #B } } |
#A Defines which are the intercepted methods.
#B Delegates to the actual intercepted method.
But besides using the @Around annotation, you also observe I’ve written an odd string expression as the value of the annotation, and I have added a parameter to the aspect method. What are these?
Let’s take them one by one. The peculiar expression used as a parameter to the @Around annotation tells Spring which method calls to intercept. Don’t be intimidated by this expression! This expression language is called AspectJ pointcut language, and you won’t actually need to learn it by heart to use it. As you’ll observe, in practice, you don’t use complex expressions. When I need to write such an expression, I always refer to the documentation (https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-ataspectj).
Theoretically, you can write very complex AspectJ pointcut expressions to identify a particular set of method calls to be intercepted. This language is really powerful, but it’s always better to avoid writing complex expressions. In most cases, you can find simpler alternatives.
Take a look at the expression I used (figure 2). It means Spring intercepts any method defined in a class that is in the services package, regardless of the method’s return type, the class it belongs to, the name of the method, or the parameters the method receives.
At a second look, the expression doesn’t seem so complicated anymore, does it? I know these AspectJ pointcut expressions tend to scare beginners but trust me, you don’t have to become an AspectJ expert to use these expressions in Spring apps.
Now let’s look at the second element I’ve added to the method: the ProceedingJoinPoint parameter. This parameter represents the intercepted method. The main thing you do with this parameter is to tell the aspect when it should delegate further to the actual method.
In listing 6, I’ve added the logic for our aspect. Now the aspect
Figure 3 visually presents the aspect’s behavior.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Aspect public class LoggingAspect { private Logger logger = Logger.getLogger(LoggingAspect.class.getName()); @Around("execution(* services.*.*(..))") public void log(ProceedingJoinPoint joinPoint) throws Throwable { logger.info("Method will execute"); #A joinPoint.proceed(); #B logger.info("Method executed"); #C } } |
#A Print a message in the console before the intercepted method’s execution
#B Call the intercepted method
#C Print a message in the console after the intercepted method’s execution
Method proceed() of the ProceedingJoinPoint parameter calls the intercepted method – publishComment() of the CommentService bean. If you don’t call proceed(), the aspect never delegates further to the intercepted method (figure 4).
So you can even implement logic where the actual method isn’t called anymore. For example, an aspect that applies some authorization rules decides whether to delegate further to a method the app protects. If the authorization rules aren’t fulfilled, the aspect doesn’t delegate to the intercepted method it protects (figure 5).
Also, observe that the proceed() method throws a Throwable. Method proceed()is designed to throw any exception coming from the intercepted method. In this example, I chose the easy way to propagate it further, but you can actually use a try-catch-finally block to treat this throwable if your requirement needs it.
Rerun the application (sq-ch6-ex1). In the console output, you’ll find the logs from both the aspect and the intercepted method. The output you see should look similar to the one presented in the following snippet.
1 2 3 4 5 6 7 | Sep 27, 2020 1:11:11 PM aspects.LoggingAspect log INFO: Method will execute #A Sep 27, 2020 1:11:11 PM services.CommentService publishComment INFO: Publishing comment:Demo comment #B Sep 27, 2020 1:11:11 PM aspects.LoggingAspect log INFO: Method executed #C |
#A This line is printed from the aspect
#B This line is printed from the actual method
#C This line is printed from the aspect
That’s all for this article. If you want to learn more about the book, you can check it out on our browser-based liveBook platform here.