Regex in one blog
In this post, we are going to learn Regex expression in one blog Let’s start with defining what is Regex or Regex expression. What is Regex? …
In this post, we are going to look at what is AOP and how your spring application matches with AOP concepts using Spring AOP module. After that we are going to look at the what is AspectJ, differences between Spring AOP and AspectJ.
Note: Spring AOP Example in this topic is not the fully compliant example. I will hopefully write another post for only spring aop and aspectJ example.
Today topics are:
Note: Concern is a particular set of information that has an effect on the code of an computer programming. For example, business logic is a concern that effects our program execution.
Definition is easily to be understandable if we know what is the cross-cutting concern
It can be more understandable with examples:
public class UserLoginStatus{
public static boolean isLoggedInUser(HttpServletRequest req){
// returns true if user is logged in
}
}
// ..
public class Controller{
public String accessibleEndpoint(HttpServletRequest req){
return "success"
}
public String endpoint1(HttpServletRequest requestForEndpoint1){
if (!UserLoginStatus.isLoggedInUser(..){
return "error";
}
return "success"
}
public String endpoint2(HttpServletRequest requestForEndpoint2){
if (!UserLoginStatus.isLoggedInUser(..)){
return "error";
}
return "success"
}
public String endpoint3(HttpServletRequest requestForEndpoint3){
if (!UserLoginStatus.isLoggedInUser(..)){
return "error";
}
return "success"
}
}
Solution from the above may seem the correct one, because you have only one method(isLoggedInUser(..)
) that determines whether user is logged in or not. However, in that case, we are adding extra logic to our business which is not directly needed. In other words, for each business logic you have, you are extending with the security concern. At the end your business logic will be equal to business logic + security concern
public class UserLoginStatus{
public static boolean isLoggedInUser(HttpServletRequest req){
// return true if loggedin user for that request
}
}
// ..
public class Controller{
private static final Logger LOGGER = LoggerFactory...;
public String accessibleEndpoint(HttpServletRequest req){
final String methodName = "accessibleEndpoint";
LOGGER.info("log for method: {} and reques: {} ", methodName, req);
return "success"
}
public String endpoint1(HttpServletRequest requestForEndpoint1){
final String methodName = "endpoint1";
LOGGER.info("log for method: {} and reques: {} ", methodName, req);
if (!UserLoginStatus.isLoggedInUser(..){
LOGGER.info("not logged in user");
return "error";
}
LOGGER.info("another log")
return "success"
}
public String endpoint2(HttpServletRequest requestForEndpoint2){
final String methodName = ...
LOGGER.info("log for method: {} and reques: {} ", methodName, req);
if (!UserLoginStatus.isLoggedInUser(..)){
return "error";
}
LOGGER.info("another log")
return "success"
}
public String endpoint3(HttpServletRequest requestForEndpoint3){
final String methodName = ...
LOGGER.info("log for method: {} and reques: {} ", methodName, req);
if (!UserLoginStatus.isLoggedInUser(..)){
LOGGER.info("not logged in user");
return "error";
}
LOGGER.info("another log")
return "success"
}
}
As you can see, right now for each business logic you have, you are extending with logging concern. At the end your business logic will be equal to business logic + security concern + logging concern
It is clear that solutions like the above are not the feasible. It would be nice if there is central class that handles all these concerns (which are not directly related to our business logic).
That’s the AOP is trying to solve. AOP allows centralized implementation of cross-cutting concerns. Because without AOP, they can not be implemented in a single place.
Actually all terms are related to each. So understand what aspect does mean will also allows us to understand other concepts as well.
LoggingAspect
implements logging feature for us.
There are more definitions than these. But for now let’s look at the how we can implement aspect in spring framework.
Keep reading, you will understand what is method execution
public
methodsBefore doing some examples, let’s quick recap what is proxy pattern.
Let’s say you have an service which returns user’s image/icon from database. After some time, you have decided to cache those images to response as much as quickly. Before caching those requests, you have a code structure like this:
public UserImageDTO{
private Long id;
private Image userImage;
}
public interface UserImageService{
Image returnUserImageById(long id);
}
public class UserImageServiceImpl implements UserImageService{
@Override
public Image returnUserImageById(long id){
// get userImageDTO from repository
UserImageDTO userImageDTO = userRepository.findById(id);
return userImageDTO.userImage();
}
}
public class Client{
UserImageService userImageService = new UserImageServiceImpl();
Image userImage = userImageService.returnUserImageById(1);
}
After using proxy pattern for caching those requests:
public UserImageServiceProxyImpl implements UserImageService{
private UserImageService userImageService;
private Map<Long, Image> cacheImages;
public UserImageServiceProxyImpl(){
cacheImages = new HashMap<>();
this.userImageService = new UserImageServiceImpl();
}
@Override
public Image returnUserImageById(long id){
if (cacheImages.size() >= 1000){
removeFirstElementFromCache();
}
if (cacheImages.get(id) == null){
Image userImage = userImageService.returnUserImageById(id);
addNewImageToCache(userImage);
return userImage;
}else{
return cacheImages.get(id);
}
}
}
public class Client{
UserImageService userImageService = new UserImageServiceProxyImpl();
Image userImage = userImageService.returnUserImageById(1);
}
As you noticed, client does not know whether it is talking with proxy object or real service, what client gets back as a response is user image instance.
And using proxy, you are caching those images before returning to the client, and also there is second check. You are checking that your cache size is not grater than 1000.
Remember that, with these strategy you can also catch the errors from real object, or you can check the client’s parameters etc..
AOP does this in more concrete and dynamic way.
Create simple spring boot project from Spring Initializr with these dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
This simple project contains two controller classes:
@RestController
public class HelloController {
@GetMapping("/hello")
public String helloController(){
return "Hello";
}
}
// ...
@RestController
public class HomeController {
@GetMapping("/")
public String homeController(){
return "Home";
}
}
For two controllers it is easy to do that without aop:
@RestController
public class HelloController {
private static final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String helloController(){
final String methodName = "helloController";
LOGGER.info("Log for method : {}", methodName);
return "Hello";
}
}
@RestController
public class HomeController {
private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);
@GetMapping("/")
public String homeController(){
final String methodName = "home";
LOGGER.info("Log for method : {}", methodName);
return "Home";
}
}
2020-11-07 21:20:51.671 INFO 25655 --- [nio-8080-exec-1] c.s.s.controller.HelloController : Log for method : helloController
2020-11-07 21:20:55.432 INFO 25655 --- [nio-8080-exec-2] c.s.springaop.controller.HomeController : Log for method : home
homeController
with AOPTo create an aspect, you should follow these steps:
@Aspect
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component // make sure that class is annotated with component, otherwise spring can not find your aspect
public class LoggingAspect {
}
@Aspect
@Component
public class LoggingAspect {
@Before("execution(String homeController())") // pointcut expression
private void loggingAspectForHomeController(){
}
}
@Aspect
@Component
public class LoggingAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(String homeController())")
private void loggingAspectForHomeController(){
// code to be executed
LOGGER.info("aspect works");
}
}
You have declared an aspect for your homeController. After that just run the project and browse the http://localhost:8080/
:
2020-11-07 21:33:54.854 INFO 26837 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect works
2020-11-07 21:33:54.863 INFO 26837 --- [nio-8080-exec-1] c.s.springaop.controller.HomeController : Log for method : home
@Before
and execution(...)
?@Before
is one of the advice type and execution(..)
part is the pointcut expression.
@Before
)@After
)@AfterThrowing
)@AfterReturning
)@Around
)@Before
advice, but at the end it will be propagated to the caller)ProceedingJoinPoint
which extends JoinPoint
in that advice. This class includes method proceed()
that allows us to continue the original method call.// execution means that it is a method execution pointcut expression
// execute **before advice**, before the method execution called homeController which returns String and takes no parameter.
@Before("execution(String homeController())")
We can also use wild-cards for parts of the expression:
*
*
..
// expression for any method name which takes any parameter and any return type
"execution(* *(..))"
There are also other pointcut expressions such as
within, args
. For more info you can check the spring-aop-pointcut-tutorial from Bealdung.
"execution(* *(..))"
"execution(* set*(..))"
"execution(* com.xyz.service.AccountService.*(..))"
"execution(* com.xyz.service.*.*(..))"
"execution(* com.xyz.service..*.*(..))"
@After and @AfterReturning
@Aspect
@Component
public class LoggingAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(String homeController())")
private void loggingBeforeAspectForHomeController(){
LOGGER.info("aspect BEFORE advice");
}
@After("execution(String homeController())")
private void loggingAfterAspectForHomeController(){
LOGGER.info("aspect AFTER advice");
}
@AfterReturning("execution(String homeController())")
private void loggingAfterReturningAspectForHomeController(){
LOGGER.info("aspect AFTER-RETURNING if calling method was finished successfully");
}
}
Just run the project and hit the http://localhost:8080/
2020-11-08 15:14:07.888 INFO 7847 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect BEFORE advice
2020-11-08 15:14:07.903 INFO 7847 --- [nio-8080-exec-1] c.s.springaop.controller.HomeController : Log for method : home
2020-11-08 15:14:07.905 INFO 7847 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect AFTER-RETURNING if calling method was finished successfully
2020-11-08 15:14:07.905 INFO 7847 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect AFTER advice
@AfterThrowing
If you want to see what was the exception inside the homeController, you can use @AfterThrowing
. But be aware of the execution will be propagated to the caller:
First update the homeController()
:
@RestController
public class HomeController {
private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);
@GetMapping("/")
public String homeController(){
final String methodName = "home";
LOGGER.info("Log for method : {}", methodName);
throw new RuntimeException("Exception inside home controller");
}
}
Then update the LoggingAspect
@Aspect
@Component
public class LoggingAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(String homeController())")
private void loggingBeforeAspectForHomeController(){
LOGGER.info("aspect BEFORE advice");
}
@After("execution(String homeController())")
private void loggingAfterAspectForHomeController(){
LOGGER.info("aspect AFTER advice");
}
@AfterReturning("execution(String homeController())")
private void loggingAfterReturningAspectForHomeController(){
LOGGER.info("aspect AFTER-RETURNING if calling method was finished successfully");
}
@AfterThrowing(pointcut = "execution(String homeController())", throwing = "exception")
private void homeExceptionAdvice(Throwable exception){
LOGGER.error("aspect AFTER-THROWING: ", exception);
}
}
Then after hit the localhost:8080
2020-11-08 15:26:32.146 INFO 8401 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect BEFORE advice
2020-11-08 15:26:32.155 INFO 8401 --- [nio-8080-exec-1] c.s.springaop.controller.HomeController : Log for method : home
2020-11-08 15:26:32.160 ERROR 8401 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect AFTER-THROWING:
java.lang.RuntimeException: Exception inside home controller
at com.springexamples.springaop.controller.HomeController.homeController(HomeController.java:15) ~[classes/:na]
// ...
2020-11-08 15:26:32.160 INFO 8401 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect AFTER advice
2020-11-08 15:26:32.163 ERROR 8401 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Exception inside home controller] with root cause
java.lang.RuntimeException: Exception inside home controller
at com.springexamples.springaop.controller.HomeController.homeController(HomeController.java:15) ~[classes/:na]
at com.springexamples.springaop.controller.HomeController$$FastClassBySpringCGLIB$$c5b9afa9.invoke(<generated>) ~[classes/:na]
// ...
As you can see from the above, first @AfterThrowing
method will be executed, then execution will be propagated to the actual caller. That’s why you will have two logs for the same exception.
@Around
This one is the most powerful advice type. Let’s catch the exception from the previous example. In that case execution will not be propagated to the caller.
@Aspect
@Component
public class LoggingAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(String homeController())")
private Object loggingAroundAspectForHomeController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
LOGGER.info("aspect AROUND advice for signature name: {}", proceedingJoinPoint.getSignature().getName());
try{
return proceedingJoinPoint.proceed();
}catch (Exception e){
return "homeNotFound";
}
}
}
After hit the localhost:8080
2020-11-08 15:34:13.725 INFO 8590 --- [nio-8080-exec-1] c.s.springaop.aspects.LoggingAspect : aspect AROUND advice for signature name: homeController
2020-11-08 15:34:13.739 INFO 8590 --- [nio-8080-exec-1] c.s.springaop.controller.HomeController : Log for method : home
That’s it. No error log messages, and in the browser you will have seen homeNotFound response. Because @Around
advice is the only advice that can change response of the calling method.
You most probably are interesting of AspectJ rather than Spring-AOP because:
@Aspect
, @Before
, @After
and @Pointcut
works in the AspectJ also.Bytecode is the code that is executed by the JVM(Java Virtual Machine)
Weaving can be done:
In general weaving can be defined in this picture:
I am not going into detail of the configuration setup for load time weaving. You can learn more from this link. For instance, you need to enable aspectj java agent for load time weaving. That Java agents can modify the bytecode at load time.
You can enable compile time weaving only updating the pom.xml
. There is no need such as java agent.
And also I am not going into detail of the configuration setup for compile time weaving. You can look at the eclipse AspectJ developer guide from this link or you can search for also maven plugin aspectj-maven-plugin
There are so much things to write, but that’s the end of this part, wait for the next one…
In this post, we are going to learn Regex expression in one blog Let’s start with defining what is Regex or Regex expression. What is Regex? …
Sometimes you may need to create your sql queries on runtime with some specification given by your client. In these cases, you may create many JPQLs …