关闭

基于Spring的包含特定注解bean的package扫描工具

1348人阅读 评论(0) 收藏 举报
分类:

本文出处:http://blog.csdn.net/chaijunkun/article/details/23921547,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。


Spring框架为Java开发提供了很多便利的工具和编程方式,最近在研究LDAP认证,多数技术问题都已经搞定,但是针对LDAP的ODM(Object-Directory Mapping,也就是LDAP层面的ORM)还有些不足。


问题描述:

Spring项目中有一个名为Spring LDAP的子项目,可以简化查询逻辑,但是其中的LDAPTemplate需要做手动的Mapping,另外在查询时默认使用的是类似于SQL中的select *,这样也许会造成很多的网络流量浪费和性能下降。如果需指定返回哪些字段,必须输入一个String[]。这样做的结果就是,同样的一个查询,既要指定字段的String[],又要将返回的字段一个一个地Mapping到Bean属性上,一旦将来字段增加或者减少,需要维护两个地方,增加了出错的几率。于是想到了能否像JPA的注解方式那样来配置ODM,后来查阅相关资料,还真有——使用OdmManagerImplFactoryBean。

首先需要在被映射的对象上增加Entry注解,然后在Bean属性上增加对应的Attribute注解就完成了映射。问题出来了,除了要加入注解外,还要将这些Bean加入到OdmManagerImplFactoryBean的managedClasses属性中,通知管理器哪些Bean属于受管Bean。这有点像早期的Spring对于Hibernate的支持。配置AnnotationSessionFactoryBean的时候需要设置annotatedClasses一样,不过从Spring 2.5.6开始增加了packagesToScan参数设置,它的作用是从指定的包下面扫描全部带有Entity、Embeddable、MappedSuperclass、org.hibernate.annotations.Entity.class注解的Bean,并进行管理。这样做的好处就是你把需要ODM的Bean都统一放到同一个package下,然后让配置去自动扫描,这样在你增加或减少Bean的时候不用再去关心配置中哪些Bean是受管的。

代码:

首先声明,这个代码是受org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean中scanPackages方法的启发,借鉴了其中的大部分代码,先贴出来:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package net.csdn.blog.chaijunkun.config;  
  2.   
  3. import java.io.IOException;  
  4. import java.lang.annotation.Annotation;  
  5. import java.util.HashSet;  
  6. import java.util.LinkedList;  
  7. import java.util.List;  
  8. import java.util.Set;  
  9.   
  10. import org.apache.commons.logging.Log;  
  11. import org.apache.commons.logging.LogFactory;  
  12. import org.springframework.core.io.Resource;  
  13. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
  14. import org.springframework.core.io.support.ResourcePatternResolver;  
  15. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;  
  16. import org.springframework.core.type.classreading.MetadataReader;  
  17. import org.springframework.core.type.classreading.MetadataReaderFactory;  
  18. import org.springframework.core.type.filter.AnnotationTypeFilter;  
  19. import org.springframework.core.type.filter.TypeFilter;  
  20. import org.springframework.util.ClassUtils;  
  21.   
  22.   
  23. public class LoadPackageClasses {  
  24.       
  25.     protected final Log logger = LogFactory.getLog(getClass());  
  26.       
  27.     private static final String RESOURCE_PATTERN = "/**/*.class";  
  28.       
  29.     private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();  
  30.       
  31.     private List<String> packagesList= new LinkedList<String>();  
  32.       
  33.     private List<TypeFilter> typeFilters = new LinkedList<TypeFilter>();  
  34.       
  35.     private Set<Class<?>> classSet= new HashSet<Class<?>>();  
  36.       
  37.     /** 
  38.      * 构造函数 
  39.      * @param packagesToScan 指定哪些包需要被扫描,支持多个包"package.a,package.b"并对每个包都会递归搜索 
  40.      * @param annotationFilter 指定扫描包中含有特定注解标记的bean,支持多个注解 
  41.      */  
  42.     public LoadPackageClasses(String[] packagesToScan, Class<? extends Annotation>... annotationFilter){  
  43.         if (packagesToScan != null) {  
  44.             for (String packagePath : packagesToScan) {  
  45.                 this.packagesList.add(packagePath);  
  46.             }  
  47.         }  
  48.         if (annotationFilter != null){  
  49.             for (Class<? extends Annotation> annotation : annotationFilter) {  
  50.                 typeFilters.add(new AnnotationTypeFilter(annotation, false));  
  51.             }  
  52.         }  
  53.     }  
  54.       
  55.     /** 
  56.      * 将符合条件的Bean以Class集合的形式返回 
  57.      * @return 
  58.      * @throws IOException 
  59.      * @throws ClassNotFoundException 
  60.      */  
  61.     public Set<Class<?>> getClassSet() throws IOException, ClassNotFoundException {  
  62.         this.classSet.clear();  
  63.         if (!this.packagesList.isEmpty()) {  
  64.                 for (String pkg : this.packagesList) {  
  65.                     String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
  66.                             ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;  
  67.                     Resource[] resources = this.resourcePatternResolver.getResources(pattern);  
  68.                     MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);  
  69.                     for (Resource resource : resources) {  
  70.                         if (resource.isReadable()) {  
  71.                             MetadataReader reader = readerFactory.getMetadataReader(resource);  
  72.                             String className = reader.getClassMetadata().getClassName();  
  73.                             if (matchesEntityTypeFilter(reader, readerFactory)) {  
  74.                                 this.classSet.add(Class.forName(className));  
  75.                             }  
  76.                         }  
  77.                     }  
  78.                 }  
  79.         }  
  80.         //输出日志  
  81.         if (logger.isInfoEnabled()){  
  82.             for (Class<?> clazz : this.classSet) {  
  83.                 logger.info(String.format("Found class:%s", clazz.getName()));  
  84.             }  
  85.         }  
  86.         return this.classSet;  
  87.     }  
  88.       
  89.       
  90.   
  91.     /** 
  92.      * 检查当前扫描到的Bean含有任何一个指定的注解标记 
  93.      * @param reader 
  94.      * @param readerFactory 
  95.      * @return 
  96.      * @throws IOException 
  97.      */  
  98.     private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {  
  99.         if (!this.typeFilters.isEmpty()) {  
  100.             for (TypeFilter filter : this.typeFilters) {  
  101.                 if (filter.match(reader, readerFactory)) {  
  102.                     return true;  
  103.                 }  
  104.             }  
  105.         }  
  106.         return false;  
  107.     }  
  108.   
  109. }  

