You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

问题原因

核心是执行顺序错误:

  1. Spring创建Bean时优先调用类的构造方法,此时trackID还未被赋值(beforeEnter方法在Bean实例化完成、路由跳转时才会执行)。
  2. 构造方法中调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:39:53