实战代理模式,模拟Mybatis
在使用mybatis操作数据库时,我们只需要定义一个接口,然后在xml里编写对应的sql,就能查询数据。其原理是Mybatis通过@mypperscan
指定扫描的mapper接口路径,对mapper接口进行动态代理,生成的代理类通过解析xml得到对应sql,最终开发人员只需要调用接口就能执行sql了。
今天我们来实现一个简单的demo,利用动态代理,模拟mybatis查询数据。
代码实现
自定义注解
代码语言:javascript复制@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
// sql语句
String value() default "";
}
DAO层接口使用注解绑定sql
代码语言:javascript复制public interface IUserDAO {
@Select("'select user from user where userId = ' #userId")
String queryUser(String userId);
}
生产代理类bean的工厂bean
代码语言:javascript复制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;
}
}
- 由于需要定制化bean(DAO接口的动态代理类),这里定义了个工厂bean,来生产代理类bean。
-
getObjectType()
反馈生产的bean的类型,使用构造函数透传被代理类,在mybatis中也是使用这样的方式进行透传。 - 将参数名,参数值放入
SpEL
表达式的上下文里,再去解析出真正的sql - 通过反射获取的参数名不对
注册工厂bean的bean定义到容器
代码语言:javascript复制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 {
}
}
- 覆盖name为
userDAO
的bean,下面会定义个同名的bean
注入到IOC里
代码语言:javascript复制<?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>
- beanName同样设置为
userDAO
测试
代码语言:javascript复制@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"));
}
结果
代码语言:javascript复制执行SQL:select user from user where userId = 100001
查询结果:xxx
分析流程
解析spring配置文件
- 读取到name为userDAO,class为
com.yuyy.java.training.base.动态代理模拟Mybatis.RegisterBeanFactory
的bean
运行bean的postProcessBeanDefinitionRegistry()方法
- bean实现了bean定义注册后置处理接口BeanDefinitionRegistryPostProcessor
- 在spring初始化核心方法refresh中的这个阶段
- 将beanName为userDAO的bean定义,覆盖成我们写的工厂bean的bean定义
- 将被代理类透传到工厂bean
创建工厂bean
- 在refresh中的实例化所有非懒加载单例bean阶段
获取name为userDAO的bean
- 指定需要的bean类型
通过工厂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());
}
}
运行代理类的增强方法
- 通过lambda表达式传入的增强方法