一、回顾一下Spring整合Mybatis的正常流程
1.导入以下的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.11.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
2.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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
3.Spring的配置类
这里大家思考一下!为什么要将SqlSessionFactory
注入到容器中?
@ComponentScan("com.lzl")
@Configuration
@MapperScan("com.lzl.mapper")
public class MainConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
Reader reader = Resources.getResourceAsReader("conf.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
return sessionFactory;
}
}
4.Mapper接口
public interface OrderMapper {
@Select("select 'order'; ")
String getOrder();
}
5.Service
@Service
public class MyService {
@Autowired
private OrderMapper orderMapper;
public void test(){
System.out.println(orderMapper.getOrder());
}
}
6.测试类
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
MyService bean = context.getBean(MyService.class);
bean.test();
}
以上代码是能够正常运行的,但如果去掉mybatis-spring依赖
提供的@MapperScan注解,就会导致service中依赖的OrderMapper注入失败,程序就会发生报错。而本文的目的就是手写mybatis-spring
的功能,并也提供一个类似@MapperScan的注解来完成spring对Mybatis的接入。从而使我们更加的清楚为什么Spring接入MyBatis需要将SqlSessionFactory
注到IOC中。
二、正文
首先我们要移除掉上面项目中的mybatis-spring依赖
和 配置类上的@MapperScan注解
,避免它对我们编写造成影响!
1.书写下面的FactoryBean
//无需任何注解
public class LuzelongFactoryBean implements FactoryBean {
private SqlSession session;
private Class mapperClass;
public LuzelongFactoryBean(Class mapperClass){
this.mapperClass = mapperClass;
}
@Autowired
public void setSession(SqlSessionFactory sqlSessionFactory) {
sqlSessionFactory.getConfiguration().addMapper(mapperClass);
this.session = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
return session.getMapper(mapperClass);
}
@Override
public Class<?> getObjectType() {
return mapperClass;
}
}
2.书写下面的Register
将上面的FactoryBean注册进BeanDefinition中,通过构造方法完成上面mapperClass字段
的赋值操作。
//这个类比较特殊,只能通过@Import进行导入
public class LuzelongRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(LuzelongFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
beanDefinitionRegistry.registerBeanDefinition("orderMapper",beanDefinition);
//当有其他Mapper需要注入时,需要复制上面的逻辑
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.setBeanClass(LuzelongFactoryBean.class);
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinitionRegistry.registerBeanDefinition("userMapper",beanDefinition1);
}
}
3.在配置类上面加上@Import注解,导入上面的Register
@ComponentScan("com.lzl")
@Configuration
@Import(LuzelongRegister.class)
public class MainConfig {
.....
}
通过这样的操作,代码也能正常的运行!但我们发现一旦添加新的Mapper,我们就需要在LuzelongRegister类
中对registerBeanDefinitions
方法进行修改。解决办法请看下面!
三、正文2
1.创造LuzelongScan注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(LuzelongRegister.class)
public @interface LuzelongScan {
String value() default "";
}
2.配置类修改
因为@LuzelongScan注解上面有@Import,所以我们可以删掉配置类上的@Import注解,加上了@LuzelongScan注解即可!
@ComponentScan("com.lzl")
@Configuration
@LuzelongScan("com.lzl.mapper")
public class MainConfig {
.....
}
3.继承Spring提供的扫描类,快速完成扫描逻辑
public class LuzelongScanner extends ClassPathBeanDefinitionScanner {
public LuzelongScanner(BeanDefinitionRegistry registry){
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(LuzelongFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
}
4.修改LuzelongRegister 的方法逻辑
//这个类比较特殊,只能通过@Import进行导入
public class LuzelongRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(LuzelongScan.class.getName());
//获取@LuzelongScan的扫描路径
String path = (String)annotationAttributes.get("value");
// System.out.println(path);
LuzelongScanner luzelongScanner = new LuzelongScanner(beanDefinitionRegistry);
luzelongScanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
luzelongScanner.scan(path);
}
}
通过上述操作!即完成了@MapperScan注解提供的所以有功能!