Use Spring Boot and AspectJ implementation trace infrastructure

Learn how to use Spring Boot and AspectJ implementation tracking infrastructure! In recent sharp distinctions learning lesson learned a lot, recorded progress with everyone!

In our application, the method for obtaining stack trace information may save a lot of time. Time has input and output parameters and methods of spending can make it easier to find the problem. In this article, we will examine how to use Spring Boot, AspectJ and Threadlocal method of tracking infrastructure to achieve the starting point.

In this example, I use: Spring Boot Starter Web 2.1.7

  • Java 1.8 +
  • AspectJ 1.8
  • Maven 3.2

1. Overview

In this tutorial, we will prepare a simple REST service that will retrieve detailed information about a book in a bookstore. Then, we will add a ThreadLocalmodel that will leave the stack structure throughout the life cycle of the thread. Finally, we will add a method to reduce the terms of the call stack in order to obtain input / output parameter values. let's start!

Project structure

 

 

2. Maven relies

  • Spring Boot Starter Web - using Spring MVC RESTful service
  • Spring - with Aspect function
  • AspectJ weaver introduced recommendations to the Java class
  • Apache Commons Lang - A string utility
   
 1  <parent>
 2         <groupId>org.springframework.boot</groupId>
 3         <artifactId>spring-boot-starter-parent</artifactId>
 4         <version>2.1.7.RELEASE</version>
 5     </parent>
 6 <properties>
 7         <java.version>1.8</java.version>
 8     </properties>
 9 <dependencies>
10         <dependency>
11             <groupId>org.springframework.boot</groupId>
12             <artifactId>spring-boot-starter-web</artifactId>
13             <version>2.1.7.RELEASE</version>
14         </dependency>
15         <dependency>
16             <groupId>org.springframework</groupId>
17             <artifactId>spring-aop</artifactId>
18             <version>5.0.9.RELEASE</version>
19         </dependency>
20         <dependency>
21             <groupId>org.aspectj</groupId>
22             <artifactId>aspectjweaver</artifactId>
23             <version>1.8.9</version>
24         </dependency>
25         <dependency>
26             <groupId>org.apache.commons</groupId>
27             <artifactId>commons-lang3</artifactId>
28             <version>3.8.1</version>
29         </dependency>
30     </dependencies>

 

3. practical operation

Creating a Spring Boot application

You can use these templates to create a simple progressive realization of Spring Boot Application, you can also download the final project directly here.

For IntelliJ:

https://www.javadevjournal.com/spring-boot/spring-boot-application-intellij/

For Eclipse:

https://dzone.com/articles/building-your-first-spring-boot-web-application-ex

Rest Service and simple method

First, we will create our service. We will book the item number as an input parameter, and provides title, price and content of information services output.

We will offer three simple service:

PriceService:

 1 package com.example.demo.service;
 2 import org.springframework.stereotype.Service;
 3 @Service
 4 public class PriceService {
 5     public double getPrice(int itemNo){
 6             switch (itemNo) {
 7                 case 1 :
 8                     return 10.d;
 9                 case 2 :
10                     return 20.d;
11                 default:
12                     return 0.d;
13             }
14     }
15 }

 

CatalogueService :

 1 package com.example.demo.service;
 2 import org.springframework.stereotype.Service;
 3 @Service
 4 public class CatalogueService {
 5     public String getContent(int itemNo){
 6         switch (itemNo) {
 7             case 1 :
 8                 return "Lorem ipsum content 1.";
 9             case 2 :
10                 return "Lorem ipsum content 2.";
11             default:
12                 return "Content not found.";
13         }
14     }
15     public String getTitle(int itemNo){
16         switch (itemNo) {
17             case 1 :
18                 return "For whom the bell tolls";
19             case 2 :
20                 return "Of mice and men";
21             default:
22                 return "Title not found.";
23         }
24     }
25 }

 

