本文最后更新于 1009 天前,其中的信息可能已经有所发展或是发生改变。
实战代理模式,模拟Mybatis
在使用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 "";
}
DAO层接口使用注解绑定sql
public interface IUserDAO {
@Select("'select user from user where userId = ' + #userId")
String queryUser(String userId);
}
生产代理类bean的工厂bean
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 -
通过反射获取的参数名不对
-
解决方式一:javac编译时增加参数
javac -parameters
-
解决方式二:maven设置编译参数,重新编译项目即可
- 成功获取到参数名
-
注册工厂bean的bean定义到容器
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里
<?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
测试
@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
分析流程
解析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表达式传入的增强方法