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;