https://github.com/cmdch2017/SpringNative_Graalvm_Mybatis
首先安装步骤参考这篇博客
https://blog.csdn.net/weixin_38943666/article/details/129505945
其次如何处理反射
https://blog.csdn.net/qq_32740973/article/details/131799510
第一步,直接拷贝我项目中的config文件夹到你的项目
package com.example.communicationinterface30003.config; import cn.hutool.core.util.ClassUtil; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Set; /** * 反射将所有项目类扫描加入到服务, 大力出奇迹的操作,感觉不太合适,不过先让服务跑起来 * * @author PC * */ @Component public class ClassReflectConfig { static boolean begin = true; private Boolean scanclass=true; @Autowired private ThreadPoolTaskExecutor executorService; @PostConstruct public void init() { if (scanclass) { System.err.println("配置文件下 scanclass 开启了生成反射类"); } else { System.err.println("配置文件下 scanclass 关闭了生成反射类"); } synchronized (ClassReflectConfig.class) { if (begin && scanclass) { begin = false; executorService.submit(() -> { { // 扫描系统第二级开始的包 String packageName = ClassReflectConfig.class.getPackageName(); String proPackageName = packageName.substring(0, packageName.indexOf(".", packageName.indexOf(".") + 1)); // 可以在这个地方,添加除了服务以外其他的包,将会加入反射,以供graalvm生成配置 ListasList = Arrays.asList(proPackageName); for (String spn : asList) { try { Set > doScan = ClassUtil.scanPackage(spn); for (Class clazz : doScan) { handlerClass(clazz); } } catch (Throwable e) { e.printStackTrace(); } } } }); } } } private void handlerClass(Class clazz) { if (clazz.equals(ClassReflectConfig.class)) { // 跳过自己,避免形成循环 return; } executorService.submit(() -> { try { System.err.println("反射注入:" + clazz.getName()); // 生成所有的构造器 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); // 找到无参构造器然后实例化 Constructor declaredConstructor = clazz.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Object newInstance = declaredConstructor.newInstance(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { try { // 实例化成功,那么调用一下 method.setAccessible(true); // graalvm必须需要声明方法 method.invoke(newInstance); } catch (Throwable e) { } } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); field.getType(); String name = field.getName(); field.get(newInstance); } catch (Throwable e) { } } System.err.println("反射注入完成:" + clazz.getName()); } catch (Throwable e) { } }); } }
package org.example.config; 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.cache.decorators.FifoCache; import org.apache.ibatis.cache.decorators.LruCache; import org.apache.ibatis.cache.decorators.SoftCache; import org.apache.ibatis.cache.decorators.WeakCache; import org.apache.ibatis.cache.impl.PerpetualCache; import org.apache.ibatis.javassist.util.proxy.ProxyFactory; import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; import org.apache.ibatis.logging.log4j2.Log4j2Impl; import org.apache.ibatis.logging.nologging.NoLoggingImpl; import org.apache.ibatis.logging.slf4j.Slf4jImpl; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.reflection.TypeParameterResolver; import org.apache.ibatis.scripting.defaults.RawLanguageDriver; import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; 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.aot.hint.RuntimeHintsRegistrar; 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.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.context.annotation.ImportRuntimeHints; import org.springframework.core.ResolvableType; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @Configuration(proxyBeanMethods = false) @ImportRuntimeHints(MyBatisNativeConfiguration.MyBaitsRuntimeHintsRegistrar.class) public class MyBatisNativeConfiguration { @Bean MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() { return new MyBatisBeanFactoryInitializationAotProcessor(); } @Bean static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() { return new MyBatisMapperFactoryBeanPostProcessor(); } static class MyBaitsRuntimeHintsRegistrar implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { Stream.of(RawLanguageDriver.class, XMLLanguageDriver.class, RuntimeSupport.class, ProxyFactory.class, Slf4jImpl.class, Log.class, JakartaCommonsLoggingImpl.class, Log4j2Impl.class, Jdk14LoggingImpl.class, StdOutImpl.class, NoLoggingImpl.class, SqlSessionFactory.class, PerpetualCache.class, FifoCache.class, LruCache.class, SoftCache.class, WeakCache.class, SqlSessionFactoryBean.class, ArrayList.class, HashMap.class, TreeSet.class, HashSet.class ).forEach(x -> hints.reflection().registerType(x, MemberCategory.values())); Stream.of( "org/apache/ibatis/builder/xml/*.dtd", "org/apache/ibatis/builder/xml/*.xsd" ).forEach(hints.resources()::registerPattern); } } static class MyBatisBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter { private final Set> excludeClasses = new HashSet<>(); MyBatisBeanFactoryInitializationAotProcessor() { excludeClasses.add(MapperScannerConfigurer.class); } @Override public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { return excludeClasses.contains(registeredBean.getBeanClass()); } @Override public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { String[] beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class); if (beanNames.length == 0) { return null; } return (context, code) -> { RuntimeHints hints = context.getRuntimeHints(); for (String beanName : beanNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1)); PropertyValue mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface"); if (mapperInterface != null && mapperInterface.getValue() != null) { Class> mapperInterfaceType = (Class>) mapperInterface.getValue(); if (mapperInterfaceType != null) { registerReflectionTypeIfNecessary(mapperInterfaceType, hints); hints.proxies().registerJdkProxy(mapperInterfaceType); hints.resources() .registerPattern(mapperInterfaceType.getName().replace('.', '/').concat(".xml")); registerMapperRelationships(mapperInterfaceType, hints); } } } }; } private void registerMapperRelationships(Class> mapperInterfaceType, RuntimeHints hints) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType); for (Method method : methods) { if (method.getDeclaringClass() != Object.class) { ReflectionUtils.makeAccessible(method); registerSqlProviderTypes(method, hints, SelectProvider.class, SelectProvider::value, SelectProvider::type); registerSqlProviderTypes(method, hints, InsertProvider.class, InsertProvider::value, InsertProvider::type); registerSqlProviderTypes(method, hints, UpdateProvider.class, UpdateProvider::value, UpdateProvider::type); registerSqlProviderTypes(method, hints, DeleteProvider.class, DeleteProvider::value, DeleteProvider::type); Class> returnType = MyBatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method); registerReflectionTypeIfNecessary(returnType, hints); MyBatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method) .forEach(x -> registerReflectionTypeIfNecessary(x, hints)); } } } @SafeVarargs private void registerSqlProviderTypes( Method method, RuntimeHints hints, Class annotationType, Function >... providerTypeResolvers) { for (T annotation : method.getAnnotationsByType(annotationType)) { for (Function > providerTypeResolver : providerTypeResolvers) { registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), hints); } } } private void registerReflectionTypeIfNecessary(Class> type, RuntimeHints hints) { if (!type.isPrimitive() && !type.getName().startsWith("java")) { hints.reflection().registerType(type, MemberCategory.values()); } } } static class MyBatisMapperTypeUtils { private MyBatisMapperTypeUtils() { // NOP } static Class> resolveReturnClass(Class> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); return typeToClass(resolvedReturnType, method.getReturnType()); } static Set > resolveParameterClasses(Class> mapperInterface, Method method) { return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface)) .map(x -> typeToClass(x, x instanceof Class ? (Class>) x : Object.class)).collect(Collectors.toSet()); } private static Class> typeToClass(Type src, Class> fallback) { Class> result = null; if (src instanceof Class>) { if (((Class>) src).isArray()) { result = ((Class>) src).getComponentType(); } else { result = (Class>) src; } } else if (src instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) src; int index = (parameterizedType.getRawType() instanceof Class && Map.class.isAssignableFrom((Class>) parameterizedType.getRawType()) && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0; Type actualType = parameterizedType.getActualTypeArguments()[index]; result = typeToClass(actualType, fallback); } if (result == null) { result = 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); } } 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) { // Exposes a generic type information to context for prevent early initializing beanDefinition .setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface)); } } } 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; } } } }
第二步,pom.xml加入依赖
org.graalvm.buildtools native-maven-plugin org.springframework.boot spring-boot-maven-plugin
第三步执行下面的语句
java -agentlib:native-image-agent=config-output-dir=D:\eclipse_file\new_ws\springboot3-demo3\src\main\resources\META-INF\native-image -jar .\springboot3-demo3-0.0.1-SNAPSHOT.jar
1、如上图所示,点击mvn clean 2、如上图所示,点击mvn install 3、执行反射编译语句 java -agentlib:native-image-agent=config-output-dir=C:\Demos\CommunicationInterface30003\src\main\resources\META-INF\native-image -jar .\target\comu300003-1.0.0-SNAPSHOT.jar 4、打开 x64 Native Toogls Command Prompt for VS 并 cd到项目文件夹 mvn -Pnative -DskipTests clean native:compile 5、PowerShell中进入项目target目录下 .\graalvm.exe
org.apache.catalina.LifecycleException: An invalid Lifecycle transition was attempted ([before_stop]) for component
用不了反射,所以需要这个文件去
package org.wxy.example.sqlite.config; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Set; import cn.hutool.core.util.ClassUtil; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; /** * 反射将所有项目类扫描加入到服务, 大力出奇迹的操作,感觉不太合适,不过先让服务跑起来 * * @author PC * */ @Component public class ClassReflectConfig { static boolean begin = true; // @Value("${scanclass}") private Boolean scanclass=true; @Autowired private ThreadPoolTaskExecutor executorService; @PostConstruct public void init() { if (scanclass) { System.err.println("配置文件下 scanclass 开启了生成反射类"); } else { System.err.println("配置文件下 scanclass 关闭了生成反射类"); } synchronized (ClassReflectConfig.class) { if (begin && scanclass) { begin = false; executorService.submit(() -> { { // 扫描系统第二级开始的包 String packageName = ClassReflectConfig.class.getPackageName(); String proPackageName = packageName.substring(0, packageName.indexOf(".", packageName.indexOf(".") + 1)); // 可以在这个地方,添加除了服务以外其他的包,将会加入反射,以供graalvm生成配置 ListasList = Arrays.asList(proPackageName); for (String spn : asList) { try { Set > doScan = ClassUtil.scanPackage(spn); for (Class clazz : doScan) { handlerClass(clazz); } } catch (Throwable e) { e.printStackTrace(); } } } }); } } } private void handlerClass(Class clazz) { if (clazz.equals(ClassReflectConfig.class)) { // 跳过自己,避免形成循环 return; } executorService.submit(() -> { try { System.err.println("反射注入:" + clazz.getName()); // 生成所有的构造器 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); // 找到无参构造器然后实例化 Constructor declaredConstructor = clazz.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Object newInstance = declaredConstructor.newInstance(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { try { // 实例化成功,那么调用一下 method.setAccessible(true); // graalvm必须需要声明方法 method.invoke(newInstance); } catch (Throwable e) { } } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { try { field.setAccessible(true); field.getType(); String name = field.getName(); field.get(newInstance); } catch (Throwable e) { } } System.err.println("反射注入完成:" + clazz.getName()); } catch (Throwable e) { } }); } }
之后报错信息
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sqliteController': Unsatisfied dependency expressed through field 'personService': Error creating bean with name 'personService': Unsatisfied dependency expressed through field 'dao': Error creating bean with name 'personMapper': Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:195) ~[na:na] at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveObject(AutowiredFieldValueResolver.java:154) ~[na:na] at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolve(AutowiredFieldValueResolver.java:143) ~[na:na] at org.wxy.example.sqlite.controllers.SqliteController__Autowiring.apply(SqliteController__Autowiring.java:14) ~[na:na] at org.springframework.beans.factory.support.InstanceSupplier.get(InstanceSupplier.java:83) ~[na:na] at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[mysqlandmybatis.exe:6.0.8]
无法读取外部 DTD ‘mybatis-3-mapper.dtd’, 因为 accessExternalDTD 属性设置的限制导致不允许 ‘http’ 访问。
允许对 ‘http’ 的访问:
如果你确定从 ‘http’ 地址下载 DTD 文件是安全的,可以配置解析器以允许对 ‘http’ 的访问。在SpringbootApplication 中,可以使用以下代码:
System.setProperty("javax.xml.accessExternalDTD", "all");
用powerShell打开
加上支持http协议的参数
org.graalvm.buildtools native-maven-plugin --enable-url-protocols=http org.springframework.boot spring-boot-maven-plugin sonatype-oss-snapshots Sonatype OSS Snapshots Repository https://oss.sonatype.org/content/repositories/snapshots
org.apache.catalina.LifecycleException: An invalid Lifecycle transition was attempted ([before_stop]) for component [StandardEngine[Tomcat]] in state [INITIALIZED] at org.apache.catalina.util.LifecycleBase.invalidTransition(LifecycleBase.java:430) ~[mysqlandmybatis.exe:10.1.8] at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:244) ~[mysqlandmybatis.exe:10.1.8] at org.apache.catalina.core.StandardService.stopInternal(StandardService.java:491) ~[na:na] at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:257) ~[mysqlandmybatis.exe:10.1.8] at org.apache.catalina.core.StandardServer.stopInternal(StandardServer.java:966) ~[na:na] at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:257) ~[mysqlandmybatis.exe:10.1.8] at org.apache.catalina.util.LifecycleBase.destroy(LifecycleBase.java:293) ~[mysqlandmybatis.exe:10.1.8] at org.apache.catalina.startup.Tomcat.destroy(Tomcat.java:507) ~[mysqlandmybatis.exe:10.1.8] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.destroySilently(TomcatWebServer.java:262) ~[mysqlandmybatis.exe:3.0.6] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:141) ~[mysqlandmybatis.exe:3.0.6] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.(TomcatWebServer.java:104) ~[mysqlandmybatis.exe:3.0.6] at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:488) ~[mysqlandmybatis.exe:3.0.6]
或者是下面的Controller层报错
原因是你没按照网上的教程来,在加了问题1的ClassReflectConfig 内容后,mvn -Pnative -DskipTests clean native:compile之后,你得执行以下语句,注意这里C:\Demos\springboot3-demo3-master\src\main\resources\META-INF\native-image还有.\target\springboot3-demo3-0.0.1-SNAPSHOT.jar的路径看你项目中的路径
java -agentlib:native-image-agent=config-output-dir=C:\Demos\springboot3-demo3-master\src\main\resources\META-INF\native-image -jar .\target\springboot3-demo3-0.0.1-SNAPSHOT.jar
检查yml文件中是否配置好了
mybatis: mapper-locations: classpath:mapper/*.xml typeAliasesPackage: org.wxy.example.*.model
mybatis-plus暂不支持,官方也回应暂时没有计划支持graalvm
@MapperScan(basePackages ="org.wxy.example.*.mapper",sqlSessionFactoryRef = "sqlSessionFactory")
https://zhuanlan.zhihu.com/p/602486720?utm_id=0
下面将介绍把spring boot项目打包成docker镜像,大部分操作与上诉内容一致,但需要在自己本地电脑安装docker,这里需要修改一下pom文件中spring-boot-maven-plugin插件配置,如下所示
org.springframework.boot spring-boot-maven-plugin paketobuildpacks/builder:tiny true org.projectlombok lombok
搭梯子运行下面的语句
1、在终端输入指令:mvn -Pnative spring-boot:build-image
2、打开docker desktop
3、在终端输入指令:端口号注意是你自己项目的端口号
docker run -itd -p 8001:8001 --name graalvm graalvm:0.0.1-SNAPSHOT