在使用mybatis操作数据库时,我们只需要定义一个接口,然后在xml里编写对应的sql,就能查询数据。其原理是Mybatis通过@mypperscan
指定扫描的mapper接口路径,对mapper接口进行动态代理,生成的代理类通过解析xml得到对应sql,最终开发人员只需要调用接口就能执行sql了。
今天我们来实现一个简单的demo,利用动态代理,模拟mybatis查询数据。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
// sql语句
String value() default "";
}
public interface IUserDAO {
@Select("'select user from user where userId = ' + #userId")
String queryUser(String userId);
}
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() {
InvocationHandler handler = (proxy, method, args) -> {
final StandardEvaluationContext context = new StandardEvaluationContext();
final Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
context.setVariable(parameters[i].getName(), args[i]);
}
final Select select = method.getAnnotation(Select.class);
final SpelExpressionParser parser = new SpelExpressionParser();
final String sql = parser.parseExpression(select.value())
.getValue(context, String.class);
System.out.println("执行SQL:" + sql);
return "查询结果:xxx";
};
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
getObjectType()
反馈生产的bean的类型,使用构造函数透传被代理类,在mybatis中也是使用这样的方式进行透传。
SpEL
表达式的上下文里,再去解析出真正的sql
javac -parameters
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
final GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MapperFactoryBean.class);
beanDefinition.setScope("singleton");
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(IUserDAO.class);
final BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDAO");
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
userDAO
的bean,下面会定义个同名的bean<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-autowire="byName">
<bean id="userDAO" class="com.yuyy.java.training.base.动态代理模拟Mybatis.RegisterBeanFactory"/>
</beans>
userDAO
@Test
public void test_IUserDao() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("动态代理模拟Mybatis/spring-config.xml");
IUserDAO userDao = beanFactory.getBean("userDAO", IUserDAO.class);
System.out.println(userDao.queryUser("100001"));
}
执行SQL:select user from user where userId = 100001
查询结果:xxx
com.yuyy.java.training.base.动态代理模拟Mybatis.RegisterBeanFactory
的bean如果不是FactoryBean就直接返回bean,如果是,就通过工厂bean来生产需要的bean
得到工厂bean生产的动态代理类对象
根据提供的bean类型做类型转换
AbstractBeanFactory#doGetBean()
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
try {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}