本文出處: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方法的啓發,借鑑了其中的大部分代碼,先貼出來:
package net.csdn.blog.chaijunkun.config;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
public class LoadPackageClasses {
protected final Log logger = LogFactory.getLog(getClass());
private static final String RESOURCE_PATTERN = "/**/*.class";
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private List<String> packagesList= new LinkedList<String>();
private List<TypeFilter> typeFilters = new LinkedList<TypeFilter>();
private Set<Class<?>> classSet= new HashSet<Class<?>>();
/**
* 構造函數
* @param packagesToScan 指定哪些包需要被掃描,支持多個包"package.a,package.b"並對每個包都會遞歸搜索
* @param annotationFilter 指定掃描包中含有特定註解標記的bean,支持多個註解
*/
public LoadPackageClasses(String[] packagesToScan, Class<? extends Annotation>... annotationFilter){
if (packagesToScan != null) {
for (String packagePath : packagesToScan) {
this.packagesList.add(packagePath);
}
}
if (annotationFilter != null){
for (Class<? extends Annotation> annotation : annotationFilter) {
typeFilters.add(new AnnotationTypeFilter(annotation, false));
}
}
}
/**
* 將符合條件的Bean以Class集合的形式返回
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public Set<Class<?>> getClassSet() throws IOException, ClassNotFoundException {
this.classSet.clear();
if (!this.packagesList.isEmpty()) {
for (String pkg : this.packagesList) {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
Resource[] resources = this.resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
if (matchesEntityTypeFilter(reader, readerFactory)) {
this.classSet.add(Class.forName(className));
}
}
}
}
}
//輸出日誌
if (logger.isInfoEnabled()){
for (Class<?> clazz : this.classSet) {
logger.info(String.format("Found class:%s", clazz.getName()));
}
}
return this.classSet;
}
/**
* 檢查當前掃描到的Bean含有任何一個指定的註解標記
* @param reader
* @param readerFactory
* @return
* @throws IOException
*/
private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
if (!this.typeFilters.isEmpty()) {
for (TypeFilter filter : this.typeFilters) {
if (filter.match(reader, readerFactory)) {
return true;
}
}
}
return false;
}
}
接下來我們就可以來配置了(以下配置爲Spring的applicationContext.xml配置節選):
<bean id="loadPackageClasses" class="net.csdn.blog.chaijunkun.config.LoadPackageClasses">
<constructor-arg value="net.csdn.blog.chaijunkun.ldap.entity" />
<constructor-arg>
<list>
<value>org.springframework.ldap.odm.annotations.Entry</value>
</list>
</constructor-arg>
</bean>
<bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
<property name="converterManager" ref="converterManager" />
<property name="contextSource" ref="contextSource" />
<property name="managedClasses">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject">
<ref local="loadPackageClasses" />
</property>
<property name="targetMethod">
<value>getClassSet</value>
</property>
</bean>
</property>
</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掃描的配置,這樣會讓我們省好多事。