mybatis.spring中一个关键注解MapperScan,通过它可以扫描指定包下面的所有mapper(mybatis自己实现了一个扫描器
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {}
最终调用父类的doScan()方法,把bean定义交给了spring初始化管理),然后我们就可以在service中注入使用了:
UserMapper:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
@Select("SELECT * FROM user")
List<User> getAll();
}
UserService
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User getUser(String userId) {
return userMapper.getUser(userId);
}
public List<User> getAll() {
return userMapper.getAll();
}
有两个关键的点:
- 这些mapper是怎么交给spring容器管理的呢?
- mapper都是接口类型都是怎么实例化的呢?
很好奇,其实答案就在MapperScan注解当中,通过查看其源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {}
很关键的一个:@Import(MapperScannerRegistrar.class),其中:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}
看到这就会明白了吧,实现了ImportBeanDefinitionRegistrar接口的方法,这样就可以通过BeanDefinitionRegistry registry注册了。
registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
另外一个问题就是它是接口类型的到底是怎么实例化调用的呢?毫无疑问肯定是采用了代理模式,最终通过代理对象实现调用的。但这又会引出另外一个疑问:既然是实现了代理,那到底是怎么动态注册到容器之中的呢?定义BeanDefinition,是需要指定BeanClass的,既然是代理对象,怎么动态拿到它的BeanClass的呢?
其实它是采用了FactoryBean,如下可以简单模拟MapperScan的实现:
自定义一个MyScan注解:
@Import(MyImportBeanDefinitionRegistrar.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScan {
}
MyImportBeanDefinitionRegistrar实现:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//得到bd
//根据包名扫描所有的class,这里就直接写死了
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(UserMapper.class);
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
//System.out.println(beanDefinition.getBeanClassName());
//通过构造函数注入,spring内部调用下面指定的 MyFactoryBean的构造
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
//指定bd类型,因为它是一个代理对象,我们只能通过FactoryBean去动态获取其类型
beanDefinition.setBeanClass(MyFactoryBean.class);
registry.registerBeanDefinition("userMapper", beanDefinition);
}
MyFactoryBean实现:
public class MyFactoryBean implements FactoryBean, InvocationHandler {
Class clazz;
//通过构造函数注入
public MyFactoryBean(Class clazz) {
this.clazz = clazz;
}
@Override
public Object getObject() throws Exception {
Class[] clazzs = new Class[]{clazz};
Object instance = Proxy.newProxyInstance(this.getClass().getClassLoader(), clazzs, this);
return instance;
}
@Override
public Class<?> getObjectType() {
return clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy");
//可以动态拿到每个方法注解的sql语句
Method method1 = proxy.getClass().getInterfaces()[0].getMethod(method.getName(), String.class);
Select annotations = method1.getDeclaredAnnotation(Select.class);
System.out.println(annotations.value()[0]);
return null;