【 v 】 Integrate AOP aspect programming

Aspect-oriented programming (AOP) complements object-oriented programming (OOP) by providing another way to think about program structure. The key unit of modularity in OOP is the class, whereas in AOP, the modularity unit is the aspect.

The preparatory work

First, using AOP involves adding dependencies to build.gradle

// Introduce AOP dependencies
compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"
Copy the code

Then add it to application.yml

spring:
  aop:
    proxy-target-class: true
Copy the code

1. The @pointcut starting point

Define a pointcut. For example, if we want to add a pointcut to a method, we can write an expression or a specific name based on the object returned by the method, the method name, and the modifier

Let’s now define a tangent point

package com.example.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

The /** * class is defined as the facet class */
@Aspect
@Component
public class AopTestController {
    private static final Logger logger = LoggerFactory.getLogger(AopTestController.class);
    /** * defines a pointcut */
    @Pointcut(value = "execution(public String test (..) )")
    public void cutOffPoint(a) {}}Copy the code

The way the tangent point is defined here is

    @GetMapping("hello")
    public String test(a){
        logger.info("Welcome to Java Bosom friend");
        return "i love java";
    }
Copy the code

If you want to write a Pointcut in any method that returns an Area object, @pointcut (” Execution (public com.example.entity.area (..)). “) “, “private”, “private”, “private

2.@Before Pre-notification

Enter the content at the beginning of the pointcut

Add a pre-notification to the test method in the previous AopTestController class

    @Before("cutOffPoint()")
    public void beforeTest(a){
        logger.info("I execute before the test method.");
    }
Copy the code

The value in @before here is the method name annotated by the pointcut

The icon that appears to the left of the method is followed by the method that you want to notify so that the configuration is correct, so let’s go to the browser and call the method

Think about where this effect can be used, imagine if you want to extend some code, without moving the source code on the basis of extension, deliciously

3.@After Post notification

Instead of pre-notification, execute after the pointcut

    @After("cutOffPoint()")
    public void doAfter(a){
        logger.info("I did it after test.");
    }
Copy the code

Console Execution result

Defining a notification here requires a restart of the startup class, whereas modifying the contents of the notification method is hot-deployable

4.@Around Surround notification

The proceed method is used to initiate the execution of the target method. Surround notification = pre + target method execution + post method execution

    ThreadLocal<Long> startTime = new ThreadLocal<>();
    @Around("cutOffPoint()")
    public Object doAround(ProceedingJoinPoint pjp){
        startTime.set(System.currentTimeMillis());
        logger.info("I'm executing around notification.");
        Object obj;
        try{
            obj = pjp.proceed();
            logger.info("Execute return value:" + obj);
            logger.info(pjp.getSignature().getName()+"Method execution time:" + (System.currentTimeMillis() - startTime.get()));
        } catch (Throwable throwable) {
            obj=throwable.toString();
        }
        return obj;
    }
Copy the code

Execution Result:

2. Log recording 3. Used to do global data cache 4. Global transaction processing, etc

5.@AfterReturning

This one executes after the pointcut returns the result, that is, all the prefixes and all the post-wraps are done

    /** * What can be done after the request is executed@param result
     * @throws Throwable
     */
    @AfterReturning(returning = "result", pointcut = "cutOffPoint()")
    public void doAfterReturning(Object result) throws Throwable {
        logger.info("Hi, I'm @afterreturning. They're all done. It's my turn.");
    }
Copy the code

The execution result

Application scenarios can be used to verify the results twice after the order payment is completed, verify the important parameters twice, and prevent the parameters from being modified during the execution of the method, etc

6.@AfterThrowing

This is executed when an error is reported in the entry execution

    // The throw type method specified when declaring error e must raise an exception of the specified type
    // The type of e is declared to be Throwable, and exceptions are not restricted
    @AfterThrowing(throwing = "e",pointcut = "cutOffPoint()")
    public void doAfterReturning(Throwable e) {
        logger.info("Hey guys, I'm @afterthrowing and I take the blame for the mistakes they make.");
        logger.info("Error message"+e.getMessage());
    }
Copy the code

Feel free to throw the whole thing out in other content, create an environment and here’s the result of @Afterthrowing

AOP is used for global exception handling

Define a pointcut to intercept a ResultBean or PageResultBean

    @Pointcut(value = "execution(public com.example.beans.PageResultBean *(..) ))")
    public void handlerPageResultBeanMethod(a) {}@Pointcut(value = "execution(public com.example.beans.ResultBean *(..) ))")
    public void handlerResultBeanMethod(a) {}Copy the code

Below is the AopController. Java

package com.example.aop;

import com.example.beans.PageResultBean;
import com.example.beans.ResultBean;
import com.example.entity.UnloginException;
import com.example.exception.CheckException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/ * * *@AspectNote defines this class as a faceted class * modified by ControllerAOP: https://xwjie.github.io/ */
@Aspect
@Component
public class AopController {

    private static final Logger logger = LoggerFactory.getLogger(AopController.class);

    ThreadLocal<ResultBean> resultBeanThreadLocal = newThreadLocal<>(); ThreadLocal<PageResultBean<? >> pageResultBeanThreadLocal =new ThreadLocal<>();
    ThreadLocal<Long> start = new ThreadLocal<>();

