From f3b8d7138c13763244a8b57c8feea0df8863f30e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 21 Nov 2017 15:25:26 +0100 Subject: [PATCH] Consistent use of tabs for sample code in the reference documentation --- src/asciidoc/core-beans.adoc | 68 ++-- src/asciidoc/core-resources.adoc | 37 +- src/asciidoc/data-access.adoc | 80 ++--- src/asciidoc/integration.adoc | 91 ++--- src/asciidoc/testing.adoc | 590 +++++++++++++++++-------------- src/asciidoc/web-mvc.adoc | 163 ++++----- src/asciidoc/web-view.adoc | 4 +- src/asciidoc/web-websocket.adoc | 494 ++++++++++++++------------ 8 files changed, 819 insertions(+), 708 deletions(-) diff --git a/src/asciidoc/core-beans.adoc b/src/asciidoc/core-beans.adoc index aeb592a346..0f367cb711 100644 --- a/src/asciidoc/core-beans.adoc +++ b/src/asciidoc/core-beans.adoc @@ -370,7 +370,7 @@ delegates, e.g. with `XmlBeanDefinitionReader` for XML files: ---- GenericApplicationContext context = new GenericApplicationContext(); new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml"); - context.refresh(); + context.refresh(); ---- Or with `GroovyBeanDefinitionReader` for Groovy files: @@ -380,7 +380,7 @@ Or with `GroovyBeanDefinitionReader` for Groovy files: ---- GenericApplicationContext context = new GenericApplicationContext(); new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy"); - context.refresh(); + context.refresh(); ---- Such reader delegates can be mixed and matched on the same `ApplicationContext`, @@ -1682,7 +1682,7 @@ The preceding example is equivalent to the following Java code: [source,java,indent=0] [subs="verbatim,quotes"] ---- - exampleBean.setEmail("") + exampleBean.setEmail(""); ---- The `` element handles `null` values. For example: @@ -1702,7 +1702,7 @@ The above configuration is equivalent to the following Java code: [source,java,indent=0] [subs="verbatim,quotes"] ---- - exampleBean.setEmail(null) + exampleBean.setEmail(null); ---- @@ -3853,7 +3853,6 @@ Find below the custom `BeanPostProcessor` implementation class definition: package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; - import org.springframework.beans.BeansException; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { @@ -5261,6 +5260,7 @@ For details about the effects of combining various lifecycle mechanisms, see [[beans-classpath-scanning]] == Classpath scanning and managed components + Most examples in this chapter use XML to specify the configuration metadata that produces each `BeanDefinition` within the Spring container. The previous section (<>) demonstrates how to provide a lot of the configuration @@ -5427,7 +5427,7 @@ comma/semicolon/space-separated list that includes the parent package of each cl @Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { - ... + ... } ---- @@ -5532,12 +5532,12 @@ and using "stub" repositories instead. [subs="verbatim,quotes"] ---- @Configuration - @ComponentScan(basePackages = "org.example", - includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), - excludeFilters = @Filter(Repository.class)) - public class AppConfig { - ... - } + @ComponentScan(basePackages = "org.example", + includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), + excludeFilters = @Filter(Repository.class)) + public class AppConfig { + ... + } ---- and the equivalent using XML @@ -5767,10 +5767,10 @@ fully-qualified class name when configuring the scanner: [subs="verbatim,quotes"] ---- @Configuration - @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) - public class AppConfig { - ... - } + @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) + public class AppConfig { + ... + } ---- [source,xml,indent=0] @@ -5824,8 +5824,8 @@ fully-qualified class name when configuring the scanner: @Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { - ... - } + ... + } ---- [source,xml,indent=0] @@ -5849,8 +5849,8 @@ the following configuration will result in standard JDK dynamic proxies: @Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) public class AppConfig { - ... - } + ... + } ---- [source,xml,indent=0] @@ -6108,7 +6108,7 @@ exact same way as when using Spring annotations: @Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { - ... + ... } ---- @@ -6338,7 +6338,7 @@ To enable component scanning, just annotate your `@Configuration` class as follo @Configuration @ComponentScan(basePackages = "com.acme") public class AppConfig { - ... + ... } ---- @@ -6657,7 +6657,7 @@ method directly during construction: public Foo foo() { Foo foo = new Foo(); foo.init(); - return foo; + return foo; } // ... @@ -6975,7 +6975,7 @@ another configuration class: @Configuration public class ConfigA { - @Bean + @Bean public A a() { return new A(); } @@ -8645,18 +8645,18 @@ environment provides: [subs="verbatim,quotes"] ---- public class EntityCreatedEvent - extends ApplicationEvent implements ResolvableTypeProvider { + extends ApplicationEvent implements ResolvableTypeProvider { - public EntityCreatedEvent(T entity) { - super(entity); - } + public EntityCreatedEvent(T entity) { + super(entity); + } - @Override - public ResolvableType getResolvableType() { - return ResolvableType.forClassWithGenerics(getClass(), - ResolvableType.forInstance(getSource())); - } - } + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forClassWithGenerics(getClass(), + ResolvableType.forInstance(getSource())); + } + } ---- [TIP] diff --git a/src/asciidoc/core-resources.adoc b/src/asciidoc/core-resources.adoc index 0646c82fa7..4ba4ce1b94 100644 --- a/src/asciidoc/core-resources.adoc +++ b/src/asciidoc/core-resources.adoc @@ -3,8 +3,11 @@ = Resources + + [[resources-introduction]] == Introduction + Java's standard `java.net.URL` class and standard handlers for various URL prefixes unfortunately are not quite adequate enough for all access to low-level resources. For example, there is no standardized `URL` implementation that may be used to access a @@ -15,6 +18,8 @@ quite complicated, and the `URL` interface still lacks some desirable functional such as a method to check for the existence of the resource being pointed to. + + [[resources-resource]] == The Resource interface @@ -378,6 +383,7 @@ used. The following two examples show how to force a `ClassPathResource` and a [[resources-app-ctx-construction]] === Constructing application contexts + An application context constructor (for a specific application context type) generally takes a string or array of strings as the location path(s) of the resource(s) such as XML files that make up the definition of the context. @@ -439,8 +445,8 @@ this: ---- com/ foo/ - services.xml - daos.xml + services.xml + daos.xml MessengerService.class ---- @@ -461,6 +467,7 @@ on the various constructors. [[resources-app-ctx-wildcards-in-resource-paths]] === Wildcards in application context constructor resource paths + The resource paths in application context constructor values may be a simple path (as shown above) which has a one-to-one mapping to a target Resource, or alternately may contain the special "classpath*:" prefix and/or internal Ant-style regular expressions @@ -481,6 +488,7 @@ a resource points to just one resource at a time. [[resources-app-ctx-ant-patterns-in-paths]] ==== Ant-style Patterns + When the path location contains an Ant-style pattern, for example: [literal] @@ -503,6 +511,7 @@ the wildcards. [[resources-app-ctx-portability]] ===== Implications on portability + If the specified path is already a file URL (either explicitly, or implicitly because the base `ResourceLoader` is a filesystem one, then wildcarding is guaranteed to work in a completely portable fashion. @@ -525,7 +534,7 @@ environment before you rely on it. [[resources-classpath-wildcards]] -==== The Classpath*: portability classpath*: prefix +==== The classpath*: prefix When constructing an XML-based application context, a location string may use the special `classpath*:` prefix: @@ -565,13 +574,28 @@ strategy described above is used for the wildcard subpath. [[resources-wildcards-in-path-other-stuff]] ==== Other notes relating to wildcards + Please note that `classpath*:` when combined with Ant-style patterns will only work reliably with at least one root directory before the pattern starts, unless the actual target files reside in the file system. This means that a pattern like -`classpath*:*.xml` will not retrieve files from the root of jar files but rather only -from the root of expanded directories. This originates from a limitation in the JDK's +`classpath*:*.xml` might not retrieve files from the root of jar files but rather only +from the root of expanded directories. + +Spring's ability to retrieve classpath entries originates from the JDK's `ClassLoader.getResources()` method which only returns file system locations for a -passed-in empty string (indicating potential roots to search). +passed-in empty string (indicating potential roots to search). Spring evaluates +`URLClassLoader` runtime configuration and the "java.class.path" manifest in jar files +as well but this is not guaranteed to lead to portable behavior. + +[NOTE] +==== +The scanning of classpath packages requires the presence of corresponding directory +entries in the classpath. When you build JARs with Ant, make sure that you do __not__ +activate the files-only switch of the JAR task. Also, classpath directories may not +get exposed based on security policies in some environments, e.g. standalone apps on +JDK 1.7.0_45 and higher (which requires 'Trusted-Library' setup in your manifests; see +http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources). +==== Ant-style patterns with `classpath:` resources are not guaranteed to find matching resources if the root package to search is available in multiple class path locations. @@ -663,4 +687,3 @@ just force the use of a `UrlResource`, by using the `file:` URL prefix. ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml"); ---- - diff --git a/src/asciidoc/data-access.adoc b/src/asciidoc/data-access.adoc index 4c85115cfa..257189ef9e 100644 --- a/src/asciidoc/data-access.adoc +++ b/src/asciidoc/data-access.adoc @@ -1951,13 +1951,13 @@ transaction in which it has been published as committed successfully: [subs="verbatim,quotes"] ---- @Component - public class MyComponent { + public class MyComponent { - @TransactionalEventListener - public void handleOrderCreatedEvent(CreationEvent creationEvent) { - ... - } - } + @TransactionalEventListener + public void handleOrderCreatedEvent(CreationEvent creationEvent) { + ... + } + } ---- The `TransactionalEventListener` annotation exposes a `phase` attribute that allows to customize @@ -3275,6 +3275,7 @@ based on entries in a list. The entire list is used as the batch in this example [subs="verbatim,quotes"] ---- public class JdbcActorDao implements ActorDao { + private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { @@ -3282,20 +3283,18 @@ based on entries in a list. The entire list is used as the batch in this example } public int[] batchUpdate(final List actors) { - int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " + - "last_name = ? where id = ?", - new BatchPreparedStatementSetter() { - public void setValues(PreparedStatement ps, int i) throws SQLException { + return this.jdbcTemplate.batchUpdate( + "update t_actor set first_name = ?, last_name = ? where id = ?", + new BatchPreparedStatementSetter() { + public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, actors.get(i).getFirstName()); ps.setString(2, actors.get(i).getLastName()); ps.setLong(3, actors.get(i).getId().longValue()); } - public int getBatchSize() { return actors.size(); } }); - return updateCounts; } // ... additional methods @@ -3318,8 +3317,9 @@ provide all parameter values in the call as a list. The framework loops over the values and uses an internal prepared statement setter. The API varies depending on whether you use named parameters. For the named parameters you provide an array of `SqlParameterSource`, one entry for each member of the batch. You can use the -`SqlParameterSource.createBatch` method to create this array, passing in either an array -of JavaBeans or an array of Maps containing the parameter values. +`SqlParameterSourceUtils.createBatch` convenience methods to create this array, passing +in an array of bean-style objects (with getter methods corresponding to parameters) +and/or String-keyed Maps (containing the corresponding parameters as values). This example shows a batch update using named parameters: @@ -3327,18 +3327,17 @@ This example shows a batch update using named parameters: [subs="verbatim,quotes"] ---- public class JdbcActorDao implements ActorDao { + private NamedParameterTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } - public int[] batchUpdate(final List actors) { - SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray()); - int[] updateCounts = namedParameterJdbcTemplate.batchUpdate( + public int[] batchUpdate(List actors) { + return this.namedParameterJdbcTemplate.batchUpdate( "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", - batch); - return updateCounts; + SqlParameterSourceUtils.createBatch(actors.toArray())); } // ... additional methods @@ -3367,15 +3366,12 @@ The same example using classic JDBC "?" placeholders: List batch = new ArrayList(); for (Actor actor : actors) { Object[] values = new Object[] { - actor.getFirstName(), - actor.getLastName(), - actor.getId()}; + actor.getFirstName(), actor.getLastName(), actor.getId()}; batch.add(values); } - int[] updateCounts = jdbcTemplate.batchUpdate( + return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", batch); - return updateCounts; } // ... additional methods @@ -4630,13 +4626,13 @@ standalone environment or in a standalone integration test like in the following [subs="verbatim,quotes"] ---- EmbeddedDatabase db = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(H2) - .setScriptEncoding("UTF-8") - .ignoreFailedDrops(true) - .addScript("schema.sql") - .addScripts("user_data.sql", "country_data.sql") - .build(); + .generateUniqueName(true) + .setType(H2) + .setScriptEncoding("UTF-8") + .ignoreFailedDrops(true) + .addScript("schema.sql") + .addScripts("user_data.sql", "country_data.sql") + .build(); // perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource) @@ -4655,17 +4651,17 @@ Config like in the following example. @Configuration public class DataSourceConfig { - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(H2) - .setScriptEncoding("UTF-8") - .ignoreFailedDrops(true) - .addScript("schema.sql") - .addScripts("user_data.sql", "country_data.sql") - .build(); - } + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(H2) + .setScriptEncoding("UTF-8") + .ignoreFailedDrops(true) + .addScript("schema.sql") + .addScripts("user_data.sql", "country_data.sql") + .build(); + } } ---- diff --git a/src/asciidoc/integration.adoc b/src/asciidoc/integration.adoc index 26dc0772bc..975570c87d 100644 --- a/src/asciidoc/integration.adoc +++ b/src/asciidoc/integration.adoc @@ -1148,8 +1148,8 @@ construct a `HttpComponentsClientHttpRequestFactory` like so: [subs="verbatim,quotes"] ---- HttpClient httpClient = HttpClientBuilder.create().build(); - ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - RestTemplate restTemplate = new RestTemplate(requestFactory); + ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + RestTemplate restTemplate = new RestTemplate(requestFactory); ---- ==== @@ -2600,12 +2600,12 @@ bean as a JMS listener endpoint. [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Component - public class MyService { + @Component + public class MyService { - @JmsListener(destination = "myDestination") - public void processOrder(String data) { ... } - } + @JmsListener(destination = "myDestination") + public void processOrder(String data) { ... } + } ---- The idea of the example above is that whenever a message is available on the @@ -2670,12 +2670,12 @@ element. ---- - - - - - + + + + + ---- @@ -2727,10 +2727,10 @@ a custom header: @Component public class MyService { - @JmsListener(destination = "myDestination") - public void processOrder(Order order, @Header("order_type") String orderType) { - ... - } + @JmsListener(destination = "myDestination") + public void processOrder(Order order, @Header("order_type") String orderType) { + ... + } } ---- @@ -2777,17 +2777,17 @@ annotate the payload with `@Valid` and configure the necessary validator as foll @EnableJms public class AppConfig implements JmsListenerConfigurer { - @Override - public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { - registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory()); - } + @Override + public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) { + registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory()); + } - @Bean - public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { - DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); - factory.setValidator(myValidator()); - return factory; - } + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + factory.setValidator(myValidator()); + return factory; + } } ---- @@ -2811,8 +2811,8 @@ as follow to automatically send a response: @JmsListener(destination = "myDestination") @SendTo("status") public OrderStatus processOrder(Order order) { - // order processing - return status; + // order processing + return status; } ---- @@ -2831,11 +2831,11 @@ If you need to set additional headers in a transport-independent manner, you cou @JmsListener(destination = "myDestination") @SendTo("status") public Message processOrder(Order order) { - // order processing - return MessageBuilder - .withPayload(status) - .setHeader("code", 1234) - .build(); + // order processing + return MessageBuilder + .withPayload(status) + .setHeader("code", 1234) + .build(); } ---- @@ -2848,11 +2848,11 @@ example can be rewritten as follows: ---- @JmsListener(destination = "myDestination") public JmsResponse> processOrder(Order order) { - // order processing - Message response = MessageBuilder - .withPayload(status) - .setHeader("code", 1234) - .build(); + // order processing + Message response = MessageBuilder + .withPayload(status) + .setHeader("code", 1234) + .build(); return JmsResponse.forQueue(response, "status"); } ---- @@ -3851,7 +3851,7 @@ this interface as the definition for the management interface: - + ---- @@ -6059,6 +6059,7 @@ using the Velocity template library to create email content. [[mail-templates-example]] ===== A Velocity-based example + To use http://velocity.apache.org[Velocity] to create your email template(s), you will need to have the Velocity libraries available on your classpath. You will also need to create one or more Velocity templates for the email content that your application needs. @@ -6712,10 +6713,10 @@ however, the exception is uncaught and cannot be transmitted. For those cases, a ---- public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { - @Override - public void handleUncaughtException(Throwable ex, Method method, Object... params) { + @Override + public void handleUncaughtException(Throwable ex, Method method, Object... params) { // handle exception - } + } } ---- @@ -9243,8 +9244,8 @@ Again, to use it, one simply needs to declare the appropriate `CacheManager`: [subs="verbatim,quotes"] ---- + class="org.springframework.cache.jcache.JCacheCacheManager" + p:cache-manager-ref="jCacheManager"/> diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index 94a0a8fc65..5071f2b4ac 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -14,8 +14,11 @@ testing will hopefully convince you of this as well. -- + + [[testing-introduction]] == Introduction to Spring Testing + Testing is an integral part of enterprise software development. This chapter focuses on the value-add of the IoC principle to <> and on the benefits of the Spring Framework's support for <>. __(A @@ -27,6 +30,7 @@ manual.)__ [[unit-testing]] == Unit Testing + Dependency Injection should make your code less dependent on the container than it would be with traditional Java EE development. The POJOs that make up your application should be testable in JUnit or TestNG tests, with objects simply instantiated using the `new` @@ -53,6 +57,7 @@ support classes. [[mock-objects-env]] ==== Environment + The `org.springframework.mock.env` package contains mock implementations of the `Environment` and `PropertySource` abstractions (see <> and <>). `MockEnvironment` and @@ -62,6 +67,7 @@ depends on environment-specific properties. [[mock-objects-jndi]] ==== JNDI + The `org.springframework.mock.jndi` package contains an implementation of the JNDI SPI, which you can use to set up a simple JNDI environment for test suites or stand-alone applications. If, for example, JDBC ``DataSource``s get bound to the same JNDI names in @@ -71,8 +77,9 @@ configuration in testing scenarios without modification. [[mock-objects-servlet]] ==== Servlet API + The `org.springframework.mock.web` package contains a comprehensive set of Servlet API -mock objects, which are useful for testing web contexts, controllers, and filters. These +mock objects that are useful for testing web contexts, controllers, and filters. These mock objects are targeted at usage with Spring's Web MVC framework and are generally more convenient to use than dynamic mock objects such as http://www.easymock.org[EasyMock] or alternative Servlet API mock objects such as http://www.mockobjects.com[MockObjects]. Since @@ -86,6 +93,7 @@ conjunction with your `WebApplicationContext` configuration for Spring MVC, see [[mock-objects-portlet]] ==== Portlet API + The `org.springframework.mock.web.portlet` package contains a set of Portlet API mock objects, targeted at usage with Spring's Portlet MVC framework. @@ -97,6 +105,7 @@ objects, targeted at usage with Spring's Portlet MVC framework. [[unit-testing-utilities]] ==== General testing utilities + The `org.springframework.test.util` package contains several general purpose utilities for use in unit and integration testing. @@ -122,9 +131,9 @@ access to the underlying mock in order to configure expectations on it and perfo verifications. For Spring's core AOP utilities, see `AopUtils` and `AopProxyUtils`. - [[unit-testing-spring-mvc]] ==== Spring MVC + The `org.springframework.test.web` package contains `ModelAndViewAssert`, which you can use in combination with JUnit, TestNG, or any other testing framework for unit tests dealing with Spring MVC `ModelAndView` objects. @@ -150,6 +159,7 @@ Framework_>> instead. [[integration-testing-overview]] === Overview + It is important to be able to perform some integration testing without requiring deployment to your application server or connecting to other enterprise infrastructure. This will enable you to test things such as: @@ -177,6 +187,7 @@ instrumentation of tests in various environments including JUnit, TestNG, and so [[integration-testing-goals]] === Goals of Integration Testing + Spring's integration testing support has the following primary goals: * To manage <> between test @@ -192,6 +203,7 @@ configuration details. [[testing-ctx-management]] ==== Context management and caching + The Spring TestContext Framework provides consistent loading of Spring ``ApplicationContext``s and ``WebApplicationContext``s as well as caching of those contexts. Support for the caching of loaded contexts is important, because startup time @@ -222,6 +234,7 @@ TestContext framework. [[testing-fixture-di]] ==== Dependency Injection of test fixtures + When the TestContext framework loads your application context, it can optionally configure instances of your test classes via Dependency Injection. This provides a convenient mechanism for setting up test fixtures using preconfigured beans from your @@ -247,6 +260,7 @@ framework>>. [[testing-tx]] ==== Transaction management + One common issue in tests that access a real database is their effect on the state of the persistence store. Even when you're using a development database, changes to the state may affect future tests. Also, many operations -- such as inserting or modifying @@ -272,6 +286,7 @@ See transaction management with the <>. [[testing-support-classes]] ==== Support classes for integration testing + The Spring TestContext Framework provides several `abstract` support classes that simplify the writing of integration tests. These base test classes provide well-defined hooks into the testing framework as well as convenient instance variables and methods, @@ -294,6 +309,7 @@ See support classes for the < [[integration-testing-support-jdbc]] === JDBC Testing Support + The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a collection of JDBC related utility functions intended to simplify standard database testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility @@ -324,20 +340,24 @@ details, see <> and === Annotations + [[integration-testing-annotations-spring]] ==== Spring Testing Annotations + The Spring Framework provides the following set of __Spring-specific__ annotations that you can use in your unit and integration tests in conjunction with the TestContext framework. Refer to the corresponding javadocs for further information, including default attribute values, attribute aliases, and so on. ===== @BootstrapWith + `@BootstrapWith` is a class-level annotation that is used to configure how the _Spring TestContext Framework_ is bootstrapped. Specifically, `@BootstrapWith` is used to specify a custom `TestContextBootstrapper`. Consult the <> section for further details. ===== @ContextConfiguration + `@ContextConfiguration` defines class-level metadata that is used to determine how to load and configure an `ApplicationContext` for integration tests. Specifically, `@ContextConfiguration` declares the application context resource `locations` or the @@ -403,6 +423,7 @@ See <> and the `@ContextConfiguration` javadocs for further details. ===== @WebAppConfiguration + `@WebAppConfiguration` is a class-level annotation that is used to declare that the `ApplicationContext` loaded for an integration test should be a `WebApplicationContext`. The mere presence of `@WebAppConfiguration` on a test class ensures that a @@ -441,6 +462,7 @@ Note that `@WebAppConfiguration` must be used in conjunction with hierarchy. See the `@WebAppConfiguration` javadocs for further details. ===== @ContextHierarchy + `@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of ``ApplicationContext``s for integration tests. `@ContextHierarchy` should be declared with a list of one or more `@ContextConfiguration` instances, each of which defines a @@ -481,6 +503,7 @@ corresponding level in the class hierarchy. See for further examples. ===== @ActiveProfiles + `@ActiveProfiles` is a class-level annotation that is used to declare which __bean definition profiles__ should be active when loading an `ApplicationContext` for an integration test. @@ -518,6 +541,7 @@ See <> and the `@ActiveProfiles` javado for examples and further details. ===== @TestPropertySource + `@TestPropertySource` is a class-level annotation that is used to configure the locations of properties files and inlined properties to be added to the set of `PropertySources` in the `Environment` for an `ApplicationContext` loaded for an integration test. @@ -554,6 +578,7 @@ The following example demonstrates how to declare _inlined_ properties. ---- ===== @DirtiesContext + `@DirtiesContext` indicates that the underlying Spring `ApplicationContext` has been __dirtied__ during the execution of a test (i.e., modified or corrupted in some manner -- for example, by changing the state of a singleton bean) and should be closed. When an @@ -700,6 +725,7 @@ For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms se `DirtiesContext.HierarchyMode` javadocs. ===== @TestExecutionListeners + `@TestExecutionListeners` defines class-level metadata for configuring the `TestExecutionListener` implementations that should be registered with the `TestContextManager`. Typically, `@TestExecutionListeners` is used in conjunction with @@ -719,6 +745,7 @@ For further details regarding the `EXHAUSTIVE` and `CURRENT_LEVEL` algorithms se for an example and further details. ===== @Commit + `@Commit` indicates that the transaction for a transactional test method should be __committed__ after the test method has completed. `@Commit` can be used as a direct replacement for `@Rollback(false)` in order to more explicitly convey the intent of the @@ -736,6 +763,7 @@ method-level annotation. ---- ===== @Rollback + `@Rollback` indicates whether the transaction for a transactional test method should be __rolled back__ after the test method has completed. If `true`, the transaction is rolled back; otherwise, the transaction is committed (see also `@Commit`). Rollback semantics @@ -758,6 +786,7 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics. ---- ===== @BeforeTransaction + `@BeforeTransaction` indicates that the annotated `void` method should be executed __before__ a transaction is started for test methods configured to run within a transaction via Spring's `@Transactional` annotation. As of Spring Framework 4.3, @@ -774,6 +803,7 @@ transaction via Spring's `@Transactional` annotation. As of Spring Framework 4.3 ---- ===== @AfterTransaction + `@AfterTransaction` indicates that the annotated `void` method should be executed __after__ a transaction is ended for test methods configured to run within a transaction via Spring's `@Transactional` annotation. As of Spring Framework 4.3, `@AfterTransaction` @@ -790,6 +820,7 @@ default methods. ---- ===== @Sql + `@Sql` is used to annotate a test class or test method to configure SQL scripts to be executed against a given database during integration tests. @@ -806,6 +837,7 @@ executed against a given database during integration tests. See <> for further details. ===== @SqlConfig + `@SqlConfig` defines metadata that is used to determine how to parse and execute SQL scripts configured via the `@Sql` annotation. @@ -823,6 +855,7 @@ scripts configured via the `@Sql` annotation. ---- ===== @SqlGroup + `@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. `@SqlGroup` can be used natively, declaring several nested `@Sql` annotations, or it can be used in conjunction with Java 8's support for repeatable annotations, where `@Sql` can @@ -845,6 +878,7 @@ container annotation. [[integration-testing-annotations-standard]] ==== Standard Annotation Support + The following annotations are supported with standard semantics for all configurations of the Spring TestContext Framework. Note that these annotations are not specific to tests and can be used anywhere in the Spring Framework. @@ -879,11 +913,13 @@ instead of `@PostConstruct` and `@PreDestroy`. [[integration-testing-annotations-junit]] ==== Spring JUnit 4 Testing Annotations + The following annotations are __only__ supported when used in conjunction with the <>, <>, or <>. ===== @IfProfileValue + `@IfProfileValue` indicates that the annotated test is enabled for a specific testing environment. If the configured `ProfileValueSource` returns a matching `value` for the provided `name`, the test is enabled. Otherwise, the test will be disabled and @@ -922,6 +958,7 @@ Consider the following example: ---- ===== @ProfileValueSourceConfiguration + `@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type of `ProfileValueSource` to use when retrieving __profile values__ configured through the `@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a @@ -937,6 +974,7 @@ test, `SystemProfileValueSource` is used by default. ---- ===== @Timed + `@Timed` indicates that the annotated test method must finish execution in a specified time period (in milliseconds). If the text execution time exceeds the specified time period, the test fails. @@ -961,6 +999,7 @@ hand, does not preemptively fail the test but rather waits for the test to compl before failing. ===== @Repeat + `@Repeat` indicates that the annotated test method must be executed repeatedly. The number of times that the test method is to be executed is specified in the annotation. @@ -980,6 +1019,7 @@ well as any __set up__ or __tear down__ of the test fixture. [[integration-testing-annotations-meta]] ==== Meta-Annotation Support for Testing + It is possible to use most test-related annotations as <> in order to create custom _composed annotations_ and reduce configuration duplication across a test suite. @@ -1059,8 +1099,10 @@ configuration of individual test classes as follows: For further details, consult the <>. + [[testcontext-framework]] === Spring TestContext Framework + The __Spring TestContext Framework__ (located in the `org.springframework.test.context` package) provides generic, annotation-driven unit and integration testing support that is agnostic of the testing framework in use. The @@ -1085,6 +1127,7 @@ management>>), <>, and [[testcontext-key-abstractions]] ==== Key abstractions + The core of the framework consists of the `TestContextManager` class and the `TestContext`, `TestExecutionListener`, and `SmartContextLoader` interfaces. A `TestContextManager` is created per test class (e.g., for the execution of all test @@ -1098,12 +1141,14 @@ javadocs and the Spring test suite for further information and examples of vario implementations. ===== TestContext + `TestContext` encapsulates the context in which a test is executed, agnostic of the actual testing framework in use, and provides context management and caching support for the test instance for which it is responsible. The `TestContext` also delegates to a `SmartContextLoader` to load an `ApplicationContext` if requested. ===== TestContextManager + `TestContextManager` is the main entry point into the __Spring TestContext Framework__, which manages a single `TestContext` and signals events to each registered `TestExecutionListener` at well-defined test execution points: @@ -1115,11 +1160,13 @@ which manages a single `TestContext` and signals events to each registered * after any __after class__ or __after all__ methods of a particular testing framework ===== TestExecutionListener + `TestExecutionListener` defines the API for reacting to test execution events published by the `TestContextManager` with which the listener is registered. See <>. ===== Context Loaders + `ContextLoader` is a strategy interface that was introduced in Spring 2.5 for loading an `ApplicationContext` for an integration test managed by the Spring TestContext Framework. Implement `SmartContextLoader` instead of this interface in order to provide support for @@ -1161,6 +1208,7 @@ locations__. * `GenericPropertiesContextLoader`: loads a standard `ApplicationContext` from Java Properties files. + [[testcontext-bootstrapping]] ==== Bootstrapping the TestContext framework @@ -1186,6 +1234,7 @@ accommodate new requirements, implementers are strongly encouraged not to implem interface directly but rather to extend `AbstractTestContextBootstrapper` or one of its concrete subclasses instead. + [[testcontext-tel-config]] ==== TestExecutionListener configuration @@ -1624,6 +1673,7 @@ from, but you still have the freedom to include or import the other type of conf [[testcontext-ctx-management-initializers]] ===== Context configuration with context initializers + To configure an `ApplicationContext` for your tests using context initializers, annotate your test class with `@ContextConfiguration` and configure the `initializers` attribute with an array that contains references to classes that implement @@ -1670,6 +1720,7 @@ files or configuration classes. [[testcontext-ctx-management-inheritance]] ===== Context configuration inheritance + `@ContextConfiguration` supports boolean `inheritLocations` and `inheritInitializers` attributes that denote whether resource locations or annotated classes and context initializers declared by superclasses should be __inherited__. The default value for @@ -1758,6 +1809,7 @@ with Spring's `@Order` annotation or the standard `@Priority` annotation. [[testcontext-ctx-management-env-profiles]] ===== Context configuration with environment profiles + Spring 3.1 introduced first-class support in the framework for the notion of environments and profiles (a.k.a., __bean definition profiles__), and integration tests can be configured to activate particular bean definition profiles for various testing @@ -2252,6 +2304,7 @@ loaded using the _inlined_ `key1` and `key2` properties. [[testcontext-ctx-management-web]] ===== Loading a WebApplicationContext + Spring 3.2 introduced support for loading a `WebApplicationContext` in integration tests. To instruct the TestContext framework to load a `WebApplicationContext` instead of a standard `ApplicationContext`, simply annotate the respective test class with @@ -2645,6 +2698,7 @@ cleared. For further details consult the discussion of `@DirtiesContext` in [[testcontext-fixture-di]] ==== Dependency injection of test fixtures + When you use the `DependencyInjectionTestExecutionListener` -- which is configured by default -- the dependencies of your test instances are __injected__ from beans in the application context that you configured with `@ContextConfiguration`. You may use setter @@ -2932,6 +2986,7 @@ configured theme. } ---- + [[testcontext-tx]] ==== Transaction management @@ -3034,6 +3089,7 @@ via the `@Commit` and `@Rollback` annotations. See the corresponding entries in [[testcontext-tx-programmatic-tx-mgt]] ===== Programmatic transaction management + Since Spring Framework 4.1, it is possible to interact with test-managed transactions _programmatically_ via the static methods in `TestTransaction`. For example, `TestTransaction` may be used within _test_ methods, _before_ methods, and _after_ @@ -3050,7 +3106,7 @@ javadocs for `TestTransaction` for further details. @ContextConfiguration(classes = TestConfig.class) public class ProgrammaticTransactionManagementTests extends AbstractTransactionalJUnit4SpringContextTests { - + @Test public void transactionalTest() { // assert initial state in test database: @@ -3282,8 +3338,8 @@ scripts against a `DataSource`. public void databaseTest { ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.addScripts( - new ClassPathResource("test-schema.sql"), - new ClassPathResource("test-data.sql")); + new ClassPathResource("test-schema.sql"), + new ClassPathResource("test-data.sql")); populator.setSeparator("@@"); populator.execute(this.dataSource); // execute code that uses the test schema and data @@ -3297,7 +3353,6 @@ and executing SQL scripts. Similarly, the `executeSqlScript(..)` methods in internally use a `ResourceDatabasePopulator` for executing SQL scripts. Consult the javadocs for the various `executeSqlScript(..)` methods for further details. - [[testcontext-executing-sql-declaratively]] ===== Executing SQL scripts declaratively with @Sql @@ -3509,7 +3564,6 @@ be automatically rolled back by the `TransactionalTestExecutionListener` (see [[testcontext-support-classes]] ==== TestContext Framework support classes - [[testcontext-junit4-runner]] ===== Spring JUnit 4 Runner @@ -3532,18 +3586,17 @@ empty list in order to disable the default listeners, which otherwise would requ [source,java,indent=0] [subs="verbatim,quotes"] ---- -@RunWith(SpringRunner.class) -@TestExecutionListeners({}) -public class SimpleTest { + @RunWith(SpringRunner.class) + @TestExecutionListeners({}) + public class SimpleTest { - @Test - public void testMethod() { - // execute test logic... - } -} + @Test + public void testMethod() { + // execute test logic... + } + } ---- - [[testcontext-junit4-rules]] ===== Spring JUnit 4 Rules @@ -3570,24 +3623,23 @@ demonstrates the proper way to declare these rules in an integration test. [source,java,indent=0] [subs="verbatim,quotes"] ---- -// Optionally specify a non-Spring Runner via @RunWith(...) -@ContextConfiguration -public class IntegrationTest { + // Optionally specify a non-Spring Runner via @RunWith(...) + @ContextConfiguration + public class IntegrationTest { - @ClassRule - public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); + @ClassRule + public static final SpringClassRule springClassRule = new SpringClassRule(); - @Rule - public final SpringMethodRule springMethodRule = new SpringMethodRule(); + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); - @Test - public void testMethod() { - // execute test logic... - } -} + @Test + public void testMethod() { + // execute test logic... + } + } ---- - [[testcontext-support-classes-junit4]] ===== JUnit 4 support classes @@ -3697,9 +3749,9 @@ integration tests, see <>. ==== - [[spring-mvc-test-server]] ==== Server-Side Tests + It's easy to write a plain unit test for a Spring MVC controller using JUnit or TestNG: simply instantiate the controller, inject it with mocked or stubbed dependencies, and call its methods passing `MockHttpServletRequest`, `MockHttpServletResponse`, etc., as necessary. @@ -3745,7 +3797,6 @@ JUnit 4 based example of using Spring MVC Test: .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); } - } ---- @@ -3763,6 +3814,7 @@ request that will be discussed below. [[spring-mvc-test-server-static-imports]] ===== Static Imports + The fluent API in the example above requires a few static imports such as `MockMvcRequestBuilders.{asterisk}`, `MockMvcResultMatchers.{asterisk}`, and `MockMvcBuilders.{asterisk}`. An easy way to find these classes is to search for @@ -3774,7 +3826,8 @@ IntelliJ) may not require any additional configuration. Just check the support f completion on static members. [[spring-mvc-test-server-setup-options]] -===== Setup Options +===== Setup Choices + There are two main options for creating an instance of `MockMvc`. The first is to load Spring MVC configuration through the __TestContext framework__, which loads the Spring configuration and injects a `WebApplicationContext` @@ -3881,6 +3934,7 @@ always test against your actual Spring MVC configuration. [[spring-mvc-test-server-performing-requests]] ===== Performing Requests + It's easy to perform requests using any HTTP method: [source,java,indent=0] @@ -3957,6 +4011,7 @@ specified on every request. [[spring-mvc-test-server-defining-expectations]] ===== Defining Expectations + Expectations can be defined by appending one or more `.andExpect(..)` calls after performing a request: @@ -4063,6 +4118,7 @@ be verified using XPath expressions: [[spring-mvc-test-server-filters]] ===== Filter Registrations + When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` instances: [source,java,indent=0] @@ -4121,9 +4177,9 @@ integration tests. At the same time it's important not to lose sight of the fact the response is the most important thing to check. In short, there is room here for multiple styles and strategies of testing even within the same project. - [[spring-mvc-test-server-resources]] ===== Further Server-Side Test Examples + The framework's own tests include https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples[many sample tests] intended to demonstrate how to use Spring MVC Test. Browse these examples @@ -4164,45 +4220,45 @@ paging through all messages. How would you go about testing it? With Spring MVC Test, we can easily test if we are able to create a `Message`. -[source,java] +[source,java,indent=0] ---- -MockHttpServletRequestBuilder createMessage = post("/messages/") - .param("summary", "Spring Rocks") - .param("text", "In case you didn't know, Spring Rocks!"); + MockHttpServletRequestBuilder createMessage = post("/messages/") + .param("summary", "Spring Rocks") + .param("text", "In case you didn't know, Spring Rocks!"); -mockMvc.perform(createMessage) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/messages/123")); + mockMvc.perform(createMessage) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/messages/123")); ---- What if we want to test our form view that allows us to create the message? For example, assume our form looks like the following snippet: -[source,xml] +[source,xml,indent=0] ---- -
- + + - - + + - - + + -
- -
-
+
+ +
+ ---- How do we ensure that our form will produce the correct request to create a new message? A naive attempt would look like this: -[source,java] +[source,java,indent=0] ---- -mockMvc.perform(get("/messages/form")) - .andExpect(xpath("//input[@name='summary']").exists()) - .andExpect(xpath("//textarea[@name='text']").exists()); + mockMvc.perform(get("/messages/form")) + .andExpect(xpath("//input[@name='summary']").exists()) + .andExpect(xpath("//textarea[@name='text']").exists()); ---- This test has some obvious drawbacks. If we update our controller to use the parameter @@ -4210,21 +4266,21 @@ This test has some obvious drawbacks. If we update our controller to use the par form is out of synch with the controller. To resolve this we can combine our two tests. [[spring-mvc-test-server-htmlunit-mock-mvc-test]] -[source,java] +[source,java,indent=0] ---- -String summaryParamName = "summary"; -String textParamName = "text"; -mockMvc.perform(get("/messages/form")) - .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists()) - .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists()); + String summaryParamName = "summary"; + String textParamName = "text"; + mockMvc.perform(get("/messages/form")) + .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists()) + .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists()); -MockHttpServletRequestBuilder createMessage = post("/messages/") - .param(summaryParamName, "Spring Rocks") - .param(textParamName, "In case you didn't know, Spring Rocks!"); + MockHttpServletRequestBuilder createMessage = post("/messages/") + .param(summaryParamName, "Spring Rocks") + .param(textParamName, "In case you didn't know, Spring Rocks!"); -mockMvc.perform(createMessage) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/messages/123")); + mockMvc.perform(createMessage) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/messages/123")); ---- This would reduce the risk of our test incorrectly passing, but there are still some @@ -4311,19 +4367,19 @@ In order to use HtmlUnit with Apache HttpComponents 4.5+, you will need to use H We can easily create an HtmlUnit `WebClient` that integrates with `MockMvc` using the `MockMvcWebClientBuilder` as follows. -[source,java] +[source,java,indent=0] ---- -@Autowired -WebApplicationContext context; + @Autowired + WebApplicationContext context; -WebClient webClient; + WebClient webClient; -@Before -public void setup() { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build(); -} + @Before + public void setup() { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build(); + } ---- [NOTE] @@ -4344,9 +4400,9 @@ Now we can use HtmlUnit as we normally would, but without the need to deploy our application to a Servlet container. For example, we can request the view to create a message with the following. -[source,java] +[source,java,indent=0] ---- -HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); + HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form"); ---- [NOTE] @@ -4358,29 +4414,29 @@ illustrated in <>. Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it to create a message. -[source,java] +[source,java,indent=0] ---- -HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); -HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); -summaryInput.setValueAttribute("Spring Rocks"); -HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text"); -textInput.setText("In case you didn't know, Spring Rocks!"); -HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); -HtmlPage newMessagePage = submit.click(); + HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm"); + HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute("Spring Rocks"); + HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text"); + textInput.setText("In case you didn't know, Spring Rocks!"); + HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit"); + HtmlPage newMessagePage = submit.click(); ---- Finally, we can verify that a new message was created successfully. The following assertions use the http://joel-costigliola.github.io/assertj/[AssertJ] library. -[source,java] +[source,java,indent=0] ---- -assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); -String id = newMessagePage.getHtmlElementById("id").getTextContent(); -assertThat(id).isEqualTo("123"); -String summary = newMessagePage.getHtmlElementById("summary").getTextContent(); -assertThat(summary).isEqualTo("Spring Rocks"); -String text = newMessagePage.getHtmlElementById("text").getTextContent(); -assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); + assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123"); + String id = newMessagePage.getHtmlElementById("id").getTextContent(); + assertThat(id).isEqualTo("123"); + String summary = newMessagePage.getHtmlElementById("summary").getTextContent(); + assertThat(summary).isEqualTo("Spring Rocks"); + String text = newMessagePage.getHtmlElementById("text").getTextContent(); + assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!"); ---- This improves on our <> in a @@ -4402,59 +4458,59 @@ In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest w by building a `WebClient` based on the `WebApplicationContext` loaded for us by the Spring TestContext Framework. This approach is repeated here. -[source,java] +[source,java,indent=0] ---- -@Autowired -WebApplicationContext context; + @Autowired + WebApplicationContext context; -WebClient webClient; + WebClient webClient; -@Before -public void setup() { - webClient = MockMvcWebClientBuilder - .webAppContextSetup(context) - .build(); -} + @Before + public void setup() { + webClient = MockMvcWebClientBuilder + .webAppContextSetup(context) + .build(); + } ---- We can also specify additional configuration options. -[source,java] +[source,java,indent=0] ---- -WebClient webClient; + WebClient webClient; -@Before -public void setup() { - webClient = MockMvcWebClientBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); -} + @Before + public void setup() { + webClient = MockMvcWebClientBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); + } ---- As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcWebClientBuilder` as follows. -[source,java] +[source,java,indent=0] ---- -MockMvc mockMvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); -webClient = MockMvcWebClientBuilder - .mockMvcSetup(mockMvc) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); + webClient = MockMvcWebClientBuilder + .mockMvcSetup(mockMvc) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); ---- This is more verbose, but by building the `WebClient` with a `MockMvc` instance we have @@ -4497,27 +4553,27 @@ be displayed afterwards. If one of the fields were named "summary", then we might have something like the following repeated in multiple places within our tests. -[source,java] +[source,java,indent=0] ---- -HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); -summaryInput.setValueAttribute(summary); + HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute(summary); ---- So what happens if we change the `id` to "smmry"? Doing so would force us to update all of our tests to incorporate this change! Of course, this violates the _DRY Principle_; so we should ideally extract this code into its own method as follows. -[source,java] +[source,java,indent=0] ---- -public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { - setSummary(currentPage, summary); - // ... -} + public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) { + setSummary(currentPage, summary); + // ... + } -public void setSummary(HtmlPage currentPage, String summary) { - HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); - summaryInput.setValueAttribute(summary); -} + public void setSummary(HtmlPage currentPage, String summary) { + HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary"); + summaryInput.setValueAttribute(summary); + } ---- This ensures that we do not have to update all of our tests if we change the UI. @@ -4525,39 +4581,39 @@ This ensures that we do not have to update all of our tests if we change the UI. We might even take this a step further and place this logic within an Object that represents the `HtmlPage` we are currently on. -[source,java] +[source,java,indent=0] ---- -public class CreateMessagePage { + public class CreateMessagePage { - final HtmlPage currentPage; + final HtmlPage currentPage; - final HtmlTextInput summaryInput; + final HtmlTextInput summaryInput; - final HtmlSubmitInput submit; + final HtmlSubmitInput submit; - public CreateMessagePage(HtmlPage currentPage) { - this.currentPage = currentPage; - this.summaryInput = currentPage.getHtmlElementById("summary"); - this.submit = currentPage.getHtmlElementById("submit"); + public CreateMessagePage(HtmlPage currentPage) { + this.currentPage = currentPage; + this.summaryInput = currentPage.getHtmlElementById("summary"); + this.submit = currentPage.getHtmlElementById("submit"); + } + + public T createMessage(String summary, String text) throws Exception { + setSummary(summary); + + HtmlPage result = submit.click(); + boolean error = CreateMessagePage.at(result); + + return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result)); + } + + public void setSummary(String summary) throws Exception { + summaryInput.setValueAttribute(summary); + } + + public static boolean at(HtmlPage page) { + return "Create Message".equals(page.getTitleText()); + } } - - public T createMessage(String summary, String text) throws Exception { - setSummary(summary); - - HtmlPage result = submit.click(); - boolean error = CreateMessagePage.at(result); - - return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result)); - } - - public void setSummary(String summary) throws Exception { - summaryInput.setValueAttribute(summary); - } - - public static boolean at(HtmlPage page) { - return "Create Message".equals(page.getTitleText()); - } -} ---- Formerly, this pattern is known as the @@ -4574,18 +4630,18 @@ includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver` We can easily create a Selenium `WebDriver` that integrates with `MockMvc` using the `MockMvcHtmlUnitDriverBuilder` as follows. -[source,java] +[source,java,indent=0] ---- -@Autowired -WebApplicationContext context; + @Autowired + WebApplicationContext context; -WebDriver driver; + WebDriver driver; -@Before -public void setup() { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build(); + @Before + public void setup() { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build(); } ---- @@ -4607,17 +4663,17 @@ Now we can use WebDriver as we normally would, but without the need to deploy ou application to a Servlet container. For example, we can request the view to create a message with the following. -[source,java] +[source,java,indent=0] ---- -CreateMessagePage page = CreateMessagePage.to(driver); + CreateMessagePage page = CreateMessagePage.to(driver); ---- We can then fill out the form and submit it to create a message. -[source,java] +[source,java,indent=0] ---- -ViewMessagePage viewMessagePage = - page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); + ViewMessagePage viewMessagePage = + page.createMessage(ViewMessagePage.class, expectedSummary, expectedText); ---- This improves on the design of our @@ -4626,35 +4682,35 @@ Pattern_. As we mentioned in <>, use the Page Object Pattern with HtmlUnit, but it is much easier with WebDriver. Let's take a look at our new `CreateMessagePage` implementation. -[source,java] +[source,java,indent=0] ---- -public class CreateMessagePage - extends AbstractPage { // <1> + public class CreateMessagePage + extends AbstractPage { // <1> - // <2> - private WebElement summary; - private WebElement text; + // <2> + private WebElement summary; + private WebElement text; - // <3> - @FindBy(css = "input[type=submit]") - private WebElement submit; + // <3> + @FindBy(css = "input[type=submit]") + private WebElement submit; - public CreateMessagePage(WebDriver driver) { - super(driver); + public CreateMessagePage(WebDriver driver) { + super(driver); + } + + public T createMessage(Class resultPage, String summary, String details) { + this.summary.sendKeys(summary); + this.text.sendKeys(details); + this.submit.click(); + return PageFactory.initElements(driver, resultPage); + } + + public static CreateMessagePage to(WebDriver driver) { + driver.get("http://localhost:9990/mail/messages/form"); + return PageFactory.initElements(driver, CreateMessagePage.class); + } } - - public T createMessage(Class resultPage, String summary, String details) { - this.summary.sendKeys(summary); - this.text.sendKeys(details); - this.submit.click(); - return PageFactory.initElements(driver, resultPage); - } - - public static CreateMessagePage to(WebDriver driver) { - driver.get("http://localhost:9990/mail/messages/form"); - return PageFactory.initElements(driver, CreateMessagePage.class); - } -} ---- <1> The first thing you will notice is that `CreateMessagePage` extends the @@ -4680,39 +4736,39 @@ annotation to look up our submit button using a css selector, *input[type=submit Finally, we can verify that a new message was created successfully. The following assertions use the https://code.google.com/p/fest/[FEST assertion library]. -[source,java] +[source,java,indent=0] ---- -assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); -assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); + assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage); + assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message"); ---- We can see that our `ViewMessagePage` allows us to interact with our custom domain model. For example, it exposes a method that returns a `Message` object. -[source,java] +[source,java,indent=0] ---- -public Message getMessage() throws ParseException { - Message message = new Message(); - message.setId(getId()); - message.setCreated(getCreated()); - message.setSummary(getSummary()); - message.setText(getText()); - return message; -} + public Message getMessage() throws ParseException { + Message message = new Message(); + message.setId(getId()); + message.setCreated(getCreated()); + message.setSummary(getSummary()); + message.setText(getText()); + return message; + } ---- We can then leverage the rich domain objects in our assertions. Lastly, don't forget to _close_ the `WebDriver` instance when the test is complete. -[source,java] +[source,java,indent=0] ---- -@After -public void destroy() { - if (driver != null) { - driver.close(); + @After + public void destroy() { + if (driver != null) { + driver.close(); + } } -} ---- For additional information on using WebDriver, refer to the Selenium @@ -4725,59 +4781,59 @@ In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simpl possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by the Spring TestContext Framework. This approach is repeated here. -[source,java] +[source,java,indent=0] ---- -@Autowired -WebApplicationContext context; + @Autowired + WebApplicationContext context; -WebDriver driver; + WebDriver driver; -@Before -public void setup() { - driver = MockMvcHtmlUnitDriverBuilder - .webAppContextSetup(context) - .build(); -} + @Before + public void setup() { + driver = MockMvcHtmlUnitDriverBuilder + .webAppContextSetup(context) + .build(); + } ---- We can also specify additional configuration options. -[source,java] +[source,java,indent=0] ---- -WebDriver driver; + WebDriver driver; -@Before -public void setup() { - driver = MockMvcHtmlUnitDriverBuilder - // demonstrates applying a MockMvcConfigurer (Spring Security) - .webAppContextSetup(context, springSecurity()) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); + @Before + public void setup() { + driver = MockMvcHtmlUnitDriverBuilder + // demonstrates applying a MockMvcConfigurer (Spring Security) + .webAppContextSetup(context, springSecurity()) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); } ---- As an alternative, we can perform the exact same setup by configuring the `MockMvc` instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder` as follows. -[source,java] +[source,java,indent=0] ---- -MockMvc mockMvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); + MockMvc mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); -driver = MockMvcHtmlUnitDriverBuilder - .mockMvcSetup(mockMvc) - // for illustration only - defaults to "" - .contextPath("") - // By default MockMvc is used for localhost only; - // the following will use MockMvc for example.com and example.org as well - .useMockMvcForHosts("example.com","example.org") - .build(); + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + // for illustration only - defaults to "" + .contextPath("") + // By default MockMvc is used for localhost only; + // the following will use MockMvc for example.com and example.org as well + .useMockMvcForHosts("example.com","example.org") + .build(); ---- This is more verbose, but by building the `WebDriver` with a `MockMvc` instance we have @@ -4795,7 +4851,6 @@ For additional information on creating a `MockMvc` instance refer to In the previous section, we saw how to use `MockMvc` with `WebDriver`. In this section, we will use http://www.gebish.org/[Geb] to make our tests even Groovy-er. - [[spring-mvc-test-server-htmlunit-geb-why]] ====== Why Geb and MockMvc? @@ -4926,6 +4981,7 @@ http://www.gebish.org/manual/current/[The Book of Geb] user's manual. [[spring-mvc-test-client]] ==== Client-Side REST Tests + Client-side tests can be used to test code that internally uses the `RestTemplate`. The idea is to declare expected requests and to provide "stub" responses so that you can focus on testing the code in isolation, i.e. without running a server. @@ -5005,10 +5061,9 @@ server-side logic but without running a server. Here is an example: mockServer.verify(); ---- - - [[spring-mvc-test-client-static-imports]] ===== Static Imports + Just like with server-side tests, the fluent API for client-side tests requires a few static imports. Those are easy to find by searching __"MockRest*"__. Eclipse users should add `"MockRestRequestMatchers.{asterisk}"` and `"MockRestResponseCreators.{asterisk}"` @@ -5020,6 +5075,7 @@ configuration. Just check the support for code completion on static members. [[spring-mvc-test-client-resources]] ===== Further Examples of Client-side REST Tests + Spring MVC Test's own tests include https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/client/samples[example tests] of client-side REST tests. @@ -5131,6 +5187,7 @@ PetClinic application for an example. [[testing-resources]] == Further Resources + Consult the following resources for more information about testing: * http://www.junit.org/[JUnit]: "__A programmer-oriented testing framework for Java__". @@ -5153,4 +5210,3 @@ Consult the following resources for more information about testing: Maven) targeted for database-driven projects that, among other things, puts your database into a known state between test runs. * http://grinder.sourceforge.net/[The Grinder]: Java load testing framework. - diff --git a/src/asciidoc/web-mvc.adoc b/src/asciidoc/web-mvc.adoc index af6656f002..e1f3e3a7ab 100644 --- a/src/asciidoc/web-mvc.adoc +++ b/src/asciidoc/web-mvc.adoc @@ -2,8 +2,11 @@ = Web MVC framework + + [[mvc-introduction]] == Introduction to Spring Web MVC framework + The Spring Web model-view-controller (MVC) framework is designed around a `DispatcherServlet` that dispatches requests to handlers, with configurable handler mappings, view resolution, locale, time zone and theme resolution as well as support for @@ -119,6 +122,7 @@ Spring's web module includes many unique web support features: [[mvc-introduction-pluggability]] === Pluggability of other MVC implementations + Non-Spring MVC implementations are preferable for some projects. Many teams expect to leverage their existing investment in skills and tools, for example with JSF. @@ -175,7 +179,6 @@ Java EE Servlet configuration in a Servlet 3.0+ environment: registration.setLoadOnStartup(1); registration.addMapping("/example/*"); } - } ---- @@ -323,7 +326,6 @@ Note that we can achieve the same with java-based configurations: protected String[] getServletMappings() { return new String[] { "/golfing/*" }; } - } ---- @@ -1792,22 +1794,22 @@ controller or alternatively use the `binding` flag on the annotation: [source,java,indent=0] [subs="verbatim,quotes"] ---- -@ModelAttribute -public AccountForm setUpForm() { - return new AccountForm(); -} + @ModelAttribute + public AccountForm setUpForm() { + return new AccountForm(); + } -@ModelAttribute -public Account findAccount(@PathVariable String accountId) { - return accountRepository.findOne(accountId); -} + @ModelAttribute + public Account findAccount(@PathVariable String accountId) { + return accountRepository.findOne(accountId); + } -@PostMapping("update") -public String update(@Valid AccountUpdateForm form, BindingResult result, - **@ModelAttribute(binding=false)** Account account) { + @PostMapping("update") + public String update(@Valid AccountUpdateForm form, BindingResult result, + **@ModelAttribute(binding=false)** Account account) { - // ... -} + // ... + } ---- In addition to data binding you can also invoke validation using your own custom @@ -3323,9 +3325,8 @@ Spring MVC also provides a mechanism for building links to controller methods. F @GetMapping("/bookings/{booking}") public String getBooking(@PathVariable Long booking) { - - // ... - } + // ... + } } ---- @@ -3444,12 +3445,12 @@ For example given: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @RequestMapping("/people/{id}/addresses") - public class PersonAddressController { + @RequestMapping("/people/{id}/addresses") + public class PersonAddressController { - @RequestMapping("/{country}") - public HttpEntity getAddress(@PathVariable String country) { ... } - } + @RequestMapping("/{country}") + public HttpEntity getAddress(@PathVariable String country) { ... } + } ---- You can prepare a link from a JSP as follows: @@ -4331,7 +4332,7 @@ semantics of name generation for collections clearer: [[mvc-coc-r2vnt]] -=== The View - RequestToViewNameTranslator +=== Default view name The `RequestToViewNameTranslator` interface determines a logical `View` name when no such logical view name is explicitly supplied. It has just one implementation, the @@ -4410,6 +4411,7 @@ that can be configured. + [[mvc-caching]] == HTTP caching support @@ -4431,6 +4433,7 @@ This section describes the different choices available to configure HTTP caching Spring Web MVC application. + [[mvc-caching-cachecontrol]] === Cache-Control HTTP header @@ -4457,18 +4460,20 @@ accepted as an argument in several Spring Web MVC APIs. [subs="verbatim,quotes"] ---- // Cache for an hour - "Cache-Control: max-age=3600" - CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); + CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); - // Prevent caching - "Cache-Control: no-store" - CacheControl ccNoStore = CacheControl.noStore(); + // Prevent caching - "Cache-Control: no-store" + CacheControl ccNoStore = CacheControl.noStore(); - // Cache for ten days in public and private caches, - // public caches should not transform the response - // "Cache-Control: max-age=864000, public, no-transform" - CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS) - .noTransform().cachePublic(); + // Cache for ten days in public and private caches, + // public caches should not transform the response + // "Cache-Control: max-age=864000, public, no-transform" + CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS) + .noTransform().cachePublic(); ---- + + [[mvc-caching-static-resources]] === HTTP caching support for static resources @@ -4481,7 +4486,6 @@ metadata, but also `'Cache-Control'` headers if properly configured. You can set the `cachePeriod` attribute on a `ResourceHttpRequestHandler` or use a `CacheControl` instance, which supports more specific directives: - [source,java,indent=0] [subs="verbatim"] ---- @@ -4510,6 +4514,7 @@ And in XML: ---- + [[mvc-caching-etag-lastmodified]] === Support for the Cache-Control, ETag and Last-Modified response headers in Controllers @@ -4617,8 +4622,8 @@ You configure the `ShallowEtagHeaderFilter` in `web.xml`: org.springframework.web.filter.ShallowEtagHeaderFilter @@ -4769,6 +4774,7 @@ override the `createDispatcherServlet` method. [[mvc-config]] == Configuring Spring MVC + <> and <> explained about Spring MVC's special beans and the default implementations used by the `DispatcherServlet`. In this section you'll learn about two additional ways of configuring Spring MVC. Namely @@ -4789,6 +4795,7 @@ to the created Spring MVC beans. But let's start from the beginning. [[mvc-config-enable]] === Enabling the MVC Java Config or the MVC XML Namespace + To enable MVC Java config add the annotation `@EnableWebMvc` to one of your `@Configuration` classes: @@ -4798,7 +4805,6 @@ To enable MVC Java config add the annotation `@EnableWebMvc` to one of your @Configuration @EnableWebMvc public class WebConfig { - } ---- @@ -4905,7 +4911,6 @@ and override the methods you need: public class WebConfig extends WebMvcConfigurerAdapter { // Override configuration methods... - } ---- @@ -4933,9 +4938,8 @@ register custom formatters and converters, override the `addFormatters` method: @Override public void addFormatters(FormatterRegistry registry) { - // Add formatters and/or converters + // ... } - } ---- @@ -5029,7 +5033,6 @@ Alternatively you can configure your own global `Validator` instance: public Validator getValidator(); { // return "global" validator } - } ---- @@ -5123,6 +5126,7 @@ And in XML use the `` element: [[mvc-config-content-negotiation]] === Content Negotiation + You can configure how Spring MVC determines the requested media types from the request. The available options are to check the URL path for a file extension, check the "Accept" header, a specific query parameter, or to fall back on a default content @@ -5190,6 +5194,7 @@ JSON) if no content types were requested. [[mvc-config-view-controller]] === View Controllers + This is a shortcut for defining a `ParameterizableViewController` that immediately forwards to a view when invoked. Use it in static cases when there is no Java controller logic to execute before the view generates the response. @@ -5207,7 +5212,6 @@ An example of forwarding a request for `"/"` to a view called `"home"` in Java: public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("home"); } - } ---- @@ -5220,8 +5224,10 @@ And the same in XML use the `` element: ---- + [[mvc-config-view-resolvers]] === View Resolvers + The MVC config simplifies the registration of view resolvers. The following is a Java config example that configures content negotiation view @@ -5240,7 +5246,6 @@ JSON rendering: registry.enableContentNegotiation(new MappingJackson2JsonView()); registry.jsp(); } - } ---- @@ -5304,7 +5309,6 @@ In Java config simply add the respective "Configurer" bean: configurer.setTemplateLoaderPath("/WEB-INF/"); return configurer; } - } ---- @@ -5442,10 +5446,9 @@ Java config example; public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") .addResourceLocations("/public-resources/") - .resourceChain(true).addResolver( - new VersionResourceResolver().addContentVersionStrategy("/**")); + .resourceChain(true) + .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); } - } ---- @@ -5483,7 +5486,8 @@ It also works by rewriting resource URLs in templates [[mvc-default-servlet-handler]] -=== Falling Back On the "Default" Servlet To Serve Resources +=== Default Servlet + This allows for mapping the `DispatcherServlet` to "/" (thus overriding the mapping of the container's default Servlet), while still allowing static resource requests to be handled by the container's default Servlet. It configures a @@ -5509,7 +5513,6 @@ To enable the feature using the default setup use: public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } - } ---- @@ -5573,21 +5576,21 @@ Below is an example in Java config: @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer - .setUseSuffixPatternMatch(true) - .setUseTrailingSlashMatch(false) - .setUseRegisteredSuffixPatternMatch(true) - .setPathMatcher(antPathMatcher()) - .setUrlPathHelper(urlPathHelper()); + .setUseSuffixPatternMatch(true) + .setUseTrailingSlashMatch(false) + .setUseRegisteredSuffixPatternMatch(true) + .setPathMatcher(antPathMatcher()) + .setUrlPathHelper(urlPathHelper()); } @Bean public UrlPathHelper urlPathHelper() { - //... + //... } @Bean public PathMatcher antPathMatcher() { - //... + //... } } @@ -5598,17 +5601,17 @@ And the same in XML, use the `` element: [source,xml,indent=0] [subs="verbatim,quotes"] ---- - - - + + + - - + + ---- @@ -5668,23 +5671,23 @@ It is also possible to do the same in XML: [source,xml,indent=0] [subs="verbatim,quotes"] ---- - - - - - - - - - - + + + + + + + + + + - + - + ---- diff --git a/src/asciidoc/web-view.adoc b/src/asciidoc/web-view.adoc index 7458ab2937..54cfc47eb3 100644 --- a/src/asciidoc/web-view.adoc +++ b/src/asciidoc/web-view.adoc @@ -34,7 +34,7 @@ documentation section for more details. [[view-groovymarkup]] -== Groovy Markup Templates +== Groovy Markup The http://groovy-lang.org/templating.html#_the_markuptemplateengine[Groovy Markup Template Engine] is another view technology, supported by Spring. This template engine is a template engine primarily @@ -57,7 +57,7 @@ Configuring the Groovy Markup Template Engine is quite easy: @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { - @Override + @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.groovy(); } diff --git a/src/asciidoc/web-websocket.adoc b/src/asciidoc/web-websocket.adoc index 9f6c176fb0..93821949fd 100644 --- a/src/asciidoc/web-websocket.adoc +++ b/src/asciidoc/web-websocket.adoc @@ -1,5 +1,6 @@ [[websocket]] = WebSocket Support + This part of the reference documentation covers Spring Framework's support for WebSocket-style messaging in web applications including use of STOMP as an application level WebSocket sub-protocol. @@ -22,8 +23,10 @@ applications. + [[websocket-intro]] == Introduction + The WebSocket protocol http://tools.ietf.org/html/rfc6455[RFC 6455] defines an important new capability for web applications: full-duplex, two-way communication between client and server. It is an exciting new capability on the heels of a long history of @@ -47,6 +50,7 @@ and also provides additional value-add as explained in the rest of the introduct [[websocket-into-fallback-options]] === WebSocket Fallback Options + An important challenge to adoption is the lack of support for WebSocket in some browsers. Notably the first Internet Explorer version to support WebSocket is version 10 (see http://caniuse.com/websockets for support by browser versions). @@ -66,6 +70,7 @@ application otherwise. [[websocket-intro-architecture]] === A Messaging Architecture + Aside from short-to-midterm adoption challenges, using WebSocket brings up important design considerations that are important to recognize early on, especially in contrast to what we know about building web applications today. @@ -93,6 +98,7 @@ annotation based programming model. [[websocket-intro-sub-protocol]] === Sub-Protocol Support in WebSocket + WebSocket does imply a __messaging architecture__ but does not mandate the use of any specific __messaging protocol__. It is a very thin layer over TCP that transforms a stream of bytes into a stream of messages @@ -126,6 +132,7 @@ WebSocket and over the web. [[websocket-intro-when-to-use]] === Should I Use WebSocket? + With all the design considerations surrounding the use of WebSocket, it is reasonable to ask, "When is it appropriate to use?". @@ -169,6 +176,7 @@ WebSocket clients or to a specific user. [[websocket-server]] == WebSocket API + The Spring Framework provides a WebSocket API designed to adapt to various WebSocket engines. Currently the list includes WebSocket runtimes such as Tomcat 7.0.47+, Jetty 9.1+, GlassFish 4.1+, WebLogic 12.1.3+, and Undertow 1.0+ (and WildFly 8.0+). Additional support @@ -191,7 +199,8 @@ directly. [[websocket-server-handler]] -=== Create and Configure a WebSocketHandler +=== WebSocketHandler + Creating a WebSocket server is as simple as implementing `WebSocketHandler` or more likely extending either `TextWebSocketHandler` or `BinaryWebSocketHandler`: @@ -271,7 +280,8 @@ into other HTTP serving environments with the help of [[websocket-server-handshake]] -=== Customizing the WebSocket Handshake +=== WebSocket Handshake + The easiest way to customize the initial HTTP WebSocket handshake request is through a `HandshakeInterceptor`, which exposes "before" and "after" the handshake methods. Such an interceptor can be used to preclude the handshake or to make any attributes @@ -333,6 +343,7 @@ Both the Java-config and XML namespace make it possible to configure a custom [[websocket-server-decorators]] === WebSocketHandler Decoration + Spring provides a `WebSocketHandlerDecorator` base class that can be used to decorate a `WebSocketHandler` with additional behavior. Logging and exception handling implementations are provided and added by default when using the WebSocket Java-config @@ -343,7 +354,8 @@ session with status `1011` that indicates a server error. [[websocket-server-deployment]] -=== Deployment Considerations +=== Deployment + The Spring WebSocket API is easy to integrate into a Spring MVC application where the `DispatcherServlet` serves both HTTP WebSocket handshake as well as other HTTP requests. It is also easy to integrate into other HTTP processing scenarios @@ -415,6 +427,8 @@ Java initialization API, if required: ---- + + [[websocket-server-runtime-configuration]] === Configuring the WebSocket Engine @@ -541,6 +555,8 @@ or WebSocket XML namespace: ---- + + [[websocket-server-allowed-origins]] === Configuring allowed origins @@ -613,16 +629,21 @@ XML configuration equivalent: ---- + + [[websocket-fallback]] -== SockJS Fallback Options +== SockJS Fallback + As explained in the <>, WebSocket is not supported in all browsers yet and may be precluded by restrictive network proxies. This is why Spring provides fallback options that emulate the WebSocket API as close as possible based on the https://github.com/sockjs/sockjs-protocol[SockJS protocol] (version 0.3.3). + + [[websocket-fallback-sockjs-overview]] -=== Overview of SockJS +=== Overview The goal of SockJS is to let applications use a WebSocket API but fall back to non-WebSocket alternatives when necessary at runtime, i.e. without the need to @@ -682,8 +703,10 @@ For even more detail refer to the SockJS protocol http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html[narrated test]. + [[websocket-fallback-sockjs-enable]] === Enable SockJS + SockJS is easy to enable through Java configuration: [source,java,indent=0] @@ -744,8 +767,10 @@ https://github.com/sockjs/sockjs-client/[sockjs-client] page and the list of transport types supported by browser. The client also provides several configuration options, for example, to specify which transports to include. + + [[websocket-fallback-xhr-vs-iframe]] -=== HTTP Streaming in IE 8, 9: Ajax/XHR vs IFrame +=== IE 8, 9 Internet Explorer 8 and 9 are and will remain common for some time. They are a key reason for having SockJS. This section covers important @@ -825,8 +850,10 @@ be cached. For details on how to enable it see the https://github.com/sockjs/sockjs-client/[SockJS client] page. ==== + + [[websocket-fallback-sockjs-heartbeat]] -=== Heartbeat Messages +=== Heartbeats The SockJS protocol requires servers to send heartbeat messages to preclude proxies from concluding a connection is hung. The Spring SockJS configuration has a property @@ -846,8 +873,10 @@ for scheduling heartbeats tasks. The task scheduler is backed by a thread pool with default settings based on the number of available processors. Applications should consider customizing the settings according to their specific needs. + + [[websocket-fallback-sockjs-servlet3-async]] -=== Servlet 3 Async Requests +=== Client disconnects HTTP streaming and HTTP long polling SockJS transports require a connection to remain open longer than usual. For an overview of these techniques see @@ -874,8 +903,10 @@ defined in `AbstractSockJsSession`. If you need to see the stack traces, set tha log category to TRACE. ==== + + [[websocket-fallback-cors]] -=== CORS Headers for SockJS +=== SockJS and CORS If you allow cross-origin requests (see <>), the SockJS protocol uses CORS for cross-domain support in the XHR streaming and polling transports. Therefore @@ -901,8 +932,9 @@ Alternatively if the CORS configuration allows it consider excluding URLs with t SockJS endpoint prefix thus letting Spring's `SockJsService` handle it. + [[websocket-fallback-sockjs-client]] -=== SockJS Client +=== SockJsClient A SockJS Java client is provided in order to connect to remote SockJS endpoints without using a browser. This can be especially useful when there is a need for bidirectional @@ -963,27 +995,27 @@ Consider also customizing these server-side SockJS related properties (see Javad [source,java,indent=0] [subs="verbatim,quotes"] ---- -@Configuration -public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport { + @Configuration + public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport { - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/sockjs").withSockJS() - .setStreamBytesLimit(512 * 1024) - .setHttpMessageCacheSize(1000) - .setDisconnectDelay(30 * 1000); - } + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/sockjs").withSockJS() + .setStreamBytesLimit(512 * 1024) + .setHttpMessageCacheSize(1000) + .setDisconnectDelay(30 * 1000); + } - // ... - -} + // ... + } ---- [[websocket-stomp]] -== STOMP Over WebSocket Messaging Architecture +== STOMP + The WebSocket protocol defines two types of messages, text and binary, but their content is undefined. It's expected that the client and server may agree on using a sub-protocol (i.e. a higher-level protocol) to define message semantics. @@ -994,7 +1026,8 @@ messages. [[websocket-stomp-overview]] -=== Overview of STOMP +=== Overview + http://stomp.github.io/stomp-specification-1.2.html#Abstract[STOMP] is a simple text-oriented messaging protocol that was originally created for scripting languages such as Ruby, Python, and Perl to connect to enterprise message brokers. It is @@ -1099,7 +1132,8 @@ Spring MVC provides a programming model based on HTTP. [[websocket-stomp-enable]] -=== Enable STOMP over WebSocket +=== Enable STOMP + The Spring Framework provides support for using STOMP over WebSocket through the +spring-messaging+ and +spring-websocket+ modules. Here is an example of exposing a STOMP WebSocket/SockJS endpoint at the URL path @@ -1124,11 +1158,10 @@ broadcasting to other connected clients): } @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - config.setApplicationDestinationPrefixes("/app"); - config.enableSimpleBroker("/topic", "/queue"); - } - + public void configureMessageBroker(MessageBrokerRegistry config) { + config.setApplicationDestinationPrefixes("/app"); + config.enableSimpleBroker("/topic", "/queue"); + } } ---- @@ -1205,6 +1238,7 @@ sections <> and <> for more information on authentication. + [[websocket-stomp-message-flow]] === Flow of Messages @@ -1314,7 +1348,7 @@ kinds of arguments and return values supported. [[websocket-stomp-handle-annotations]] -=== Annotation Message Handling +=== Handler methods The `@MessageMapping` annotation is supported on methods of `@Controller` classes. It can be used for mapping methods to message destinations and can also be combined @@ -1394,7 +1428,7 @@ change to ``. [[websocket-stomp-handle-send]] -=== Sending Messages +=== Send Messages What if you want to send messages to connected clients from any part of the application? Any application component can send messages to the `"brokerChannel"`. @@ -1428,6 +1462,7 @@ But it can also be qualified by its name "brokerMessagingTemplate" if another bean of the same type exists. + [[websocket-stomp-handle-simple-broker]] === Simple Broker @@ -1444,9 +1479,8 @@ See <>. - [[websocket-stomp-handle-broker-relay]] -=== Full-Featured Broker +=== External Broker The simple broker is great for getting started but supports only a subset of STOMP commands (e.g. no acks, receipts, etc.), relies on a simple message @@ -1535,8 +1569,10 @@ subscribed WebSocket clients. In effect, the broker relay enables robust and scalable message broadcasting. + + [[websocket-stomp-handle-broker-relay-configure]] -=== Connections To Full-Featured Broker +=== Connect to Broker A STOMP broker relay maintains a single "system" TCP connection to the broker. This connection is used for messages originating from the server-side application @@ -1580,8 +1616,10 @@ and may be useful for example in a cloud environment where the actual host to wh the TCP connection is established is different from the host providing the cloud-based STOMP service. + + [[websocket-stomp-destination-separator]] -=== Using Dot as Separator in @MessageMapping Destinations +=== Dot as Separator Although slash-separated path patterns are familiar to web developers, in messaging it is common to use a "." as the separator, for example in the names of topics, queues, @@ -1593,20 +1631,19 @@ In Java config: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Configuration - @EnableWebSocketMessageBroker - public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { + @Configuration + @EnableWebSocketMessageBroker + public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { - // ... + // ... - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableStompBrokerRelay("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/app"); - registry.setPathMatcher(new AntPathMatcher(".")); - } - - } + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableStompBrokerRelay("/queue/", "/topic/"); + registry.setApplicationDestinationPrefixes("/app"); + registry.setPathMatcher(new AntPathMatcher(".")); + } + } ---- In XML config: @@ -1614,25 +1651,25 @@ In XML config: [source,xml,indent=0] [subs="verbatim,quotes,attributes"] ---- - + - - - - + + + + - - - + + + - + ---- And below is a simple example to illustrate a controller with "." separator: @@ -1640,22 +1677,21 @@ And below is a simple example to illustrate a controller with "." separator: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Controller - @MessageMapping("foo") - public class FooController { - - @MessageMapping("bar.{baz}") - public void handleBaz(@DestinationVariable String baz) { - } + @Controller + @MessageMapping("foo") + public class FooController { - } + @MessageMapping("bar.{baz}") + public void handleBaz(@DestinationVariable String baz) { + // ... + } + } ---- If the application prefix is set to "/app" then the foo method is effectively mapped to "/app/foo.bar.{baz}". - [[websocket-stomp-authentication]] === Authentication @@ -1702,7 +1738,7 @@ that ensures the user HTTP session does not expire when the WebSocket session is [[websocket-stomp-authentication-token-based]] -=== Token-based Authentication +=== Token Authentication https://github.com/spring-projects/spring-security-oauth[Spring Security OAuth] provides support for token based security including JSON Web Token (JWT). @@ -1748,30 +1784,26 @@ user and associate it with subsequent STOMP messages on the same session: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Configuration - @EnableWebSocketMessageBroker - public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer { + @Configuration + @EnableWebSocketMessageBroker + public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer { - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.setInterceptors(new ChannelInterceptorAdapter() { - - @Override - public Message preSend(Message message, MessageChannel channel) { - - StompHeaderAccessor accessor = - MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - - if (StompCommand.CONNECT.equals(accessor.getCommand())) { - Authentication user = ... ; // access authentication header(s) - accessor.setUser(user); - } - - return message; - } - }); - } - } + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.setInterceptors(new ChannelInterceptorAdapter() { + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = + MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + if (StompCommand.CONNECT.equals(accessor.getCommand())) { + Authentication user = ... ; // access authentication header(s) + accessor.setUser(user); + } + return message; + } + }); + } + } ---- Also note that when using Spring Security's authorization for messages, at present @@ -1782,7 +1814,6 @@ its own sub-class of `AbstractWebSocketMessageBrokerConfigurer` marked with - [[websocket-stomp-user-destination]] === User Destinations @@ -1811,16 +1842,16 @@ the class-level to share a common destination): [source,java,indent=0] [subs="verbatim,quotes"] ---- -@Controller -public class PortfolioController { + @Controller + public class PortfolioController { - @MessageMapping("/trade") - @SendToUser("/queue/position-updates") - public TradeResult executeTrade(Trade trade, Principal principal) { - // ... - return tradeResult; - } -} + @MessageMapping("/trade") + @SendToUser("/queue/position-updates") + public TradeResult executeTrade(Trade trade, Principal principal) { + // ... + return tradeResult; + } + } ---- If the user has more than one session, by default all of the sessions subscribed @@ -1831,24 +1862,23 @@ setting the `broadcast` attribute to false, for example: [source,java,indent=0] [subs="verbatim,quotes"] ---- -@Controller -public class MyController { + @Controller + public class MyController { - @MessageMapping("/action") - public void handleAction() throws Exception{ - // raise MyBusinessException here - } + @MessageMapping("/action") + public void handleAction() throws Exception{ + // raise MyBusinessException here + } - @MessageExceptionHandler - @SendToUser(destinations="/queue/errors", broadcast=false) - public ApplicationError handleException(MyBusinessException exception) { - // ... - return appError; - } -} + @MessageExceptionHandler + @SendToUser(destinations="/queue/errors", broadcast=false) + public ApplicationError handleException(MyBusinessException exception) { + // ... + return appError; + } + } ---- - [NOTE] ==== While user destinations generally imply an authenticated user, it isn't required @@ -1906,9 +1936,8 @@ of the `message-broker` element in XML. - [[websocket-stomp-appplication-context-events]] -=== Listening To ApplicationContext Events and Intercepting Messages +=== Events and Interception Several `ApplicationContext` events (listed below) are published and can be received by implementing Spring's `ApplicationListener` interface. @@ -1955,15 +1984,15 @@ to intercept inbound messages: [source,java,indent=0] [subs="verbatim,quotes"] ---- - @Configuration - @EnableWebSocketMessageBroker - public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { + @Configuration + @EnableWebSocketMessageBroker + public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.setInterceptors(new MyChannelInterceptor()); - } - } + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.setInterceptors(new MyChannelInterceptor()); + } + } ---- A custom `ChannelInterceptor` can extend the empty method base class @@ -1973,19 +2002,20 @@ to access information about the message. [source,java,indent=0] [subs="verbatim,quotes"] ---- - public class MyChannelInterceptor extends ChannelInterceptorAdapter { + public class MyChannelInterceptor extends ChannelInterceptorAdapter { - @Override - public Message preSend(Message message, MessageChannel channel) { - StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); - StompCommand command = accessor.getStompCommand(); - // ... - return message; - } - } + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + StompCommand command = accessor.getStompCommand(); + // ... + return message; + } + } ---- + [[websocket-stomp-client]] === STOMP Client @@ -1996,10 +2026,10 @@ To begin create and configure `WebSocketStompClient`: [source,java,indent=0] [subs="verbatim,quotes"] ---- -WebSocketClient webSocketClient = new StandardWebSocketClient(); -WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); -stompClient.setMessageConverter(new StringMessageConverter()); -stompClient.setTaskScheduler(taskScheduler); // for heartbeats + WebSocketClient webSocketClient = new StandardWebSocketClient(); + WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient); + stompClient.setMessageConverter(new StringMessageConverter()); + stompClient.setTaskScheduler(taskScheduler); // for heartbeats ---- In the above example `StandardWebSocketClient` could be replaced with `SockJsClient` @@ -2012,9 +2042,9 @@ Next establish a connection and provide a handler for the STOMP session: [source,java,indent=0] [subs="verbatim,quotes"] ---- -String url = "ws://127.0.0.1:8080/endpoint"; -StompSessionHandler sessionHandler = new MyStompSessionHandler(); -stompClient.connect(url, sessionHandler); + String url = "ws://127.0.0.1:8080/endpoint"; + StompSessionHandler sessionHandler = new MyStompSessionHandler(); + stompClient.connect(url, sessionHandler); ---- When the session is ready for use the handler is notified: @@ -2024,10 +2054,10 @@ When the session is ready for use the handler is notified: ---- public class MyStompSessionHandler extends StompSessionHandlerAdapter { - @Override - public void afterConnected(StompSession session, StompHeaders connectedHeaders) { - // ... - } + @Override + public void afterConnected(StompSession session, StompHeaders connectedHeaders) { + // ... + } } ---- @@ -2050,15 +2080,15 @@ Object type the payload should be deserialized to: ---- session.subscribe("/topic/foo", new StompFrameHandler() { - @Override - public Type getPayloadType(StompHeaders headers) { - return String.class; - } + @Override + public Type getPayloadType(StompHeaders headers) { + return String.class; + } - @Override - public void handleFrame(StompHeaders headers, Object payload) { - // ... - } + @Override + public void handleFrame(StompHeaders headers, Object payload) { + // ... + } }); ---- @@ -2107,11 +2137,11 @@ inbound client messages and may be accessed from a controller method, for exampl @Controller public class MyController { - @MessageMapping("/action") - public void handle(SimpMessageHeaderAccessor headerAccessor) { - Map attrs = headerAccessor.getSessionAttributes(); - // ... - } + @MessageMapping("/action") + public void handle(SimpMessageHeaderAccessor headerAccessor) { + Map attrs = headerAccessor.getSessionAttributes(); + // ... + } } ---- @@ -2124,38 +2154,38 @@ scope proxy mode for WebSocket-scoped beans: [source,java,indent=0] [subs="verbatim,quotes"] ---- -@Component -@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) -public class MyBean { + @Component + @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) + public class MyBean { - @PostConstruct - public void init() { - // Invoked after dependencies injected - } + @PostConstruct + public void init() { + // Invoked after dependencies injected + } - // ... + // ... - @PreDestroy - public void destroy() { - // Invoked when the WebSocket session ends - } -} + @PreDestroy + public void destroy() { + // Invoked when the WebSocket session ends + } + } -@Controller -public class MyController { + @Controller + public class MyController { - private final MyBean myBean; + private final MyBean myBean; - @Autowired - public MyController(MyBean myBean) { - this.myBean = myBean; - } + @Autowired + public MyController(MyBean myBean) { + this.myBean = myBean; + } - @MessageMapping("/action") - public void handle() { - // this.myBean from the current WebSocket session - } -} + @MessageMapping("/action") + public void handle() { + // this.myBean from the current WebSocket session + } + } ---- As with any custom scope, Spring initializes a new `MyBean` instance the first @@ -2167,7 +2197,7 @@ shown in the examples above. [[websocket-stomp-configuration-performance]] -=== Configuration and Performance +=== Performance There is no silver bullet when it comes to performance. Many factors may affect it including the size of messages, the volume, whether application @@ -2330,7 +2360,7 @@ through any other application instances. [[websocket-stomp-stats]] -=== Runtime Monitoring +=== Monitoring When using `@EnableWebSocketMessageBroker` or `` key infrastructure components automatically gather stats and counters that provide @@ -2342,52 +2372,54 @@ every 30 minutes. This bean can be exported to JMX through Spring's Below is a summary of the available information. Client WebSocket Sessions:: - Current::: indicates how many client sessions there are - currently with the count further broken down by WebSocket vs HTTP - streaming and polling SockJS sessions. - Total::: indicates how many total sessions have been established. - Abnormally Closed::: - Connect Failures:::: these are sessions that got established but were - closed after not having received any messages within 60 seconds. This is - usually an indication of proxy or network issues. - Send Limit Exceeded:::: sessions closed after exceeding the configured send - timeout or the send buffer limits which can occur with slow clients - (see previous section). - Transport Errors:::: sessions closed after a transport error such as - failure to read or write to a WebSocket connection or - HTTP request/response. - STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames - processed indicating how many clients connected on the STOMP level. Note that - the DISCONNECT count may be lower when sessions get closed abnormally or when - clients close without sending a DISCONNECT frame. + Current::: indicates how many client sessions there are + currently with the count further broken down by WebSocket vs HTTP + streaming and polling SockJS sessions. + Total::: indicates how many total sessions have been established. + Abnormally Closed::: + Connect Failures:::: these are sessions that got established but were + closed after not having received any messages within 60 seconds. This is + usually an indication of proxy or network issues. + Send Limit Exceeded:::: sessions closed after exceeding the configured send + timeout or the send buffer limits which can occur with slow clients + (see previous section). + Transport Errors:::: sessions closed after a transport error such as + failure to read or write to a WebSocket connection or + HTTP request/response. + STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames + processed indicating how many clients connected on the STOMP level. Note that + the DISCONNECT count may be lower when sessions get closed abnormally or when + clients close without sending a DISCONNECT frame. STOMP Broker Relay:: - TCP Connections::: indicates how many TCP connections on behalf of client - WebSocket sessions are established to the broker. This should be equal to the - number of client WebSocket sessions + 1 additional shared "system" connection - for sending messages from within the application. - STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames - forwarded to or received from the broker on behalf of clients. Note that a - DISCONNECT frame is sent to the broker regardless of how the client WebSocket - session was closed. Therefore a lower DISCONNECT frame count is an indication - that the broker is pro-actively closing connections, may be because of a - heartbeat that didn't arrive in time, an invalid input frame, or other. + TCP Connections::: indicates how many TCP connections on behalf of client + WebSocket sessions are established to the broker. This should be equal to the + number of client WebSocket sessions + 1 additional shared "system" connection + for sending messages from within the application. + STOMP Frames::: the total number of CONNECT, CONNECTED, and DISCONNECT frames + forwarded to or received from the broker on behalf of clients. Note that a + DISCONNECT frame is sent to the broker regardless of how the client WebSocket + session was closed. Therefore a lower DISCONNECT frame count is an indication + that the broker is pro-actively closing connections, may be because of a + heartbeat that didn't arrive in time, an invalid input frame, or other. Client Inbound Channel:: stats from thread pool backing the "clientInboundChannel" - providing insight into the health of incoming message processing. Tasks queueing - up here is an indication the application may be too slow to handle messages. - If there I/O bound tasks (e.g. slow database query, HTTP request to 3rd party - REST API, etc) consider increasing the thread pool size. + providing insight into the health of incoming message processing. Tasks queueing + up here is an indication the application may be too slow to handle messages. + If there I/O bound tasks (e.g. slow database query, HTTP request to 3rd party + REST API, etc) consider increasing the thread pool size. Client Outbound Channel:: stats from the thread pool backing the "clientOutboundChannel" - providing insight into the health of broadcasting messages to clients. Tasks - queueing up here is an indication clients are too slow to consume messages. - One way to address this is to increase the thread pool size to accommodate the - number of concurrent slow clients expected. Another option is to reduce the - send timeout and send buffer size limits (see the previous section). + providing insight into the health of broadcasting messages to clients. Tasks + queueing up here is an indication clients are too slow to consume messages. + One way to address this is to increase the thread pool size to accommodate the + number of concurrent slow clients expected. Another option is to reduce the + send timeout and send buffer size limits (see the previous section). SockJS Task Scheduler:: stats from thread pool of the SockJS task scheduler which - is used to send heartbeats. Note that when heartbeats are negotiated on the - STOMP level the SockJS heartbeats are disabled. + is used to send heartbeats. Note that when heartbeats are negotiated on the + STOMP level the SockJS heartbeats are disabled. + + [[websocket-stomp-testing]] -=== Testing Annotated Controller Methods +=== Testing There are two main approaches to testing applications using Spring's STOMP over WebSocket support. The first is to write server-side tests verifying the functionality