Consistent use of tabs for sample code in the reference documentation
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
----
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user