## 問題場景:
> 很多項目中使用了mybatis,mapper定義了查詢語句,結果集的返回對象通常會使用**resultType**或者**resultMap**來指定一個POJO,而這個POJO通常是一個**值對象**,這個對象在接收查詢的返回結果時被mybatis的對象工廠所創建,mybatis默認的對象工廠類是DefaultObjectFactory,通過查看源碼可知這個POJO是被DefaultObjectFactory通過反射創建的。當POJO類中有“**行為**”時,通常會用到通過注解注入的其他**service bean**,這種情況下顯然再通過反射創建該POJO是行不通的。
## 解決方案:
> 擴展默認的對象工廠DefaultObjectFactory,改為從spring上下文中獲取bean對象。
寫一個ExtendObjectFactory類繼承DefaultObjectFactory,由於除了創建POJO對象,ObjectFactory還會根據結果集類型創建**集合類型對象**(如java.util.List),這種按原方式創建即可,上代碼:
```
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* 自定義擴展類,擴展mybatis默認的對象工廠,改為從spring上下文中構建原型 bean
*
* @author luyiming
*
*/
@Component
@Configuration
public class ExtendObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = 5207735177663874712L;
@Override
public <T> T create(Class<T> type) {
try {
T t = BeanFactory.getBean(type);
return t;
} catch (RuntimeException e) {
}
return super.create(type, null, null);
}
}
```
BeanFactory.java
```
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
/**
* Bean工廠類
*
* @author lu_yming1
*
*/
@Service
public class BeanFactory {
@Resource
private ApplicationContext context;
private static BeanFactory bf;
@PostConstruct
public void init() {
bf = this;
bf.context = this.context;
}
/**
* 從spring上下文中獲取bean實例
*
* @param name
* @return
*/
public static Object getBean(String name) {
return bf.context.getBean(name);
}
/**
* 從spring上下文中獲取bean實例
*
* @param name
* @param c
* @return
*/
public static <T> T getBean(String name, Class<T> c) {
return bf.context.getBean(name, c);
}
/**
* 從spring上下文中獲取bean實例
*
* @param c
* @return
*/
public static <T> T getBean(Class<T> c) {
return bf.context.getBean(c);
}
}
```
這個ExtendObjectFactory類如何配置到項目中生效呢?
### 情況一:springmvc項目
在mybatis-config.xml中這樣引入:
```
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
</objectFactory>
```
### 情況二:springboot項目
在springboot項目中,由於沒有了xml配置文件,故需要在application.properties里添加我們擴展的對象工廠相關配置,如下
```
#application.properties
mybatis.configuration.object-factory=com.cvicse.jr.commons.mybatis.ExtendObjectFactory
```
如果你單單配置了這一句就可以了,那你真是幸運,但實際我在配到這一步的時候,啟動springboot一直報錯,提示找不到合適的converter將string轉化為ObjectFactory對象。這又是什么鬼呢?
看字面意思,應該是缺少對應的converter,難道mybatis沒有提供這個converter嗎?簡直有點坑。而且springboot也不提供用反射機制來構件對象的converter?:joy:是的,springboot沒有這樣做。通過查資料得知springboot提供了一種擴展機制,允許你來寫一個converter來完成你想要的轉換工作。於是,我又寫了一個converter:
```
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
/**
* custom type ObjectFactroy converter for application.proerties
*
* @author luyiming
*
*/
@Component
@ConfigurationPropertiesBinding
public class ObjectFactoryConverter implements Converter<String, ObjectFactory> {
@Override
public ObjectFactory convert(String source) {
try {
return (ObjectFactory) Class.forName(source).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
```
再啟動springboot,ok,簡直完美 :blush:
