AOP combined with SpEL log output notes one or two

Using AOP to print logs we are very familiar with, recently in the process of using, found several interesting problems, one is the parsing of SpEL, one is the output of JSON format parameters

I. Project environment

1. Project dependencies

This project is developed by SpringBoot 2.2.1.RELEASE + Maven 3.5.3 + IDEA

Open a Web service for testing

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
Copy the code

II. AOP & SpEL

AOP and SpEL knowledge, have previously had a special introduction, here to do a aggregation, a very simple Log output aspect, in the need to print Log method, add annotation @log, this annotation definition of a key, as the Log output mark; Key supports SpEL expressions

1. The AOP aspects

Annotations to define

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String key(a);
}
Copy the code

Section of logic

@Slf4j
@Aspect
@Component
public class AopAspect implements ApplicationContextAware {
    private ExpressionParser parser = new SpelExpressionParser();
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(logAno)")
    public Object around(ProceedingJoinPoint joinPoint, Log logAno) throws Throwable {
        long start = System.currentTimeMillis();
        String key = loadKey(logAno.key(), joinPoint);
        try {
            return joinPoint.proceed();
        } finally {
            log.info("key: {}, args: {}, cost: {}", key, JSONObject.toJSONString(joinPoint.getArgs()), System.currentTimeMillis() - start); }}private String loadKey(String key, ProceedingJoinPoint joinPoint) {
        if (key == null) {
            return key;
        }

        StandardEvaluationContext context = new StandardEvaluationContext();

        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(params[i], args[i]);
        }

        return parser.parseExpression(key).getValue(context, String.class);
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext; }}Copy the code

The above logic is relatively simple, and we are familiar with the use of posture is not much different

2. StandardEvaluationContext security issues

Questions about the injection of StandardEvaluationContext. Interested can check the related articles; For higher security checks, only SimpleEvaluationContext is required, which limits SpEL’s capabilities

Let’s add a test

@Data
@Accessors(chain = true)
public class DemoDo {

    private String name;

    private Integer age;
}
Copy the code

Service class

@Service
public class HelloService {

    @Log(key = "#demo.getName()")
    public String say(DemoDo demo, String prefix) {
        return prefix + ":"+ demo; }}Copy the code

To verify the SimpleEvaluationContext, let’s modify the loadKeys method above

private String loadKey(String key, ProceedingJoinPoint joinPoint) {
    if (key == null) {
        return key;
    }

    SimpleEvaluationContext context = new SimpleEvaluationContext.Builder().build();
    String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
    Object[] args = joinPoint.getArgs();
    for (int i = 0; i < args.length; i++) {
        context.setVariable(params[i], args[i]);
    }

    return parser.parseExpression(key).getValue(context, String.class);
}
Copy the code

Start the test

@SpringBootApplication
public class Application {

    public Application(HelloService helloService) {
        helloService.say(new DemoDo().setName("A Gray Blog").setAge(18), "welcome");
    }

    public static void main(String[] args) { SpringApplication.run(Application.class); }}Copy the code

Direct hint method not found!!

3. Gson serialization problem

In the above case, FastJson was used to serialize the pass-through. Next, Gson was used to serialize the pass-through

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
Copy the code

And then add a special method

@Service
public class HelloService {
    /** * literals, note that they are enclosed in single quotes *@param key
     * @return* /
    @Log(key = "'yihuihuiblog'")
    public String hello(String key, HelloService helloService) {
        return key + "_" + helloService.say(new DemoDo().setName(key).setAge(10), "prefix"); }}Copy the code

Note the second argument to the above method, which, interestingly enough, is its own instance; Performed again

public Application(HelloService helloService) {
    helloService.say(new DemoDo().setName("A Gray Blog").setAge(18), "welcome");

    String ans = helloService.hello("A lump of ash", helloService);
    System.out.println(ans);
}
Copy the code

I threw an exception

This would be awkward, a log output aid, because serialization directly makes the interface unavailable, which is not elegant; There is no way for us, as the log output section, to control this parameter, there is no way to require the use of parameters, must be serialized, here we need to pay extra attention to (a good way is simple objects implement toString, and then output toString results; Instead of json string)

4. Summary

Although the above a long string of content, summed up, but also two points

  • SpEL if used isSimpleEvaluationContext, then note that spEL functionality is weakened and some features are not supported
  • If the method parameter is serialized as JSON, be aware that some classes may throw exceptions during the serialization process

(See here small partners, might as well click a like, and pay attention to the wechat public number “A gray blog”, my public number has been lonely long grass 😭)

III. Can’t miss the source code and related knowledge points

0. Project

  • Project: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…

AOP blog series

  • SpringBoot base series AOP cannot intercept annotation scenario compatibility on the interface
  • The SpringBoot Foundation series implements a simple distributed timing task
  • SpringBoot foundation AOP interception priority details
  • AOP implementation of logging function in SpringBoot application
  • Advanced use skills of AOP in SpringBoot Basics
  • SpringBoot foundation AOP basic use posture summary

1. An ashy Blog

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll

  • A grey Blog Personal Blog blog.hhui.top
  • A Grey Blog-Spring feature Blog Spring.hhui.top