码迷,mamicode.com
首页 > 其他好文 > 详细

Zebra DAO 工作原理

时间:2016-07-17 17:05:39      阅读:726      评论:0      收藏:0      [点我收藏+]

标签:

Zebra-Dao是为了解决用户访问数据库进行一步方式而封装的Dao,目前java异步访问方式主要有线程池技术、消息中间件技术等。
**目前数据库异步访问方案主要有以下两种。
1.业务在使用时自己将每一次的dao调用放到异步线程池中去。 优点:简单,不需要架构支持什么。缺点: 因为是业务迁移,迁移的代价比较大。
2.使用google的async-mysql-connector。优点:背后实现是基于NIO的方式,性能更高。缺点:异步的jdbc接口上层没有任何的DAO框架支持,公司业务基本无法使用。**
而目前常用的DAO框架mybatis和hibernate都是基于同步方式访问数据库,一旦数据库操作时间过长,线程将会一直阻塞下去。这对一些要求时效比较快的业务显然无法满足。因此Zebra尝试在mybatis基础上异步化封装。Zebra主要基于在Spring-mybatis基础上做了相应的封装,低层仍然采用mybatis。下面将对Zebra-Dao具体封装过程及原理做简要分析。
Zebra的核心配置类是ZebraMapperScannerConfigurer ,该类在MapperScannerConfigurer基础上增减了线程池配置功能,因为Zebra-Dao是基础线程池来进行异步化封装。该类实
现了BeanDefinitionRegistryPostProcessor, InitializingBean,ApplicationContextAware, BeanNameAware这几个接口,通过实现这几个接口,在Spring初始化过程中为Mapper接口创
建了动态代理类,并将其注册到Spring容器中。事实上Zebra-Dao为Mapper对象重新创建了一次动态代理过程,在分析Zebra-Dao过程中发现,原有Spring-mybatis其实已经为接口创建
了动态代理,更准确地讲是Spring-mybatis是动态地实现了Mapper接口,深入源码中发现Spring-mybatis并没有在代理类中调用 method.invoke(object)方法。Zebra-Dao在Spring-mybatis
动态实现接口的基础上获取起实现接口对象,在该对象的基础上进一步利用JDK动态代理技术将spring-mybatis动态实现的接口对象创建了代理类,并在代理类中进行异步化操作数据库。
实际使用接口时,将会将会调用代理类的method.invoke(object)。在改方法中Zebra-Dao将操作数据库封装成线程扔给线程池去执行,从而实现了异步化处理。下面本文对整个初始化过程中
源码进行浅要分析。
下面将以UserMapper接口作为示例。

public interface UserMapper {

   public void findUserById(@Param("userId") int userId, AsyncDaoCallback<UserEntity> callback);

   public UserEntity findUserById(@Param("userId") int userId);

   @TargetMethod(name = "findUserById")
   public Future<UserEntity> findUserByIdByFuture(@Param("userId") int userId);

   public List<UserEntity> getAll();

   @TargetMethod(name = "getAll")
   public Future<List<UserEntity>> getAll1();

   public int insertUser(UserEntity user);

   @TargetMethod(name = "insertUser")
   public void insertUser1(UserEntity user, AsyncDaoCallback<Integer> callback);

   @TargetMethod(name = "insertUser")
   public Future<Integer> insertUser2(UserEntity user);

}

在Zebra-Dao的使用过程中需要对ZebraMapperScannerConfigurer进行配置,与spring-mybatis唯一区别的是需要配置线程池。

<bean class="com.dianping.zebra.dao.mybatis.ZebraMapperScannerConfigurer">
 <property name="basePackage" value="com.dianping.zebra.dao.mapper" />
 <property name="corePoolSize" value="${zebra-dao.executepool.corePoolSize}"></property>
</bean>

ZebraMapperScannerConfigurer重新实现了Spring-mybatis中的MapperScannerConfigurer类,该类的主要功能是将base 包中搜索所有下面所有 interface,并将其注册到 Spring Bean容器中,其注册的class bean是ZebraMapperFactoryBean。 下面将对这个类中的重要方法做一个介绍。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
   if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
 }
 ZebraClassPathMapperScanner scanner = new ZebraClassPathMapperScanner(registry);
 scanner.setAddToConfig(this.addToConfig);
 scanner.setAnnotationClass(this.annotationClass);
 scanner.setMarkerInterface(this.markerInterface);
 scanner.setSqlSessionFactory(this.sqlSessionFactory);
 scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
 scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
 scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
 scanner.setResourceLoader(this.applicationContext);
 scanner.setBeanNameGenerator(this.nameGenerator);
 scanner.registerFilters();
 //为接口创建了动态代理
 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
 ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

该方法将在spring初始化时执行。主要用于是搜索 base packages 下的所有mapper class,并将其注册至 spring 的 benfinitionHolder中。 其中主要方法在scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));执行,该方法调用了父类ClassPathBeanDefinitionScanner的scan方法,其中csan方法中调用了doScan(basePackages);改方法
被protected修饰,用于子类实现,在Zebra-Dao中设计了ZebraClassPathMapperScanner类,该类重写了父类ClassPathBeanDefinitionScanner的doSan(basePackages)方法。

