论坛首页 Java企业应用论坛

一劳永逸——让Spring自动加载Hibernate Annotated Classes

浏览 19979 次
精华帖 (5) :: 良好帖 (15) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-05-30  
通常,spring整合Hibernate的SessionFactory是这样做的:
<property name="annotatedClasses">
   <list><value>com.systop.common.core.dao.testmodel.TestDept</value></list>
</property>
<property name="mappingLocations">
     <list><value>classpath*:org/jbpm/**/*.hbm.xml</value></list>
</property>

Spring可以根据mappingLocations属性中定义的Path Pattern自动加载hbm文件,但是对于annotatedClasses则只能一个一个的苦恼的写上去。无论是Hibernate还是Spring,都不能自动的加载某个package下的Anntated Classes。这样,一旦项目需要重构或者增加/减少Tables就会带来一些麻烦。尤其是对于那些已经打包的应用来说更是如此。
能不能让Spring自动加载AnnotatedClasses呢,我想到了Spring2.5中component-scan,于是便照猫画虎的写了一个AnnotationSessionFactoryBean的子类:
package com.systop.common.core.dao.hibernate;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Entity;

import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternCompiler;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.hibernate.HibernateException;
import org.hibernate.cfg.AnnotationConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import com.systop.common.core.exception.ApplicationException;

@SuppressWarnings("unchecked")
public class AnnotationSessionFactoryBeanEx extends AnnotationSessionFactoryBean {
  private static final Logger logger = LoggerFactory
      .getLogger(AnnotationSessionFactoryBeanEx.class);

  /**
   * The locations of the hibernate enity class files. They are often some of the string with
   * Sping-style resource. A ".class" subfix can make the scaning more precise.
   * <p> example:
   * <pre>
   * classpath*:com/systop/** /model/*.class
   * </pre>
   */
  private String[] annotatedClassesLocations;

  /**
   * Which classes are not included in the session.
   * They are some of the regular expression.
   */
  private String[] excludedClassesRegexPatterns;  

  /**
   * @param annotatedClassesLocations the annotatedClassesLocations to set
   */
  public void setAnnotatedClassesLocations(String[] annotatedClassesLocations) {
    this.annotatedClassesLocations = annotatedClassesLocations;
  }

  /**
   * @see AnnotationSessionFactoryBean#postProcessAnnotationConfiguration(org.hibernate.cfg.AnnotationConfiguration)
   */
  @Override
  protected void postProcessAnnotationConfiguration(AnnotationConfiguration config)
      throws HibernateException {
    Set<Class> annClasses = scanAnnotatedClasses(); //Scan enity classes.
    // Add entity classes to the configuration.
    if (!CollectionUtils.isEmpty(annClasses)) {
      for (Class annClass : annClasses) {
        config.addAnnotatedClass(annClass);
      }
    }
  }
  
  /**
   * Scan annotated hibernate classes in the locations.
   * @return Set of the annotated classes, if no matched class, return empty Set.
   */
  private Set<Class> scanAnnotatedClasses() {
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
        resourcePatternResolver);
    Set<Class> annotatedClasses = new HashSet<Class>();
    if (annotatedClassesLocations != null) {
      try {
        for (String annClassesLocation : annotatedClassesLocations) {
          //Resolve the resources
          Resource[] resources = resourcePatternResolver.getResources(annClassesLocation);
          for (Resource resource : resources) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            String className = metadataReader.getClassMetadata().getClassName();
            //If the class is hibernate enity class, and it does not match the excluded class patterns.
            if (isEntityClass(metadataReader) && !isExcludedClass(className)) {
              Class clazz = ClassUtils.forName(className);
              annotatedClasses.add(clazz);
              logger.debug("A entity class has been found. \n({})", clazz.getName());
            }
          }

        }
      } catch (IOException e) {
        logger.error("I/O failure during classpath scanning, ({})", e.getMessage());
        e.printStackTrace();
        throw new ApplicationException(e);
      } catch (ClassNotFoundException e) {
        logger.error("Class not found, ({})", e.getMessage());
        e.printStackTrace();
        throw new ApplicationException(e);
      } catch (LinkageError e) {
        logger.error("LinkageError ({})", e.getMessage());
        e.printStackTrace();
        throw new ApplicationException(e);
      }
    }

    return annotatedClasses;
  }
  
  /**
   * @return True if the given MetadataReader shows 
   * that the class is annotated by <code>javax.persistence.Enity</code>
   */
  private boolean isEntityClass(MetadataReader metadataReader) {
    Set<String> annTypes = metadataReader.getAnnotationMetadata().getAnnotationTypes();
    if (CollectionUtils.isEmpty(annTypes)) {
      return false;
    }
    
    return annTypes.contains(Entity.class.getName());
  }
  
  /**
   * 
   * @return True if the given class name match the excluded class patterns. 
   */
  private boolean isExcludedClass(String className) {
    if (excludedClassesRegexPatterns == null) { // All class is included.
      return false;
    }

    PatternCompiler compiler = new Perl5Compiler();
    PatternMatcher matcher = new Perl5Matcher();
    try {
      for (String regex : excludedClassesRegexPatterns) { //Test each patterns.
        logger.debug("Pattern is: {}", regex);
        Pattern pattern = compiler.compile(regex);
        if (matcher.matches(className, pattern)) {
          logger.debug("class [{}], matches [{}], so it is excluded.", className, pattern
              .getPattern());
          return true;
        }
      }
    } catch (MalformedPatternException e) {
      logger.warn("Malformed pattern [{}]", e.getMessage());
    }

    return false;
  }

  /**
   * @param exculdePatterns the exculdePatterns to set
   */
  public void setExcludedClassesRegexPatterns(String[] excludedClassesRegexPatterns) {
    this.excludedClassesRegexPatterns = excludedClassesRegexPatterns;
  }
}

