Skip to content

Conversation

@Absurdon
Copy link

This PR aims to improve the SpringInstantiator provided by the vaadin-spring module.

Do not rely on counting the beans of a type oneselves but instead let spring do its own resolving logic so ambiguity might actually not be an issue. If it still is fall back to the previous behavior.

This fixes an issue where one might have multiple beans of an abstract or interface type in the context but has already taken care of ambiguity by hinting spring on which one to use (e.g. by using @Primary, @Fallback or any other spring provided mechanisms, even future ones). With the old implementation this would lead to an exception as it would try to instantiate a bean of an abstract type or interface, when spring would have been able to find a unique bean if one would have let it try.

This especially fixes cases where one has multiple beans of I18NProvider or MenuAccessControl but marked the bean definitions so spring knows which one to prefer.

The unit tests had to be changed slightly as they use mocking and the used method on the context was changed.

Fixes #18918

@CLAassistant
Copy link

CLAassistant commented Dec 23, 2025

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link

github-actions bot commented Dec 30, 2025

Test Results

1 307 files  + 1  1 307 suites  +1   1h 15m 16s ⏱️ + 1m 19s
9 266 tests + 2  9 197 ✅ + 1  68 💤 ±0  1 ❌ +1 
9 692 runs   - 15  9 616 ✅  - 15  75 💤  - 1  1 ❌ +1 

For more details on these failures, see this check.

Results for commit be35435. ± Comparison against base commit dd5ce25.

♻️ This comment has been updated with latest results.

@tltv
Copy link
Member

tltv commented Dec 30, 2025

Formatter failed writing comment here automatically. Please run mvn spotless:apply on vaadin-spring module and push formatted changes in.

Copy link
Member

@tltv tltv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes works well. There's no new tests to verify the feature with primary beans so at minimum I would want to have at least the following unit tests added in vaadin-spring module's tests:

/*
 * Copyright 2000-2025 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.flow.spring.i18n;

import jakarta.servlet.ServletException;

import java.util.List;
import java.util.Locale;
import java.util.Properties;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;

import com.vaadin.flow.di.Instantiator;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.server.InitParameters;
import com.vaadin.flow.spring.instantiator.SpringInstantiatorTest;

@RunWith(SpringRunner.class)
@Import(PrimaryI18NProviderInstantiationTest.I18NTestConfig.class)
public class PrimaryI18NProviderInstantiationTest {

    @Autowired
    private ApplicationContext context;

    @Configuration
    @ComponentScan
    public static class I18NTestConfig {

    }

    @Component
    public static class I18NTestProvider implements I18NProvider {

        @Override
        public List<Locale> getProvidedLocales() {
            return null;
        }

        @Override
        public String getTranslation(String key, Locale locale,
                Object... params) {
            return null;
        }

    }

    @Component
    public static class I18NTestProvider1 extends I18NTestProvider {

    }

    @Primary
    @Component
    public static class PrimaryI18NTestProvider extends I18NTestProvider {

    }

    public static class DefaultI18NTestProvider extends I18NTestProvider {

    }

    @Test
    public void getI18NProvider_usePrimaryBean() throws ServletException {
        Instantiator instantiator = SpringInstantiatorTest
                .getService(context, null).getInstantiator();

        Assert.assertNotNull(instantiator.getI18NProvider());
        Assert.assertEquals(PrimaryI18NTestProvider.class,
                instantiator.getI18NProvider().getClass());
    }

    @Test
    public void getI18NProvider_givenDefaultI18NProvider_usePrimaryBean()
            throws ServletException {
        Instantiator instantiator = getInstantiator(context);

        Assert.assertNotNull(instantiator.getI18NProvider());
        Assert.assertEquals(PrimaryI18NTestProvider.class,
                instantiator.getI18NProvider().getClass());
    }

    public static Instantiator getInstantiator(ApplicationContext context)
            throws ServletException {
        Properties properties = new Properties();
        properties.put(InitParameters.I18N_PROVIDER,
                DefaultI18NTestProvider.class.getName());
        return SpringInstantiatorTest.getService(context, properties)
                .getInstantiator();
    }
}

Do not rely on counting the beans of a type oneselves but instead let spring do its own resolving logic so ambiguity might actually not be an issue. If it still is fall back to the previous behavior.

This fixes an issue where one might have multiple beans of an abstract or interface type in the context but has already taken care of ambiguity by hinting spring on which one to use (e.g. by using @primary, @fallback or other spring provided mechanisms). With the old implementation this would lead to an exception as it would try to instantiate a bean of an abstract type or interface, when spring would have been able to find a unique bean.

This especially fixes cases where one has multiple beans of I18NProvider or MenuAccessControl which themselves are a perfect example where the old implementation was lacking.

The unit tests had to be changed slightly as they use mocking and the used method on the context was changed.

Fixes vaadin#18918
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 2, 2026

Copy link
Member

@tltv tltv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I18NProviderInstantiationTest fails due to the new test class adding new beans being scanned by other tests too. ComponentScan need to be more specific which classes test needs. I18NProviderInstantiationTest.I18NTestConfig should have this @ComponentScan annotation:

    @ComponentScan(useDefaultFilters = false, includeFilters = {
            @ComponentScan.Filter(classes = { I18NTestProvider.class,
                    I18NTestProvider1.class }, type = FilterType.ASSIGNABLE_TYPE) })

Co-authored-by: Tomi Virtanen <tltv@vaadin.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SpringInstantiator is not honoring @Primary annotation

4 participants