Spring扩展点之Mybatis整合模拟
Spring扩展点之Mybatis整合
- 单独使用MyBaitis
- 模拟整合MyBatis到Spring
单独使用MyBaitis
通过配置文件生成sqlSessionFactory,用sqlSessionFactory开启session。通过session获取到mapper执行对应的sql。
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
String result = userMapper.selectById();
sqlSession.commit();
sqlSession.flushStatements();
sqlSession.close();
模拟整合MyBatis到Spring
在Spring中我们可以通过如下方式使用mapper。通过分析我们知道userMapper是MyBatis生成的代理对象。但是在下面代码中的mapper并不是代理对象,并且运行还会报错,因为接口不会扫描成为bean。
public interface UserMapper {
@Select("select 'user'")
String selectById();
}
@Component
public class UserService {
@Autowired
private UserMapper userMapper;
public void test() {
System.out.println(userMapper.selectById());
}
}
@ComponentScan("org.example")
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
UserService userService = context.getBean(UserService.class);
userService.test();
}
}
我们可以通过FactoryBean接口模拟代理对象的实现。
@Component
public class XianNuFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理代理逻辑
return null;
}
});
return proxyInstance;
}
public Class<?> getObjectType() {
return UserMapper.class;
}
}
添加上述代码后,代码可以正常运行,可以userMapper可以注入成功。但是还有一个问题,这里的代理对象是写死的,我们不能每个mapper都写一个工厂bean吧。此时,我们可以想到利用spring的BeanDefinition把工厂bean注册到容器中。
public class XianNuFactoryBean implements FactoryBean {
public Class clazz;
public XianNuFactoryBean(Class clazz) {
this.clazz = clazz;
}
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理代理逻辑
return null;
}
});
return proxyInstance;
}
public Class<?> getObjectType() {
return clazz;
}
}
工厂bean进行改造,增加构造器指定bean类型。
@Component
public class XianNuBeanDefinitionRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//...
}
}
接下来就顺利成章了,我们想要扫描用户所有的mapper然后自动注册到Spring容器中。利用@Import注解配合ImportBeanDefinitionRegistrar。@Import可以获取到所有类的元数据信息,如其他注解的扫描路径。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface XianNuMapperScan {
String value();
}
@ComponentScan("org.example")
@XianNuMapperScan("org.example.mapper")
@Import(XianNuImportBeanDefinitionRegistrar.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
UserService userService = context.getBean(UserService.class);
userService.test();
}
}
public class XianNuImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 解析扫描路径
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(XianNuMapperScan.class.getName());
String path = (String) annotationAttributes.get("value");
System.out.println(path);
// 扫描路径下的bean?
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
}
}
得到扫描路径后需要扫描路径下的类,我们可以利用spring的扫描器来处理。由于Spring本身的扫描器不处理接口,因此我们需要重新 isCandidateComponent(AnnotatedBeanDefinition beanDefinition)方法让他处理接口。此外扫描时还有一个exclude/includeFilters的判断,Spring默认添加component注解进行扫描,因此我们可以在mapper上加注解就能扫描到,此外还有一个方法就是在扫描器添加includeFilter。
public class XianNuBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public XianNuBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
public class XianNuImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 解析扫描路径
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(XianNuMapperScan.class.getName());
String path = (String) annotationAttributes.get("value");
System.out.println(path);
// 扫描路径下的bean
XianNuBeanDefinitionScanner scanner = new XianNuBeanDefinitionScanner(beanDefinitionRegistry);
scanner.addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(path);
// BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XianNuFactoryBean.class);
// AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// beanDefinitionRegistry.registerBeanDefinition("userMapper", beanDefinition);
}
此时扫描出的mapper的BeanDefinition的class是它本身,并不是代理对象,因此还需要对beanDefinition做一些处理。重写scanner父类的doScan方法。
public class XianNuBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public XianNuBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
// 修改BeanDefinition的class为工厂类
@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(XianNuFactoryBean.class.getName());
}
return beanDefinitionHolders;
}
// 让扫描器扫描接口
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
此时还有一个问题就,就是mapper的代理逻辑是我们自己写的,不是用的MyBatis的。我们看mybatis的原始用法发现,mapper可以通过sqlSessionFactory得到,而sqlSessionFactory是通过配置文件得到。
public class XianNuFactoryBean implements FactoryBean {
public Class clazz;
@Autowired
private SqlSessionFactory sqlSessionFactory;
public XianNuFactoryBean(Class clazz) {
this.clazz = clazz;
}
public Object getObject() throws Exception {
// Object proxyInstance = Proxy.newProxyInstance(XianNuFactoryBean.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// // 处理代理逻辑
// System.out.println(method.getName());
// return null;
// }
// });
// return proxyInstance;
sqlSessionFactory.getConfiguration().addMapper(clazz);
return sqlSessionFactory.openSession().getMapper(clazz);
}
public Class<?> getObjectType() {
return clazz;
}
}
@ComponentScan("org.example")
@XianNuMapperScan("org.example.mapper")
@Import(XianNuImportBeanDefinitionRegistrar.class)
@Configuration
public class AppConfig {
@Bean
public SqlSessionFactory getSqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
至此模拟整合完成。