BookInfoService:

 1 package com.example.demo.service;
 2 import org.springframework.beans.factory.annotation.Autowired;
 3 import org.springframework.stereotype.Service;
 4 @Service
 5 public class BookInfoService {
 6     @Autowired
 7     PriceService priceService;
 8     @Autowired
 9     CatalogueService catalogueService;
10     public String getBookInfo(int itemNo){
11         StringBuilder sb = new StringBuilder();
12         sb.append(" Title :" + catalogueService.getTitle(itemNo));
13         sb.append(" Price:" + priceService.getPrice(itemNo));
14         sb.append(" Content:" + catalogueService.getContent(itemNo));
15         return sb.toString();
16     }
17 }

 

BookController:  This is our REST controller to create a searchable library of information RET service. We will prepare a TraceMonitorservice for later print stack trace.

 1 package com.example.demo.controller;
 2 import com.example.demo.service.BookInfoService;
 3 import com.example.demo.trace.TraceMonitor;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.web.bind.annotation.GetMapping;
 6 import org.springframework.web.bind.annotation.PathVariable;
 7 import org.springframework.web.bind.annotation.RestController;
 8 @RestController
 9 public class BookController {
10     @Autowired
11     BookInfoService bookInfoService;
12     @Autowired
13     TraceMonitor traceMonitor;
14     @GetMapping("/getBookInfo/{itemNo}")
15     public String getBookInfo(@PathVariable int itemNo) {
16         try{
17             return bookInfoService.getBookInfo(itemNo);
18         }finally {
19             traceMonitor.printTrace();
20         }
21     }
22 }

 

 

Our REST controller ready for use. If we have not yet commented implemented traceMonitor.printTrace()method, and then use the @SpringBootApplicationannotated classes run our application:

1 package com.example.demo;
2 import org.springframework.boot.SpringApplication;
3 import org.springframework.boot.autoconfigure.SpringBootApplication;
4 @SpringBootApplication
5 public class DemoApplication {
6     public static void main(String[] args) {
7         SpringApplication.run(DemoApplication.class, args);
8     }
9 }

 

http://localhost:8080/getBookInfo/2

> Title :Of mice and men Price:20.0 Content:Lorem ipsum content 2.

Thread Local model

Now, we will prepare our Method object that will hold information about any method call. Later, we will be ready to stack structure and ThreadLocalobjects that will leave the stack structure throughout the life cycle of thread.

Method, : This is our model object, it will retain all the details about the method of execution. Input / output parameters of the method which comprises the time spent and the method methodListobject is a list of method calls directly from the process.

 1 package com.example.demo.util.log.standartlogger;
 2 import java.util.List;
 3 public class Method {
 4 private String methodName;
 5 private String input;
 6 private List<Method> methodList;
 7 private String output;
 8 private Long timeInMs;
 9 public Long getTimeInMs() {
10 return timeInMs;
11 }
12 public void setTimeInMs(Long timeInMs) {
13 this.timeInMs = timeInMs;
14 }
15 public String getInput() {
16 return input;
17 }
18 public void setInput(String input) {
19 this.input = input;
20 }
21 public String getOutput() {
22 return output;
23 }
24 public void setOutput(String output) {
25 this.output = output;
26 }
27 public List<Method> getMethodList() {
28 return methodList;
29 }
30 public void setMethodList(List<Method> methodList) {
31 this.methodList = methodList;
32 }
33 public String getMethodName() {
34 return methodName;
35 }
36 public void setMethodName(String methodName) {
37 this.methodName = methodName;
38 }
39 }

 

ThreadLocalValues : Reserved main methods of tracking information. The method mainMethodincludes List<Method>methodListobject that contains child method called from the main method.

 Deque<Method>methodStack The method is reserved for the call stack objects. It runs through the entire life cycle of the thread. When calling sub-method, the Method object pushed methodStackon, when the sub-method returns, from methodStackMethod objects pop top.

 1 package com.example.demo.util.log.standartlogger;
 2 import java.util.Deque;
 3 public class ThreadLocalValues {
 4 private Deque<Method> methodStack;
 5 private Method mainMethod;
 6 public ThreadLocalValues() {
 7 super();
 8 }
 9 public Method getMainMethod() {
10 return mainMethod;
11 }
12 public void setMainMethod(Method mainMethod) {
13 this.mainMethod = mainMethod;
14 }
15 public Deque<Method> getMethodStack() {
16 return methodStack;
17 }
18 public void setMethodStack(Deque<Method> methodStack) {
19 this.methodStack = methodStack;
20 }
21 }

 

 

