實戰代理模式,模拟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。
-
回報生産的bean的類型,使用構造函數透傳被代理類,在mybatis中也是使用這樣的方式進行透傳。getObjectType()
- 将參數名,參數值放入
表達式的上下文裡,再去解析出真正的sqlSpEL
- 通過反射擷取的參數名不對
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2Pn5GcuIjNzUWYyEjYzADZ2AzMmdzN4EmYhNGMxcTZ1ATMyI2NvwlM4kDNyYTOtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
- 解決方式一: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為
的bean,下面會定義個同名的beanuserDAO
注入到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為
的beancom.yuyy.java.training.base.動态代理模拟Mybatis.RegisterBeanFactory
運作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表達式傳入的增強方法