Topic: Cyanosis
Preface
There was an issue when obtaining the bean object to operate on using SelectProvider in the previous article, which made it necessary to pass the generic object to every method in the DAO layer, which was highly unreasonable. Later, I referred to online tutorials and used AOP for aspect-oriented programming. The aspect method retrieves the current DAO content into ThreadLocal, and the data operation method gets the DAO content when executing.
AOP Working Principle Diagram
Main Content
Module Overview
| Name | Description |
|---|---|
| nott-mybatis-curd | MyBatis Basics |
| nott-mybatis-dynamic-datasource | MyBatis Dynamic Data Source |
| nott-web-test | Web Test Module |
The complete project files can be found in the GITHUB repository. Please leave a star if it's helpful to you.
Import Dependencies
Import the required ASPECT package in the root directory.
<dependency>
<groupid>org.aspectj</groupid>
<artifactid>aspectjweaver</artifactid>
<version>1.9.21.1</version>
<scope>runtime</scope>
</dependency>
Pointcut Configuration
Here we need to 'intercept' the methods under the base CommonMapper, whose package path is org.nott.mybatis.mapper, so the AOP expression should be execution(* org.nott.mybatis.mapper.*.*(..)), which means the Pointcut includes all methods under org.nott.mybatis.mapper, and any method under it is a JoinPoint. Then, before executing the method, put its current generic type into the required place, and the BaseSelectProvider will obtain the generic content and instantiate the bean when executing.
CommonMapperCreate an AOP-related configuration class under the curd module package to read the AOP expressions.
@Data @Component @ConfigurationProperties("nott.mybatis.aop") public class MybatisAopConfig {/** * Intercept base CommonMapper AOP expression */ private String baseAopPackageExpression = "execution(* org.nott.mybatis.mapper.*.*(..))"; /** * Custom added AOP expressions */ private String[] appendAopPackageExpression;
}
Create Information Storage Object
Our AOP Pointcut has been defined, now we need to define an information object that is stored before each method executes, to store the current mapper's class, executed method, parameters, etc.
@Data public class ExecuteMapperContextBean {/** * Current mapper's class */ private Class<!--?--> currentMapperClass; /** * Executed method */ private Method executeMethod; /** * Parameters */ private Parameter[] parameters;
}
Advice
So far, we have defined the method to obtain information and the object used for storage, next we will define how to get the method's content.
Create MybatisAopInterceptor that inherits from MethodInterceptor, get the currently executing method, convert it to ExecuteMapperContextBean and store it in ThreadLocal.
@Component public class MybatisAopInterceptor implements MethodInterceptor {private static final ThreadLocal<executemappercontextbean> mapperContextThreadLocal = new ThreadLocal<>(); public void set(MethodInvocation invocation){ ExecuteMapperContextBean bean = new ExecuteMapperContextBean(); Class<!--?--> aClass = Objects.requireNonNull(invocation.getThis()).getClass(); Class<!--?-->[] interfacesForClass = ClassUtils.getAllInterfacesForClass(aClass, aClass.getClassLoader()); Class<!--?-->[] interfaces = interfacesForClass[0].getInterfaces(); bean.setExtendMapperClass(interfaces[0]); bean.setCurrentMapperClass(interfacesForClass[0]); bean.setParameters(invocation.getMethod().getParameters()); bean.setExecuteMethod(invocation.getMethod()); mapperContextThreadLocal.set(bean); } @Nullable @Override public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable { // Store information this.set(invocation); Object result = null; try { // Execute JoinPoint method result = invocation.proceed(); return result; } catch (Exception e) { throw e; } finally { // Finally, clear the information in mapperContextThreadLocal mapperContextThreadLocal.remove(); } } public static ThreadLocal<executemappercontextbean> getContext(){ return mapperContextThreadLocal; }
}
In this way, we have defined the behavior of storing information when the DAO layer method executes, and its execution timing, now we just need to put the entire set of behaviors together.
PointcutAdvisor
Advisor is the top-level interface for AOP to manage Advice and Pointcut, its partial inheritance relationship is shown in the figure.
Here we need to set the properties of Advice and Pointcut, so we need to define the bean for AspectJExpressionPointcutAdvisor.
@Configuration @EnableConfigurationProperties(MybatisAopConfig.class) @RequiredArgsConstructor public class MapperContextAutoConfiguration {private final MybatisAopConfig mybatisAopConfig; @Bean public Interceptor setMybatisAopInterceptor(){ return new MybatisAopInterceptor(); } @Bean public PointcutAdvisor setMyBatisPointcutAdvisor(){ // Set interception expression (Pointcut) AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); String baseAopPackage = mybatisAopConfig.getBaseAopPackageExpression(); advisor.setExpression(baseAopPackage); String[] appendAopPackage = mybatisAopConfig.getAppendAopPackageExpression(); if (appendAopPackage != null && appendAopPackage.length > 0) { for (String packageExpression : appendAopPackage) { advisor.setExpression(packageExpression); } } // Execute action advisor.setAdvice(setMybatisAopInterceptor()); return advisor; }
}
You may notice that MybatisAopInterceptor is used as the parameter of the setAdvice(Advice advice) method, and it implements the MethodInterceptor interface. This diagram can explain why the Interceptor can be used as the parameter of setAdvice.
Verification
In the web module, create UserMapper that inherits from CommonMapper which acts as the JoinPoint. CommonMapper defines a general query method selectList
public interface UserMapper extends CommonMapper {
public interface CommonMapper {@SelectProvider(type = BaseSelectProvider.class,method = "selectList") public List<t> selectList();
}
Set a breakpoint in MybatisAopInterceptor. If the execution is correct, the program will pause here. At this point, the set method has already put the information into ThreadLocal, and you can see the currently executed method's information via debug tracing.
Write test class
Breakpoint
Summary
What you learn from books is superficial; you must practice it to understand it thoroughly.
I used to watch AOP tutorials online and was completely confused, the content was very abstract. When I actually used it, I realized that the design is very clever, with many tricks worth learning. During actual use, I also referred to many relevant online tutorials and learned many unexpected things.
This is a discussion topic separated from the original topic at https://juejin.cn/post/7368669650576867337






