今日一说:
生活从来没有容易的事,
如果你感觉到轻松,
那一定是有人在为你负重前行。
愿我们最终都能实现自己的梦想!
——小美同学
AOP详细说明:
AOP为Aspect Oriented Programming的缩写,
意为:面向切面编程,
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,
AOP是OOP的延续,是软件开发中的一个热点,
也是Spring框架中的一个重要内容,
是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,
从而使得业务逻辑各部分之间的耦合度降低
提高程序的可重用性,同时提高了开发的效率。
使用AOP编程可以做哪些事情:
Spring AOP面向切面编程,
可以用来配置事务、
做日志、权限验证、
在用户请求时做一些处理等等。
用@Aspect做一个切面,就可以直接实现
使用AOP编程集成多数据源的maven仓库具体有哪些?
<!-- springboot-aop包,AOP切面注解,aspect等相关注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
step1、自定义注解接口类,设置默认数据源主库
我本地的包名(com.app.info)
创建datasource包,设置数据源主库从库
相关注解介绍:
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。
方法签名必须是 public及void型。
可以将Pointcut中的方法看作是一个被Advice引用的助记符,
因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,
而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行
DataSourceContextHolder
具体实现:
1、使用线程局部变量来存放数据源的名称
2、读写数据源、设置数据源,清空数据源
package com.egc.app.info.datasource;
import lombok.extern.slf4j.Slf4j;
/**
* 创建一个ThreadLocal类来存放数据源
* @author ZB
*/
@Slf4j
public class DataSourceContextHolder {
/**
* 设置写数据源
*/
public static final String MASTER_DATABASE = "MASTER_DATABASE";
/**
* 设置读数据源
*/
public static final String SLAVE_DATABASE = "SLAVE_DATABASE";
/**
* 设置一个线程局部变量来存放数据源的名称
*/
private static final ThreadLocal<String> contextLoader = new ThreadLocal<>();
/**
* set数据源
*
* @param dbName
*/
public static void setDB(String dbName) {
contextLoader.set(dbName);
}
/**
* 获取数据源
*
* @return
*/
public static String getDB() {
return contextLoader.get();
}
/**
* 清空数据源
*/
public static void clearDB() {
contextLoader.remove();
}
}
DS
具体实现:
1、定义自定义注解接口类,设置默认数据源主库
2、进行加载的时候,会第一时间先去加载主库的默认配置
package com.egc.app.info.datasource;
import java.lang.annotation.*;
/**
* @author ZB
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DS {
String value() default DataSourceContextHolder.MASTER_DATABASE;
}
step2、读取配置文件默认值,配置主从数据库
创建database包,设置数据源主库从库
application.properties
(备注:此处的用户名、密码、以及ip地址需根据自身情况
进行调整,此处仅做出参考)
#数据源1
spring.datasource.master.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.master.url = jdbc:mysql://192.168.1.152:3306/database1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.master.username = username1
spring.datasource.master.password = password1
#以下为连接池的相关参数配置
spring.datasource.master.max-idle = 10
spring.datasource.master.max-wait = 10000
spring.datasource.master.min-idle = 5
spring.datasource.master.initial-size = 5
spring.datasource.master.validation-query = SELECT 1
spring.datasource.master.test-on-borrow = false
spring.datasource.master.test-while-idle = true
spring.datasource.master.time-between-eviction-runs-millis = 18800
#数据源2
spring.datasource.slave.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.slave.url = jdbc:mysql://192.168.1.152:3306/database2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.slave.password = username2
spring.datasource.slave.username = password2
#以下为连接池的相关参数配置
spring.datasource.slave.max-idle = 10
spring.datasource.slave.max-wait = 10000
spring.datasource.slave.min-idle = 5
spring.datasource.slave.initial-size = 5
spring.datasource.slave.validation-query = SELECT 1
spring.datasource.slave.test-on-borrow = false
spring.datasource.slave.test-while-idle = true
spring.datasource.slave.time-between-eviction-runs-millis = 18800
DataSourceConfig
具体实现:
1、获取配置文件中spring.datasource.master
前缀的主数据库信息
2、获取配置文件中spring.datasource.slave
前缀的从数据库信息
3、设置了动态数据源交给AOP处理
这边使用AOP来管理数据源,设置为默认数据源
package com.egc.app.info.config.database;
import com.egc.app.info.datasource.DataSourceContextHolder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 配置数据源
* @author ZB
*/
@Configuration
public class DataSourceConfig {
/**
* 写数据源
*
* @return
*/
@Bean(DataSourceContextHolder.MASTER_DATABASE)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDatabase() {
return DataSourceBuilder.create().build();
}
/**
* 读数据源
*
* @return
*/
@Bean(DataSourceContextHolder.SLAVE_DATABASE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDatabase() {
return DataSourceBuilder.create().build();
}
/**
* 设置了动态数据源交给AOP处理
* 这边使用AOP来管理数据源,设置为默认数据源
*
* @return
*/
@Bean("dynamicDataSource")
@Primary
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//设置默认数据源,如果没有设置数据源,采用此数据源
dynamicDataSource.setDefaultTargetDataSource(masterDatabase());
//设置多数据源
Map<Object, Object> map = new ConcurrentHashMap<>();
map.put(DataSourceContextHolder.MASTER_DATABASE, masterDatabase());
map.put(DataSourceContextHolder.SLAVE_DATABASE, slaveDatabase());
//设置目标数据源
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}
DynamicDataSource
具体实现:
1、继承AbstractRoutingDataSource类,来重写数据源
获取方式
2、使用log打印当前数据源
package com.egc.app.info.config.database;
import com.egc.app.info.datasource.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 继承AbstractRoutingDataSource类,来重写数据源获取方式
* @author ZB
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//获取当前的数据源
String db = DataSourceContextHolder.getDB();
log.info("==> AbstractRoutingDataSource当前的数据源是:{}",db);
return db;
}
}
step3、创建切面类切换数据源
具体实现:
1、加载时需要获取当前的类,数据源是通过method方法获取的
2、切换数据源的时候,我们必须知道需要切换的数据源是多少
package com.egc.app.info.config.aop;
import com.egc.app.info.datasource.DS;
import com.egc.app.info.datasource.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 创建一个切面来切换数据源
* @author ZB
*/
@Aspect
@Component
@Slf4j
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.egc.app.info.datasource.DS)")
public void pointCut(){};
@Before("pointCut()")
public void beforeSwitchDS(JoinPoint joinPoint) {
/**
* 注意:采用倒推思想,前提是对反射原理一定要了解
*
* 1-->6.既然想切换数据源,至少我们要知道切换的数据源value是多少
* 2-->5.获取数据源的value,前提要获取当前的注解DS
* 3-->4.DS是通过method方法获取的
* 4-->3.所以先要获取当前的操作method aClass.method("方法名","参数");
* 5-->2.获取当前的类
* 6-->1.获取方法名是是什么,当前参数是什么
*
* 思路理清楚,开始写代码
*/
/**
* 1.获取当前的方法名、获取当前的参数列表
*
* 分析思路:
* joinPoint.getSignature 获取的是 Signature 对象
* MemberSignature 继承了 Signature对象
* CodeSignature 继承了 MemberSignature对象
* MethodSignature 继承了 CodeSignature
* 将Signature 向下转换为 MethodSignature 去获取 CodeSignature 的属性 getParameterTypes
* ok,开始
*/
String methodName = joinPoint.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
//2.获取当前的类
Class<?> aClass = joinPoint.getTarget().getClass();
//设置默认数据源
String dataBaseName = DataSourceContextHolder.MASTER_DATABASE;
try {
//3.通过aClass.method("方法名","参数");获取当前方法
Method method = aClass.getMethod(methodName, parameterTypes);
//判断当前类是不是存在DS注解
if (method.isAnnotationPresent(DS.class)) {
//4.存在,获取当前的注解
DS ds = method.getAnnotation(DS.class);
//5.获取value值
dataBaseName = ds.value();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//6.切换数据源
log.info("==> AOP切面before:切换数据源,{}", dataBaseName);
DataSourceContextHolder.setDB(dataBaseName);
}
@After("pointCut()")
public void afterSwitchDS(){
log.info("==> AOP切面after:清除数据源");
DataSourceContextHolder.clearDB();
}
}
执行效果:
常见错误解析:
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection
如果是阿里巴巴的数据源
数据配置这里需要更改为
spring.datasource.slave.jdbc-url
如果是其他的数据源必须更改为url
两个都可以尝试一下