Permalink
Browse files

add @enable java configuration option

todo: annotationClass and markerInterface support, more testing & example code
  • Loading branch information...
lanyonm committed Jan 7, 2013
1 parent 4215851 commit 9972cdba5538acf3ac527b446bd73dd450afabe0
Showing with 388 additions and 153 deletions.
  1. +1 −1 pom.xml
  2. +47 −0 src/main/java/org/mybatis/spring/mapper/MapperBeanRegistrar.java
  3. +201 −0 src/main/java/org/mybatis/spring/mapper/MapperScanner.java
  4. +11 −139 src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java
  5. +43 −0 src/main/java/org/mybatis/spring/mapper/annotation/EnableMyBatisMapperScanner.java
  6. +9 −9 src/test/java/org/mybatis/spring/mapper/MapperScannerConfigurerTest.java
  7. +1 −1 src/test/java/org/mybatis/spring/sample/SampleBatchTest.java
  8. +18 −0 src/test/java/org/mybatis/spring/sample/SampleEnableAnnotationBasePackageTest.java
  9. +18 −0 src/test/java/org/mybatis/spring/sample/SampleEnableAnnotationValueTest.java
  10. +1 −1 src/test/java/org/mybatis/spring/sample/SampleMapperTest.java
  11. +1 −1 src/test/java/org/mybatis/spring/sample/SampleScannerTest.java
  12. +1 −1 src/test/java/org/mybatis/spring/sample/SampleSqlSessionTest.java
  13. +18 −0 src/test/java/org/mybatis/spring/sample/config/AppConfig1.java
  14. +18 −0 src/test/java/org/mybatis/spring/sample/config/AppConfig2.java
  15. 0 src/test/java/org/mybatis/spring/sample/{ → config}/applicationContext-batch.xml
  16. 0 src/test/java/org/mybatis/spring/sample/{ → config}/applicationContext-infrastructure.xml
  17. 0 src/test/java/org/mybatis/spring/sample/{ → config}/applicationContext-mapper.xml
  18. 0 src/test/java/org/mybatis/spring/sample/{ → config}/applicationContext-scanner.xml
  19. 0 src/test/java/org/mybatis/spring/sample/{ → config}/applicationContext-sqlsession.xml
@@ -85,7 +85,7 @@

<properties>
<findbugs.onlyAnalyze>org.mybatis.spring.*,org.mybatis.spring.mapper.*,org.mybatis.spring.support.*,org.mybatis.spring.transaction.*</findbugs.onlyAnalyze>
<spring.version>3.1.1.RELEASE</spring.version>
<spring.version>3.2.0.RELEASE</spring.version>
<clirr.comparisonVersion>1.1.0</clirr.comparisonVersion>
<gcu.product>Spring</gcu.product>
<osgi.import>org.springframework.batch;resolution:=optional,*</osgi.import>
@@ -0,0 +1,47 @@
package org.mybatis.spring.mapper;

import java.util.ArrayList;
import java.util.List;

import org.mybatis.spring.mapper.annotation.EnableMyBatisMapperScanner;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

/**
* A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration
* of MyBatis mapper scanning. Using an @Enable annotation allows beans to
* be registered via @Component configuration, whereas implementing
* {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
*
* @author lanyonm
* @see MapperFactoryBean
* @since 1.1.2
* @version $Id$
*/
public class MapperBeanRegistrar implements ImportBeanDefinitionRegistrar {

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(EnableMyBatisMapperScanner.class.getName()));

List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}

MapperScanner scanner = new MapperScanner(registry);
scanner.doScan(StringUtils.toStringArray(basePackages));
}

}
@@ -0,0 +1,201 @@
package org.mybatis.spring.mapper;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;

import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
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.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;

/**
* A {@link ClassPathBeanDefinitionScanner} that registers Mappers by
* {@code basePackage}, {@code annotationClass}, or {@code markerInterface}.
* If an {@code annotationClass} and/or {@code markerInterface} is specified,
* only the specified types will be searched (searching for all interfaces
* will be disabled).
*
* <p>This functionality was previously a private class of
* {@link MapperScannerConfigurer}, but was broken out in version 1.1.2.</p>
*
* @see MapperFactoryBean
* @see MapperScannerConfigurer
* @see MapperRegistry
* @since 1.1.2
* @version $Id$
*/
class MapperScanner extends ClassPathBeanDefinitionScanner {

private boolean addToConfig = true;
private Class<? extends Annotation> annotationClass;
private Class<?> markerInterface;
private SqlSessionFactory sqlSessionFactory;
private SqlSessionTemplate sqlSessionTemplate;
private String sqlSessionTemplateBeanName;
private String sqlSessionFactoryBeanName;

public MapperScanner(BeanDefinitionRegistry registry) {
this(registry, true);
}

public MapperScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
super(registry, useDefaultFilters);
}

public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}

public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}

public void setMarkerInterface(Class<?> markerInterface) {
this.markerInterface = markerInterface;
}

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}

public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) {
this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName;
}

public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) {
this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName;
}

/**
* Configures parent scanner to search for the right interfaces. It can search for all
* interfaces or just for those that extends a markerInterface or/and those annotated with
* the annotationClass
*/
@Override
protected void registerDefaultFilters() {
boolean acceptAllInterfaces = true;

// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}

// override AssignableTypeFilter to ignore matches on the actual marker interface
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}

// exclude package-info.java
addExcludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
}

/**
* Calls the parent search that will search and register all the candidates. Then the
* registered objects are post processed to set them as MapperFactoryBeans
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
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 (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}

// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

boolean explicitFactoryUsed = false;
if (StringUtils.hasLength(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;
}

if (StringUtils.hasLength(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
definition.getPropertyValues().add("sqlSessionFactory", null);
} 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);
definition.getPropertyValues().add("sqlSessionFactory", null);
}
}
}

return beanDefinitions;
}

@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return (beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent());
}

@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
logger.warn("Skipping MapperFactoryBean with name '" + beanName
+ "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface"
+ ". Bean already defined with the same name!");
return false;
}
}
}
Oops, something went wrong.

0 comments on commit 9972cdb

Please sign in to comment.