接下来我们就可以来配置了(以下配置为Spring的applicationContext.xml配置节选):

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <bean id="loadPackageClasses" class="net.csdn.blog.chaijunkun.config.LoadPackageClasses">  
  2.     <constructor-arg value="net.csdn.blog.chaijunkun.ldap.entity" />  
  3.     <constructor-arg>  
  4.         <list>  
  5.             <value>org.springframework.ldap.odm.annotations.Entry</value>  
  6.         </list>  
  7.     </constructor-arg>  
  8. </bean>  
  9. <bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">  
  10.     <property name="converterManager" ref="converterManager" />  
  11.     <property name="contextSource" ref="contextSource" />  
  12.     <property name="managedClasses">  
  13.         <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
  14.             <property name="targetObject">  
  15.                 <ref local="loadPackageClasses" />  
  16.             </property>  
  17.             <property name="targetMethod">  
  18.                 <value>getClassSet</value>  
  19.             </property>  
  20.         </bean>  
  21.     </property>  
  22. </bean>  

在上述配置中,指定了扫描的包为net.csdn.blog.chaijunkun.ldap.entity,然后指定了筛选条件是包含org.springframework.ldap.odm.annotations.Entry注解(指定的注解必须在Class级,不能是property级和method级,也就是Bean头部的注解)的所有Bean。

因为获取筛选出类的集合要注入到OdmManagerImplFactoryBean中的managedClasses属性,类型为Set<Class<?>>,所以我们需要调用getClassSet()方法,用其返回值进行注入。于是使用了一个MethodInvokingFactoryBean来实现。

实验结果表明,加入了这个组件之后确实达到了预期的效果。另外由于LoadPackageClasses本身配置上很灵活,可以用于筛选任何带有特定注解的Bean,所以其他类似的场合也可以使用。当然,我更希望OdmManagerImplFactoryBean中能自带package扫描的配置,这样会让我们省好多事。


我自己的实现

public class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean {

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private static Logger logger = LoggerFactory.getLogger(PackagesSqlSessionFactoryBean.class);

    @Override
    public void setTypeAliasesPackage(String typeAliasesPackage) {
        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
        typeAliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(typeAliasesPackage) + "/" + DEFAULT_RESOURCE_PATTERN;

        //将加载多个绝对匹配的所有Resource
        //将首先通过ClassLoader.getResource("META-INF")加载非模式路径部分
        //然后进行遍历模式匹配
        try {
            List<String> result = new ArrayList<String>();
            Resource[] resources =  resolver.getResources(typeAliasesPackage);
            if(resources != null && resources.length > 0){
                MetadataReader metadataReader = null;
                for(Resource resource : resources){
                    if(resource.isReadable()){
                       metadataReader =  metadataReaderFactory.getMetadataReader(resource);
                        try {
                            result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if(result.size() > 0) {
                super.setTypeAliasesPackage(StringUtils.join(result.toArray(), ","));
            }else{
                logger.warn("参数typeAliasesPackage:"+typeAliasesPackage+",未找到任何包");
            }
            //logger.info("d");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:7838491次
    • 积分:67242
    • 等级:
    • 排名:第28名
    • 原创:254篇
    • 转载:2716篇
    • 译文:3篇
    • 评论:717条
    文章分类
    最新评论