    /** * defines a pointcut */
    @Pointcut(value = "execution(public com.example.beans.PageResultBean *(..) ))")
    public void handlerPageResultBeanMethod(a) {}@Pointcut(value = "execution(public com.example.beans.ResultBean *(..) ))")
    public void handlerResultBeanMethod(a) {}@Around("handlerPageResultBeanMethod()")
    public Object handlerPageResultBeanMethod(ProceedingJoinPoint pjp) {
        start.set(System.currentTimeMillis());
        try{ pageResultBeanThreadLocal.set((PageResultBean<? >)pjp.proceed()); logger.info(pjp.getSignature() +"Method execution time :" + (System.currentTimeMillis() - start.get()));
        } catch(Throwable e) { ResultBean<? > resultBean = handlerException(pjp , e); pageResultBeanThreadLocal.set(new PageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));
        }
        return pageResultBeanThreadLocal.get();
    }

    @Around("handlerResultBeanMethod()")
    public Object handlerResultBeanMethod(ProceedingJoinPoint pjp) {
        start.set(System.currentTimeMillis());
        try{ resultBeanThreadLocal.set((ResultBean<? >)pjp.proceed()); logger.info(pjp.getSignature() +"Method execution time :" + (System.currentTimeMillis() - start.get()));
        } catch (Throwable e) {
            resultBeanThreadLocal.set(handlerException(pjp , e));
        }
        return resultBeanThreadLocal.get();
    }
    /** * Encapsulates the exception information, distinguishing between known exceptions (thrown by oneself) and unknown exceptions */
    privateResultBean<? > handlerException(ProceedingJoinPoint pjp, Throwable e) { ResultBean<? > result =new PageResultBean();
        logger.error(pjp.getSignature() + " error ", e);

        // There is a known exception
        if (e instanceof CheckException) {
            result.setMsg(e.getLocalizedMessage());
            result.setCode(ResultBean.FAIL);
        } else if (e instanceof UnloginException) {
            result.setMsg("Unlogin");
            result.setCode(ResultBean.NO_LOGIN);
        } else {
            result.setMsg(e.toString());
            result.setCode(ResultBean.FAIL);
        }
        returnresult; }}Copy the code

Using the wrap notification above, you can access all methods that return a ResultBean or PageResultBean. This way, you don’t need to catch errors at the business level, just print your own INFO log as shown in the code below

    @Transactional
    @Override
    public int insertSelective(Area record) {
        record.setAddress("test");
        record.setPostalcode(88888);
        record.setType(3);
        int i=0;
        try {
            i = areaMapper.insertSelective(record);
        }catch (Exception e){
            logger.error(AreaServiceImpl insertSelective error:+e.getMessage());
        }
        return i;
    }
Copy the code

What if the insert operation above failed and went wrong? Do you think it will roll back?

The answer is: no.

Why is that?

Because you catch errors, things don’t roll back until they detect exceptions.

So how do you roll back?

Add throw new RuntimeException() to catch.

However, so many business methods are added to the operation of each design modification, the code is cumbersome, how to handle it?

Here use the above AOP cut into processing, error does not need to pipe, throw directly, throw to the control layer for processing, so that when the interface call, error, the interface will not return anything, but will return to you error code, and error information, easy to check the developer

8. Log4j2 log processing is used above

First remove springBoot log processing added in build.gradle

configurations {
    providedRuntime
    // Remove SpringBoot logs
    all*.exclude group: 'org.springframework.boot'.module: 'spring-boot-starter-logging'
}
ext {
    springBootVersion = '2.0.1. RELEASE'
}
dependencies {
    compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"
}
Copy the code

Then add it to application.yml

# display mysql execution log
logging:
  level:
    com:
      example:
        dao: debug
  config: classpath:log4j2-spring.xml
Copy the code

log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"? >
<! -- Log levels and priorities: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<! If log4j2 is traced, you will see the details of the internal output of log4j2 -->
<! --monitorInterval: Log4j automatically detects changes to configuration files and reconfigures itself, sets the interval -->
<configuration status="INFO" monitorInterval="30">
    <! Define all appenders -->
    <appenders>
        <! -- Configuration of the output console -->
        <console name="Console" target="SYSTEM_OUT">
            <! -- Output log format -->
            <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
        </console>

        <! -- The file will print out all the information. This log will be automatically cleared every time the program runs, as determined by the append property.
        <File name="Test" fileName="logs/test.log" append="false">
            <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
        </File>

        <RollingFile name="RollingFileInfo" fileName="logs/log.log" filePattern="logs/info.log.%d{yyyy-MM-dd}">
            <! -- Only accept logs above level=INFO -->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <RollingFile name="RollingFileError" fileName="logs/error.log" filePattern="logs/error.log.%d{yyyy-MM-dd}">
            <! Log level=WARN only
            <Filters>
                <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
            </Filters>
            <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
                <SizeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

    </appenders>

    <! An appender will not work until logger is defined and appender is appended.
    <loggers>
        <! -- Filter out spring and Mybatis DEBUG messages -->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>
        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="Test"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>
Copy the code

Then add it to the class where you want to print logs

private static finalLogger Logger = LoggerFactory.getLogger(your class name.class);public static void main(String[] args) {
        logger.error("Error Level Logs");
        logger.warn("Warning Logs");
        logger.info("Info level logs");
    }
Copy the code

Log is very convenient, in your method to receive the object to print, and then execute the logic to print, error is very clear, you will rarely go to Debug, develop a good habit of typing more log, print more info level log, used in the development environment, Set the minimum level to warning when you go live so that your info level logs do not affect the printing of important bugs on your project

When I was writing this blog, I was running this project at the same time. Sometimes there would be some errors, such as JAR package version, invalid reference of business layer, ineffective AOP Settings and so on, and I was also troubleshooting. If you encounter the same error, you can contact me on GitHub. Thanks Github address: github.com/cuifuan