代理模式我们会先讲一下静态代理,然后讲一下动态代理,为了能从JDK的静态代理和动态代理之中体会他们之间的不同,
加深对他们的理解,在这里面引入的业务场景,是你们非常感兴趣的,也就是说我们会引入一个分库的业务场景,那这里面
会简单扩展一下,Spring的分库使用方式,并不会重点的来讲Spring的分库,而是重点来讲我们的静态代理和动态代理,
毕竟我们还是基于JDK模式的课程,但是我相信你们,如果这章节理解透彻的话,自己也是能实现的,首先我们找到这个包,
在结构型里面创建这个包,proxy,代理包,这里面有个实体,Order
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
我们还会配置默认的目标defaultTargetDataSource,其他的我们先不展开来讲,
看源码的时候,再分享一个看源码的技巧,很多类都是分在不同包的,我们看一个大包的时候怎么看呢,;里面有这种package,
我们右键点这个展开节点,当然节点也可以收缩回去,把各项属性都打开,还有依赖,上面这里非常清晰,代理和具体被代理的目标对象
首先这两个类实现DAO层,和service层的接口,然后service的实现里边,组合了OrderDao这个接口,
向左看,静态代理类里面,组合了OrderService这个接口,也就是这个,两个类实现对应的接口
然后service的实现组合了OrderDao,OrderServiceStaticProxy组合了OrderService,同时又创建了
OrderService的一个实现,为了增强目标对象,那这个静态代理类,到此就讲完了,希望你们能吸收好,
理解好,后续我们在使用各种框架,代理相关的类库的时候,这样才会融会贯通
package com.learn.design.pattern.structural.proxy;
/**
* 那如果我们使用Mybatis的话
* 这个类一般叫IOrderMapper
* 或者OrderMapper
* 只不过我们现在来手写
*
*
* @author Leon.Sun
*
*/
public interface IOrderDao {
/**
* DAO层肯定是和业务无关的
* 只是一个insert
* 把class改成interface
* 接下来我们写一个DAO层的实现
* OrderDaoImpl
*
*
* @param order
* @return
*/
int insert(Order order);
}
package com.learn.design.pattern.structural.proxy;
/**
* @author Leon.Sun
*
*/
public interface IOrderService {
/**
* 这里面有一个方法
* saveOrder
* 里面传一个实体Order
* 保存这个订单
* 再写一个DAO层的接口
*
* 我们要增强这个方法
* 所以我们使用同样的方法名
*
*
*
* @param order
* @return
*/
int saveOrder(Order order);
}
package com.learn.design.pattern.structural.proxy;
/**
* 订单
*
* 我们现在有两个库
* 那因为我们有两个库
* 所以索引一个是0
* 一个是1
* 我们使用userId对2进行取模
* 用余数对数据库dataSource声明的后缀
* 假设在我们生成订单的时候
* 是必须要登陆的
* 必须获取userId
* 那这里面是为了简化
* 我们代理模式的讲解
* 我们关注点还是要放在代理模式上
* 那现在我们创建一个包
* 为了区分
* staticproxy静态代理包
*
*
* @author Leon.Sun
*
*/
public class Order {
/**
* 订单就有订单数据
* 这个订单属于哪个用户呢
*
*
*/
private Object orderInfo;
/**
* 所以我们关联一个userId
* 那这里不用纠结
* 是使用Integer还是String
* 还是long类型
* 然后写一下他的get/set方法
* 然后这个实体就写完了
* 那当然我们在存储订单的时候
* 也可以通过service层来接收
* user对象和Order对象
* 然后再进行处理
* 只不过我们这里把两个属性合成一个
* Order这个类当中
* 也是为了方便
* 接下来我们还要写一下他的service层
* 我们直接声明一个service的接口
* IOrderService
*
*
*/
private Integer userId;
public Object getOrderInfo() {
return orderInfo;
}
public void setOrderInfo(Object orderInfo) {
this.orderInfo = orderInfo;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
package com.learn.design.pattern.structural.proxy;
/**
* 实现IOrderDao
* 然后实现里面的方法
*
*
* @author Leon.Sun
*
*/
public class OrderDaoImpl implements IOrderDao {
@Override
public int insert(Order order) {
/**
* 这个很简单
* 我们就认为
* 和DB的交互就在这里面
* 所以呢我们直接输出
* Dao层添加Order成功
* return 1
* 返回生效函数为1行
* 代表Order插入数据库成功
* 然后我们再写一个类
* 当然是Service的实现
*
*
*/
System.out.println("Dao层添加Order成功");
return 1;
}
}
package com.learn.design.pattern.structural.proxy;
/**
* 他来实现IOrderService
* 实现IOrderService里的方法
* 当然我们现在这个分层比较简单
* Service下边是DAO
* 那在Service之上是Controller
* 那也有业务逻辑复杂的情况下呢
* 加一个Manager层
* Manager位于DAO层之上
*
*
* @author Leon.Sun
*
*/
public class OrderServiceImpl implements IOrderService {
/**
* 把DAO层注入进来
* 只不过我们这里并没有集成Spring
* 所以像@Autowired和@Resource我们并不用加
* 然后我们会通过人工注入的方式
* 把OrderDao注入进来
* 那我们写一个Service的实现
*
*/
private IOrderDao iOrderDao;
@Override
public int saveOrder(Order order) {
//Spring会自己注入,我们课程中就直接new了
/**
* 我们课程中就直接new了
* 直接new出来
* DAO层的implement
* 当然如果这一行使用Spring容器管理的话
* 我们就不用显示的来写了
* 或者在应用层使用set方法注入进来
* 或者通过构造器注入也是OK的
* 我们为了简单
* 直接在这里new了
* 但是我们实际在也业务的时候
* 是不应该这么写的
* 因为这样DAO层会new很多对象
* 那对于DAO层来说
* 保持一个单例是OK的
*
*
*/
iOrderDao = new OrderDaoImpl();
/**
* 然后我们输出
* service层调用DAO添加Order
*
*
*/
System.out.println("Service层调用Dao层添加Order");
/**
* DAO层调用insert方法
* 把Order传进来
* 那这个Service实现就写完了
* 那我们再看一下包
* 这个包的类是非常的common的
* 实体
* DAO层
* service层
* 只不过我们讲设计模式都放在这个包下了
* 下面我们就不分包了
* 那现在我们要进行分库了
* 怎么分库呢
* 我们看一下Order
*
*
*/
return iOrderDao.insert(order);
}
}
package com.learn.design.pattern.structural.proxy.staticproxy;
import com.learn.design.pattern.structural.proxy.IOrderService;
import com.learn.design.pattern.structural.proxy.Order;
import com.learn.design.pattern.structural.proxy.OrderServiceImpl;
import com.learn.design.pattern.structural.proxy.db.DataSourceContextHolder;
/**
* 这个类是静态代理类
* OrderService的静态代理
* 这里面也很简单
* 我们首先在这个静态代理类里边
*
*
* @author Leon.Sun
*
*/
public class OrderServiceStaticProxy {
/**
* 来注入目标对象
* 那这里面的IOrderService
* 是目标对象
* 我们要对saveOrder方法
* 进行增强
* 那我们来到orderService里边
*
*
*/
private IOrderService iOrderService;
/**
* 当然使用不同的方法名也是OK的
* 这里面并没有严格的限制
* 写一个before和after
* 那后面我们讲源码解析的时候
* 也会领着大家来看一下
* 那就是Spring里面的beforeAdvice
* 在AOP包下的
* 还有afterAdvice
* 只不过我们通过静态代理
* 非常简单的一个实现
* 所以我们声明两个方法
* 一个beforeMethod
* 还有一个
* afterMethod
* 直接输出
* 输出什么呢
*
*
* @param order
* @return
*/
public int saveOrder(Order order){
/**
* 首先调用beforeMethod
*
*
*/
beforeMethod(order);
/**
* 先把这个Service new出来
* 这里面和DAO层一样
* 如果使用Spring容器的话就不需要显示的来new了
* 那现在我们获取userId
*
* service的实现
* 如果使用Spring容器的话
* 这里面不需要显示的来new他
*
*
*/
iOrderService = new OrderServiceImpl();
/**
* 调用saveOrder方法
* 然后把Order传进来
* Spring的分库实现
* 首先我们创建一个包
* 这个包叫db
* 这里面我们创建一个类
* DynamicDataSource
* 动态的数据源
*
*
*/
int result = iOrderService.saveOrder(order);
afterMethod();
return result;
}
/**
* 执行saveOrder之前要执行的
*
* 那首先在saveOrder之前
* 我们调用一下beforeMethod
* 对saveOrder进行一个增强
*
*
* @param order
*/
private void beforeMethod(Order order){
/**
* order获取userId
*
*
*
*/
int userId = order.getUserId();
/**
* 获得DB的路由编号
* 用userId对2进行取模
* 这样只会得到0或者1
*
* 在这里取完模之后呢
* 我们就要写一段代码
* 设置dataSource
* 怎么设置呢
*
* 然后对2取模
* 2对2取余余数是0
* 所以dbRouter是0
*
*
*/
int dbRouter = userId % 2;
/**
* 然后我们输出一下
* 静态代理增强了OrderService的实现
* 把DB切到DB Router这个上
* db0或者db1
*
* 输出这一行
*
*
*/
System.out.println("静态代理分配到【db"+dbRouter+"】处理数据");
//todo 设置dataSource;
/**
* 我们直接调用DataSourceContextHolder
* 我们自己写的
* 然后setDBType
* 把什么传进来呢
* 把dbRouter
* 只不过我们现在这里没有和Spring容器集成
* String类型的
* String.valueOf
* 那这个静态代理类就写完了
* 根据userId的不同
* 插入到不同的DB当中
* 一个是db0
* 一个是db1
* 那我们现在来到静态代理这个包下
* 写一个Test
*
* 然后把DataSourceContextHolder里面的DBType
* 设置为0
* 当然这里讲代理模式的时候
* 这一行执不执行都没关系
* 只不过这里面只是为了扩展
* 所以分库的一些知识点
* 调一下他的getDBType
* 可以看到这里面是0
* 那这里面是有问题的
* 这里面要存的是db0
* 也就是说在写分库的时候
* 这里很容易忽略
* 里面的dbType和我们配置文件里面的是一样
* 这里不要出bug
* 而我在这里面就做了一个反面教材
*
*
*
*/
DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));
System.out.println("静态代理 before code");
}
/**
* 这个是执行saveOrder之后要执行的代码
*
*
*/
private void afterMethod(){
System.out.println("静态代理 after code");
}
}
package com.learn.design.pattern.structural.proxy.db;
/**
* 创建这么一个类
* 那么这个类的核心就是ThreadLocal
* 那在讲单例的时候
* 我们也讲过ThreadLocal
* 他是线程和线程之间隔离的
* 里面是通过一个ThreadLocalMap
* 来进行实现的
* 那我们来写一下
*
*
*
* @author Leon.Sun
*
*/
public class DataSourceContextHolder {
/**
* 现在我们来声明一个
* 这里面就放String就可以了
* 里面要放的值正是DataSource的beanName
* 那一会我们会模拟配置一下
* 保证你们能理解
* 我们声明一些上下文的holder
* CONTEXT_HOLDER
* 等于什么呢
* new ThreadLocal<String>
* 也是String泛型的
* 那这里面我们再开放两个方法
* 静态方法就可以了
*
*
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();
/**
* 里面传一个String的dbType
* 很简单
* 我们向ThreadLocal里面放这个值
*
*
* @param dbType
*/
public static void setDBType(String dbType){
CONTEXT_HOLDER.set(dbType);
}
/**
* 同时我们再返回一个dbType
* 返回值就是String类型
* 那这里就不需要入参了
* 直接调用ThreadLocal的get方法
* 对他进行强转
* 因为我们放的就是String类型
* 当然这里不强转也是OK的
* 如果我们使用Object泛型的话
* 那这里就一定要强转
* 只不过我们这里使用的是String类型
* 不强转也是OK的
* 对于dataSource的上下文holder
* 我们还可以清除
*
*
*
* @return
*/
public static String getDBType(){
return (String)CONTEXT_HOLDER.get();
}
/**
* 这里面也很简单
* 调用上下文的remove方法
* 那就把ThreadLocal里面包含的
* map里面的值
* 清除掉了
* 也就是说在执行DAO层之前
* 如果我们通过setDBType
* 设置了这个DB为db0
* 或者db1
* 那么下面的DAO层就会连接对应的数据库
* 那么这个db0和db1呢
* 就是Spring容器中
* 我们在配置里面
* 声明的beanId
* 那么接着回来
*
*
*
*/
public static void clearDBType(){
CONTEXT_HOLDER.remove();
}
}
package com.learn.design.pattern.structural.proxy.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 用它来继承AbstractRoutingDataSource
* 这个类是Spring里面提供的
* 然后我们实现一下具体的方法
* 注意这个方法是protected
* 返回值是一个Object
* 那这里面的返回值就代表着
* 当前线程是哪个DB
* 于是我们要写一个dataSource上下文
* 我们再创建一个类
* DataSourceContextHolder
*
* DynamicDataSource这个类里面
* 写他的实现
* 那这个实现非常的简单
* 我们可以直接return
* return什么呢
*
* 这一块可以跳过
* 这里都没有关系的
* 只不过想扩展来讲解一下
* Spring的一个分库
* 对分库都是非常感兴趣的
* 而我们对原始的实现方案
* 来实现分库
* 这样我们的基础就会比较牢固
* 后续再学习应用层的
* 分库工具的时候
* 也会得心应手
* 那基础还是非常重要的
* 正如我们设计模式课程一样
* 他就是一个编程的内功
* 非常值得温故而知新
* 那每当工作一段时间回头看设计模式的时候
* 都会有一种新的体会
* 一时半会理解不了
* 没有关系
* 把能理解的理解了
* 理解不了的过一阵再回头来看
* 那现在的Spring分库就实现了
* 那具体这些是如何对应的呢
*
*
*
* @author Leon.Sun
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
/**
* return DataSourceContextHolder.getDBType
* 把这个值返回回去
* 那
*
*
*/
return DataSourceContextHolder.getDBType();
}
/**
* 关于bean的配置
* 这个就是在Spring当中DB的配置
* 所以这个dataSource我们要声明db0
*
*
*/
// <bean id="db0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
// <property name="driverClassName" value="${db.driverClassName}"/>
// <property name="url" value="${db.url}"/>
// <property name="username" value="${db.username}"/>
// <property name="password" value="${db.password}"/>
// <!-- 连接池启动时的初始值 -->
// <property name="initialSize" value="${db.initialSize}"/>
// <!-- 连接池的最大值 -->
// <property name="maxActive" value="${db.maxActive}"/>
// <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
// <property name="maxIdle" value="${db.maxIdle}"/>
// <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
// <property name="minIdle" value="${db.minIdle}"/>
// <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 -->
// <property name="maxWait" value="${db.maxWait}"/>
// <!--#给出一条简单的sql语句进行验证 -->
// <!--<property name="validationQuery" value="select getdate()" />-->
// <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/>
// <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 -->
// <!--<property name="removeAbandoned" value="true" />-->
// <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 -->
// <!--<property name="removeAbandonedTimeout" value="120" />-->
// <!-- #连接的超时时间,默认为半小时。 -->
// <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
//
// <!--# 失效检查线程运行时间间隔,要小于MySQL默认-->
// <property name="timeBetweenEvictionRunsMillis" value="40000"/>
// <!--# 检查连接是否有效-->
// <property name="testWhileIdle" value="true"/>
// <!--# 检查连接有效性的SQL语句-->
// <property name="validationQuery" value="SELECT 1 FROM dual"/>
// </bean>
/**
* 然后再copy一份过来
* 如果我们要在Spring里面实现分库的话
* 刚刚也说了
* 要把sqlSessionFactory里面的dataSource
* property指定成dataSource的集合
*
* 这个是db1
* 这里面包括url
* 包括账号密码
* 端口等等
* 都可以根据实际的需要来修改了
* 首先这个beanId是db0
* 这个beanId是db1
* 然后下面的dataSource
*
*
*/
// <bean id="db1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
// <property name="driverClassName" value="${db.driverClassName}"/>
// <property name="url" value="${db.url}"/>
// <property name="username" value="${db.username}"/>
// <property name="password" value="${db.password}"/>
// <!-- 连接池启动时的初始值 -->
// <property name="initialSize" value="${db.initialSize}"/>
// <!-- 连接池的最大值 -->
// <property name="maxActive" value="${db.maxActive}"/>
// <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
// <property name="maxIdle" value="${db.maxIdle}"/>
// <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
// <property name="minIdle" value="${db.minIdle}"/>
// <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 -->
// <property name="maxWait" value="${db.maxWait}"/>
// <!--#给出一条简单的sql语句进行验证 -->
// <!--<property name="validationQuery" value="select getdate()" />-->
// <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/>
// <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 -->
// <!--<property name="removeAbandoned" value="true" />-->
// <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 -->
// <!--<property name="removeAbandonedTimeout" value="120" />-->
// <!-- #连接的超时时间,默认为半小时。 -->
// <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
//
// <!--# 失效检查线程运行时间间隔,要小于MySQL默认-->
// <property name="timeBetweenEvictionRunsMillis" value="40000"/>
// <!--# 检查连接是否有效-->
// <property name="testWhileIdle" value="true"/>
// <!--# 检查连接有效性的SQL语句-->
// <property name="validationQuery" value="SELECT 1 FROM dual"/>
// </bean>
/**
* 这里是一个dataSource
*
*
*/
// <bean id="dataSource" class="com.learn.design.pattern.structural.proxy.db.DynamicDataSource">
/**
* 声明了targetDataSources
* 里面注入了targetDataSources
* 复数的一个property
* 然后赋值了里面的db0和db1
* 注意依赖的是db0
* 也就是依赖于上面的beanId为db0和db1
* 一般我们把key和ref声明成一样的
* 那这个key就是CONTEXT_HOLDER里面放的
* dbType
* 对于这个动态的dataSource来说
* return DataSourceContextHolder.getDBType()
* 那就是这里的返回值
* 我们要在DataSourceContextHolder里面放的就是db0和db1
* 接着往下看
* 上边OK了
*
*
*/
// <property name="targetDataSources">
// <map key-type="java.lang.String">
// <entry value-ref="db0" key="db0"></entry>
// <entry value-ref="db1" key="db1"></entry>
// </map>
// </property>
/**
* 然后默认的dataSource
* 也就是我们如果不指定的话
* 默认就是这个db0这个beanId
*
*
*/
// <property name="defaultTargetDataSource" ref="db0"></property>
// </bean>
/**
* 注意看这里面的sqlSessionFactory
* 这里面加了分页的插件
* 但是我们主要关注的是dataSource
* 注入的这个property
* 那接下来由于分库的原因
* 这个dataSource就不能具体的指定到具体的某一个数据库
* 而是要指定到数据库的集合
* 并且配置他默认db的beanId
*
* 下边sqlSessionFactory
* 也就是说mybatis的sqlSessionFactory
* ref依赖的就是dataSource
* dataSource里边有两个DB
* 那这些xml配置就放到这里边
* 供你们自己尝试使用
* 作为一个代理模式的一个扩展
* 那这一块也是特意加的
* 为了你们的提高
*
*
*
*/
// <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
// <property name="dataSource" ref="dataSource" />
// </bean>
}
package com.learn.design.pattern.structural.proxy.staticproxy;
import com.learn.design.pattern.structural.proxy.Order;
/**
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) {
Order order = new Order();
order.setUserId(2);
OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
orderServiceStaticProxy.saveOrder(order);
}
}