Skip to content

Inconsistency between field and constructor injection #32493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
quaff opened this issue Mar 20, 2024 · 4 comments
Closed

Inconsistency between field and constructor injection #32493

quaff opened this issue Mar 20, 2024 · 4 comments
Labels
status: invalid An issue that we don't feel is valid

Comments

@quaff
Copy link
Contributor

quaff commented Mar 20, 2024

I'm not sure it's a feature or bug, test against 6.1.4.

Field injection:

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig
@ContextConfiguration(classes = ConstructorInjectTests.Config.class)
public class ConstructorInjectTests {

	@Autowired
	Service service;

	@Test
	void test() {
	}

	static class Config {

		@Bean
		Service service() {
			return new Service();
		}
	}

	public static class Service {

		@Autowired
		List<String> strings;

		public Service() {

		}

	}

}

Exception is thrown as expected:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<java.lang.String>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1880)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1406)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784)
	... 90 common frames omitted

Constructor injection:

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig
@ContextConfiguration(classes = ConstructorInjectTests.class)
@ComponentScan
public class ConstructorInjectTests {

	@Autowired
	Service service;

	@Test
	void test() {
	}

	@Component
	public static class Service {

		public Service(List<String> strings) {

		}

	}

}

Empty List is injected instead of throwing exception.

Factory method injection:

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig
@ContextConfiguration(classes = ConstructorInjectTests.Config.class)
public class ConstructorInjectTests {

	@Autowired
	Service service;

	@Test
	void test() {
	}

	static class Config {

		@Bean
		Service service(List<String> strings) {
			return new Service(strings);
		}
	}

	public static class Service {

		public Service(List<String> strings) {

		}

	}

}

Empty List is injected instead of throwing exception.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 20, 2024
@snicoll
Copy link
Member

snicoll commented Mar 20, 2024

It's not about field or constructor injection, but about @Autowired being applied on a single injection type vs. a constructor that takes an arbitrary number of arguments. @Autowired has required set to true by default so you're getting exactly what you asked.

@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Mar 20, 2024
@snicoll snicoll added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 20, 2024
@quaff
Copy link
Contributor Author

quaff commented Mar 20, 2024

It's not about field or constructor injection, but about @Autowired being applied on a single injection type vs. a constructor that takes an arbitrary number of arguments. @Autowired has required set to true by default so you're getting exactly what you asked.

I'm not sure I understand you fully, for field injection it's exactly what I expected throwing exception, but for constructor injection it should not inject empty list since I didn't put @Autowired(required = false).

@SpringJUnitConfig
@ContextConfiguration(classes = ConstructorInjectTests.class)
@ComponentScan
public class ConstructorInjectTests {

	@Autowired
	Service service;

	@Test
	void test() {
	}

	@Component
	public static class Service {

		public Service(List<String> strings) {

		}

	}

}

The test should throw exception but not.

@jhoeller
Copy link
Contributor

jhoeller commented Mar 20, 2024

As of 5.1, this is intentional for such single-constructor (or equivalent factory method) scenarios as far as we would not be able to construct the bean at all otherwise: see #19901.

Since constructors are not commonly annotated for dependency injection purposes, we have no strict notion of required there anymore. And even if a constructor is annotated with @Autowired(required=false), that is effectively overridden by the constructor being the only one available which implicitly makes it required.

For field and method injection, we back out of such methods if an argument is not resolvable since it is fine to simply skip such injections points. Whereas for constructors and factory methods with their showstopper characteristics (and potential overloading etc.) we always had a different algorithm in place that got refined in 5.1 there.

@quaff
Copy link
Contributor Author

quaff commented Mar 21, 2024

Would it be better inject empty list/set/map for field injection?

BTW, Does it documented somewhere? I didn't find anything related in https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html#beans-constructor-injection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

4 participants