Прежде чем представить принцип интеграции Mybatis в Spring, мы должны сначала представить принцип работы Mybatis.
Основной принцип работы Mybatis
Кроме того, я собрал сборник 20-летних исследований и вопросов для интервью, в том числе краткий обзор Spring, Concurrency, базы данных, Redis, Distributed, Dubbo, JVM, микросервисов и т. д. Если вам это нужно, нажмите, чтобы получить его самостоятельно:Документация Тенсент
В Mybatis мы можем использовать интерфейс для определения выполняемого sql.Упрощенный код выглядит следующим образом:
Определите интерфейс, @Select указывает, что оператор запроса sql должен быть выполнен.
public interface UserMapper {
@Select("select * from user where id = #{id}")
User selectById(Integer id);
}
Ниже приведен код выполнения sql:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 以下使我们需要关注的重点
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
User user = mapper.selectById(id);
Целью Mybatis является:Он позволяет программистам выполнять указанный sql, вызывая метод, и инкапсулирует базовую логику выполнения sql.
Здесь мы сосредоточимся на следующем объекте сопоставления: при вызове метода getMapper класса SqlSession будет создана карта для входящего интерфейса.прокси-объект, и программа фактически использует этот прокси-объект.При вызове метода прокси-объекта Mybatis выведет оператор sql, соответствующий методу, а затем использует JDBC для выполнения оператора sql и, наконец, получит результат.
Проанализируйте проблему, которую необходимо решить
Когда используются Spring и Mybatis, мы должны сосредоточиться на этом прокси-объекте. Потому что цель интеграции состоит в том, чтобы:Поместите прокси-объект Mapper как bean-компонент в контейнер Spring, чтобы прокси-объект можно было использовать как обычный bean-компонент, например, его можно было автоматически внедрить @Autowire.
Например, когда Spring и Mybatis интегрированы, мы можем использовать следующий код для использования прокси-объекта в Mybatis:
@Component
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
}
Свойство userMapper в serService будет автоматически внедрено в качестве прокси-объекта в Mybatis. Если вы отлаживаете на основе интегрированного проекта, вы можете обнаружить, что тип userMapper:org.apache.ibatis.binding.MapperProxy@41a0aa7d
. Доказательство действительно является прокси-объектом в Mybatis.
Итак, теперь нам нужно решить проблему: как я могу поместить прокси-объект Mybatis в виде bean-компонента в контейнер Spring?
Чтобы решить эту проблему, нам нужно иметь представление о процессе генерации bean-компонентов Spring.
Процесс генерации бобов в Spring
Во время процесса запуска Spring bean-компонент будет создан примерно с помощью следующих шагов.
- Сканировать файлы классов по указанному пути пакета
- Создайте соответствующее BeanDefinition в соответствии с информацией о классе.
- Здесь программист может использовать некоторый механизм для модификации BeanDefinition.
- Создание экземпляра компонента на основе BeanDefinition
- Поместите сгенерированный экземпляр компонента в контейнер Spring.
Предположим, есть класс A, допустим, есть следующий код: Класс А:
@Component
public class A {
}
Класс B, нет аннотации @Component
public class B {
}
Выполните следующий код:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
Результат:com.luban.util.A@6acdbdf5
Тип объекта bean, соответствующий классу A, по-прежнему остается классом A. Но этот вывод неопределенный, мы можем использовать постпроцессор BeanFactory для изменения BeanDefinition, мы добавляем постпроцессор BeanFactory:
@Component
public class LubanBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a");
beanDefinition.setBeanClassName(B.class.getName());
}
}
Это приведет к тому, что BeanDefiniton, соответствующий исходному классу A, будет изменен и изменен на класс B, тогда тип объекта bean, который обычно генерируется впоследствии, будет классом B. На этом этапе вызов следующего кода сообщит об ошибке:
context.getBean(A.class);
Но вызов следующего кода не сообщит об ошибке, хотя в классе B нет аннотации @Component:
context.getBean(B.class);
И результат, возвращаемый следующим кодом:com.luban.util.B@4b1c1ea0
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(context.getBean("a"));
Причина этого вопроса - прояснить проблему:В Spring объект bean не имеет прямой связи с классом, имеет прямую связь с BeanDefinition.
Итак, вернемся к проблеме, которую мы хотим решить:Как поместить прокси-объект Mybatis в виде bean-компонента в контейнер Spring?
Весной,Если вы хотите сгенерировать bean-компонент, вам нужно сначала сгенерировать BeanDefinition., точно так же, как если вы хотите создать новый экземпляр объекта, вы должны сначала иметь класс. .
Решать проблему
Продолжая возвращаться к нашей проблеме, теперь мы хотим сгенерировать bean-компонент самостоятельно, поэтому мы должны сначала сгенерировать BeanDefinition, пока существует BeanDefinition, установив его в BeanDefinition.тип объекта bean, а затем добавьте BeanDefinition в Spring, и Spring автоматически сгенерирует для нас объект bean, соответствующий типу согласно BeanDefinition.
Итак, теперь нам предстоит решить две задачи:
- Каков тип прокси-объекта Mybatis? Поскольку мы хотим установить BeanDefinition
- Как добавить BeanDefinition в контейнер Spring?
Примечание. Постпроцессор BeanFactory, который мы использовали выше, может только изменять BeanDefinition и не может добавлять новое BeanDefinition. Мы должны использовать технику импорта, чтобы добавить BeanDefinition. Позже, если вы используете технологию Import для добавления BeanDefinition, вы можете сначала взглянуть на идеи реализации псевдокода.
Предположение: у нас есть интерфейс UserMapper, а его прокси-объект имеет тип UserMapperProxy. Итак, наша идея такова, псевдокод выглядит следующим образом:
BeanDefinitoin bd = new BeanDefinitoin();
bd.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(bd);
Однако здесь есть серьезная проблема, то есть UserMapperProxy выше — это то, что мы предполагаем, он представляет тип прокси-класса, но прокси-объект в Mybatis реализован с использованием динамической прокси-технологии JDK, то есть прокси прокси-объекта, классы генерируются динамически, и мы просто не можем определить, что такое прокси-класс прокси-объекта.
Итак, вернемся к нашему вопросу:Каков тип прокси-объекта Mybatis?
Ответа могло быть два:
- Прокси-класс, соответствующий прокси-объекту
- Соответствующий интерфейс прокси-объекта
Тогда ответ 1 эквивалентен нету, потому что прокси-класс генерируется динамически, тогда давайте посмотрим на ответ 2:Интерфейс, соответствующий прокси-объекту
Если мы возьмем ответ 2, то ход наших мыслей таков:
BeanDefinition bd = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
bd.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bd);
Однако невозможно установить соответствующий тип BeanDefinition в качестве интерфейса, потому что Spring не имеет возможности обновить экземпляр соответствующего типа в соответствии с этим BeanDefinition, а интерфейс не может напрямую обновить экземпляр.
Итак, теперь возникает проблема, проблема, которую я хочу решить:Каков тип прокси-объекта Mybatis?Оба ответа были отвергнуты нами, поэтому этот вопрос неразрешим, поэтому мы больше не можем думать в этом направлении, а можем только вернуться к исходному вопросу:Как я могу поместить прокси-объект Mybatis в качестве компонента в контейнер Spring?
Подводя итог приведенным выше рассуждениям:Мы хотим установить тип класса BeanDefinition, и тогда Spring автоматически поможет нам сгенерировать соответствующий bean-компонент, но этот способ невозможен.
окончательное решение
Так есть ли у нас другой способ генерировать бобы? а такжеЛогика генерации bean-компонентов не может быть выполнена Spring за нас., мы должны сделать это сами.
FactoryBean
Да, это FactoryBean в Spring. Мы можем использовать FactoryBean для настройки объекта компонента, который мы хотим сгенерировать, например:
@Component
public class LubanFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
Мы определяем LubanFactoryBean, который реализует FactoryBean, а метод getObject используется для настройки логики создания объектов bean.
Выполните следующий код:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
}
}
будет печатать: lubanFactoryBean: com.luban.util.LubanFactoryBean1@4d41cee &lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94 lubanFactoryBean-class: class com.sun.proxy.Proxy20
Из результатов видно, что объект bean-компонента с именем «lubanFactoryBean» из контейнера Spring является прокси-объектом, сгенерированным нашим настраиваемым динамическим прокси-сервером jdk.
Итак, мы можем добавить пользовательский объект bean в контейнер Spring через FactoryBean. Определенный выше LubanFactoryBean соответствует UserMapper, что означает, что мы определили LubanFactoryBean, что эквивалентно помещению прокси-объекта, соответствующего UserMapper, в контейнер в качестве bean-компонента.
Но как программисты, мы не можем определять LubanFactoryBean каждый раз, когда мы определяем Mapper, что очень проблематично.Давайте преобразуем LubanFactoryBean, чтобы сделать его более общим, например:
@Component
public class LubanFactoryBean implements FactoryBean {
// 注意这里
private Class mapperInterface;
public LubanFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object getObject() throws Exception {
Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 执行代理逻辑
return null;
}
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
}
После преобразования LubanFactoryBean LubanFactoryBean становится гибким, и различные интерфейсы Mapper могут передаваться через конструкцию при создании LubanFactoryBean.
На самом деле LubanFactoryBean также является Bean.Мы также можем сгенерировать LubanFactoryBean, сгенерировав BeanDefinition, и установить разные значения для параметров конструктора.Например, псевдокод выглядит следующим образом:
BeanDefinition bd = new BeanDefinitoin();
// 注意一:设置的是LubanFactoryBean
bd.setBeanClassName(LubanFactoryBean.class.getName());
// 注意二:表示当前BeanDefinition在生成bean对象时,会通过调用LubanFactoryBean的构造方法来生成,并传入UserMapper
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(bd);
В частности, примечание 2 означает, что когда текущее BeanDefinition генерирует объект bean, он будет создан путем вызова конструктора LubanFactoryBean, и будет передан объект класса UserMapper. Затем при создании LubanFactoryBean прокси-объект, соответствующий интерфейсу UserMapper, будет создан как компонент.
На данный момент мы фактически завершили задачу, которую хотим решить:Поместите прокси-объект в Mybatis как bean-компонент в контейнер Spring.. Просто мы используем простой объект прокси JDK для имитации объекта прокси в MyBatis. Если у нас есть время, мы можем вызвать область метода, предоставляемой в MyBatis, чтобы создать объект прокси. Я не займу время, чтобы представить это здесь.
Import
На данный момент нам еще нужно сделать одно, а именно, как на самом деле определить BeanDefinition и добавить его в Spring.Как упоминалось выше, нам нужно использовать технологию импорта.Например, мы можем добиться этого:
Определите следующий класс:
public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setBeanClass(LubanFactoryBean.class);
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 添加beanDefinition
registry.registerBeanDefinition("luban"+UserMapper.class.getSimpleName(), beanDefinition);
}
}
И добавьте аннотацию @Import в AppConfig:
@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {
Таким образом, при запуске Spring будет добавлен новый BeanDefinition. BeanDefinition сгенерирует объект LubanFactoryBean, а когда будет сгенерирован объект LubanFactoryBean, будет передан объект UserMapper.class. эквивалентно автоматическому созданию прокси для объекта интерфейса UserMapper в виде bean-компонента.
Суммировать
Подводя итог нашему анализу, мы должны сделать следующее для интеграции Spring и Mybatis:
- Определить LubanFactoryBean
- Определите LubanImportBeanDefinitionRegistrar
- Добавьте аннотацию @Import(LubanImportBeanDefinitionRegistrar.class) в AppConfig.
оптимизация
Таким образом, требования к интеграции могут быть в основном выполнены.Конечно, есть два момента, которые можно оптимизировать.
Сначала определите отдельную аннотацию @LubanScan следующим образом:
@Retention(RetentionPolicy.RUNTIME)
@Import(LubanImportBeanDefinitionRegistrar.class)
public @interface LubanScan {
}
Таким образом, вы можете использовать @LubanScan непосредственно в AppConfig.
Во-вторых, в LubanImportBeanDefinitionRegistrar мы можем сканировать Mapper.В LubanImportBeanDefinitionRegistrar мы можем получить соответствующую аннотацию @LubanScan через AnnotationMetadata, поэтому мы можем установить значение @LubanScan, чтобы указать путь пакета для сканирования. Затем получите заданный путь к пакету в LubanImportBeanDefinitionRegistrar, затем просканируйте все Mapper по указанному пути, сгенерируйте BeanDefinition и поместите его в контейнер Spring.
Итак, основной принцип интеграции Mybatis в Spring исчерпан, и снова резюмируем:
- Определите LubanFactoryBean для создания объекта bean из прокси-объекта Mybatis.
- Определите LubanImportBeanDefinitionRegistrar для создания LubanFactoryBean для различных объектов Mapper.
- Определите @LubanScan для выполнения логики LubanImportBeanDefinitionRegistrar при запуске Spring и укажите путь к пакету.
Вышеупомянутые три элемента относятся к org.mybatis.spring в:
- MapperFactoryBean
- MapperScannerRegistrar
- @MapperScan
наконец
Небольшой лайк, удачи, обратите внимание, молодость постоянна