Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/graalvm-native-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ src/main/resources/META-INF/native-image/
- `@MapperScan` 显式声明 `sqlSessionFactoryRef`,否则 AOT 丢 `AUTOWIRE_BY_TYPE` 装配;
- `spring-cloud-starter-bootstrap` 与 AOT 不兼容,改用 `spring.config.import`;
- MyBatis mapper 代理与实体由官方 `MyBatisNativeConfiguration` 模板的 AOT 处理器注册,
bean 名勿与 MP 自带的 `MyBatisPlusNativeImageConfiguration` 重复;
bean 名勿与 MP 自带的 `MyBatisPlusNativeImageConfiguration` 重复;Boot 4 模块用的
mybatis-spring 4.1.0 已内置构造参数修正与 AOT 运行时跳过重复扫描(mybatis/spring#1238、
#1239),模板因此只剩该处理器;`springboot3-sample` 解析到的 mybatis-spring 3.0.5
尚无这两处修复,保留完整模板;
- `MySQLContainer` 的挂载问题见 testcontainers-java#7954(子类跳过挂载或用 `GenericContainer`);
- ShardingSphere 用 `config-native.yaml`(算法与行表达式限制见官方文档),另需显式依赖
`javax.transaction-api`;其自带元数据在 JDBC 用法下多不生效,以本仓库条目为准;
Expand Down
5 changes: 5 additions & 0 deletions docs/upstream-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
- TODO https://github.com/spring-projects/spring-framework/issues/37500 :Spring AOT
保留 `autowireMode` 后,去掉各模块 `@MapperScan` 的 `sqlSessionFactoryRef`
(逐模块去属性并跑 nativeTest 验证)。
- TODO https://github.com/mybatis/spring/issues/1309 :mybatis-spring 内置 mapper 的 AOT
处理器(为 mapper 接口注册反射、JDK 代理与同名 XML 等 hints)后,升级父 POM 的
`mybatis-spring.version`,删除 Boot 4 各模块的 `MyBatisNativeConfiguration` 并逐模块跑
nativeTest 验证;`springboot3-sample` 不必等此项:MP 3.5.17 起自带
`mybatis-plus-spring-boot-native-image`,升级 MP 后即可删除其副本。
- TODO https://github.com/baomidou/dynamic-datasource/issues/809 :DD 补上
`@NestedConfigurationProperty` 或自带元数据后,删除各模块
`dynamic-datasource-creator` 目录下的 `DatasourceInitProperties` 条目。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,81 +19,52 @@
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
* MyBatis 的 Spring AOT 集成。
*
* <p>取自 MyBatis 官方的 MyBatisNativeConfiguration 模板;静态的 GraalVM 元数据(日志实现、 缓存装饰器、dtd/xsd 资源等)已按依赖分目录改为
* {@code META-INF/native-image/<groupId>/<artifactId>/<version>/reachability-metadata.json},
* 这里只保留无法用静态元数据表达的部分:
* <p>取自 MyBatis 官方的 MyBatisNativeConfiguration 模板;静态的 GraalVM 元数据(日志实现、缓存装饰器、dtd/xsd 资源等)已按依赖分目录改为
* {@code META-INF/native-image/<groupId>/<artifactId>/<version>/reachability-metadata.json}。
*
* <ul>
* <li>{@code MyBatisMapperFactoryBeanPostProcessor}:为 MapperFactoryBean 的 BeanDefinition
* 补齐类型信息,避免 AOT 代码生成退回到自动装配 Class;
* <li>{@code MyBatisBeanFactoryInitializationAotProcessor}:处理 mapper 扫描器生成的 MapperFactoryBean
* 定义,并为 mapper 接口及其实体注册反射(由 AOT 扫描结果驱动)。
* </ul>
* <p>模板中的 {@code MyBatisMapperFactoryBeanPostProcessor} 与对 {@code MapperScannerConfigurer} 的 AOT
* 排除已删除:mybatis-spring 4.1.0 起扫描器直接以 {@code Class} 登记 mapper 构造参数(mybatis/spring#1238),并在使用 AOT
* 产物运行时跳过重复扫描(mybatis/spring#1239)。该后处理器补齐的泛型 targetType 随之不再生成,native 运行时按类型匹配时会先实例化 {@code
* MapperFactoryBean}(只调用构造器)以取得 mapper 类型,nativeTest 实测无影响。
*
* <p>这里只保留 mybatis-spring 尚未内置、又无法用静态元数据表达的部分:{@code MyBatisBeanFactoryInitializationAotProcessor}
* 按 AOT 扫描结果为 mapper 接口注册反射、JDK 代理与同名 XML 资源,并为其方法的参数、返回值及 SQL Provider 类型注册反射。
*/
@Configuration(proxyBeanMethods = false)
public class MyBatisNativeConfiguration {

@Bean
static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() {
return new MyBatisMapperFactoryBeanPostProcessor();
}

@Bean
MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() {
return new MyBatisBeanFactoryInitializationAotProcessor();
}

static class MyBatisBeanFactoryInitializationAotProcessor
implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter {

private final Set<Class<?>> excludeClasses = new HashSet<>();

MyBatisBeanFactoryInitializationAotProcessor() {
excludeClasses.add(MapperScannerConfigurer.class);
}

@Override
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
return excludeClasses.contains(registeredBean.getBeanClass());
}
implements BeanFactoryInitializationAotProcessor {

@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(
Expand Down Expand Up @@ -228,63 +199,4 @@ private static Class<?> typeToClass(Type src, Class<?> fallback) {
return result;
}
}

static class MyBatisMapperFactoryBeanPostProcessor
implements MergedBeanDefinitionPostProcessor, BeanFactoryAware {

private static final org.apache.commons.logging.Log LOG =
LogFactory.getLog(MyBatisMapperFactoryBeanPostProcessor.class);

private static final String MAPPER_FACTORY_BEAN =
"org.mybatis.spring.mapper.MapperFactoryBean";

private ConfigurableBeanFactory beanFactory;

@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}

@Override
public void postProcessMergedBeanDefinition(
RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) {
resolveMapperFactoryBeanTypeIfNecessary(beanDefinition);
}
}

/**
* 为 {@code MapperFactoryBean} 的 BeanDefinition 补齐类型信息。
*
* <p>向上下文暴露泛型类型信息以避免过早初始化;同时把构造参数重新注册为 {@code Class} 字面量——扫描器只会记录 mapper 类名字符串,这会使 AOT
* 代码生成退回到 自动装配 {@code MapperFactoryBean} 构造器,native 测试下会尝试自动装配 {@code Class<?>}。
*/
private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) {
if (!beanDefinition.hasBeanClass()
|| !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) {
return;
}
if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) {
Class<?> mapperInterface = getMapperInterface(beanDefinition);
if (mapperInterface != null) {
beanDefinition.setTargetType(
ResolvableType.forClassWithGenerics(
beanDefinition.getBeanClass(), mapperInterface));
ConstructorArgumentValues constructorArgumentValues =
new ConstructorArgumentValues();
constructorArgumentValues.addGenericArgumentValue(mapperInterface);
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
}
}
}

private Class<?> getMapperInterface(RootBeanDefinition beanDefinition) {
try {
return (Class<?>) beanDefinition.getPropertyValues().get("mapperInterface");
} catch (Exception e) {
LOG.debug("Fail getting mapper interface type.", e);
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,81 +19,52 @@
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

/**
* MyBatis 的 Spring AOT 集成。
*
* <p>取自 MyBatis 官方的 MyBatisNativeConfiguration 模板;静态的 GraalVM 元数据(日志实现、 缓存装饰器、dtd/xsd 资源等)已按依赖分目录改为
* {@code META-INF/native-image/<groupId>/<artifactId>/<version>/reachability-metadata.json},
* 这里只保留无法用静态元数据表达的部分:
* <p>取自 MyBatis 官方的 MyBatisNativeConfiguration 模板;静态的 GraalVM 元数据(日志实现、缓存装饰器、dtd/xsd 资源等)已按依赖分目录改为
* {@code META-INF/native-image/<groupId>/<artifactId>/<version>/reachability-metadata.json}。
*
* <ul>
* <li>{@code MyBatisMapperFactoryBeanPostProcessor}:为 MapperFactoryBean 的 BeanDefinition
* 补齐类型信息,避免 AOT 代码生成退回到自动装配 Class;
* <li>{@code MyBatisBeanFactoryInitializationAotProcessor}:处理 mapper 扫描器生成的 MapperFactoryBean
* 定义,并为 mapper 接口及其实体注册反射(由 AOT 扫描结果驱动)。
* </ul>
* <p>模板中的 {@code MyBatisMapperFactoryBeanPostProcessor} 与对 {@code MapperScannerConfigurer} 的 AOT
* 排除已删除:mybatis-spring 4.1.0 起扫描器直接以 {@code Class} 登记 mapper 构造参数(mybatis/spring#1238),并在使用 AOT
* 产物运行时跳过重复扫描(mybatis/spring#1239)。该后处理器补齐的泛型 targetType 随之不再生成,native 运行时按类型匹配时会先实例化 {@code
* MapperFactoryBean}(只调用构造器)以取得 mapper 类型,nativeTest 实测无影响。
*
* <p>这里只保留 mybatis-spring 尚未内置、又无法用静态元数据表达的部分:{@code MyBatisBeanFactoryInitializationAotProcessor}
* 按 AOT 扫描结果为 mapper 接口注册反射、JDK 代理与同名 XML 资源,并为其方法的参数、返回值及 SQL Provider 类型注册反射。
*/
@Configuration(proxyBeanMethods = false)
public class MyBatisNativeConfiguration {

@Bean
static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() {
return new MyBatisMapperFactoryBeanPostProcessor();
}

@Bean
MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() {
return new MyBatisBeanFactoryInitializationAotProcessor();
}

static class MyBatisBeanFactoryInitializationAotProcessor
implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter {

private final Set<Class<?>> excludeClasses = new HashSet<>();

MyBatisBeanFactoryInitializationAotProcessor() {
excludeClasses.add(MapperScannerConfigurer.class);
}

@Override
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
return excludeClasses.contains(registeredBean.getBeanClass());
}
implements BeanFactoryInitializationAotProcessor {

@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(
Expand Down Expand Up @@ -228,63 +199,4 @@ private static Class<?> typeToClass(Type src, Class<?> fallback) {
return result;
}
}

static class MyBatisMapperFactoryBeanPostProcessor
implements MergedBeanDefinitionPostProcessor, BeanFactoryAware {

private static final org.apache.commons.logging.Log LOG =
LogFactory.getLog(MyBatisMapperFactoryBeanPostProcessor.class);

private static final String MAPPER_FACTORY_BEAN =
"org.mybatis.spring.mapper.MapperFactoryBean";

private ConfigurableBeanFactory beanFactory;

@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}

@Override
public void postProcessMergedBeanDefinition(
RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) {
resolveMapperFactoryBeanTypeIfNecessary(beanDefinition);
}
}

/**
* 为 {@code MapperFactoryBean} 的 BeanDefinition 补齐类型信息。
*
* <p>向上下文暴露泛型类型信息以避免过早初始化;同时把构造参数重新注册为 {@code Class} 字面量——扫描器只会记录 mapper 类名字符串,这会使 AOT
* 代码生成退回到 自动装配 {@code MapperFactoryBean} 构造器,native 测试下会尝试自动装配 {@code Class<?>}。
*/
private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) {
if (!beanDefinition.hasBeanClass()
|| !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) {
return;
}
if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) {
Class<?> mapperInterface = getMapperInterface(beanDefinition);
if (mapperInterface != null) {
beanDefinition.setTargetType(
ResolvableType.forClassWithGenerics(
beanDefinition.getBeanClass(), mapperInterface));
ConstructorArgumentValues constructorArgumentValues =
new ConstructorArgumentValues();
constructorArgumentValues.addGenericArgumentValue(mapperInterface);
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
}
}
}

private Class<?> getMapperInterface(RootBeanDefinition beanDefinition) {
try {
return (Class<?>) beanDefinition.getPropertyValues().get("mapperInterface");
} catch (Exception e) {
LOG.debug("Fail getting mapper interface type.", e);
return null;
}
}
}
}
Loading
Loading