define

I do not want to copy the content of Baidu Encyclopedia here, the following content is purely personal understanding:

  • AOP: Aspect oriented Programming? NO, at the low end, it is a very powerful decorator that can run in parallel with the business logic and is suitable for some logging/permission verification operations

  • SpEL: SpEL expression, can be understood as a super enhanced version of JSP, used properly can save us code (this is a copy), the most common use of SpEL expression is to introduce configuration, such as:

// Do you think I'm familiar with it?
@Value("#{file.root}")
Copy the code

So when do you use them together?

In fact, many experienced bosses subconsciously can answer, record the system log

Yes, AOP is parallel to business logic, and SpEL is parallel to business data, and combining them allows us to take traditional object-oriented/procedural programming to the next level

I’ll use an implementation of the actual logging business logging function to document how to use AOP and SpEL in Spring Boot

Understand them

To use them, and as a first mover to highlight personal obligations, let’s take a look at some of the concepts that need special attention:

AOP

Clear concept:

  • @ Aspect: section
  • @ Poincut: tangent point
  • JoinPoint: indicates the common JoinPoint
  • ProceedingJoinPoint: Specifies the surround join point

Timing:

  • Before: before the target method starts executing
  • After: after the target method starts executing
  • AfterReturning: After the target method has returned
  • AfterThrowing: After the target method throws an exception
  • Around: around the target method, most specifically

Let’s use the code to show where each entry time is:

try{
    try{
        @around
        @before
        method.invoke();
        @around
    }catch() {throw new Exception();
    }finally{
        @after
    }
    @afterReturning
}catch() {@afterThrowing
}
Copy the code

The “around” is the most special entry point, and its entry point must also be The ProceedingJoinPoint, while the others are JoinPoints

We need to manually call the ProceedingJoinPoint’s proceed method, which will execute the business logic of the target method

Around the most troublesome, but also the strongest

SpEL

It’s hard to use SpEL, but how does it translate from a simple literal expression to data content in action?

Spring and Java already provide most of the functionality. We only need to manually handle the following parts:

  1. TemplateParserContext: Expression parsing template. How do I extract SpEL expressions
  2. RootObject: Business data content from which the data needed during the parsing of the SpEL expression is retrieved
  3. ParameterNameDiscoverer: Parameter parser that attempts to retrieve data directly from rootObject during SpEL parsing
  4. EvaluationContext: The parse context, which contains RootObject, ParameterNameDiscoverer, and other data, is the context in which the entire SpEL is parsed

So the process of SpEL can be roughly summarized as follows:

Design RootObject-> Design the runtime context for SpEL expressions -> Design the SpEL parser (including expression parsing templates and parameter parsers)

True knowledge comes from practice

The business scenario

To implement the service log function, record the following:

  • Function module
  • Service description, including service data ID
  • Target method details
  • Target Class details
  • The ginseng
  • The return value
  • User information, such as user ID/IP

At a glance, all of this data needs to be retrieved at run time, remember? In the introduction, running this state word is a feature of AOP and SpEL

So, we will use AOP and SpEL to fulfill this requirement

Business analysis

By looking carefully at the content of the data to be recorded, we can analyze them and get from there:

  • Functional modules: Obtained through annotations to pointcuts in AOP
  • Business description: Write SpEL expressions into annotations of AOP pointcuts and translate the expressions while AOP is running
  • Target method details: obtained through an AOP pointcut
  • Target class details: obtained through an AOP pointcut
  • Input parameters: obtained through an AOP pointcut
  • Return value: obtained through an AOP pointcut
  • User information: Obtained in the code as AOP is running

With the data sources in mind, let’s start with aOP-related design

AOP Annotation design

The purpose of AOP annotations is to record data at the code level and provide a pointcut where the functional modules and business descriptions mentioned above need to be written to start writing code:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface KoalaLog {

    /** * Function module **@returnFunction module */
    String type(a) default "";

    /** * Service description **@returnService description */
    String content(a) default "";

}
Copy the code

AOP aspect design

Section design is actually two contents:

  • The breakthrough point
  • Cut to the timing

Do you notice that this is similar to the introduction just now?

Returning to the subject, our entry point is the annotations we just designed, and the entry point is after the target method has been implemented, i.e. afterReturning

As careful partners know, method execution can go wrong. In addition to afterReturning, we need to add an afterThrowing exception

@Aspect
@Component
@Slf4j
public class KoalaLogAspect {

    @Pointcut(value = "@annotation(cn.houtaroy.springboot.koala.starter.log.annotations.KoalaLog)")
    public void logPointCut(a) {}
    
    @AfterReturning(value = "logPointCut()", returning = "returnValue")
    public void log(JoinPoint joinPoint, Object returnValue) {
        // Record normal logs
    }
    
