乐趣区

Spring-Boot中自定义注解AOP实现主备库切换

摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用 TDDL 实现分表分库,需求: 实现关键业务的查询监控, 当用 Mybatis 查询数据时需要从主库切换到备库或者直接连到备库上查询, 从而减小主库的压力,在本篇文章中主要记录在 Spring Boot 中通过自定义注解结合 AOP 实现直接连接备库查询。

一. 通过 AOP 自定义注解实现主库到备库的切换

1.1 自定义注解

自定义注解如下代码所示

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SwitchDataBase {boolean switch2Backup() default false;
}

1.2 实现方法拦截器对自定义注解处理

import java.lang.reflect.Method;
import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 处理走备库逻辑的注解
 */
@Component
public class SwitchDataBaseInterceptor implements MethodInterceptor {private final Logger log = LoggerFactory.getLogger(SwitchDataBaseInterceptor.class);

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();
        SwitchDataBase annotation = getAnnotation(method);
        if (annotation == null) {return invocation.proceed();
        }
        Object val = null;
        if(!ThreadLocalMap.containsKey(GroupDataSourceRouteHelper.DATASOURCE_INDEX)) {if (annotation.switch2Backup()) {log.info("query back up DB, method:" + method.getName());
                GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(1, true);
            } else {log.info("query primary DB, method:" + method.getName());
                GroupDataSourceRouteHelper.executeByGroupDataSourceIndex(0, true);
            }
        }
        try {val = invocation.proceed();
        } catch (Exception e) {log.info(method.getDeclaringClass().getName() + "." +
                    invocation.getMethod().getName() + "方法调用失败,arguments:" +
                    Arrays.toString(invocation.getArguments()));
            throw new RuntimeException(e);
        } finally {GroupDataSourceRouteHelper.removeGroupDataSourceIndex();
        }

        return val;
    }

    /**
     * 找方法上面声明的注解
     */
    private SwitchDataBase getAnnotation(Method method) {if (method.isAnnotationPresent(SwitchDataBase.class)) {return method.getAnnotation(SwitchDataBase.class);
        }
        return null;
    }

}

1.3 配置 OverallQueryConfiguration

在 Spring Boot 中装配 AOP Bean,实现扫描特定目录下的注解,实现切面变成形成通知处理。示例代码如下

import com.wdk.wms.annotation.SwitchDataBaseInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwitchDataBaseConfiguration {@Bean(name = "overallQueryInterceptor")
    public SwitchDataBaseInterceptor overallQueryInterceptor() {return new SwitchDataBaseInterceptor();
    }


    // 添加 aop 的 pointcut
    @Bean(name = "jdkRegexpMethodPointcut")
    public JdkRegexpMethodPointcut jdkRegexpMethodPointcut() {JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
        jdkRegexpMethodPointcut.setPatterns("com.wdk.wms.mapper.*");
        return jdkRegexpMethodPointcut;
    }

    // 设置默认的 aop 配置对应的是原来的 <aop:advisor>
    @Bean
    public Advisor druidAdvisor() {DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
        defaultPointcutAdvisor.setPointcut(jdkRegexpMethodPointcut());
        defaultPointcutAdvisor.setAdvice(overallQueryInterceptor());
        return defaultPointcutAdvisor;
    }

}

1.4 如何使用注解从主库到备库的切换

@SwitchDataBase(switch2Backup = true)
List<ConsumerNoticeMsg> listByTemplateOver3(@Param("templates") List<Integer> templates);
退出移动版