Static proxy proxy mode coding-

代理模式我们会先讲一下静态代理,然后讲一下动态代理,为了能从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);
    }
}

 

Guess you like

Origin blog.csdn.net/Leon_Jinhai_Sun/article/details/91376870