写这个文章的前因:我们在日常工作中使用@MapperScan("com.mybatis.dao")
就可以扫描到dao层下面的包,然后在service 或者 controller 层通过注入的方式就能够直接使用里面的方法,可是想一下,dao层里面是接口啊,那接口怎么可以直接调用呢?而接口又是如何交给spring管理的。这些问题将在下面的过程中一点点解析。
一、首先建立一下配置类,连接上数据库
import com.wengzw.mybatis.spring.MyImportBeanDefinitionRegistrar;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.mybatis")
// 加上这个注解就能扫描包下面的注解 加上代理,这里在演示第一个例子需要打开
//@MapperScan("com.mybatis.dao")
@Import(MyImportBeanDefinitionRegistrar.class)
public class AppConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("11111");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/blog?useUnicode=yes&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC");
return driverManagerDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean (DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean. setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
二、日常使用
mapper层
public interface TestDao {
@Select("select * from sys_user")
List<Map<String,Object>> query();
}
测试类
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
// 第一个知识点
// 这是接口 是不能调用方法的,原因只有可能这个接口已经不是接口了,而是一个代理类,所以这里是使用了JDK动态代理
TestDao dao = context.getBean(TestDao.class);
System.out.println(dao.query());
}
}
通过上面简单的例子可以知道,我们之所以能简单调用接口里的方法,是因为spring帮我们把扫描出来的接口都进行了动态代理,下面简单操作一下实现动态代理过程
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
// 第二知识点 实现动态代理简单 但如何把代理出来的对象放到spring中比较难
// 而且这个对象是存在内存中的,无法通过扫描 xml配置去放到spring容器中
// 为什么要放到spring中,因为这个产生的代理对象在各个地方都可能需要用到
Class[] clazz = new Class[]{TestDao.class};
TestDao testDao = (TestDao) Proxy.newProxyInstance(Test.class.getClassLoader(), clazz, new TestInvocationHandler());
testDao.query();
}
}
动态代理处理类
public class TestInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getAnnotation(Select.class).value()[0]);
System.out.println("查询DB");
return null;
}
}
实现动态代理简单 但如何把代理出来的对象放到spring中比较难,由于这个对象是存在内存中的,无法通过扫描 xml配置或者加注解@Bean去放到spring容器中
为什么要放到spring中?
因为这个产生的代理对象在各个地方都可能需要用到
三、接下来讲怎么将创建的代理对象优雅地放到spring容器中
在mybatis官网上,有这样的代码
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
所以依照这样的思想,我们首先创建自己的FactoryBean
先解释一下FactoryBean
是什么
FactoryBean
是一个普通的bean,加了@Component
注解之后 可以被spring扫描到,最后这个bean就能被加入spring容器。
其次他是一个特殊bean,他是一个接口,可以通过实现该接⼝定制实例化Bean的逻辑,当使用容器中FactoryBean时,该容器不会返回FactoryBean本身,而是返回其生成的对象。
创建MyMapperFactoryBean
// 这里不需要扫描了因为是通过注入beanDefinition产生的
//@Component
public class MyMapperFactoryBean implements FactoryBean {
// 可以通过xml配置,也可以使用下面的方法
// 这里需要构造方法,在MyImportBeanDefinitionRegistrar循环的时候会传过来
// 就不用写死 灵活运用
Class aClass;
public MyMapperFactoryBean(Class aClass) {
this.aClass = aClass;
}
// 这里会返回代理对象
@Override
public Object getObject() throws Exception {
Class[] clazz = new Class[]{aClass};
Object dao = Proxy.newProxyInstance(MyMapperFactoryBean.class.getClassLoader(), clazz, new TestInvocationHandler());
return dao;
}
@Override
public Class<?> getObjectType() {
return aClass;
}
}
编写一个注册器。MyImportBeanDefinitionRegistrar
**这里需要普及一个知识点:**一个类变成一个对象,我们可以自己new出来。但spring的做法是先将类的信息先封装到BeanDefinition
对象中,然后会put到一个键为beanName
值为BeanDefinition
的map中,然后循环扫描出每个beanName,根据他们的信息最终经过一系列的生命周期,最终才加到spring容器中。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata
* @param registry BeanDefinition的注册器
* @param importBeanNameGenerator
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
// 本来这里是要把代理类的BeanDefinition传输进去,但是不行,因为他已经是一个创建好的对象了,拿不到BeanDefinition
// 所以我不注入这个,注入MyMapperFactoryBean的BeanDefinition
/**
* 知识点 这里可以通过MapperScan 扫描处理 循环 就能产生不同的代理对象
*/
// 这个类不需要扫描了,因为这里构建了MyMapperFactoryBean 的 BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 非常重要 可以传类给构造方法,这里可以灵活,使用for循环后,将扫描的接口一个一个加入,我这里是写固定了
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("com.mybatis.dao.TestDao");
// 添加完后再把这个完善后的beanDefinition注册进去,然后下一步spring扫描时就可以交给spring容器管理了,这样自然而然生成的代理对象就加入spring容器了
registry.registerBeanDefinition("test",beanDefinition);
}
}
测试类
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
// 证明FactoryBean是一个普通bean 交给spring进行管理,能够拿出来
// 这里会打印MyMapperFactoryBean
// System.out.println(context.getBean("&myMapperFactoryBean").getClass().getSimpleName());
// 不加& 就是返回getObject返回的对象 而不是myMapperFactoryBean了
// System.out.println(context.getBean("myMapperFactoryBean").getClass().getSimpleName());
// 这里打印UserDao 由于是特殊bean 所以返回的是一个代理对象
System.out.println(context.getBean("test").getClass().getSimpleName());
TestDao test = (TestDao) context.getBean("test");
test.query();
// System.out.println(context.getBean(UserDao.class));
}
}
通过以上就可以理解为什么接口里的方法可以直接调用,因为它已经被代理了,而代理对象又交给了spring管理,所以在任意位置注入就能够使用它。
**扩展点:**其实可以通过BeanFactoryPostProcessor
后置处理器将代理对象注入到容器中,但会有缺点,以下代码有说明
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// GenericBeanDefinition demoDao = (GenericBeanDefinition) beanFactory.getBeanDefinition("demoDao");
// demoDao.setBeanClass(UserDao.class);
/** 这种方法可以产生代理类 但有缺陷 因为TestInvocationHandler 是我们自己new出来的
* 它并不属于spring管理,但里面要引用数据源这些东西却是要spring进行管理的,所以这种方案不行
Class[] clazz = new Class[]{TestDao.class};
TestDao dao = (TestDao) Proxy.newProxyInstance(this.getClass().getClassLoader(), clazz, new TestInvocationHandler());
beanFactory.registerSingleton("dao",dao);
*/
}
}
以上是对spring+mybatis源码的见解,有错误欢迎指正