    @AfterThrowing(pointcut = "logPointCut()", throwing = "error")
    public void errorLog(JoinPoint joinPoint, Exception error) {
        // Record the exception log}}Copy the code

So, with all the AOP design done, how to implement the logging logic will wait until the SpEL design is done

SpEL model design

To implement SpEL, we need the following models:

  • LogRootObject: Source of run data
  • LogEvaluationContext: Parse context, used for the entire parse environment
  • LogEvaluator: Parser that parses the SpEL to get the data

LogRootObject

LogRootObject is the data source for the SpEL expression, the business description

As mentioned above, the ID of the business data needs to be recorded in the business description, which can be obtained through the method parameter, then:

@Getter
@AllArgsConstructor
public class LogRootObject {

    /** * Method parameters */
    private final Object[] args;

}
Copy the code

However, it is important to note that its structure directly determines the result of the translation of the SpEL expression of the business description, so it is important to communicate the most comprehensive data scope of the business description with the requirements in advance

For example, if we also need to record the target method/target class information, then this design is not satisfied. It should be:

@Getter
@AllArgsConstructor
public class LogRootObject {

    /** * Target method */
    private final Method method;

    /** * Method parameters */
    private final Object[] args;

    /** * Type information for the target class */
    private finalClass<? > targetClass; }Copy the code

LogEvaluationContext

Spring provides MethodBasedEvaluationContext, we only need to inherit it, and implement the corresponding constructor:

public class LogEvaluationContext extends MethodBasedEvaluationContext {

    /** * constructor **@paramRootObject Data source object *@paramDiscoverer Parameter parser */
    public LogEvaluationContext(LogRootObject rootObject, ParameterNameDiscoverer discoverer) {
        super(rootObject, rootObject.getMethod(), rootObject.getArgs(), discoverer); }}Copy the code

LogEvaluator

This is our core parser, which parses the SpEL and returns what you really expect from the data

We need to initialize the expression compiler/expression compiler template/parameter parser

@Getter
public class LogEvaluator {

    /** * SpEL parser
    private final SpelExpressionParser parser = new SpelExpressionParser();

    /** * parameter parser */
    private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    /** * Expression template */
    private final ParserContext template = new TemplateParserContext("${"."}");

    /** ** *@paramExpression Expression *@paramContext Log expression context *@returnThe result of the expression is */
    public Object parse(String expression, LogEvaluationContext context) {
        return getExpression(expression).getValue(context);
    }

    /** * get the translated expression **@paramExpression String expression *@returnThe translated expression is */
    private Expression getExpression(String expression) {
        returngetParser().parseExpression(expression, template); }}Copy the code

At this point, the entire content of the SpEL expression is complete, again emphasizing its logic:

Design RootObject-> Design the runtime context for SpEL expressions -> Design the SpEL parser (including expression parsing templates and parameter parsers)

AOP business logic

With SpEL designed, we can return to the business code that was not implemented in AOP earlier, and the process here is very simple:

Parse SpEL-> Generate log entities -> Save logs

The content here is not to repeat, friends only need to carefully look at the code to understand all

@Aspect
@Slf4j
public class KoalaLogAspect {

    /** * Log SpEL parser */
    private final LogEvaluator evaluator = new LogEvaluator();

    /** * jackson */
    @Autowired
    private ObjectMapper objectMapper;

    /** * Log pointcut */
    @Pointcut(value = "@annotation(cn.houtaroy.springboot.koala.starter.log.annotations.KoalaLog)")
    public void logPointCut(a) {}/** * method returns pointcut **@paramJoinPoint Pointcut *@paramReturnValue Returned value */
    @AfterReturning(value = "logPointCut()", returning = "returnValue")
    public void log(JoinPoint joinPoint, Object returnValue) {
        // Record normal logs
        Log koalaLog = generateLog(joinPoint);
        try {
            koalaLog.setReturnValue(objectMapper.writeValueAsString(returnValue));
            // Log code...
        } catch (JsonProcessingException e) {
            log.error("Koala-log: Serialization return value failed", e); }}/** * Method throws an exception@paramJoinPoint Pointcut *@paramThe error exception * /
    @AfterThrowing(pointcut = "logPointCut()", throwing = "error")
    public void errorLog(JoinPoint joinPoint, Exception error) {
        // Record the exception log
        Log koalaLog = generateLog(joinPoint);
        koalaLog.setReturnValue(error.getMessage());
        // Log code...
    }

    /** * Generate log entity **@paramJoinPoint Pointcut *@returnLog entity */
    private Log generateLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); Object[] args = joinPoint.getArgs(); Class<? > targetClass = AopProxyUtils.ultimateTargetClass(joinPoint.getTarget()); KoalaLog annotation = method.getAnnotation(KoalaLog.class); LogRootObject rootObject =new LogRootObject(method, args, targetClass);
        LogEvaluationContext context = new LogEvaluationContext(rootObject, evaluator.getDiscoverer());
        Object content = evaluator.parse(annotation.content(), context);
        Log koalaLog = Log.builder().type(annotation.type()).content(content.toString()).createTime(new Date()).build();
        try {
            koalaLog.setArguments(objectMapper.writeValueAsString(args));
        } catch (JsonProcessingException e) {
            log.error("Koala-log: Failed to serialize method parameters", e);
        }
        returnkoalaLog; }}Copy the code

The above content lacks the specific code for logging, you can add according to the actual situation (I will not admit that I have not finished writing ORM encapsulation).

conclusion

AOP and SpEL are focused on data processing at runtime and are not necessarily used in simple businesses

Code is our tool, how to use it correctly and easily is also a capability

All of the above code can be found in my practice project, the code is very simple, welcome to the big guy criticism