spring整合myabtis无非就是,把@Mapper修饰的接口扫描到容器中,然后spring中接口是不能呗扫描成为bean定义的,还有spring中connection与mybatis中connection的整合无非就是同一个线程spring生成的connection放到ThreadLocal中,mybatis用到时直接从中取保证了同一个业务方法中spring的连接与Mybatis时同一个。(此文只针对相应的@Mapper接口扫描beandefinition)

一、如何把@Mapper接口扫描到容器中

(1) spring中接口是不能扫描成bean定义的,而且bean定义的class肯定不能设置为接口类型,因接口不能实例化。spring提供了FactoryBean,因此我们可以在生成bean定义的时候指定beanClass为FactoryBean类型当getObject时根据当前接口生成代理对象
看下伪代码:

public class MybatisFactoryBean implements FactoryBean {

    private Class  classInterface;

    public MybatisFactoryBean(Class<?> classInterface) {
        this.classInterface = classInterface;
    }

    @Override
    public Object getObject() throws Exception {
        return  生成相应代理对象;
    }

    @Override
    public Class<?> getObjectType() {
        return classInterface;
    }
}

///  扫描无非就是把没过bean定义的先把构造参数设置当前的接口的,再把beanClass设置FactoryBean类型因为需要实例化

            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(MybatisFactoryBean.class.getName());
            beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

生成代理对象mybatis中sqlSession.getMapper(classInterface);
所以FactoryBean需要有SqlSession属性

public class MybatisFactoryBean implements FactoryBean {

    private Class  classInterface;

    private DefaultSqlSession sqlSession;
	//设置by_type注入会扫描所有set方法进行注入不多做解释
    public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = (DefaultSqlSession)sqlSessionFactory.openSession();
        //此处必须把这个接口加入进来负责报错
        sqlSession.getConfiguration().addMapper(classInterface);
    }

    public MybatisFactoryBean(Class<?> classInterface) {
        this.classInterface = classInterface;
    }

    @Override
    public Object getObject() throws Exception {
        return  sqlSession.getMapper(classInterface);
    }

    @Override
    public Class<?> getObjectType() {
        return classInterface;
    }
}

因SqlSessionFactory 需要在容器中

(2)、SqlSessionFactory 需要交给spring容器

@ComponentScan("com.spring.demo.component")
@Configuration
@MapperScan("com.spring.demo.mybatis")
public class ApplicationConfig {

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws IOException {
			InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
			SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
			return sqlSessionFactory;
	}

}

mybatis配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test_db?useUnicode=true&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

(3)、考虑如何把接口扫描成bean定义呢

先来了解下 spring中扫描bean定义:
1、org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
2、org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
3、org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				try {
					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					//1此处就是改造关键点
					if (isCandidateComponent(metadataReader)) {
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						sbd.setSource(resource);
						//2、此处会把接口类型过滤掉
						if (isCandidateComponent(sbd)) {
							if (debugEnabled) {
								logger.debug("Identified candidate component class: " + resource);
							}
							candidates.add(sbd);
						}
					}
				}

		return candidates;
	}

	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	 
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		//此处扫描只会this.includeFilters.add(new AnnotationTypeFilter(Component.class));@Componet注解的bean
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}


因此我们自定义扫描器肯定需要把1、不是@Component注解修饰的也需要扫描进来,2、还要把接口类行不要过滤掉
为了方便自定义扫描器继承ClassPathBeanDefinitionScanner只需要把上面注释中的1、2两处重写即可

public class MybatisMapperScan extends ClassPathBeanDefinitionScanner {


    public MybatisMapperScan(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);

        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(MybatisFactoryBean.class.getName());
            beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

        return beanDefinitionHolders;
    }

    @Override//把接口类型放开
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface();
    }
}


public class MybatisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      Map<String,Object> mapScan=  importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName());
      String value= (String) mapScan.get("value");
      MybatisMapperScan mybatisMapperScan=new MybatisMapperScan(registry);
      //2、添加过滤条件此处把所有条件都放开,spring中此处之后把@Copmonet注解放开,此处后续可以改造只扫描@Mapper注解的放开
      mybatisMapperScan.addIncludeFilter(new TypeFilter(){

            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
        mybatisMapperScan.doScan(value);
    }
}

何时会触发这个扫描器呢?
ImportBeanDefinitionRegistrar接口Spring启动时候调用该接口的registerBeanDefinitions()因此我们只需在配置类上导入该类即可

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MybatisImportBeanDefinitionRegistrar.class)
public @interface MapperScan {
     String value() default "";
}


@ComponentScan("com.spring.demo.component")
@Configuration
@MapperScan("com.spring.demo.mybatis")
public class ApplicationConfig {

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws IOException {
			InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
			SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
			return sqlSessionFactory;
	}

}

一切准备就绪测试(注意我的TestMapper接口没有加@Mapper注解因为上面代码过滤中会把所有类都扫描到,此处可以扩展,即加上@Mapper,上面过滤条件只扫描带此注解的):

public interface TestMapper {
    @Select("select 'hello' from  dual ")
    String select();
}


@Component
public class UserService {


    @Autowired
    private TestMapper testMapper;


    public void hello(){
        System.out.println(testMapper.select());
    }


}

在这里插入图片描述

二、代码demo路径
https://github.com/kangchangchang/SpringMybatis.git

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