Consistent use of tabs for sample code in the reference documentation

This commit is contained in:
Juergen Hoeller
2017-11-21 15:25:26 +01:00
parent 47383fce97
commit f3b8d7138c
8 changed files with 819 additions and 708 deletions

View File

@@ -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 `<null/>` 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
(<<beans-annotation-config>>) 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<T>
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]

View File

@@ -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");
----

View File

@@ -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<Order> creationEvent) {
...
}
}
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> 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<Actor> 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<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
public int[] batchUpdate(List<Actor> 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<Object[]> batch = new ArrayList<Object[]>();
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();
}
}
----

View File

@@ -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.
----
<jms:annotation-driven/>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="concurrency" value="3-10"/>
</bean>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="concurrency" value="3-10"/>
</bean>
----
@@ -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<OrderStatus> 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<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
// order processing
Message<OrderStatus> 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:
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</bean>
</beans>
----
@@ -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"]
----
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>

File diff suppressed because it is too large Load Diff

View File

@@ -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`:
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
<!-- Optional parameter that configures the filter to write weak ETags
<init-param>
<param-name>writeWeakETag</param-name>
<param-value>true</param-value>
<param-name>writeWeakETag</param-name>
<param-value>true</param-value>
</init-param>
-->
</filter>
@@ -4769,6 +4774,7 @@ override the `createDispatcherServlet` method.
[[mvc-config]]
== Configuring Spring MVC
<<mvc-servlet-special-bean-types>> and <<mvc-servlet-config>> 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 `<mvc:interceptors>` 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 `<mvc:view-controller>` 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 `<mvc:path-matching>` element:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
----
@@ -5668,23 +5671,23 @@ It is also possible to do the same in XML:
[source,xml,indent=0]
[subs="verbatim,quotes"]
----
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
----

View File

@@ -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();
}

View File

@@ -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:
</web-app>
----
[[websocket-server-runtime-configuration]]
=== Configuring the WebSocket Engine
@@ -541,6 +555,8 @@ or WebSocket XML namespace:
</beans>
----
[[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-into-fallback-options,introduction>>, 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 <<websocket-server-allowed-origins>>), 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 <<websocket-stomp-handle-broker-relay-configure>> and
<<websocket-stomp-authentication>> 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 `<tx:annotation-driven proxy-target-class="true" />`.
[[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-destination-separator>>.
[[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"]
----
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
<websocket:stomp-endpoint path="/stomp" />
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
<websocket:stomp-endpoint path="/stomp"/>
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<constructor-arg index="0" value="." />
</bean>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<constructor-arg index="0" value="."/>
</bean>
</beans>
</beans>
----
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<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> 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 `<websocket:message-broker>` 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