2222
2323import javax .net .ServerSocketFactory ;
2424
25+ import org .springframework .util .Assert ;
26+
2527/**
26- * Simple utility methods for finding available ports on {@code localhost} for
27- * use in integration testing scenarios.
28- *
29- * <p>This is a limited form of {@code SocketUtils} which is deprecated in Spring
30- * Framework 5.3 and removed in Spring Framework 6.0.
28+ * Simple utility for finding available TCP ports on {@code localhost} for use in
29+ * integration testing scenarios.
3130 *
32- * <p>{@code SocketUtils} was introduced in Spring Framework 4.0, primarily to
33- * assist in writing integration tests which start an external server on an
34- * available random port. However, these utilities make no guarantee about the
35- * subsequent availability of a given port and are therefore unreliable (the reason
36- * for deprecation and removal).
31+ * <p>This is a limited form of {@link org.springframework.util.SocketUtils} which
32+ * has been deprecated since Spring Framework 5.3.16 and removed in Spring
33+ * Framework 6.0.
3734 *
38- * <p>Instead of using {@code TestSocketUtils} to find an available local port for a server,
39- * it is recommended that you rely on a server's ability to start on a random port
40- * that it selects or is assigned by the operating system. To interact with that
41- * server, you should query the server for the port it is currently using.
35+ * <p>{@code TestSocketUtils} can be used in integration tests which start an
36+ * external server on an available random port. However, these utilities make no
37+ * guarantee about the subsequent availability of a given port and are therefore
38+ * unreliable. Instead of using {@code TestSocketUtils} to find an available local
39+ * port for a server, it is recommended that you rely on a server's ability to
40+ * start on a random <em>ephemeral</em> port that it selects or is assigned by the
41+ * operating system. To interact with that server, you should query the server
42+ * for the port it is currently using.
4243 *
4344 * @author Sam Brannen
4445 * @author Ben Hale
4546 * @author Arjen Poutsma
4647 * @author Gunnar Hillert
4748 * @author Gary Russell
4849 * @author Chris Bono
49- * @since 5.3
50+ * @since 5.3.24
5051 */
51- public final class TestSocketUtils {
52+ public class TestSocketUtils {
5253
5354 /**
5455 * The minimum value for port ranges used when finding an available TCP port.
5556 */
56- private static final int PORT_RANGE_MIN = 1024 ;
57+ static final int PORT_RANGE_MIN = 1024 ;
5758
5859 /**
5960 * The maximum value for port ranges used when finding an available TCP port.
6061 */
61- private static final int PORT_RANGE_MAX = 65535 ;
62+ static final int PORT_RANGE_MAX = 65535 ;
6263
63- private static final int PORT_RANGE = PORT_RANGE_MAX - PORT_RANGE_MIN ;
64+ private static final int PORT_RANGE_PLUS_ONE = PORT_RANGE_MAX - PORT_RANGE_MIN + 1 ;
6465
6566 private static final int MAX_ATTEMPTS = 1_000 ;
6667
6768 private static final Random random = new Random (System .nanoTime ());
6869
69- private TestSocketUtils () {
70+ private static final TestSocketUtils INSTANCE = new TestSocketUtils ();
71+
72+
73+ /**
74+ * Although {@code TestSocketUtils} consists solely of static utility methods,
75+ * this constructor is intentionally {@code public}.
76+ * <h4>Rationale</h4>
77+ * <p>Static methods from this class may be invoked from within XML
78+ * configuration files using the Spring Expression Language (SpEL) and the
79+ * following syntax.
80+ * <pre><code>
81+ * <bean id="myBean" ... p:port="#{T(org.springframework.test.util.TestSocketUtils).findAvailableTcpPort()}" /></code>
82+ * </pre>
83+ * <p>If this constructor were {@code private}, you would be required to supply
84+ * the fully qualified class name to SpEL's {@code T()} function for each usage.
85+ * Thus, the fact that this constructor is {@code public} allows you to reduce
86+ * boilerplate configuration with SpEL as can be seen in the following example.
87+ * <pre><code>
88+ * <bean id="socketUtils" class="org.springframework.test.util.TestSocketUtils" />
89+ * <bean id="myBean" ... p:port="#{socketUtils.findAvailableTcpPort()}" /></code>
90+ * </pre>
91+ */
92+ public TestSocketUtils () {
7093 }
7194
7295 /**
7396 * Find an available TCP port randomly selected from the range [1024, 65535].
7497 * @return an available TCP port number
75- * @throws IllegalStateException if no available port could be found within max attempts
98+ * @throws IllegalStateException if no available port could be found
7699 */
77100 public static int findAvailableTcpPort () {
101+ return INSTANCE .findAvailableTcpPortInternal ();
102+ }
103+
104+
105+ /**
106+ * Internal implementation of {@link #findAvailableTcpPort()}.
107+ * <p>Package-private solely for testing purposes.
108+ */
109+ int findAvailableTcpPortInternal () {
78110 int candidatePort ;
79111 int searchCounter = 0 ;
80112 do {
81- if (searchCounter > MAX_ATTEMPTS ) {
82- throw new IllegalStateException (String .format (
83- "Could not find an available TCP port in the range [%d, %d] after %d attempts" ,
84- PORT_RANGE_MIN , PORT_RANGE_MAX , MAX_ATTEMPTS ));
85- }
86- candidatePort = PORT_RANGE_MIN + random .nextInt (PORT_RANGE + 1 );
87- searchCounter ++;
113+ Assert .state (++searchCounter <= MAX_ATTEMPTS , () -> String .format (
114+ "Could not find an available TCP port in the range [%d, %d] after %d attempts" ,
115+ PORT_RANGE_MIN , PORT_RANGE_MAX , MAX_ATTEMPTS ));
116+ candidatePort = PORT_RANGE_MIN + random .nextInt (PORT_RANGE_PLUS_ONE );
88117 }
89118 while (!isPortAvailable (candidatePort ));
90119
@@ -93,11 +122,12 @@ public static int findAvailableTcpPort() {
93122
94123 /**
95124 * Determine if the specified TCP port is currently available on {@code localhost}.
125+ * <p>Package-private solely for testing purposes.
96126 */
97- private static boolean isPortAvailable (int port ) {
127+ boolean isPortAvailable (int port ) {
98128 try {
99- ServerSocket serverSocket = ServerSocketFactory .getDefault (). createServerSocket (
100- port , 1 , InetAddress .getByName ("localhost" ));
129+ ServerSocket serverSocket = ServerSocketFactory .getDefault ()
130+ . createServerSocket ( port , 1 , InetAddress .getByName ("localhost" ));
101131 serverSocket .close ();
102132 return true ;
103133 }
0 commit comments