LoggerThreadLocal: Such reservations ThreadLocalValuesof the ThreadLocalobject. This object has always existed throughout the life cycle of thread.

 1 package com.example.demo.util.log.standartlogger;
 2 import java.util.ArrayDeque;
 3 import java.util.Deque;
 4 public class LoggerThreadLocal {
 5 static final ThreadLocal<ThreadLocalValues> threadLocal = new ThreadLocal<>();
 6 private LoggerThreadLocal() {
 7 super();
 8 }
 9 public static void setMethodStack(Deque<Method> methodStack) {
10 ThreadLocalValues threadLocalValues = threadLocal.get();
11 if (null == threadLocalValues) {
12 threadLocalValues = new ThreadLocalValues();
13 }
14 threadLocalValues.setMethodStack(methodStack);
15 threadLocal.set(threadLocalValues);
16 }
17 public static void setMainMethod(Method mainMethod){
18 ThreadLocalValues threadLocalValues = threadLocal.get();
19 if (null == threadLocalValues) {
20 threadLocalValues = new ThreadLocalValues();
21 }
22 threadLocalValues.setMainMethod(mainMethod);
23 threadLocal.set(threadLocalValues);
24 }
25 public static Method getMainMethod() {
26 if (threadLocal.get() == null) {
27 return null;
28 }
29 return threadLocal.get().getMainMethod();
30 }
31 public static Deque<Method> getMethodStack() {
32 if (threadLocal.get() == null) {
33 setMethodStack(new ArrayDeque<>());
34 }
35 return threadLocal.get().getMethodStack();
36 }
37 }

 

 

Aspect Implementations:

TraceMonitor : these are our terms of configuration class. In this class, we define a starting point, the code section cut at the entry point of the stream. Our entry point defines all the names of all class methods with the word "Service" in the end.

 @Pointcut(value = "execution(* com.example.demo.service.*Service.*(..))") 

pushStackInBean : This method is a method performed prior to the current method in the pointcut method of pushing the stack.

popStackInBean : This method will return after the starting point of the method, remove the top of the stack method.

printTrace : This is a print will JSON format threadLocalvalue ( mainMethodmethod).

 1 package com.example.demo.trace;
 2 import java.util.ArrayList;
 3 import com.example.demo.util.log.standartlogger.LoggerThreadLocal;
 4 import com.example.demo.util.log.standartlogger.Method;
 5 import com.fasterxml.jackson.core.JsonProcessingException;
 6 import com.fasterxml.jackson.databind.ObjectMapper;
 7 import org.apache.commons.lang3.StringUtils;
 8 import org.apache.commons.lang3.exception.ExceptionUtils;
 9 import org.aspectj.lang.JoinPoint;
