22
22
23
23
import javax .net .ServerSocketFactory ;
24
24
25
+ import org .springframework .util .Assert ;
26
+
25
27
/**
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.
31
30
*
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.
37
34
*
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.
42
43
*
43
44
* @author Sam Brannen
44
45
* @author Ben Hale
45
46
* @author Arjen Poutsma
46
47
* @author Gunnar Hillert
47
48
* @author Gary Russell
48
49
* @author Chris Bono
49
- * @since 5.3
50
+ * @since 5.3.24
50
51
*/
51
- public final class TestSocketUtils {
52
+ public class TestSocketUtils {
52
53
53
54
/**
54
55
* The minimum value for port ranges used when finding an available TCP port.
55
56
*/
56
- private static final int PORT_RANGE_MIN = 1024 ;
57
+ static final int PORT_RANGE_MIN = 1024 ;
57
58
58
59
/**
59
60
* The maximum value for port ranges used when finding an available TCP port.
60
61
*/
61
- private static final int PORT_RANGE_MAX = 65535 ;
62
+ static final int PORT_RANGE_MAX = 65535 ;
62
63
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 ;
64
65
65
66
private static final int MAX_ATTEMPTS = 1_000 ;
66
67
67
68
private static final Random random = new Random (System .nanoTime ());
68
69
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 () {
70
93
}
71
94
72
95
/**
73
96
* Find an available TCP port randomly selected from the range [1024, 65535].
74
97
* @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
76
99
*/
77
100
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 () {
78
110
int candidatePort ;
79
111
int searchCounter = 0 ;
80
112
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 );
88
117
}
89
118
while (!isPortAvailable (candidatePort ));
90
119
@@ -93,11 +122,12 @@ public static int findAvailableTcpPort() {
93
122
94
123
/**
95
124
* Determine if the specified TCP port is currently available on {@code localhost}.
125
+ * <p>Package-private solely for testing purposes.
96
126
*/
97
- private static boolean isPortAvailable (int port ) {
127
+ boolean isPortAvailable (int port ) {
98
128
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" ));
101
131
serverSocket .close ();
102
132
return true ;
103
133
}
0 commit comments