Skip to content
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

Relocate @Local*Port to spring-boot-test #29589

Closed
mihaelaDev opened this issue Jan 28, 2022 · 10 comments
Closed

Relocate @Local*Port to spring-boot-test #29589

mihaelaDev opened this issue Jan 28, 2022 · 10 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@mihaelaDev
Copy link

From a Spring Boot test I am starting the embedded Tomcat on a random port:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestApplicationTest {

	@Test
	void contextLoads() {
	}

}

Inside the test scope, I have a configuration file in which I need to get the random port:

@Configuration
public class ConfigurationTest {

  @LocalServerPort
  int serverPort;

}

When I run the test I get an IllegalStateException:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'local.server.port' in value "${local.server.port}"
	at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:180)
	at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
	at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239)
	at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210)
	at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:175)
	at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:936)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1330)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)

If I use ServletWebServerApplicationContext, or @Value("${server.port}") to get the port, everything is working fine.

Spring Boot version: 2.6.3
With Apache CXF version 3.5.0

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 28, 2022
@bclozel
Copy link
Member

bclozel commented Jan 28, 2022

Configuration properties are resolved and bound from the environment before the application starts. To get a random port from the OS, you need to start the application and the server, using the configuration properties you've provided.

This is by design and I'm closing this issue as a result.

@bclozel bclozel 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 labels Jan 28, 2022
@bclozel bclozel closed this as completed Jan 28, 2022
@philwebb
Copy link
Member

It would be nice if we could get @LocalServerPort to work outside of tests.

@mihaelaDev You can use an event listener to grab the port once the web server has started. Depending on what you're trying to do this might work, but be aware that they even fires after beans have been created.

	@EventListener
	void onWebInit(WebServerInitializedEvent event) {
		int port = event.getWebServer().getPort();
	}

@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Jan 28, 2022
@snicoll
Copy link
Member

snicoll commented Jan 31, 2022

LocalServerPort uses field injection so I don't like the idea of expanding the scope beyond the test class.

@philwebb
Copy link
Member

philwebb commented Feb 1, 2022

It's an @Value so you can also use it on a constructor.

@philwebb philwebb reopened this Feb 3, 2022
@philwebb philwebb added type: enhancement A general enhancement and removed status: invalid An issue that we don't feel is valid for: team-meeting An issue we'd like to discuss as a team to make progress labels Feb 3, 2022
@philwebb philwebb added this to the 2.7.x milestone Feb 3, 2022
@philwebb philwebb changed the title @LocalServerPort not working in a Configuration file Relocate @LocalServerPort to spring-boot-test Feb 3, 2022
@philwebb philwebb changed the title Relocate @LocalServerPort to spring-boot-test Relocate @Local*Port to spring-boot-test Feb 3, 2022
@philwebb
Copy link
Member

philwebb commented Feb 3, 2022

We're going to relocate the Local*Port annotations to the spring-boot-test jar to make it clearer that they are only intended for tests.

@wilkinsona
Copy link
Member

wilkinsona commented Feb 10, 2022

We have three annotations to move:

  • org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort
  • org.springframework.boot.rsocket.context.LocalRSocketServerPort
  • org.springframework.boot.web.server.LocalServerPort

I'm not keen on an actuate package in spring-boot-test. I'm also not sure why LocalRSocketServerPort is in a context package. There's a org.springframework.boot.rsocket.server package and that feels better aligned with the current location of LocalServerPort. With that in mind, how about these new packages in spring-boot-test:

  • org.springframework.boot.test.web.server.LocalServerPort
  • org.springframework.boot.test.web.server.LocalManagementPort
  • org.springframework.boot.test.rsocket.LocalRSocketServerPort

@wilkinsona wilkinsona self-assigned this Feb 10, 2022
@wilkinsona wilkinsona added the for: team-attention An issue we'd like other members of the team to review label Feb 10, 2022
@mihaelaDev
Copy link
Author

Maybe it was not clear that my configuration class is not productive code. I need the @LocalServerPort inside a configuration class that is in test scope:

  • Inside the productive code: I have an SOAP CXF service implemented.
  • Inside the Test Sources Root folder: I have the @SpringBootTest class and the @Configuration file.

I want to start a @SpringBootTest with webEnvironment = RANDOM_PORT in which I want to integartion test the SOAP CXF service. In order to be able to do this, along the test, I have a @Configuration file to define the SOAP service client. In order to define the client I need the server port:

@Configuration
public class ConfigurationTest {

  @LocalServerPort
  private int randomServerPort;

  @Bean
  CXFSoapServiceMyService testBean() {
    final String address = String.format("http://localhost:%d/MyService", randomServerPort);
    JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
    factoryBean.setAddress(address);
    return factoryBean.create(CXFSoapServiceMyService.class);
  }
}

Please find attached a project example with my use case.

test.zip

@wilkinsona
Copy link
Member

wilkinsona commented Feb 11, 2022

@mihaelaDev The annotation can only be used for injection into something that's created after the web server has been started which happens right at the end of application context creation. Typically, this means injection directly into the test class itself. Your ConfigurationTest is created during application context refresh and, crucially, before the web server has been started and its port number is available.

One way to get this to work as you'd like is to defer the creation of your CXFSoapServiceMyService bean by marking it as @Lazy and inject the local server port into the bean method:

@Bean
@Lazy
CXFSoapServiceMyService testBean(@LocalServerPort int randomServerPort) {
  final String address = String.format("http://localhost:%d/MyService", randomServerPort);
  JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
  factoryBean.setAddress(address);
  return factoryBean.create(CXFSoapServiceMyService.class);
}

In your sample at least, this defers the need for local.server.port to have been set until after the web server has been started.

@mihaelaDev
Copy link
Author

@mihaelaDev The annotation can only be used for injection into something that's created after the web server has been started which happens right at the end of application context creation. Typically, this means injection directly into the test class itself. Your ConfigurationTest is created during application context refresh and, crucially, before the web server has been started and its port number is available.

One way to get this to work as you'd like is to defer the creation of your CXFSoapServiceMyService bean by marking it as @Lazy and inject the local server port into the bean method:

@Bean
@Lazy
CXFSoapServiceMyService testBean(@LocalServerPort int randomServerPort) {
  final String address = String.format("http://localhost:%d/MyService", randomServerPort);
  JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
  factoryBean.setAddress(address);
  return factoryBean.create(CXFSoapServiceMyService.class);
}

In your sample at least, this defers the need for local.server.port to have been set until after the web server has been started.

Now it's clear. Thank you @wilkinsona ! I'll use your suggestion.
Thank you all for the support!

@philwebb philwebb modified the milestones: 2.7.x, 2.x Apr 11, 2022
@wilkinsona wilkinsona removed the for: team-attention An issue we'd like other members of the team to review label Apr 12, 2022
@wilkinsona wilkinsona removed this from the 2.7.x milestone Apr 12, 2022
@mohamedmiranfazil29
Copy link

Excellent work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

7 participants