Spring FactoryBean到仿照mybatis @Mapper的实现
目录
- FactoryBean原理
- FactoryBean例子
- org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
- mybatis mapper bean的手动实现思考
- 复习下Jdbc传统sql查询做法
- Mapper接口实现思路
- 复习批量注册beanDefinition: ConfigurationClassPostProcessor
- 自定义实现@Mapper功能
- mapper结构
- 定义FactoryBean
- 编写代理对象
- 将mapper Bean定义出来给容器,即往容器加入所有mapper的BeanDefinition
- 测试
- 总结
本文想说明的是: 只要懂Java反射以及Spring中FactoryBean, BeanFactoryPostProcessor,BeanPostProcessor 知识点,就能自己实现spring-mybatis中的@Mapper了。
知识点简单记忆即:
- 代理、动态代理,FactoryBean
- BeanFactoryPostProcessor 干预BeanDefinition(Bean怎么来的,依靠BeanDefinition)
- BeanPostProcessor干预Bean生命周期
FactoryBean原理
FactoryBean
The FactoryBean interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.
The FactoryBean interface provides three methods:
-
Object getObject()
: Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes. -
boolean isSingleton()
: Returns true if this FactoryBean returns singletons or false otherwise. -
Class getObjectType()
: Returns the object type returned by the getObject() method or null if the type is not known in advance.
首先FactoryBean它是一个Bean,但又不仅仅是一个Bean,它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。
FactoryBean例子
即然是工厂,就成批量大规模生产,直接来看例子:
接口和实现类:
public interface Go {void out();
}
public class BikeGo implements Go {@Overridepublic void out() {System.out.println("go by bike");}
}
public class CarGo implements Go {@Overridepublic void out() {System.out.println("go by car");}
}
定义FactoryBean
@Service
public class MyGoFactoryBean implements FactoryBean<Go> {/*** {@link GoEnum}*/private String type;private Go getDefaultGo(){return new Go() {@Overridepublic void out() {System.out.println("just go on foot");}};}public String getType() {return type;}public void setType(String type) {this.type = type;}@Overridepublic Go getObject(){if (type == null) {return getDefaultGo();}if (type.equalsIgnoreCase(GoEnum.BIKE.getType())) {return new BikeGo();}if (type.equalsIgnoreCase(GoEnum.CAR.getType())) {return new CarGo();}return getDefaultGo();}@Overridepublic Class<Go> getObjectType() { return Go.class ; }@Overridepublic boolean isSingleton() { return false; }
}
批量产生:
emum:
public enum GoEnum {BIKE("bike"),CAR("car");String type;GoEnum(String type) {this.type = type;}public String getType() {return type;}
}
使用FactoryBean:
@Configuration
public class GoConfig {@Bean("go1")public MyGoFactoryBean type1Instance() {MyGoFactoryBean factoryBean = new MyGoFactoryBean();factoryBean.setType(GoEnum.BIKE.getType());return factoryBean;}@Bean("go2")public MyGoFactoryBean type2Instance() {MyGoFactoryBean factoryBean = new MyGoFactoryBean();factoryBean.setType(GoEnum.CAR.getType());return factoryBean;}}
测试如下:
:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
"go1"获取的时候sharedInstance为MyGoFactoryBean
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
是FactoryBean类型的时候会判断并从FactoryBean获取
具体执行:org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
最后执行到org.springframework.beans.factory.FactoryBean#getObject
获取到实例对象
mybatis mapper bean的手动实现思考
复习下Jdbc传统sql查询做法
- 导入jdbc驱动包
- 通过DriverManager注册驱动
- 创建连接
- 创建statement
- 执行curd sql语句
- 操作结果集
- 关闭连接
Mapper接口实现思路
一般的mapper定义如下,其实只要解析出sql, 其执行都是jdbc执行,然后返回结果。
想想每个mapper都是这样,完全可以写一个通用的代理类
public interface TbUserMapper {/*** select 映射* @param id* @return*/@Select("SELECT * FROM tb_user WHERE id = #{id}")TbUser selectUserById(int id);
}
然后这些mapper注册beanDefinition到Spring容器中,那么就自己实现了@Mapper注解了
复习批量注册beanDefinition: ConfigurationClassPostProcessor
之前看过@Configuration全注解的ConfigurationClassPostProcessor,这个beanFactoryPostProcessor就是批量扫描并注册BeanDefinition的。
比如处理@Import注解的
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
见文章:https://blog.csdn.net/qq_26437925/article/details/144865082
自定义实现@Mapper功能
mapper结构
public interface TbUserMapper {/*** select 映射* @param id* @return*/@Select("SELECT * FROM t_user WHERE id = #{id}")TbUser selectUserById(int id);
}
sql执行过程要代理掉,然后所有的mapper都是这么个代理执行且要成为spring容器的bean。那么显然想到的是FactoryBean, 返回代理对象就好了
定义FactoryBean
- 这个FactoryBean的属性就是mapper interface的class了, getObject返回就是代理对象(和本文中的测试例子传递不同type返回不同类型的Go类似)
@Service
public class DbMapperFactoryBean implements FactoryBean<Object> {Class<?> mapperInterface;public DbMapperFactoryBean() {}public DbMapperFactoryBean(Class<?> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {Object object = Proxy.newProxyInstance(DbMapperFactoryBean.class.getClassLoader(),new Class<?>[]{mapperInterface},new DbInvocationHandler());return object;}@Overridepublic Class<?> getObjectType() {return mapperInterface;}
}
编写代理对象
一个简单的Java动态代理,就是要代理mapper有@Select注解的方法,代理其执行, 除了sql和返回外,其它就是jdbc数据库的操作了
public class DbInvocationHandler implements InvocationHandler {/*** mapper 里面的方法,逻辑是一个* 1. 解析sql* 2. 执行sql(jdbc连接)*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();if ("toString".equals(methodName)) {return proxy.getClass().getName();}// 1. parse sqlSelect annotation = method.getAnnotation(Select.class);String sql = annotation.value();Object val = args[0];String parseSql = sql.replace("#{id}", String.valueOf(val));System.out.println("parse sql:" + parseSql);// 2. execute sqlreturn exeSql(parseSql);}static TbUser exeSql(String sql) {Connection conn = null;Statement stmt = null;Integer id = 0;String name = null;String sno = null;String password = null;try {//STEP 1: Register JDBC driverClass.forName("com.mysql.jdbc.Driver");//STEP 2: Open a connectionconn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root", "123456");//STEP 3: Execute a querystmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql);//STEP 4: Get resultswhile (rs.next()) {id = Integer.valueOf(rs.getString("id"));sno = rs.getString("sno");name = rs.getString("name");password = rs.getString("password");break;}rs.close();} catch (Exception e) {}//end tryreturn new TbUser(id, sno, name, password);}
}
将mapper Bean定义出来给容器,即往容器加入所有mapper的BeanDefinition
借鉴ConfigurationClassPostProcessor加载ImportBeanDefinitionRegistrar的做法,实现如下
public class DbImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// TODO 这里可以利用反射,for循环mapper package下的所有Mapper类,然后添加所有String[] mappers = {"com.aop.test.mymapper.mapper.TbUserMapper","com.aop.test.mymapper.mapper.TbUser2Mapper"};for (String mapperInterface : mappers) {// bean类型是个FactoryBean, 即 DbMapperFactoryBean, 有一个mapperInterface属性BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DbMapperFactoryBean.class);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperInterface);String beanName = convertToVariableName(mapperInterface);registry.registerBeanDefinition(beanName, beanDefinition);}}private String convertToVariableName(String className) {// 将类名转换为单词数组String[] words = className.split("\\.");// 获取最后一个单词,即Mapper类名String mapperName = words[words.length - 1];// 将Mapper类名转换为驼峰式命名StringBuilder sb = new StringBuilder();for (int i = 0; i < mapperName.length(); i++) {char c = mapperName.charAt(i);if (i == 0) {sb.append(Character.toLowerCase(c));} else {sb.append(c);}}// 转换为变量名形式return sb.toString();}
}
上面例子就是拿到所有mapper interface,然后用了GenericBeanDefinition,只要这个对象的类型和构造参数即可以的,也就是定义出来的FactoryBean类型了。
然后注解就是@Import了, 自己重载下
@Retention(RetentionPolicy.RUNTIME)
@Import(DbImportBeanDefinitionRegistrar.class)
public @interface DbMapperScan {
}
需要说明的是,这里方法很多,目的就是往beanFactory加BeanDefinition
测试
把自定义的注解配置到全注解类上面
测试如下:
拿到bean,注解执行sql方法,正常返回sql数据
debug加入beanDefinition:
总结
本文例子显然没有优雅的处理不同的sql语句返回对象,也没有反射拿到所有的mapper inteface(或自己写一个ConfigurationClassPostProcessor扫描) , 不过这些也绝非难事。 只要清楚Spring执行过程中的扩展点,就能自行扩展加入。
Spring面试的时候好像也会问BeanFactory和FactoryBean的区别:所以怎么用的,例子说明清楚就行了