@Override
 public Set<BeanDefinitionHolder> doScan(String... basePackages) {
 /**调用父类方法获取制定包下面所有接口的BeanDefinitionHolder对象,在Spring初始化Bean过程,Spring 首先会将需     要初始化的所有class先通过BeanDefinitionRegistry进行注册,并且将该Bean的一些属性信息(如scope、className、beanName等)保存至BeanDefinitionHolder中;Zebra-Dao这里首先会调用Spring中的ClassPathBeanDefinitionScanner.doScan方法,将所有Mapper接口的class注册至BeanDefinitionHolder实例中,然后返回一个Set<BeanDefinitionHolder>,其它包含了所有搜索到的Mapper class BeanDefinitionHolder对象。*/
 Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
 if (beanDefinitions.isEmpty()) {
         logger.warn("No MyBatis mapper was found in ‘" + Arrays.toString(basePackages)
               + "‘ package. Please check your configuration.");
 } else {
 /** 中for循环中替换当前所有 holder的 className为ZebraMapperFactoryBea.class,并且将 mapper interface   的class name setter 至 MapperFactoryBean属性为 mapperInterface 中*/
 for (BeanDefinitionHolder holder : beanDefinitions) {
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
 if (logger.isDebugEnabled()) {
               logger.debug("Creating MapperFactoryBean with name ‘" + holder.getBeanName() + "‘ and ‘"
 + definition.getBeanClassName() + "‘ mapperInterface");
 }
 //把接口的类型设置进去
 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
 //设置Bean的真实类型ZebraMapperFactoryBean
 definition.setBeanClass(ZebraMapperFactoryBean.class);
 //是否把Mapper接口加入到Mybatis的Config当中去
 definition.getPropertyValues().add("addToConfig", this.addToConfig);
 boolean explicitFactoryUsed = false;
 //如果sqlSessionFactoryBeanName的名字不为空 则在Spring容器中查询
 //适合多数据源
 if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory",
 new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
 explicitFactoryUsed = true;
 } else if (this.sqlSessionFactory != null) {
               definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
 explicitFactoryUsed = true;
 }
 //如果sqlSessionTemplateBeanName的名字不为空 则在Spring容器中查询,适合多数据源
 if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
       if (explicitFactoryUsed) {
           logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
 }
 definition.getPropertyValues().add("sqlSessionTemplate",
 new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
 explicitFactoryUsed = true;
 } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
           logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
 }
               definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
 explicitFactoryUsed = true;
 }
        if (!explicitFactoryUsed) {
            if (logger.isDebugEnabled()) {
                  logger.debug("Enabling autowire by type for MapperFactoryBean with name ‘" + holder.getBeanName()
                        + "‘.");
 }
 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
 }
         }
      }

      return beanDefinitions;
 }

到此为止Zebra-Dao的初始化基本完成。当我们使用Mapper接口注入实例时,由于Zebra-Dao给接口注册的都是ZebraMapperFactoryBean,因此Mapper接口实例将需要ZebraMapperFactoryBean
来创建。下面将进入到ZebraMapperFactoryBean类中,有个方法checkDaoConfig()

@Override
protected void checkDaoConfig() {
   super.checkDaoConfig();
 notNull(this.mapperInterface, "Property ‘mapperInterface‘ is required");
 Configuration configuration = getSqlSession().getConfiguration();
 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
         //将mapper接口添加到全局配置对象中;
 configuration.addMapper(this.mapperInterface);
 } catch (Throwable t) {
         logger.error("Error while adding the mapper ‘" + this.mapperInterface + "‘ to configuration.", t);
 throw new IllegalArgumentException(t);
 } finally {
         ErrorContext.instance().reset();
 }
   }
}

该方法将会在spring初始化后执行,因为该方法重写了父类SqlSessionDaoSupport中checkDaoConfig() 方法,而SqlSessionDaoSupport实现了抽象类中DaoSupport抽象方法checkDaoConfig(),DaoSupport同时也实现了InitializingBean接口并在接口方法中调用了抽象方法checkDaoConfig(),因此spring初始化后最终调用的是上述方法。上面方法中先会检测当前需要创建的 mapperInterface在全局的 Configuration中是否存在,如果不存在则添加。该方法调用完成之后,就开始调用 getObject方法来产生mapper实例,看到ZebraMapperFactoryBean.getObject的该方法代码如下:

public T getObject() throws Exception {
   T mapper = getSqlSession().getMapper(this.mapperInterface);

   AsyncMapperProxy<T> asyncMapperProxy = new AsyncMapperProxy<T>(mapper);

   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {      mapperInterface }, asyncMapperProxy);
}
/**在SqlSession中的getMapper()方法中继续调用getConfiguration().getMapper(type, this);跟踪进去发现接着调用了mapperRegistry.getMapper(type, sqlSession); 方法,再跟踪进去将会发现调用了下面方法*/

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
       return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

改方法中获取到了Mapper接口实例对象,该对象是一个代理对象。进一步深入可以发现在代理对象中执行了此方法操作数据库

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
 }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
 return mapperMethod.execute(sqlSession, args);
}

到此为止我们获得了接口的代理对象,对此,进一步回到

public T getObject() throws Exception {
   T mapper = getSqlSession().getMapper(this.mapperInterface);

 AsyncMapperProxy<T> asyncMapperProxy = new AsyncMapperProxy<T>(mapper);

 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, asyncMapperProxy);
}

方法中,此时我们的mapper对象就是接口的代理对象,也可以说是动态实现对象,获得此对象后此时该对象的一些列操作还是一个同步操作,为了实现异步操作,Zebra-Dao进一步对该对象做了异步化封装。
将mapper封装成一个异步代理类AsyncMapperProxy,最后进一步使用动态代理技术对为mapper对象创建代理对象。下面我们将到AsyncMapperProxy一探究竟。
未完待续

Zebra DAO 工作原理

标签:

原文地址:http://blog.csdn.net/wenbo20182/article/details/51931692

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!