如何在Vaadin页面中正确使用Route Parameter?
问题:Vaadin+Spring中使用路由参数调用Spotify API时触发Bean创建异常
场景说明
我用Vaadin开发Spring应用,结合Spotify API的Java封装包,需要从跳转至当前页面的RouterLink中获取路由参数(Spotify Track ID),并通过该参数展示歌曲信息。
生成携带路由参数的RouterLink代码:
RouterLink songPageLink = new RouterLink("Song Details", songDescription.class, new RouteParameters("trackID", track.getId()));
目标页面songDescription的初始代码:
package trackour.trackour.views.songDescription; import org.springframework.stereotype.Component; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; import trackour.trackour.views.api.APIController; @Route("songDescription/:trackID?") @PageTitle("Description") @AnonymousAllowed @Component public class songDescription extends VerticalLayout implements BeforeEnterObserver { private String trackID; public songDescription() { H1 songTitle = new H1("SONG NAME"); H2 songArtist = new H2("SONG ARTIST"); Image albumCover = new Image(); Paragraph acousticnessDetail = new Paragraph("VALUE"); Paragraph danceabilityDetail = new Paragraph("VALUE"); Paragraph energyDetail = new Paragraph("VALUE"); Paragraph instrumentalnessDetail = new Paragraph("VALUE"); Paragraph keyDetail = new Paragraph("VALUE"); Paragraph livenessDetail = new Paragraph("VALUE"); Paragraph loudnessDetail = new Paragraph("VALUE"); Paragraph modeDetail = new Paragraph("VALUE"); Paragraph tempoDetail = new Paragraph("VALUE"); Paragraph timeSignatureDetail = new Paragraph("VALUE"); Paragraph valenceDetail = new Paragraph("VALUE"); add(albumCover, songTitle, songArtist, acousticnessDetail, danceabilityDetail, energyDetail, instrumentalnessDetail, keyDetail, livenessDetail, loudnessDetail, modeDetail, tempoDetail, timeSignatureDetail, valenceDetail); } @Override public void beforeEnter(BeforeEnterEvent event) { trackID = event.getRouteParameters().get("trackID").orElseThrow(); } }
问题重现
当尝试在构造方法中直接使用trackID调用API时,比如:
H1 songTitle = new H1(APIController.getTrackName(trackID));
会触发Spring Bean创建异常,错误栈如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'songDescription' defined in file [C:\Users\gohar\OneDrive\SFU\CMPT276\trackour\target\classes\trackour\trackour\views\songDescription\songDescription.class]: Failed to instantiate [trackour.trackour.views.songDescription.songDescription]: Constructor threw exception at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1314) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1199) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:941) ~[spring-context-6.0.10.jar:6.0.10] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[spring-context-6.0.10.jar:6.0.10] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.1.jar:3.1.1] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-3.1.1.jar:3.1.1] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) ~[spring-boot-3.1.1.jar:3.1.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-3.1.1.jar:3.1.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.1.jar:3.1.1] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.1.jar:3.1.1] at trackour.trackour.Application.main(Application.java:26) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.1.jar:3.1.1] Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [trackour.trackour.views.songDescription.songDescription]: Constructor threw exception at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:224) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87) ~[spring-beans-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1308) ~[spring-beans-6.0.10.jar:6.0.10] ... 22 common frames omitted Caused by: java.lang.NullPointerException: Cannot invoke "String.length()" because "replacement" is null at java.base/java.util.regex.Matcher.appendExpandedReplacement(Matcher.java:1014) ~[na:na] at java.base/java.util.regex.Matcher.appendReplacement(Matcher.java:997) ~[na:na] at java.base/java.util.regex.Matcher.replaceAll(Matcher.java:1181) ~[na:na] at java.base/java.lang.String.replaceAll(String.java:2940) ~[na:na] at se.michaelthelin.spotify.requests.AbstractRequest$Builder.setPath(AbstractRequest.java:244) ~[spotify-web-api-java-8.0.0.jar:na] at se.michaelthelin.spotify.requests.data.tracks.GetTrackRequest$Builder.build(GetTrackRequest.java:89) ~[spotify-web-api-java-8.0.0.jar:na] at trackour.trackour.views.api.APIController.getTrackName(APIController.java:300) ~[classes/:na] at trackour.trackour.views.songDescription.songDescription.<init>(songDescription.java:28) ~[classes/:na] at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na] at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[na:na] at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) ~[na:na] at org.springframework.beans.factory.BeanUtils.instantiateClass(BeanUtils.java:198) ~[spring-beans-6.0.10.jar:6.0.10] ... 24 common frames omitted
问题原因
核心是执行顺序错误:
- Spring创建Bean时优先调用类的构造方法,此时
trackID还未被赋值(beforeEnter方法在Bean实例化完成、路由跳转时才会执行)。 - 构造方法中调用
APIController.getTrackName(trackID)时,trackID为null,导致Spotify API构建请求时触发空指针异常。
解决方案
将组件初始化与API调用逻辑拆分:先在构造方法中创建空组件,再在beforeEnter方法中获取路由参数并更新组件内容。
修改后的代码示例:
package trackour.trackour.views.songDescription; import org.springframework.stereotype.Component; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.auth.AnonymousAllowed; import trackour.trackour.views.api.APIController; @Route("songDescription/:trackID?") @PageTitle("Description") @AnonymousAllowed @Component // 建议修改类名为大驼峰,符合Java命名规范 public class SongDescription extends VerticalLayout implements BeforeEnterObserver { private H1 songTitle; private H2 songArtist; private Image albumCover; private Paragraph acousticnessDetail; private Paragraph danceabilityDetail; private Paragraph energyDetail; private Paragraph instrumentalnessDetail; private Paragraph keyDetail; private Paragraph livenessDetail; private Paragraph loudnessDetail; private Paragraph modeDetail; private Paragraph tempoDetail; private Paragraph timeSignatureDetail; private Paragraph valenceDetail; public SongDescription() { // 构造方法中仅初始化空组件 songTitle = new H1(); songArtist = new H2(); albumCover = new Image(); acousticnessDetail = new Paragraph(); danceabilityDetail = new Paragraph(); energyDetail = new Paragraph(); instrumentalnessDetail = new Paragraph(); keyDetail = new Paragraph(); livenessDetail = new Paragraph(); loudnessDetail = new Paragraph(); modeDetail = new Paragraph(); tempoDetail = new Paragraph(); timeSignatureDetail = new Paragraph(); valenceDetail = new Paragraph(); add(albumCover, songTitle, songArtist, acousticnessDetail, danceabilityDetail, energyDetail, instrumentalnessDetail, keyDetail, livenessDetail, loudnessDetail, modeDetail, tempoDetail, timeSignatureDetail, valenceDetail); } @Override public void beforeEnter(BeforeEnterEvent event) { // 获取路由参数 String trackID = event.getRouteParameters().get("trackID").orElseThrow(); // 调用API并更新组件内容 songTitle.setText(APIController.getTrackName(trackID)); songArtist.setText(APIController.getTrackArtist(trackID)); albumCover.setSrc(APIController.getAlbumCoverUrl(trackID)); acousticnessDetail.setText("Acousticness: " + APIController.getAcousticness(trackID)); danceabilityDetail.setText("Danceability: " + APIController.getDanceability(trackID)); // 其余属性按此方式更新 } }
内容的提问来源于stack exchange,提问作者Gyro
相关产品推荐
相关产品推荐