在Spring的配置文件中这样写:
<bean id="sessionFactory" class="com.systop.common.core.dao.hibernate.AnnotationSessionFactoryBeanEx">
        <property name="dataSource" ref="dataSource"/>
        <property name="annotatedClassesLocations">
            <list>
                <value>classpath*:com/systop/**/model/*.class</value>
            </list>
        </property>
        <!--用正则表达式匹配不被scan的类-->
        <property name="excludedClassesRegexPatterns">
            <list>
                <value><![CDATA[^[\w\.]+Test[\w]+$]]></value>
            </list>
        </property>
</bean>

好了,一劳永逸!
哦,对了,提醒一下,上述代码使用了Spring2.5中的一些API,另外还有apache oro的正则表达式API。
   发表时间:2008-06-02  
多数时候,Domain类会在项目开始阶段得到确定,此时,annotatedClasses属性也就确定了,如果项目需求比较固定,那么上述改进没有什么明显的优势,但是,下列情况下就不同了。
1.如果项目需要重构。
2.如果需求模糊或者只确定了一部分需求,项目需要经常的添加一些Domain
3.如果公司有一个可复用模块库,里面包含权限、工作流等通用模块,这个复用库以jar包的形式提供给各个项目使用,spring的配置文件也在jar中。
0 请登录后投票
   发表时间:2008-10-07  
好方法,我将它加到springside(www.springside.org.cn)里可以吗?
0 请登录后投票
   发表时间:2008-10-07  

未发布的spring2.5.6中已经实现该功能,好像是增加一个packageToScan属性.
而rapid-framework也早实现该功能,与楼主不同的是使用package类型作为参数

 

	<bean id="sessionFactory" class="cn.org.rapid_framework.spring.hibernate3.annotation.ScanAnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		
		<!-- annotatedScanPackages可以自动搜索某个package的全部标记@Entity class -->
		<property name="annotatedScanPackages">
			<list>
				<value>com.company.demo.model</value>
			</list>
		</property>
	</bean>	

 

spring2.5.5中还对Ibatis也实现的类似的功能自动搜索并加载SqlMap.xml

    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="mappingLocations">
        	<value>classpath:/org/company/demo/model/**/*SqlMap.xml</value>
        </property>
        <property name="dataSource" ref="dataSource"/>
    </bean>

 

等待spring2.5.6发布时rapid-framework下一版本会删除ScanAnnotationSessionFactoryBean类,使用官方实现.

 

7 请登录后投票
   发表时间:2008-10-08  
江南白衣 写道

好方法,我将它加到springside(www.springside.org.cn)里可以吗?

当然,很荣幸,经常参考springside,很高兴能提供一点帮助。
0 请登录后投票
   发表时间:2008-10-08  
badqiu 写道

未发布的spring2.5.6中已经实现该功能,好像是增加一个packageToScan属性.
而rapid-framework也早实现该功能,与楼主不同的是使用package类型作为参数

packageToScan和白衣的entityPackages好像更加简洁呀。
packageToScan最好提供一个模糊匹配的例子,因为不能模糊匹配就没有意义了。
期待spring2.5.6...
0 请登录后投票
   发表时间:2008-10-09  
cats_tiger 写道

badqiu 写道

未发布的spring2.5.6中已经实现该功能,好像是增加一个packageToScan属性.
而rapid-framework也早实现该功能,与楼主不同的是使用package类型作为参数


packageToScan和白衣的entityPackages好像更加简洁呀。
packageToScan最好提供一个模糊匹配的例子,因为不能模糊匹配就没有意义了。
期待spring2.5.6...


都是支持的,因为都是使用PathMatchingResourcePatternResolver这个类
引用

<bean id="sessionFactory" class="cn.org.rapid_framework.spring.hibernate3.annotation.ScanAnnotationSessionFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
      
    <!-- annotatedScanPackages可以自动搜索某个package的全部标记@Entity class --> 
    <property name="annotatedScanPackages"> 
        <list> 
            <value>com.**.model</value> 
        </list> 
    </property> 
</bean> 


0 请登录后投票
   发表时间:2008-10-09  
很棒的工作!
如果是原生的hibernate支持就好了,hibernate的配置文件也不用一个个写了
0 请登录后投票
   发表时间:2008-10-10  
都用annotation了。为什么不直接用jpa。不是更简洁。
0 请登录后投票
   发表时间:2008-10-10  
onlydo 写道
都用annotation了。为什么不直接用jpa。不是更简洁。


JPA现在大家使用的实现就是Hibernate,而且JPA的规范更新的太慢了,有很多Hibernate支持的特性JPA不支持.
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics