Mybatis代理对象

@Component  
public class UserService {  
    @Autowired  
    private UserMapper userMapper;  
  
    public void test() {  
        System.out.println(userMapper);  
    }  
}

问题:想要拿到mybatis创建的代理对象并且赋值给userMapper属性,需要让Mybatis生成的mapper代理对象变成一个bean。如何做到?

解决:
写一个MyFactoryBean类实现FactoryBean类

@Component
public class MyFactoryBean implements FactoryBean {  
    @Override  
    public Object getObject() throws Exception {  
        Object instance = Proxy.newProxyInstance(MyFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                return null;  
            }  
        });  
  
        return instance;  
    }  
  
    @Override  
    public Class<?> getObjectType() {  
        return UserMapper.class;  
    }  
}

调用applicationContext.getBean("myFactoryBean")返回的是getObject()方法返回的代理对象
调用applicationContext.getBean("&myFactoryBean")返回的是MyFactoryBean本身的bean
因此我们自己实现了创建mapper的代理对象,将其变成一个bean并赋值给service中的属性。

那如何拿到mybatis创建的代理对象?

解决:
在MyFactoryBean里,

private SqlSession sqlSession;  
  
@Autowired  
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {  
    sqlSessionFactory.getConfiguration().addMapper(UserMapper.class);  
    this.sqlSession = sqlSessionFactory.openSession();  
}  
  
@Override  
public Object getObject() throws Exception {  
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);  
    return mapper;  
}

通过SqlSessionFactory拿到sqlSession,再通过sqlSession拿到mb创建的代理对象。
而SqlSessionFactory需要再config类中自己配置。

@Bean  
public SqlSessionFactory sqlSessionFactory() throws Exception {  
    InputStream is = Resources.getResourceAsStream("mybatis. xml");  
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);  
    return sqlSessionFactory;  
}

动态mapper

问题:如何实现动态的mapper?

解决:
在MyFactoryBean添加属性private Class mapperClass,通过构造方法对mapperClass赋值

@Component  
public class MyFactoryBean implements FactoryBean {
	@Override  
	public Class<?> getObjectType() {  
	    return mapperClass;  
	}  
	  
	public MyFactoryBean(Class mapperClass) {  
	    this.mapperClass = mapperClass;  
	}  
	  
	private SqlSession sqlSession;  
	private Class mapperClass;  
	  
	@Autowired  
	public void setSqlSession(SqlSessionFactory sqlSessionFactory) {  
	    sqlSessionFactory.getConfiguration().addMapper(mapperClass);  
	    this.sqlSession = sqlSessionFactory.openSession();  
	}  
	  
	@Override  
	public Object getObject() throws Exception {  
	    //UserMapper mapper = sqlSession.getMapper(UserMapper.class);  
	    return sqlSession.getMapper(mapperClass);  
	}
}

问题:但是方法返回的只有一个bean,我们想要所有的bean,如何实现?

解决:
使用beanDefinition

在main方法中:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();  
  
applicationContext.register(AppConfig.class);  

//得到beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();  
beanDefinition.setBeanClass(MyFactoryBean.class);  
//通过该方法对构造器赋值,此处赋值为UserMapper.class,从而实现动态mapper
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);  
//注册beanDefinition ,定义一个叫UserMapper的bean
applicationContext.registerBeanDefinition("UserMapper",beanDefinition);  
applicationContext.refresh();

问题:为什么要手动写register和refresh?

答案:
因为中间插入了自定义的beanDefinition,要将这两步单独拿出来执行

问题:为什么beanDefinition.setBeanClass(MyFactoryBean.class)?

答案:因为如果传入UserMapper.class,UserMapper是接口不会产生对象
传入UserMapper.class后,会生成其对象。spring发现是一个FactoryBean,就会调用getObject方法,userMapper属性对应的代理对象作为另外一个bean对象被返回

这几行会生成两个对象,一个MyFactoryBean的对象,一个UserMapper的代理对象
因为使用了beanDefinition,所以不用在MyFactoryBean类中加@Component注解类

问题:把得到beanDefinition的定义都写在main方法里很不美观,如何解决?

解决:写一个类MyImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar接口,把定义代码写入重写的registerBeanDefinitions方法。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {  
    @Override  
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {  
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();  
        beanDefinition.setBeanClass(MyFactoryBean.class);  
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);  
  
        registry.registerBeanDefinition("UserMapper",beanDefinition);  
    }  
}

最后在config类中加入注解@Import(MyImportBeanDefinitionRegistrar.class)

Mapper扫描

问题:由于spring的FactoryBean和ImportBeanDefinitionRegistra类都封装在jar包里,我们无法在ImportBeanDefinitionRegistra类中写入自己的mapper,如何解决?

解决:创建一个注解@MyMapperScan

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
@Import(MyImportBeanDefinitionRegistrar.class)  
  
public @interface MyMapperScan {  
    String value() default "";  
}

在config类引用注解@MyMapperScan("com.xiang.mapper")
在MyImportBeanDefinitionRegistra的registerBeanDefinitions方法实现扫描所有mapper

@Override  
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {  
  
    Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName());  
    String path = (String) annotationAttributes.get("value");  //根据注解得到mapper类的路径

	//接下来扫描mapper
	MyMapperScanner myMapperScanner = new MyMapperScanner(registry);  
	myMapperScanner.scan(path);

	//注册bean
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();  
    beanDefinition.setBeanClass(MyFactoryBean.class);  

	//给构造方法添加参数(重要!)
	beanDefinition.getConstructorArgumentValues()
	.addGenericArgumentValue(UserMapper.class); 
    registry.registerBeanDefinition("UserMapper",beanDefinition);
}

新建一个扫描类MyMapperScanner继承spring创建bean的扫描类ClassPathBeanDefinitionScanner,用于扫描mapper

问题:
但是spring的扫描类不会扫描接口,因为接口无法创建bean。恰恰相反,mybatis的扫描需要扫描mapper接口,如何解决?

在MyMapperScanner中重写isCandidateComponent,设置只关心接口;重写doScan,生成beanDefinition

public class MyMapperScanner extends ClassPathBeanDefinitionScanner {  
    public MyMapperScanner(BeanDefinitionRegistry registry) {  
        super(registry);  
    }  

	//设置只关心接口  
	@Override  
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {  
	    return beanDefinition.getMetadata().isInterface();  
	}  

	//生成beanDefinition
	@Override  
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  
	    Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);  
	    return beanDefinitionHolders;
	}
}

但是doScan方法里面的beanDefinitionHolders为空,可以在MyImportBeanDefinitionRegistrar的registerBeanDefinitions方法加入myMapperScanner.addIncludeFilter()解决(以后解释)

MyMapperScanner myMapperScanner = new MyMapperScanner(registry);  

myMapperScanner.addIncludeFilter(new TypeFilter() {  
    @Override  
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {  
        return true;  
    }  
});  
  
myMapperScanner.scan(path);

问题:
这样得到的beanDefinition类型是UserMapper.class,而我们设置的是MyFactoryBean.class

解决:在MyMapperScanner中改写doScan方法,修改错误的beanDefinition类型

@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类型  
        beanDefinition.setBeanClassName(MyFactoryBean.class.getName());  
    }  
    return beanDefinitionHolders;  
}

此时可以直接通过在service中注解注入mapper来实现mapper代理对象的获取