Spring3新特性
graalvm打包Springboot+Mybatis
项目源代码
https://github.com/cmdch2017/SpringNative_Graalvm_Mybatis
如何安装与运行
安装graalvm与配置环境
首先安装步骤参考这篇博客
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生成配置
List<String> asList = Arrays.asList(proPackageName);for(String spn : asList){
try {
Set<Class<?>> 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<Class<?>> 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 <T extends Annotation> void registerSqlProviderTypes(
Method method, RuntimeHints hints, Class<T> annotationType, Function<T, Class<?>>... providerTypeResolvers){for(T annotation : method.getAnnotationsByType(annotationType)){for(Function<T, Class<?>> 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<Class<?>> 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加入依赖
<build><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
第三步执行下面的语句
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 install3、执行反射编译语句
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
遇到的零散问题
问题1
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生成配置
List<String> asList = Arrays.asList(proPackageName);for(String spn : asList){try{
Set<Class<?>> 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$1.get(InstanceSupplier.java:83)~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947)~[mysqlandmybatis.exe:6.0.8]
问题2 报错信息提示无法读取外部 DTD ‘mybatis-3-mapper.dtd’
无法读取外部 DTD ‘mybatis-3-mapper.dtd’, 因为 accessExternalDTD 属性设置的限制导致不允许 ‘http’ 访问。
允许对 ‘http’ 的访问:
如果你确定从 ‘http’ 地址下载 DTD 文件是安全的,可以配置解析器以允许对 ‘http’ 的访问。在SpringbootApplication 中,可以使用以下代码:
System.setProperty("javax.xml.accessExternalDTD","all");
问题3 exe文件闪退日志
用powerShell打开
问题4 报错信息有提示http
加上支持http协议的参数
<build><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><configuration><buildArgscombine.children="append"><buildArg>--enable-url-protocols=http</buildArg></buildArgs></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>sonatype-oss-snapshots</id><name>Sonatype OSS Snapshots Repository</name><url>https://oss.sonatype.org/content/repositories/snapshots</url></repository></repositories>
问题5 RestController层的问题
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.<init>(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
问题6 Mapper层问题
检查yml文件中是否配置好了
mybatis:mapper-locations: classpath:mapper/*.xmltypeAliasesPackage: org.wxy.example.*.model
问题7 只支持mybaits,不支持mybatis-plus
mybatis-plus暂不支持,官方也回应暂时没有计划支持graalvm
问题8 报错信息中有sqlSessionFactory
@MapperScan(basePackages ="org.wxy.example.*.mapper",sqlSessionFactoryRef ="sqlSessionFactory")
打包成Docker核心总结
https://zhuanlan.zhihu.com/p/602486720?utm_id=0
下面将介绍把spring boot项目打包成docker镜像,大部分操作与上诉内容一致,但需要在自己本地电脑安装docker,这里需要修改一下pom文件中spring-boot-maven-plugin插件配置,如下所示
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><image><builder>paketobuildpacks/builder:tiny</builder><env><BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE></env></image><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin>
搭梯子运行下面的语句
1、在终端输入指令:mvn -Pnative spring-boot:build-image
2、打开docker desktop
3、在终端输入指令:端口号注意是你自己项目的端口号
docker run -itd -p 8001:8001 --name graalvm graalvm:0.0.1-SNAPSHOT
版权归原作者 cmdch2017 所有, 如有侵权,请联系我们删除。