10 import org.aspectj.lang.annotation.AfterReturning;
11 import org.aspectj.lang.annotation.Aspect;
12 import org.aspectj.lang.annotation.Before;
13 import org.aspectj.lang.annotation.Pointcut;
14 import org.springframework.context.annotation.Configuration;
15 import org.springframework.stereotype.Service;
16 @Aspect
17 @Service
18 @Configuration
19 public class TraceMonitor {
20     @Pointcut(value = "execution(* com.example.demo.service.*Service.*(..))")
21     private void executionInService() {
22         //do nothing, just for pointcut def
23     }
24     @Before(value = "executionInService()")
25     public void pushStackInBean(JoinPoint joinPoint) {
26         pushStack(joinPoint);
27     }
28     @AfterReturning(value = "executionInService()", returning = "returnValue")
29     public void popStackInBean(Object returnValue) {
30         popStack(returnValue);
31     }
32     ObjectMapper mapper = new ObjectMapper();
33     private void pushStack(JoinPoint joinPoint) {
34             Method m = new Method();
35             m.setMethodName(StringUtils.replace(joinPoint.getSignature().toString(), "com.example.demo.service.", ""));
36             String input = getInputParametersString(joinPoint.getArgs());
37             m.setInput(input);
38             m.setTimeInMs(Long.valueOf(System.currentTimeMillis()));
39             LoggerThreadLocal.getMethodStack().push(m);
40     }
41     private String getInputParametersString(Object[] joinPointArgs) {
42         String input;
43         try {
44             input = mapper.writeValueAsString(joinPointArgs);
45         } catch (Exception e) {
46             input = "Unable to create input parameters string. Error:" + e.getMessage();
47         }
48         return input;
49     }
50     private void popStack(Object output) {
51         Method childMethod = LoggerThreadLocal.getMethodStack().pop();
52         try {
53             childMethod.setOutput(output==null?"": mapper.writeValueAsString(output));
54         } catch (JsonProcessingException e) {
55             childMethod.setOutput(e.getMessage());
56         }
57         childMethod.setTimeInMs(Long.valueOf(System.currentTimeMillis() - childMethod.getTimeInMs().longValue()));
58         if (LoggerThreadLocal.getMethodStack().isEmpty()) {
59             LoggerThreadLocal.setMainMethod(childMethod);
60         } else {
61             Method parentMethod = LoggerThreadLocal.getMethodStack().peek();
62             addChildMethod(childMethod, parentMethod);
63         }
64     }
65     private void addChildMethod(Method childMethod, Method parentMethod) {
66         if (parentMethod != null) {
67             if (parentMethod.getMethodList() == null) {
68                 parentMethod.setMethodList(new ArrayList<>());
69             }
70             parentMethod.getMethodList().add(childMethod);
71         }
72     }
73     public void printTrace() {
74         try {
75             StringBuilder sb = new StringBuilder();
76             sb.append("\n<TRACE>\n").append(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(LoggerThreadLocal.getMainMethod()));
77             sb.append("\n</TRACE>");
78             System.out.println(sb.toString());
79         } catch (JsonProcessingException e) {
80             StringUtils.abbreviate(ExceptionUtils.getStackTrace(e), 2000);
81         }
82     }
83 }

 

3. Test and printing stack

When we run Spring Boot application and send get request:

http://localhost:8080/getBookInfo/2

Reply will be:

> Title:Of mice and men Price:20.0 Content:Lorem ipsum content 2.

Note: If you before traceMonitor.printTrace()were comments, please do not forget to cancel comment.

Console output will be:

 1 <TRACE>
 2 {
 3   "methodName": "String service.BookInfoService.getBookInfo(int)",
 4   "input": "[2]",
 5   "methodList": [
 6     {
 7       "methodName": "String service.ContentService.getTitle(int)",
 8       "input": "[2]",
 9       "output": "\"Of mice and men\"",
10       "timeInMs": 3
11     },
12     {
13       "methodName": "Double service.PriceService.getPrice(int)",
14       "input": "[2]",
15       "output": "20.0",
16       "timeInMs": 1
17     },
18     {
19       "methodName": "String service.ContentService.getContent(int)",
20       "input": "[2]",
21       "output": "\"Lorem ipsum content 2.\"",
22       "timeInMs": 0
23     }
24   ],
25   "output": "\" Title :Of mice and men Price:20.0 Content:Lorem ipsum content 2.\"",
26   "timeInMs": 6
27 }
28 </TRACE>

 

 

Because we can easily track the flow of the method:

  •  getBookInfo method is called with input 2
  •  getBookInfo calls getTitle  method with input 2
  •  getTitle returns with output "Of mice and men" in 3 ms.
  •  getBookInfo calls getPrice  with input 2
  •  getPrice returns with output 20.0 in 1 ms.
  •  getBookInfo calls getContent  with input 2
  •  getContent returns with output "lorem ipsum content 2." In 0 ms.
  •  getBookInfo method returns with output "Title :Of mice and men Price:20.0 Content:Lorem ipsum content 2." in 6 ms.

Our track to achieve applies to our simple REST service call.

Further improvements should be:

  • If there is any way to get an exception, use the @AfterThrowinghandle exceptions.
  • Caching method having the opening / closing a tracking mechanism, which can be read from the service list of the tracking method or database.
  • Implemented using a recording (sl4j) to separate the printed trace log file.

Thanks for reading!

Guess you like

Origin www.cnblogs.com/youruike-/p/12040841.html