mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-22 22:19:03 +00:00
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener). These URLs were unable to be fixed. Please review them to see if they can be manually resolved. * [ ] http://aopalliance.sourceforge.net (200) with 1 occurrences could not be migrated: ([https](https://aopalliance.sourceforge.net) result AnnotatedConnectException). * [ ] http://classdoc.sourceforge.net (200) with 1 occurrences could not be migrated: ([https](https://classdoc.sourceforge.net) result AnnotatedConnectException). * [ ] http://code.jquery.com:80/jquery.js (200) with 1 occurrences could not be migrated: ([https](https://code.jquery.com:80/jquery.js) result SSLException). * [ ] http://dbunit.sourceforge.net/ (200) with 1 occurrences could not be migrated: ([https](https://dbunit.sourceforge.net/) result AnnotatedConnectException). * [ ] http://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html (200) with 1 occurrences could not be migrated: ([https](https://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html) result SSLProtocolException). * [ ] http://domain3.com (200) with 6 occurrences could not be migrated: ([https](https://domain3.com) result ConnectTimeoutException). * [ ] http://forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html (200) with 1 occurrences could not be migrated: ([https](https://forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html) result SSLHandshakeException). * [ ] http://gregfranko.com/jquery.tocify.js/ (200) with 1 occurrences could not be migrated: ([https](https://gregfranko.com/jquery.tocify.js/) result SSLHandshakeException). * [ ] http://grinder.sourceforge.net/ (200) with 1 occurrences could not be migrated: ([https](https://grinder.sourceforge.net/) result AnnotatedConnectException). * [ ] http://groovy-lang.org/templating.html (200) with 3 occurrences could not be migrated: ([https](https://groovy-lang.org/templating.html) result SSLProtocolException). * [ ] http://hsqldb.org (200) with 1 occurrences could not be migrated: ([https](https://hsqldb.org) result SSLHandshakeException). * [ ] http://htmlunit.sourceforge.net/ (200) with 3 occurrences could not be migrated: ([https](https://htmlunit.sourceforge.net/) result AnnotatedConnectException). * [ ] http://htmlunit.sourceforge.net/gettingStarted.html (200) with 1 occurrences could not be migrated: ([https](https://htmlunit.sourceforge.net/gettingStarted.html) result AnnotatedConnectException). * [ ] http://htmlunit.sourceforge.net/javascript.html (200) with 1 occurrences could not be migrated: ([https](https://htmlunit.sourceforge.net/javascript.html) result AnnotatedConnectException). * [ ] http://javadiff.cvs.sourceforge.net/ (200) with 3 occurrences could not be migrated: ([https](https://javadiff.cvs.sourceforge.net/) result SSLHandshakeException). * [ ] http://jibx.sourceforge.net/ (200) with 2 occurrences could not be migrated: ([https](https://jibx.sourceforge.net/) result AnnotatedConnectException). * [ ] http://joda-time.sourceforge.net (200) with 1 occurrences could not be migrated: ([https](https://joda-time.sourceforge.net) result AnnotatedConnectException). * [ ] http://jotm.objectweb.org/ (200) with 1 occurrences could not be migrated: ([https](https://jotm.objectweb.org/) result NotSslRecordException). * [ ] http://json-b.net/ (200) with 1 occurrences could not be migrated: ([https](https://json-b.net/) result SSLHandshakeException). * [ ] http://mx4j.sourceforge.net (200) with 1 occurrences could not be migrated: ([https](https://mx4j.sourceforge.net) result AnnotatedConnectException). * [ ] http://mx4j.sourceforge.net/ (200) with 1 occurrences could not be migrated: ([https](https://mx4j.sourceforge.net/) result AnnotatedConnectException). * [ ] http://mydomain3.com (200) with 9 occurrences could not be migrated: ([https](https://mydomain3.com) result ConnectTimeoutException). * [ ] http://objenesis.org (200) with 1 occurrences could not be migrated: ([https](https://objenesis.org) result SSLHandshakeException). * [ ] http://objenesis.org/tutorial.html (200) with 1 occurrences could not be migrated: ([https](https://objenesis.org/tutorial.html) result SSLHandshakeException). * [ ] http://other.com/ (200) with 2 occurrences could not be migrated: ([https](https://other.com/) result SSLHandshakeException). * [ ] http://reactivex.io/ (200) with 1 occurrences could not be migrated: ([https](https://reactivex.io/) result SSLHandshakeException). * [ ] http://reactivex.io/documentation/operators.html (200) with 1 occurrences could not be migrated: ([https](https://reactivex.io/documentation/operators.html) result SSLHandshakeException). * [ ] http://url.somewhereelse.com (200) with 4 occurrences could not be migrated: ([https](https://url.somewhereelse.com) result SSLHandshakeException). * [ ] http://www.beanshell.org (200) with 1 occurrences could not be migrated: ([https](https://www.beanshell.org) result SSLHandshakeException). * [ ] http://www.beanshell.org/ (200) with 1 occurrences could not be migrated: ([https](https://www.beanshell.org/) result SSLHandshakeException). * [ ] http://www.doclet.com (200) with 1 occurrences could not be migrated: ([https](https://www.doclet.com) result AnnotatedConnectException). * [ ] http://www.gebish.org/ (200) with 1 occurrences could not be migrated: ([https](https://www.gebish.org/) result SSLHandshakeException). * [ ] http://www.gebish.org/manual/current/ (200) with 2 occurrences could not be migrated: ([https](https://www.gebish.org/manual/current/) result SSLHandshakeException). * [ ] http://www.groovy-lang.org/ (200) with 2 occurrences could not be migrated: ([https](https://www.groovy-lang.org/) result SSLProtocolException). * [ ] http://www.groovy-lang.org/operators.html (200) with 2 occurrences could not be migrated: ([https](https://www.groovy-lang.org/operators.html) result SSLProtocolException). * [ ] http://www.hsqldb.org (200) with 1 occurrences could not be migrated: ([https](https://www.hsqldb.org) result SSLHandshakeException). * [ ] http://www.jensgulden.de (200) with 1 occurrences could not be migrated: ([https](https://www.jensgulden.de) result AnnotatedConnectException). * [ ] http://www.mockobjects.com (200) with 2 occurrences could not be migrated: ([https](https://www.mockobjects.com) result ClosedChannelException). * [ ] http://www.mockobjects.com/ (200) with 1 occurrences could not be migrated: ([https](https://www.mockobjects.com/) result ClosedChannelException). * [ ] http://xmlunit.sourceforge.net/ (200) with 2 occurrences could not be migrated: ([https](https://xmlunit.sourceforge.net/) result AnnotatedConnectException). * [ ] http://192.168.0.1/mvc-showcase (301) with 1 occurrences could not be migrated: ([https](https://192.168.0.1/mvc-showcase) result ConnectTimeoutException). * [ ] http://www.easymock.org (301) with 2 occurrences could not be migrated: ([https](https://www.easymock.org) result SSLHandshakeException). * [ ] http://www.easymock.org/ (301) with 1 occurrences could not be migrated: ([https](https://www.easymock.org/) result SSLHandshakeException). * [ ] http://www.jmock.org/ (301) with 1 occurrences could not be migrated: ([https](https://www.jmock.org/) result SSLHandshakeException). * [ ] http://foo.com (301) with 4 occurrences could not be migrated: ([https](https://foo.com) result SSLHandshakeException). * [ ] http://foo.com/ (301) with 1 occurrences could not be migrated: ([https](https://foo.com/) result SSLHandshakeException). * [ ] http://foo.com/a (301) with 1 occurrences could not be migrated: ([https](https://foo.com/a) result SSLHandshakeException). * [ ] http://foo.com/a?foo=bar&foo=baz (301) with 4 occurrences could not be migrated: ([https](https://foo.com/a?foo=bar&foo=baz) result SSLHandshakeException). * [ ] http://foo.com/bar (301) with 5 occurrences could not be migrated: ([https](https://foo.com/bar) result SSLHandshakeException). * [ ] http://foo.com/baz (301) with 1 occurrences could not be migrated: ([https](https://foo.com/baz) result SSLHandshakeException). * [ ] http://foo.com/rest/books/6.json (301) with 1 occurrences could not be migrated: ([https](https://foo.com/rest/books/6.json) result SSLHandshakeException). * [ ] http://foo.com/rest/books/6/pages/1.json (301) with 1 occurrences could not be migrated: ([https](https://foo.com/rest/books/6/pages/1.json) result SSLHandshakeException). * [ ] http://foo.com/v1 (301) with 1 occurrences could not be migrated: ([https](https://foo.com/v1) result SSLHandshakeException). * [ ] http://foo.com/v1/123 (301) with 1 occurrences could not be migrated: ([https](https://foo.com/v1/123) result SSLHandshakeException). * [ ] http://foo.com/v1/bar (301) with 1 occurrences could not be migrated: ([https](https://foo.com/v1/bar) result SSLHandshakeException). * [ ] http://foo.com/v1?id=123 (301) with 2 occurrences could not be migrated: ([https](https://foo.com/v1?id=123) result SSLHandshakeException). * [ ] http://foo.com/wrong (301) with 1 occurrences could not be migrated: ([https](https://foo.com/wrong) result SSLHandshakeException). * [ ] http://mydomain4.com (301) with 3 occurrences could not be migrated: ([https](https://mydomain4.com) result SSLHandshakeException). * [ ] http://www.caucho.com/hessian (302) with 6 occurrences could not be migrated: ([https](https://www.caucho.com/hessian) result SSLHandshakeException). * [ ] http://mydomain2.com (302) with 22 occurrences could not be migrated: ([https](https://mydomain2.com) result ConnectTimeoutException). * [ ] http://www.jdiff.org (302) with 1 occurrences could not be migrated: ([https](https://www.jdiff.org) result ConnectTimeoutException). * [ ] http://example.com:80/test/this/here (404) with 3 occurrences could not be migrated: ([https](https://example.com:80/test/this/here) result NotSslRecordException). * [ ] http://www.foo.com/schema/component/component.xsd (404) with 1 occurrences could not be migrated: ([https](https://www.foo.com/schema/component/component.xsd) result SSLHandshakeException). * [ ] http://www.foo.com/schema/jcache (404) with 2 occurrences could not be migrated: ([https](https://www.foo.com/schema/jcache) result SSLHandshakeException). * [ ] http://www.gebish.org/manual/current/testing.html (404) with 1 occurrences could not be migrated: ([https](https://www.gebish.org/manual/current/testing.html) result SSLHandshakeException). * [ ] http://www.mycompany.com/schema/myns/myns.xsd (404) with 1 occurrences could not be migrated: ([https](https://www.mycompany.com/schema/myns/myns.xsd) result ConnectTimeoutException). * [ ] http://xunitpatterns.com/Test%20Spy.html (404) with 1 occurrences could not be migrated: ([https](https://xunitpatterns.com/Test%20Spy.html) result AnnotatedConnectException). These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended. * [ ] http://bugs.sun.com/view_bug.do?bug_id=6342411 (302) with 1 occurrences migrated to: https://bugs.java.com/view_bug.do?bug_id=6342411 ([https](https://bugs.sun.com/view_bug.do?bug_id=6342411) result SSLHandshakeException). * [ ] http://bugs.sun.com/view_bug.do?bug_id=7023180 (302) with 2 occurrences migrated to: https://bugs.java.com/view_bug.do?bug_id=7023180 ([https](https://bugs.sun.com/view_bug.do?bug_id=7023180) result SSLHandshakeException). * [ ] http://www.freemarker.org (301) with 5 occurrences migrated to: https://freemarker.apache.org/ ([https](https://www.freemarker.org) result ConnectTimeoutException). * [ ] http://sockjs.org (303) with 2 occurrences migrated to: https://github.com/sockjs/sockjs-client ([https](https://sockjs.org) result AnnotatedConnectException). * [ ] http://network.pivotal.io/open-source (301) with 1 occurrences migrated to: https://network.pivotal.io/open-source ([https](https://network.pivotal.io/open-source) result ReadTimeoutException). * [ ] http://aaa.org:8080/a (301) with 1 occurrences migrated to: https://www.aaa.org/articles/ ([https](https://aaa.org:8080/a) result NotSslRecordException). * [ ] http://aaa.org:8080/b/c/d (301) with 1 occurrences migrated to: https://www.aaa.org/b/c/d ([https](https://aaa.org:8080/b/c/d) result ReadTimeoutException). * [ ] http://www.caucho.com/ (302) with 1 occurrences migrated to: https://www.caucho.com/ ([https](https://www.caucho.com/) result SSLHandshakeException). * [ ] http://www.caucho.com (302) with 1 occurrences migrated to: https://www.caucho.com/ ([https](https://www.caucho.com) result SSLHandshakeException). * [ ] http://jruby.org (301) with 4 occurrences migrated to: https://www.jruby.org ([https](https://jruby.org) result SSLHandshakeException). * [ ] http://jruby.org/ (301) with 1 occurrences migrated to: https://www.jruby.org ([https](https://jruby.org/) result SSLHandshakeException). * [ ] http://bbb.org:9090/b (AnnotatedConnectException) with 2 occurrences migrated to: https://bbb.org:9090/b ([https](https://bbb.org:9090/b) result AnnotatedConnectException). * [ ] http://192.168.28.42/1.jsp (ConnectTimeoutException) with 3 occurrences migrated to: https://192.168.28.42/1.jsp ([https](https://192.168.28.42/1.jsp) result ConnectTimeoutException). * [ ] http://example.com:8080/ (ConnectTimeoutException) with 1 occurrences migrated to: https://example.com:8080/ ([https](https://example.com:8080/) result ConnectTimeoutException). * [ ] http://example.com:8080/bar (ConnectTimeoutException) with 5 occurrences migrated to: https://example.com:8080/bar ([https](https://example.com:8080/bar) result ConnectTimeoutException). * [ ] http://example.org:9090 (ConnectTimeoutException) with 2 occurrences migrated to: https://example.org:9090 ([https](https://example.org:9090) result ConnectTimeoutException). * [ ] http://example.org:9090/base (ConnectTimeoutException) with 12 occurrences migrated to: https://example.org:9090/base ([https](https://example.org:9090/base) result ConnectTimeoutException). * [ ] http://example.org:9090/base/people (ConnectTimeoutException) with 2 occurrences migrated to: https://example.org:9090/base/people ([https](https://example.org:9090/base/people) result ConnectTimeoutException). * [ ] http://example.org:9090/base/something/1/foo (ConnectTimeoutException) with 2 occurrences migrated to: https://example.org:9090/base/something/1/foo ([https](https://example.org:9090/base/something/1/foo) result ConnectTimeoutException). * [ ] http://example.org:9090/base/something/else (ConnectTimeoutException) with 2 occurrences migrated to: https://example.org:9090/base/something/else ([https](https://example.org:9090/base/something/else) result ConnectTimeoutException). * [ ] http://example.org:9999/base (ConnectTimeoutException) with 1 occurrences migrated to: https://example.org:9999/base ([https](https://example.org:9999/base) result ConnectTimeoutException). * [ ] http://example.org:9999/base/people/123/addresses/DE (ConnectTimeoutException) with 1 occurrences migrated to: https://example.org:9999/base/people/123/addresses/DE ([https](https://example.org:9999/base/people/123/addresses/DE) result ConnectTimeoutException). * [ ] http://example.org:9999/next (ConnectTimeoutException) with 1 occurrences migrated to: https://example.org:9999/next ([https](https://example.org:9999/next) result ConnectTimeoutException). * [ ] http://example.org:9999/path?q=foo (ConnectTimeoutException) with 1 occurrences migrated to: https://example.org:9999/path?q=foo ([https](https://example.org:9999/path?q=foo) result ConnectTimeoutException). * [ ] http://foo.com:8080/v1/bar?id=123 (ConnectTimeoutException) with 1 occurrences migrated to: https://foo.com:8080/v1/bar?id=123 ([https](https://foo.com:8080/v1/bar?id=123) result ConnectTimeoutException). * [ ] http://mydomain1.com:123 (ConnectTimeoutException) with 1 occurrences migrated to: https://mydomain1.com:123 ([https](https://mydomain1.com:123) result ConnectTimeoutException). * [ ] http://site1.com (ConnectTimeoutException) with 13 occurrences migrated to: https://site1.com ([https](https://site1.com) result ConnectTimeoutException). * [ ] http://wwws.sun.com/software/xml/developers/diffmk/ (ConnectTimeoutException) with 1 occurrences migrated to: https://wwws.sun.com/software/xml/developers/diffmk/ ([https](https://wwws.sun.com/software/xml/developers/diffmk/) result ConnectTimeoutException). * [ ] http://www.w3.org/TR/html4/loose.dtd (ReadTimeoutException) with 5 occurrences migrated to: https://www.w3.org/TR/html4/loose.dtd ([https](https://www.w3.org/TR/html4/loose.dtd) result ReadTimeoutException). * [ ] http://DOMAIN2.com (UnknownHostException) with 2 occurrences migrated to: https://DOMAIN2.com ([https](https://DOMAIN2.com) result UnknownHostException). * [ ] http://a.example.org/mvc-showcase (UnknownHostException) with 1 occurrences migrated to: https://a.example.org/mvc-showcase ([https](https://a.example.org/mvc-showcase) result UnknownHostException). * [ ] http://arjen:foobar@java.sun.com:80 (UnknownHostException) with 1 occurrences migrated to: https://arjen:foobar@java.sun.com:80 ([https](https://arjen:foobar@java.sun.com:80) result UnknownHostException). * [ ] http://dev.bar.com (UnknownHostException) with 3 occurrences migrated to: https://dev.bar.com ([https](https://dev.bar.com) result UnknownHostException). * [ ] http://domain1.com (UnknownHostException) with 25 occurrences migrated to: https://domain1.com ([https](https://domain1.com) result UnknownHostException). * [ ] http://domain1.com/test.html (UnknownHostException) with 4 occurrences migrated to: https://domain1.com/test.html ([https](https://domain1.com/test.html) result UnknownHostException). * [ ] http://domain2.com (UnknownHostException) with 92 occurrences migrated to: https://domain2.com ([https](https://domain2.com) result UnknownHostException). * [ ] http://example.com",HttpMethod.GET (UnknownHostException) with 6 occurrences migrated to: https://example.com",HttpMethod.GET ([https](https://example.com",HttpMethod.GET) result UnknownHostException). * [ ] http://foo.bar.com (UnknownHostException) with 4 occurrences migrated to: https://foo.bar.com ([https](https://foo.bar.com) result UnknownHostException). * [ ] http://joe.openid.example.org/ (UnknownHostException) with 2 occurrences migrated to: https://joe.openid.example.org/ ([https](https://joe.openid.example.org/) result UnknownHostException). * [ ] http://myserver/logo.png (UnknownHostException) with 1 occurrences migrated to: https://myserver/logo.png ([https](https://myserver/logo.png) result UnknownHostException). * [ ] http://samples.springframework.org/order (UnknownHostException) with 2 occurrences migrated to: https://samples.springframework.org/order ([https](https://samples.springframework.org/order) result UnknownHostException). * [ ] http://url.somewhere.com (UnknownHostException) with 25 occurrences migrated to: https://url.somewhere.com ([https](https://url.somewhere.com) result UnknownHostException). * [ ] http://url.somewhere.com/path (UnknownHostException) with 1 occurrences migrated to: https://url.somewhere.com/path ([https](https://url.somewhere.com/path) result UnknownHostException). * [ ] http://url.somewhere.com/path?id=1 (UnknownHostException) with 1 occurrences migrated to: https://url.somewhere.com/path?id=1 ([https](https://url.somewhere.com/path?id=1) result UnknownHostException). * [ ] http://url.somewhere.com/test.htm (UnknownHostException) with 2 occurrences migrated to: https://url.somewhere.com/test.htm ([https](https://url.somewhere.com/test.htm) result UnknownHostException). * [ ] http://url.somewhere.com?a=b&c=d (UnknownHostException) with 1 occurrences migrated to: https://url.somewhere.com?a=b&c=d ([https](https://url.somewhere.com?a=b&c=d) result UnknownHostException). * [ ] http://url.somewhere.com?foo= (UnknownHostException) with 2 occurrences migrated to: https://url.somewhere.com?foo= ([https](https://url.somewhere.com?foo=) result UnknownHostException). * [ ] http://url.somewhere.com?foo=bar (UnknownHostException) with 4 occurrences migrated to: https://url.somewhere.com?foo=bar ([https](https://url.somewhere.com?foo=bar) result UnknownHostException). * [ ] http://url.somewhere.com?foo=bar&a=b&c=d (UnknownHostException) with 2 occurrences migrated to: https://url.somewhere.com?foo=bar&a=b&c=d ([https](https://url.somewhere.com?foo=bar&a=b&c=d) result UnknownHostException). * [ ] http://wiki.fasterxml.com/JacksonHome (UnknownHostException) with 3 occurrences migrated to: https://wiki.fasterxml.com/JacksonHome ([https](https://wiki.fasterxml.com/JacksonHome) result UnknownHostException). * [ ] http://wiki.fasterxml.com/JacksonJsonViews (UnknownHostException) with 4 occurrences migrated to: https://wiki.fasterxml.com/JacksonJsonViews ([https](https://wiki.fasterxml.com/JacksonJsonViews) result UnknownHostException). * [ ] http://x.y.z (UnknownHostException) with 2 occurrences migrated to: https://x.y.z ([https](https://x.y.z) result UnknownHostException). * [ ] http://abc.com/v1 (301) with 1 occurrences migrated to: https://abc.go.com/v1 ([https](https://abc.com/v1) result 404). * [ ] http://abc.com/v1/accounts/43 (301) with 1 occurrences migrated to: https://abc.go.com/v1/accounts/43 ([https](https://abc.com/v1/accounts/43) result 404). * [ ] http://abc.com/v1/accounts?q=12 (301) with 1 occurrences migrated to: https://abc.go.com/v1/accounts?q=12 ([https](https://abc.com/v1/accounts?q=12) result 404). * [ ] http://abc.com/v2/accounts?q=12 (301) with 1 occurrences migrated to: https://abc.go.com/v2/accounts?q=12 ([https](https://abc.com/v2/accounts?q=12) result 404). * [ ] http://docs.spring.io/v1/bar (301) with 1 occurrences migrated to: https://docs.spring.io/v1/bar ([https](https://docs.spring.io/v1/bar) result 404). * [ ] http://example.com/-foo (404) with 2 occurrences migrated to: https://example.com/-foo ([https](https://example.com/-foo) result 404). * [ ] http://example.com/1 (404) with 1 occurrences migrated to: https://example.com/1 ([https](https://example.com/1) result 404). * [ ] http://example.com/1/2 (404) with 2 occurrences migrated to: https://example.com/1/2 ([https](https://example.com/1/2) result 404). * [ ] http://example.com/2 (404) with 1 occurrences migrated to: https://example.com/2 ([https](https://example.com/2) result 404). * [ ] http://example.com/a%20b?q=a%2Bb (404) with 1 occurrences migrated to: https://example.com/a%20b?q=a%2Bb ([https](https://example.com/a%20b?q=a%2Bb) result 404). * [ ] http://example.com/abc/ (404) with 1 occurrences migrated to: https://example.com/abc/ ([https](https://example.com/abc/) result 404). * [ ] http://example.com/abc/x/y (404) with 1 occurrences migrated to: https://example.com/abc/x/y ([https](https://example.com/abc/x/y) result 404). * [ ] http://example.com/abc/x/y/z (404) with 3 occurrences migrated to: https://example.com/abc/x/y/z ([https](https://example.com/abc/x/y/z) result 404). * [ ] http://example.com/app/login/authenticate (404) with 1 occurrences migrated to: https://example.com/app/login/authenticate ([https](https://example.com/app/login/authenticate) result 404). * [ ] http://example.com/arbitrary/path (404) with 1 occurrences migrated to: https://example.com/arbitrary/path ([https](https://example.com/arbitrary/path) result 404). * [ ] http://example.com/bar (404) with 6 occurrences migrated to: https://example.com/bar ([https](https://example.com/bar) result 404). * [ ] http://example.com/context.xml (404) with 4 occurrences migrated to: https://example.com/context.xml ([https](https://example.com/context.xml) result 404). * [ ] http://example.com/example (404) with 6 occurrences migrated to: https://example.com/example ([https](https://example.com/example) result 404). * [ ] http://example.com/example/?name (404) with 1 occurrences migrated to: https://example.com/example/?name ([https](https://example.com/example/?name) result 404). * [ ] http://example.com/example/?name= (404) with 1 occurrences migrated to: https://example.com/example/?name= ([https](https://example.com/example/?name=) result 404). * [ ] http://example.com/example/?name=%20 (404) with 1 occurrences migrated to: https://example.com/example/?name=%20 ([https](https://example.com/example/?name=%20) result 404). * [ ] http://example.com/example/?name=row%5B0%5D (404) with 1 occurrences migrated to: https://example.com/example/?name=row%5B0%5D ([https](https://example.com/example/?name=row%5B0%5D) result 404). * [ ] http://example.com/example/?name=value (404) with 1 occurrences migrated to: https://example.com/example/?name=value ([https](https://example.com/example/?name=value) result 404). * [ ] http://example.com/example/?name=value¶m2=value+2 (404) with 1 occurrences migrated to: https://example.com/example/?name=value¶m2=value+2 ([https](https://example.com/example/?name=value¶m2=value+2) result 404). * [ ] http://example.com/example/?row%5B0%5D=value (404) with 1 occurrences migrated to: https://example.com/example/?row%5B0%5D=value ([https](https://example.com/example/?row%5B0%5D=value) result 404). * [ ] http://example.com/foo (404) with 5 occurrences migrated to: https://example.com/foo ([https](https://example.com/foo) result 404). * [ ] http://example.com/foo/../bar (404) with 1 occurrences migrated to: https://example.com/foo/../bar ([https](https://example.com/foo/../bar) result 404). * [ ] http://example.com/foo/foo2?bar (404) with 1 occurrences migrated to: https://example.com/foo/foo2?bar ([https](https://example.com/foo/foo2?bar) result 404). * [ ] http://example.com/foo?bar (404) with 4 occurrences migrated to: https://example.com/foo?bar ([https](https://example.com/foo?bar) result 404). * [ ] http://example.com/foo?bar= (404) with 2 occurrences migrated to: https://example.com/foo?bar= ([https](https://example.com/foo?bar=) result 404). * [ ] http://example.com/foo?bar=baz (404) with 2 occurrences migrated to: https://example.com/foo?bar=baz ([https](https://example.com/foo?bar=baz) result 404). * [ ] http://example.com/foo?baz=42 (404) with 1 occurrences migrated to: https://example.com/foo?baz=42 ([https](https://example.com/foo?baz=42) result 404). * [ ] http://example.com/foo?foo=bar&baz=qux (404) with 2 occurrences migrated to: https://example.com/foo?foo=bar&baz=qux ([https](https://example.com/foo?foo=bar&baz=qux) result 404). * [ ] http://example.com/hotel (404) with 3 occurrences migrated to: https://example.com/hotel ([https](https://example.com/hotel) result 404). * [ ] http://example.com/hotel%20list (404) with 1 occurrences migrated to: https://example.com/hotel%20list ([https](https://example.com/hotel%20list) result 404). * [ ] http://example.com/hotel%20list/Z (404) with 1 occurrences migrated to: https://example.com/hotel%20list/Z ([https](https://example.com/hotel%20list/Z) result 404). * [ ] http://example.com/hotel%20list/Z%C3%BCrich (404) with 3 occurrences migrated to: https://example.com/hotel%20list/Z%C3%BCrich ([https](https://example.com/hotel%20list/Z%C3%BCrich) result 404). * [ ] http://example.com/hotels (404) with 3 occurrences migrated to: https://example.com/hotels ([https](https://example.com/hotels) result 404). * [ ] http://example.com/hotels/ (404) with 15 occurrences migrated to: https://example.com/hotels/ ([https](https://example.com/hotels/) result 404). * [ ] http://example.com/hotels/1/bookings/42 (404) with 1 occurrences migrated to: https://example.com/hotels/1/bookings/42 ([https](https://example.com/hotels/1/bookings/42) result 404). * [ ] http://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150 (404) with 2 occurrences migrated to: https://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150 ([https](https://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150) result 404). * [ ] http://example.com/hotels/1/pic/pics/logo.png (404) with 1 occurrences migrated to: https://example.com/hotels/1/pic/pics/logo.png ([https](https://example.com/hotels/1/pic/pics/logo.png) result 404). * [ ] http://example.com/hotels/42/bookings/21 (404) with 1 occurrences migrated to: https://example.com/hotels/42/bookings/21 ([https](https://example.com/hotels/42/bookings/21) result 404). * [ ] http://example.com/hotels/42/bookings/42 (404) with 1 occurrences migrated to: https://example.com/hotels/42/bookings/42 ([https](https://example.com/hotels/42/bookings/42) result 404). * [ ] http://example.com/hotels/Rest%20%26%20Relax/bookings/42 (404) with 2 occurrences migrated to: https://example.com/hotels/Rest%20%26%20Relax/bookings/42 ([https](https://example.com/hotels/Rest%20%26%20Relax/bookings/42) result 404). * [ ] http://example.com/info (404) with 2 occurrences migrated to: https://example.com/info ([https](https://example.com/info) result 404). * [ ] http://example.com/jquery-1.11.0.min.js (404) with 2 occurrences migrated to: https://example.com/jquery-1.11.0.min.js ([https](https://example.com/jquery-1.11.0.min.js) result 404). * [ ] http://example.com/match (404) with 1 occurrences migrated to: https://example.com/match ([https](https://example.com/match) result 404). * [ ] http://example.com/myFileUpload (404) with 1 occurrences migrated to: https://example.com/myFileUpload ([https](https://example.com/myFileUpload) result 404). * [ ] http://example.com/myForm (404) with 1 occurrences migrated to: https://example.com/myForm ([https](https://example.com/myForm) result 404). * [ ] http://example.com/path (404) with 6 occurrences migrated to: https://example.com/path ([https](https://example.com/path) result 404). * [ ] http://example.com/path?query (404) with 2 occurrences migrated to: https://example.com/path?query ([https](https://example.com/path?query) result 404). * [ ] http://example.com/path?query=foo (404) with 1 occurrences migrated to: https://example.com/path?query=foo ([https](https://example.com/path?query=foo) result 404). * [ ] http://example.com/people (404) with 1 occurrences migrated to: https://example.com/people ([https](https://example.com/people) result 404). * [ ] http://example.com/people/ (404) with 1 occurrences migrated to: https://example.com/people/ ([https](https://example.com/people/) result 404). * [ ] http://example.com/prefix/path (404) with 2 occurrences migrated to: https://example.com/prefix/path ([https](https://example.com/prefix/path) result 404). * [ ] http://example.com/resource (404) with 5 occurrences migrated to: https://example.com/resource ([https](https://example.com/resource) result 404). * [ ] http://example.com/resource?access_token=123 (404) with 1 occurrences migrated to: https://example.com/resource?access_token=123 ([https](https://example.com/resource?access_token=123) result 404). * [ ] http://example.com/spaces (404) with 1 occurrences migrated to: https://example.com/spaces ([https](https://example.com/spaces) result 404). * [ ] http://example.com/spaces%20and%20%E2%82%AC (404) with 3 occurrences migrated to: https://example.com/spaces%20and%20%E2%82%AC ([https](https://example.com/spaces%20and%20%E2%82%AC) result 404). * [ ] http://example.com/spring/ (404) with 1 occurrences migrated to: https://example.com/spring/ ([https](https://example.com/spring/) result 404). * [ ] http://example.com/test/this/here (404) with 1 occurrences migrated to: https://example.com/test/this/here ([https](https://example.com/test/this/here) result 404). * [ ] http://example.com/upload (404) with 1 occurrences migrated to: https://example.com/upload ([https](https://example.com/upload) result 404). * [ ] http://example.com/user (404) with 1 occurrences migrated to: https://example.com/user ([https](https://example.com/user) result 404). * [ ] http://example.org/fonts/css (404) with 6 occurrences migrated to: https://example.org/fonts/css ([https](https://example.org/fonts/css) result 404). * [ ] http://example.org/foo/page.html (404) with 1 occurrences migrated to: https://example.org/foo/page.html ([https](https://example.org/foo/page.html) result 404). * [ ] http://example.org/image.png (404) with 4 occurrences migrated to: https://example.org/image.png ([https](https://example.org/image.png) result 404). * [ ] http://example.org/music/people (404) with 4 occurrences migrated to: https://example.org/music/people ([https](https://example.org/music/people) result 404). * [ ] http://example.org/page.cache.html (404) with 1 occurrences migrated to: https://example.org/page.cache.html ([https](https://example.org/page.cache.html) result 404). * [ ] http://example.org/path (404) with 3 occurrences migrated to: https://example.org/path ([https](https://example.org/path) result 404). * [ ] http://springframework.org/oxm-test (404) with 2 occurrences migrated to: https://springframework.org/oxm-test ([https](https://springframework.org/oxm-test) result 404). * [ ] http://aaa.org:8080/app/b/c/d (301) with 1 occurrences migrated to: https://www.aaa.org/app/b/c/d ([https](https://aaa.org:8080/app/b/c/d) result 404). * [ ] http://www.example.com/favicon.ico (404) with 2 occurrences migrated to: https://www.example.com/favicon.ico ([https](https://www.example.com/favicon.ico) result 404). * [ ] http://www.example.com/foo/bar (404) with 1 occurrences migrated to: https://www.example.com/foo/bar ([https](https://www.example.com/foo/bar) result 404). * [ ] http://www.example.com/hotels (404) with 2 occurrences migrated to: https://www.example.com/hotels ([https](https://www.example.com/hotels) result 404). * [ ] http://www.example.com/user/ (404) with 4 occurrences migrated to: https://www.example.com/user/ ([https](https://www.example.com/user/) result 404). * [ ] http://www.example.com/user/john%3Bdoe/dashboard (404) with 4 occurrences migrated to: https://www.example.com/user/john%3Bdoe/dashboard ([https](https://www.example.com/user/john%3Bdoe/dashboard) result 404). * [ ] http://www.example.com/user/john;doe/dashboard (404) with 2 occurrences migrated to: https://www.example.com/user/john;doe/dashboard ([https](https://www.example.com/user/john;doe/dashboard) result 404). * [ ] http://www.ibm.com/developerworks/websphere/library/techarticles/0404_tang/0404_tang.html (301) with 1 occurrences migrated to: https://www.ibm.com/developerworks/websphere/library/techarticles/0404_tang/0404_tang.html ([https](https://www.ibm.com/developerworks/websphere/library/techarticles/0404_tang/0404_tang.html) result 404). * [ ] http://mydomain1.com:80/path (301) with 2 occurrences migrated to: https://www.mydomain1.com/path ([https](https://mydomain1.com:80/path) result 404). * [ ] http://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03 (404) with 1 occurrences migrated to: https://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03 ([https](https://www.quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/tutorial-lesson-03) result 404). * [ ] http://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html (301) with 1 occurrences migrated to: https://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html ([https](https://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html) result 404). * [ ] http://xml.apache.org/xslt (404) with 3 occurrences migrated to: https://xml.apache.org/xslt ([https](https://xml.apache.org/xslt) result 404). * [ ] http://myhost.com/resource/path/myTemplate.txt (500) with 1 occurrences migrated to: https://myhost.com/resource/path/myTemplate.txt ([https](https://myhost.com/resource/path/myTemplate.txt) result 500). * [ ] http://myhost.com/some/arbitrary/path (500) with 1 occurrences migrated to: https://myhost.com/some/arbitrary/path ([https](https://myhost.com/some/arbitrary/path) result 500). These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended. * [ ] http://activemq.apache.org/delete-inactive-destinations.html with 1 occurrences migrated to: https://activemq.apache.org/delete-inactive-destinations.html ([https](https://activemq.apache.org/delete-inactive-destinations.html) result 200). * [ ] http://activemq.apache.org/stomp.html with 1 occurrences migrated to: https://activemq.apache.org/stomp.html ([https](https://activemq.apache.org/stomp.html) result 200). * [ ] http://ant.apache.org with 1 occurrences migrated to: https://ant.apache.org ([https](https://ant.apache.org) result 200). * [ ] http://asciidoctor.org/ with 1 occurrences migrated to: https://asciidoctor.org/ ([https](https://asciidoctor.org/) result 200). * [ ] http://asciidoctor.org/docs/editing-asciidoc-with-live-preview/ with 2 occurrences migrated to: https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/ ([https](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/) result 200). * [ ] http://beanvalidation.org/ with 1 occurrences migrated to: https://beanvalidation.org/ ([https](https://beanvalidation.org/) result 200). * [ ] http://caniuse.com/ with 2 occurrences migrated to: https://caniuse.com/ ([https](https://caniuse.com/) result 200). * [ ] http://castor-data-binding.github.io/castor/reference-guides/1.3.3/html-single/index.html with 2 occurrences migrated to: https://castor-data-binding.github.io/castor/reference-guides/1.3.3/html-single/index.html ([https](https://castor-data-binding.github.io/castor/reference-guides/1.3.3/html-single/index.html) result 200). * [ ] http://cbor.io/ with 1 occurrences migrated to: https://cbor.io/ ([https](https://cbor.io/) result 200). * [ ] http://code.jquery.com/jquery-1.11.0.min.js with 2 occurrences migrated to: https://code.jquery.com/jquery-1.11.0.min.js ([https](https://code.jquery.com/jquery-1.11.0.min.js) result 200). * [ ] http://code.jquery.com/jquery.js with 1 occurrences migrated to: https://code.jquery.com/jquery.js ([https](https://code.jquery.com/jquery.js) result 200). * [ ] http://commons.apache.org/proper/commons-lang/ with 2 occurrences migrated to: https://commons.apache.org/proper/commons-lang/ ([https](https://commons.apache.org/proper/commons-lang/) result 200). * [ ] http://docs.oracle.com/javase/6/docs/api/java/beans/package-summary.html with 1 occurrences migrated to: https://docs.oracle.com/javase/6/docs/api/java/beans/package-summary.html ([https](https://docs.oracle.com/javase/6/docs/api/java/beans/package-summary.html) result 200). * [ ] http://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html with 1 occurrences migrated to: https://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html ([https](https://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html) result 200). * [ ] http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html with 2 occurrences migrated to: https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html ([https](https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html) result 200). * [ ] http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html with 1 occurrences migrated to: https://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html ([https](https://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html) result 200). * [ ] http://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html with 2 occurrences migrated to: https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html ([https](https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngineFactory.html) result 200). * [ ] http://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html with 1 occurrences migrated to: https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html ([https](https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html) result 200). * [ ] http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html with 1 occurrences migrated to: https://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html ([https](https://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) result 200). * [ ] http://docs.seleniumhq.org/ with 1 occurrences migrated to: https://docs.seleniumhq.org/ ([https](https://docs.seleniumhq.org/) result 200). * [ ] http://docs.seleniumhq.org/projects/webdriver/ with 1 occurrences migrated to: https://docs.seleniumhq.org/projects/webdriver/ ([https](https://docs.seleniumhq.org/projects/webdriver/) result 200). * [ ] http://docs.spring.io/spring-framework/docs/current/javadoc-api/ with 1 occurrences migrated to: https://docs.spring.io/spring-framework/docs/current/javadoc-api/ ([https](https://docs.spring.io/spring-framework/docs/current/javadoc-api/) result 200). * [ ] http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/script/ScriptTemplateView.html with 1 occurrences migrated to: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/script/ScriptTemplateView.html ([https](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/script/ScriptTemplateView.html) result 200). * [ ] http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/ with 1 occurrences migrated to: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/ ([https](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) result 200). * [ ] http://docs.spring.io/spring-session/docs/current/reference/html5/ with 1 occurrences migrated to: https://docs.spring.io/spring-session/docs/current/reference/html5/ ([https](https://docs.spring.io/spring-session/docs/current/reference/html5/) result 200). * [ ] http://en.wikipedia.org/wiki/Adapter_pattern with 1 occurrences migrated to: https://en.wikipedia.org/wiki/Adapter_pattern ([https](https://en.wikipedia.org/wiki/Adapter_pattern) result 200). * [ ] http://en.wikipedia.org/wiki/Halting_Problem with 1 occurrences migrated to: https://en.wikipedia.org/wiki/Halting_Problem ([https](https://en.wikipedia.org/wiki/Halting_Problem) result 200). * [ ] http://en.wikipedia.org/wiki/JSONP with 3 occurrences migrated to: https://en.wikipedia.org/wiki/JSONP ([https](https://en.wikipedia.org/wiki/JSONP) result 200). * [ ] http://en.wikipedia.org/wiki/List_of_HTTP_status_codes with 1 occurrences migrated to: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes ([https](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) result 200). * [ ] http://en.wikipedia.org/wiki/MIME with 1 occurrences migrated to: https://en.wikipedia.org/wiki/MIME ([https](https://en.wikipedia.org/wiki/MIME) result 200). * [ ] http://en.wikipedia.org/wiki/Mock_Object with 1 occurrences migrated to: https://en.wikipedia.org/wiki/Mock_Object ([https](https://en.wikipedia.org/wiki/Mock_Object) result 200). * [ ] http://en.wikipedia.org/wiki/Polyfill with 2 occurrences migrated to: https://en.wikipedia.org/wiki/Polyfill ([https](https://en.wikipedia.org/wiki/Polyfill) result 200). * [ ] http://example.com with 139 occurrences migrated to: https://example.com ([https](https://example.com) result 200). * [ ] http://example.com/ with 16 occurrences migrated to: https://example.com/ ([https](https://example.com/) result 200). * [ ] http://example.com?foo with 2 occurrences migrated to: https://example.com?foo ([https](https://example.com?foo) result 200). * [ ] http://example.com?foo=bar with 8 occurrences migrated to: https://example.com?foo=bar ([https](https://example.com?foo=bar) result 200). * [ ] http://example.com?foo=bar@baz with 1 occurrences migrated to: https://example.com?foo=bar@baz ([https](https://example.com?foo=bar@baz) result 200). * [ ] http://example.org with 16 occurrences migrated to: https://example.org ([https](https://example.org) result 200). * [ ] http://example.org/ with 1 occurrences migrated to: https://example.org/ ([https](https://example.org/) result 200). * [ ] http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html with 2 occurrences migrated to: https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html ([https](https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html) result 200). * [ ] http://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html with 2 occurrences migrated to: https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html ([https](https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html) result 200). * [ ] http://h2database.com with 1 occurrences migrated to: https://h2database.com ([https](https://h2database.com) result 200). * [ ] http://handlebarsjs.com with 2 occurrences migrated to: https://handlebarsjs.com ([https](https://handlebarsjs.com) result 200). * [ ] http://handlebarsjs.com/ with 2 occurrences migrated to: https://handlebarsjs.com/ ([https](https://handlebarsjs.com/) result 200). * [ ] http://hc.apache.org/httpcomponents-asyncclient-dev/ with 1 occurrences migrated to: https://hc.apache.org/httpcomponents-asyncclient-dev/ ([https](https://hc.apache.org/httpcomponents-asyncclient-dev/) result 200). * [ ] http://hc.apache.org/httpcomponents-client-ga/ with 2 occurrences migrated to: https://hc.apache.org/httpcomponents-client-ga/ ([https](https://hc.apache.org/httpcomponents-client-ga/) result 200). * [ ] http://hc.apache.org/httpcomponents-client-ga/httpclient/ with 1 occurrences migrated to: https://hc.apache.org/httpcomponents-client-ga/httpclient/ ([https](https://hc.apache.org/httpcomponents-client-ga/httpclient/) result 200). * [ ] http://www.hibernate.org/ (301) with 1 occurrences migrated to: https://hibernate.org/ ([https](https://www.hibernate.org/) result 200). * [ ] http://www.hibernate.org (301) with 1 occurrences migrated to: https://hibernate.org/ ([https](https://www.hibernate.org) result 200). * [ ] http://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html with 1 occurrences migrated to: https://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html ([https](https://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html) result 200). * [ ] http://jcp.org/aboutJava/communityprocess/final/jsr160/index.html with 1 occurrences migrated to: https://jcp.org/aboutJava/communityprocess/final/jsr160/index.html ([https](https://jcp.org/aboutJava/communityprocess/final/jsr160/index.html) result 200). * [ ] http://jcp.org/en/jsr/detail?id=107 with 1 occurrences migrated to: https://jcp.org/en/jsr/detail?id=107 ([https](https://jcp.org/en/jsr/detail?id=107) result 200). * [ ] http://joel-costigliola.github.io/assertj/ with 2 occurrences migrated to: https://joel-costigliola.github.io/assertj/ ([https](https://joel-costigliola.github.io/assertj/) result 200). * [ ] http://jsonassert.skyscreamer.org with 1 occurrences migrated to: https://jsonassert.skyscreamer.org ([https](https://jsonassert.skyscreamer.org) result 200). * [ ] http://jsonassert.skyscreamer.org/ with 5 occurrences migrated to: https://jsonassert.skyscreamer.org/ ([https](https://jsonassert.skyscreamer.org/) result 200). * [ ] http://junit.org/junit5/docs/current/user-guide/ with 2 occurrences migrated to: https://junit.org/junit5/docs/current/user-guide/ ([https](https://junit.org/junit5/docs/current/user-guide/) result 200). * [ ] http://kotlinlang.org/ with 2 occurrences migrated to: https://kotlinlang.org/ ([https](https://kotlinlang.org/) result 200). * [ ] http://kotlinlang.org/docs/reference/ with 1 occurrences migrated to: https://kotlinlang.org/docs/reference/ ([https](https://kotlinlang.org/docs/reference/) result 200). * [ ] http://kotlinlang.org/docs/reference/classes.html with 3 occurrences migrated to: https://kotlinlang.org/docs/reference/classes.html ([https](https://kotlinlang.org/docs/reference/classes.html) result 200). * [ ] http://mydomain.com with 5 occurrences migrated to: https://mydomain.com ([https](https://mydomain.com) result 200). * [ ] http://myfaces.apache.org/ with 1 occurrences migrated to: https://myfaces.apache.org/ ([https](https://myfaces.apache.org/) result 200). * [ ] http://myfaces.apache.org/orchestra/ with 1 occurrences migrated to: https://myfaces.apache.org/orchestra/ ([https](https://myfaces.apache.org/orchestra/) result 200). * [ ] http://netty.io/ with 1 occurrences migrated to: https://netty.io/ ([https](https://netty.io/) result 200). * [ ] http://openjdk.java.net/jeps/290 with 1 occurrences migrated to: https://openjdk.java.net/jeps/290 ([https](https://openjdk.java.net/jeps/290) result 200). * [ ] http://openjdk.java.net/projects/nashorn/ with 10 occurrences migrated to: https://openjdk.java.net/projects/nashorn/ ([https](https://openjdk.java.net/projects/nashorn/) result 200). * [ ] http://pivotal.io/security/cve-2015-5211 with 1 occurrences migrated to: https://pivotal.io/security/cve-2015-5211 ([https](https://pivotal.io/security/cve-2015-5211) result 200). * [ ] http://poi.apache.org with 3 occurrences migrated to: https://poi.apache.org ([https](https://poi.apache.org) result 200). * [ ] http://projects.spring.io/spring-boot/ with 1 occurrences migrated to: https://projects.spring.io/spring-boot/ ([https](https://projects.spring.io/spring-boot/) result 200). * [ ] http://projects.spring.io/spring-data/ with 1 occurrences migrated to: https://projects.spring.io/spring-data/ ([https](https://projects.spring.io/spring-data/) result 200). * [ ] http://projects.spring.io/spring-framework/ with 1 occurrences migrated to: https://projects.spring.io/spring-framework/ ([https](https://projects.spring.io/spring-framework/) result 200). * [ ] http://projects.spring.io/spring-integration/ with 1 occurrences migrated to: https://projects.spring.io/spring-integration/ ([https](https://projects.spring.io/spring-integration/) result 200). * [ ] http://projects.spring.io/spring-security/ with 3 occurrences migrated to: https://projects.spring.io/spring-security/ ([https](https://projects.spring.io/spring-security/) result 200). * [ ] http://repo1.maven.org/maven2/javax/inject/javax.inject/1/ with 2 occurrences migrated to: https://repo1.maven.org/maven2/javax/inject/javax.inject/1/ ([https](https://repo1.maven.org/maven2/javax/inject/javax.inject/1/) result 200). * [ ] http://schema.spring.io/mvc/spring-mvc.xsd with 1 occurrences migrated to: https://schema.spring.io/mvc/spring-mvc.xsd ([https](https://schema.spring.io/mvc/spring-mvc.xsd) result 200). * [ ] http://search.maven.org/ with 2 occurrences migrated to: https://search.maven.org/ ([https](https://search.maven.org/) result 200). * [ ] http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html with 3 occurrences migrated to: https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html ([https](https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html) result 200). * [ ] http://sourceforge.net with 1 occurrences migrated to: https://sourceforge.net ([https](https://sourceforge.net) result 200). * [ ] http://sourceforge.net/projects/javadiff/ with 1 occurrences migrated to: https://sourceforge.net/projects/javadiff/ ([https](https://sourceforge.net/projects/javadiff/) result 200). * [ ] http://sourceforge.net/sflogo.php?group_id=37160 with 1 occurrences migrated to: https://sourceforge.net/sflogo.php?group_id=37160 ([https](https://sourceforge.net/sflogo.php?group_id=37160) result 200). * [ ] http://spring.io/blog/ with 1 occurrences migrated to: https://spring.io/blog/ ([https](https://spring.io/blog/) result 200). * [ ] http://spring.io/blog/category/news with 1 occurrences migrated to: https://spring.io/blog/category/news ([https](https://spring.io/blog/category/news) result 200). * [ ] http://square.github.io/okhttp/ with 1 occurrences migrated to: https://square.github.io/okhttp/ ([https](https://square.github.io/okhttp/) result 200). * [ ] http://stackoverflow.com/questions/1481251/what-does-document-domain-document-domain-do with 2 occurrences migrated to: https://stackoverflow.com/questions/1481251/what-does-document-domain-document-domain-do ([https](https://stackoverflow.com/questions/1481251/what-does-document-domain-document-domain-do) result 200). * [ ] http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources with 2 occurrences migrated to: https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources ([https](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources) result 200). * [ ] http://stackoverflow.com/questions/22986109/testing-spring-managed-servlet with 1 occurrences migrated to: https://stackoverflow.com/questions/22986109/testing-spring-managed-servlet ([https](https://stackoverflow.com/questions/22986109/testing-spring-managed-servlet) result 200). * [ ] http://stackoverflow.com/questions/24725438/what-could-cause-a-class-implementing-applicationlistenercontextrefreshedevent with 1 occurrences migrated to: https://stackoverflow.com/questions/24725438/what-could-cause-a-class-implementing-applicationlistenercontextrefreshedevent ([https](https://stackoverflow.com/questions/24725438/what-could-cause-a-class-implementing-applicationlistenercontextrefreshedevent) result 200). * [ ] http://stackoverflow.com/questions/35733344/can-contextconfiguration-in-a-custom-annotation-be-merged with 1 occurrences migrated to: https://stackoverflow.com/questions/35733344/can-contextconfiguration-in-a-custom-annotation-be-merged ([https](https://stackoverflow.com/questions/35733344/can-contextconfiguration-in-a-custom-annotation-be-merged) result 200). * [ ] http://start.spring.io/ with 2 occurrences migrated to: https://start.spring.io/ ([https](https://start.spring.io/) result 200). * [ ] http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html with 1 occurrences migrated to: https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html ([https](https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html) result 200). * [ ] http://stomp.github.io/stomp-specification-1.2.html with 9 occurrences migrated to: https://stomp.github.io/stomp-specification-1.2.html ([https](https://stomp.github.io/stomp-specification-1.2.html) result 200). * [ ] http://struts.apache.org with 1 occurrences migrated to: https://struts.apache.org ([https](https://struts.apache.org) result 200). * [ ] http://struts.apache.org/ with 1 occurrences migrated to: https://struts.apache.org/ ([https](https://struts.apache.org/) result 200). * [ ] http://tapestry.apache.org/ with 2 occurrences migrated to: https://tapestry.apache.org/ ([https](https://tapestry.apache.org/) result 200). * [ ] http://tiles.apache.org with 4 occurrences migrated to: https://tiles.apache.org ([https](https://tiles.apache.org) result 200). * [ ] http://tiles.apache.org/framework/dependency-management.html with 1 occurrences migrated to: https://tiles.apache.org/framework/dependency-management.html ([https](https://tiles.apache.org/framework/dependency-management.html) result 200). * [ ] http://tomcat.apache.org/ with 2 occurrences migrated to: https://tomcat.apache.org/ ([https](https://tomcat.apache.org/) result 200). * [ ] http://tomcat.apache.org/tomcat-6.0-doc/config/context.html with 1 occurrences migrated to: https://tomcat.apache.org/tomcat-6.0-doc/config/context.html ([https](https://tomcat.apache.org/tomcat-6.0-doc/config/context.html) result 200). * [ ] http://tools.ietf.org/html/rfc1945 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc1945 ([https](https://tools.ietf.org/html/rfc1945) result 200). * [ ] http://tools.ietf.org/html/rfc2109 with 2 occurrences migrated to: https://tools.ietf.org/html/rfc2109 ([https](https://tools.ietf.org/html/rfc2109) result 200). * [ ] http://tools.ietf.org/html/rfc2295 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2295 ([https](https://tools.ietf.org/html/rfc2295) result 200). * [ ] http://tools.ietf.org/html/rfc2324 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2324 ([https](https://tools.ietf.org/html/rfc2324) result 200). * [ ] http://tools.ietf.org/html/rfc2518 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2518 ([https](https://tools.ietf.org/html/rfc2518) result 200). * [ ] http://tools.ietf.org/html/rfc2616 with 3 occurrences migrated to: https://tools.ietf.org/html/rfc2616 ([https](https://tools.ietf.org/html/rfc2616) result 200). * [ ] http://tools.ietf.org/html/rfc2774 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2774 ([https](https://tools.ietf.org/html/rfc2774) result 200). * [ ] http://tools.ietf.org/html/rfc2817 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2817 ([https](https://tools.ietf.org/html/rfc2817) result 200). * [ ] http://tools.ietf.org/html/rfc2965 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc2965 ([https](https://tools.ietf.org/html/rfc2965) result 200). * [ ] http://tools.ietf.org/html/rfc3229 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc3229 ([https](https://tools.ietf.org/html/rfc3229) result 200). * [ ] http://tools.ietf.org/html/rfc3986 with 4 occurrences migrated to: https://tools.ietf.org/html/rfc3986 ([https](https://tools.ietf.org/html/rfc3986) result 200). * [ ] http://tools.ietf.org/html/rfc4918 with 5 occurrences migrated to: https://tools.ietf.org/html/rfc4918 ([https](https://tools.ietf.org/html/rfc4918) result 200). * [ ] http://tools.ietf.org/html/rfc5842 with 2 occurrences migrated to: https://tools.ietf.org/html/rfc5842 ([https](https://tools.ietf.org/html/rfc5842) result 200). * [ ] http://tools.ietf.org/html/rfc5988 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc5988 ([https](https://tools.ietf.org/html/rfc5988) result 200). * [ ] http://tools.ietf.org/html/rfc6202 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc6202 ([https](https://tools.ietf.org/html/rfc6202) result 200). * [ ] http://tools.ietf.org/html/rfc6266 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc6266 ([https](https://tools.ietf.org/html/rfc6266) result 200). * [ ] http://tools.ietf.org/html/rfc6454 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc6454 ([https](https://tools.ietf.org/html/rfc6454) result 200). * [ ] http://tools.ietf.org/html/rfc6455 with 2 occurrences migrated to: https://tools.ietf.org/html/rfc6455 ([https](https://tools.ietf.org/html/rfc6455) result 200). * [ ] http://tools.ietf.org/html/rfc6585 with 4 occurrences migrated to: https://tools.ietf.org/html/rfc6585 ([https](https://tools.ietf.org/html/rfc6585) result 200). * [ ] http://tools.ietf.org/html/rfc7230 with 10 occurrences migrated to: https://tools.ietf.org/html/rfc7230 ([https](https://tools.ietf.org/html/rfc7230) result 200). * [ ] http://tools.ietf.org/html/rfc7231 with 56 occurrences migrated to: https://tools.ietf.org/html/rfc7231 ([https](https://tools.ietf.org/html/rfc7231) result 200). * [ ] http://tools.ietf.org/html/rfc7232 with 8 occurrences migrated to: https://tools.ietf.org/html/rfc7232 ([https](https://tools.ietf.org/html/rfc7232) result 200). * [ ] http://tools.ietf.org/html/rfc7233 with 12 occurrences migrated to: https://tools.ietf.org/html/rfc7233 ([https](https://tools.ietf.org/html/rfc7233) result 200). * [ ] http://tools.ietf.org/html/rfc7234 with 5 occurrences migrated to: https://tools.ietf.org/html/rfc7234 ([https](https://tools.ietf.org/html/rfc7234) result 200). * [ ] http://tools.ietf.org/html/rfc7235 with 6 occurrences migrated to: https://tools.ietf.org/html/rfc7235 ([https](https://tools.ietf.org/html/rfc7235) result 200). * [ ] http://tools.ietf.org/html/rfc7238 with 1 occurrences migrated to: https://tools.ietf.org/html/rfc7238 ([https](https://tools.ietf.org/html/rfc7238) result 200). * [ ] http://tools.ietf.org/html/rfc7239 with 12 occurrences migrated to: https://tools.ietf.org/html/rfc7239 ([https](https://tools.ietf.org/html/rfc7239) result 200). * [ ] http://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt with 3 occurrences migrated to: https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt ([https](https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt) result 200). * [ ] http://unlicense.org with 1 occurrences migrated to: https://unlicense.org ([https](https://unlicense.org) result 200). * [ ] http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/ with 10 occurrences migrated to: https://www.amazon.com/exec/obidos/tg/detail/-/0764543857/ ([https](https://www.amazon.com/exec/obidos/tg/detail/-/0764543857/) result 200). * [ ] http://www.apache.org with 1 occurrences migrated to: https://www.apache.org ([https](https://www.apache.org) result 200). * [ ] http://www.apple.com/DTDs/PropertyList-1.0.dtd with 6 occurrences migrated to: https://www.apple.com/DTDs/PropertyList-1.0.dtd ([https](https://www.apple.com/DTDs/PropertyList-1.0.dtd) result 200). * [ ] http://www.atomikos.com/ with 1 occurrences migrated to: https://www.atomikos.com/ ([https](https://www.atomikos.com/) result 200). * [ ] http://www.baeldung.com/kotlin-null-safety with 1 occurrences migrated to: https://www.baeldung.com/kotlin-null-safety ([https](https://www.baeldung.com/kotlin-null-safety) result 200). * [ ] http://www.eclipse.org/ajdt/ with 1 occurrences migrated to: https://www.eclipse.org/ajdt/ ([https](https://www.eclipse.org/ajdt/) result 200). * [ ] http://www.eclipse.org/aspectj/doc/next/progguide/index.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/next/progguide/index.html ([https](https://www.eclipse.org/aspectj/doc/next/progguide/index.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/next/progguide/semantics-joinPoints.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/next/progguide/semantics-joinPoints.html ([https](https://www.eclipse.org/aspectj/doc/next/progguide/semantics-joinPoints.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html ([https](https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html ([https](https://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/devguide/index.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/devguide/index.html ([https](https://www.eclipse.org/aspectj/doc/released/devguide/index.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html ([https](https://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/devguide/ltw.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/devguide/ltw.html ([https](https://www.eclipse.org/aspectj/doc/released/devguide/ltw.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/progguide/index.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/progguide/index.html ([https](https://www.eclipse.org/aspectj/doc/released/progguide/index.html) result 200). * [ ] http://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html with 1 occurrences migrated to: https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html ([https](https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html) result 200). * [ ] http://www.eclipse.org/aspectj/dtd/aspectj.dtd with 1 occurrences migrated to: https://www.eclipse.org/aspectj/dtd/aspectj.dtd ([https](https://www.eclipse.org/aspectj/dtd/aspectj.dtd) result 200). * [ ] http://www.eclipse.org/jetty/ with 1 occurrences migrated to: https://www.eclipse.org/jetty/ ([https](https://www.eclipse.org/jetty/) result 200). * [ ] http://ehcache.sourceforge.net (301) with 1 occurrences migrated to: https://www.ehcache.org/ ([https](https://ehcache.sourceforge.net) result 200). * [ ] http://ehcache.org/ (301) with 1 occurrences migrated to: https://www.ehcache.org/ ([https](https://ehcache.org/) result 200). * [ ] http://www.embeddedjs.com/ with 2 occurrences migrated to: https://www.embeddedjs.com/ ([https](https://www.embeddedjs.com/) result 200). * [ ] http://www.enterpriseintegrationpatterns.com with 1 occurrences migrated to: https://www.enterpriseintegrationpatterns.com ([https](https://www.enterpriseintegrationpatterns.com) result 200). * [ ] http://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D with 1 occurrences migrated to: https://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D ([https](https://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D) result 200). * [ ] http://www.google.com with 2 occurrences migrated to: https://www.google.com ([https](https://www.google.com) result 200). * [ ] http://www.h2database.com with 1 occurrences migrated to: https://www.h2database.com ([https](https://www.h2database.com) result 200). * [ ] http://www.h2database.com/html/grammar.html with 1 occurrences migrated to: https://www.h2database.com/html/grammar.html ([https](https://www.h2database.com/html/grammar.html) result 200). * [ ] http://www.ietf.org with 1 occurrences migrated to: https://www.ietf.org ([https](https://www.ietf.org) result 200). * [ ] http://www.ietf.org/rfc/rfc1867.txt with 1 occurrences migrated to: https://www.ietf.org/rfc/rfc1867.txt ([https](https://www.ietf.org/rfc/rfc1867.txt) result 200). * [ ] http://www.ietf.org/rfc/rfc2396.txt with 2 occurrences migrated to: https://www.ietf.org/rfc/rfc2396.txt ([https](https://www.ietf.org/rfc/rfc2396.txt) result 200). * [ ] http://www.ietf.org/rfc/rfc3986.txt with 11 occurrences migrated to: https://www.ietf.org/rfc/rfc3986.txt ([https](https://www.ietf.org/rfc/rfc3986.txt) result 200). * [ ] http://www.infoq.com/ with 1 occurrences migrated to: https://www.infoq.com/ ([https](https://www.infoq.com/) result 200). * [ ] http://www.infoq.com/minibooks/JTDS with 1 occurrences migrated to: https://www.infoq.com/minibooks/JTDS ([https](https://www.infoq.com/minibooks/JTDS) result 200). * [ ] http://www.jetbrains.com/idea/ with 1 occurrences migrated to: https://www.jetbrains.com/idea/ ([https](https://www.jetbrains.com/idea/) result 200). * [ ] http://www.json.org/ with 1 occurrences migrated to: https://www.json.org/ ([https](https://www.json.org/) result 200). * [ ] http://www.jython.org with 2 occurrences migrated to: https://www.jython.org ([https](https://www.jython.org) result 200). * [ ] http://www.jython.org/ with 2 occurrences migrated to: https://www.jython.org/ ([https](https://www.jython.org/) result 200). * [ ] http://www.lowagie.com/iText with 2 occurrences migrated to: https://www.lowagie.com/iText ([https](https://www.lowagie.com/iText) result 200). * [ ] http://www.manpagez.com/man/5/crontab/ with 1 occurrences migrated to: https://www.manpagez.com/man/5/crontab/ ([https](https://www.manpagez.com/man/5/crontab/) result 200). * [ ] http://mydomain1.com:80/ (301) with 2 occurrences migrated to: https://www.mydomain1.com/ ([https](https://mydomain1.com:80/) result 200). * [ ] http://mydomain1.com:80 (301) with 1 occurrences migrated to: https://www.mydomain1.com/ ([https](https://mydomain1.com:80) result 200). * [ ] http://www.oracle.com/technetwork/articles/javaee/jpa-137156.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/articles/javaee/jpa-137156.html ([https](https://www.oracle.com/technetwork/articles/javaee/jpa-137156.html) result 200). * [ ] http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html ([https](https://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html) result 200). * [ ] http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html ([https](https://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html) result 200). * [ ] http://www.oracle.com/technetwork/java/javase/tech/download-jsp-141676.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/java/javase/tech/download-jsp-141676.html ([https](https://www.oracle.com/technetwork/java/javase/tech/download-jsp-141676.html) result 200). * [ ] http://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html ([https](https://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html) result 200). * [ ] http://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html with 1 occurrences migrated to: https://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html ([https](https://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html) result 200). * [ ] http://www.ow2.org/ with 1 occurrences migrated to: https://www.ow2.org/ ([https](https://www.ow2.org/) result 200). * [ ] http://www.owasp.org/index.php/HTTPOnly with 4 occurrences migrated to: https://www.owasp.org/index.php/HTTPOnly ([https](https://www.owasp.org/index.php/HTTPOnly) result 200). * [ ] http://quartz-scheduler.org (301) with 3 occurrences migrated to: https://www.quartz-scheduler.org/ ([https](https://quartz-scheduler.org) result 200). * [ ] http://www.rabbitmq.com/stomp.html with 1 occurrences migrated to: https://www.rabbitmq.com/stomp.html ([https](https://www.rabbitmq.com/stomp.html) result 200). * [ ] http://www.reactive-streams.org with 1 occurrences migrated to: https://www.reactive-streams.org ([https](https://www.reactive-streams.org) result 200). * [ ] http://www.reactive-streams.org/ with 2 occurrences migrated to: https://www.reactive-streams.org/ ([https](https://www.reactive-streams.org/) result 200). * [ ] http://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Publisher.html with 1 occurrences migrated to: https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Publisher.html ([https](https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Publisher.html) result 200). * [ ] http://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Subscriber.html with 1 occurrences migrated to: https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Subscriber.html ([https](https://www.reactive-streams.org/reactive-streams-1.0.1-javadoc/org/reactivestreams/Subscriber.html) result 200). * [ ] http://seleniumhq.org/projects/webdriver/ (301) with 1 occurrences migrated to: https://www.seleniumhq.org ([https](https://seleniumhq.org/projects/webdriver/) result 200). * [ ] http://www.springbyexample.org/examples/custom-thread-scope-module.html with 1 occurrences migrated to: https://www.springbyexample.org/examples/custom-thread-scope-module.html ([https](https://www.springbyexample.org/examples/custom-thread-scope-module.html) result 200). * [ ] http://www.springframework.org/dtd/spring-beans-2.0.dtd with 3 occurrences migrated to: https://www.springframework.org/dtd/spring-beans-2.0.dtd ([https](https://www.springframework.org/dtd/spring-beans-2.0.dtd) result 200). * [ ] http://www.springframework.org/schema/aop/spring-aop.xsd with 12 occurrences migrated to: https://www.springframework.org/schema/aop/spring-aop.xsd ([https](https://www.springframework.org/schema/aop/spring-aop.xsd) result 200). * [ ] http://www.springframework.org/schema/beans/spring-beans.xsd with 79 occurrences migrated to: https://www.springframework.org/schema/beans/spring-beans.xsd ([https](https://www.springframework.org/schema/beans/spring-beans.xsd) result 200). * [ ] http://www.springframework.org/schema/cache/spring-cache.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/cache/spring-cache.xsd ([https](https://www.springframework.org/schema/cache/spring-cache.xsd) result 200). * [ ] http://www.springframework.org/schema/context/spring-context.xsd with 13 occurrences migrated to: https://www.springframework.org/schema/context/spring-context.xsd ([https](https://www.springframework.org/schema/context/spring-context.xsd) result 200). * [ ] http://www.springframework.org/schema/jdbc/spring-jdbc.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/jdbc/spring-jdbc.xsd ([https](https://www.springframework.org/schema/jdbc/spring-jdbc.xsd) result 200). * [ ] http://www.springframework.org/schema/jee/spring-jee.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/jee/spring-jee.xsd ([https](https://www.springframework.org/schema/jee/spring-jee.xsd) result 200). * [ ] http://www.springframework.org/schema/jms/spring-jms.xsd with 2 occurrences migrated to: https://www.springframework.org/schema/jms/spring-jms.xsd ([https](https://www.springframework.org/schema/jms/spring-jms.xsd) result 200). * [ ] http://www.springframework.org/schema/lang/spring-lang.xsd with 4 occurrences migrated to: https://www.springframework.org/schema/lang/spring-lang.xsd ([https](https://www.springframework.org/schema/lang/spring-lang.xsd) result 200). * [ ] http://www.springframework.org/schema/mvc/spring-mvc.xsd with 3 occurrences migrated to: https://www.springframework.org/schema/mvc/spring-mvc.xsd ([https](https://www.springframework.org/schema/mvc/spring-mvc.xsd) result 200). * [ ] http://www.springframework.org/schema/oxm/spring-oxm.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/oxm/spring-oxm.xsd ([https](https://www.springframework.org/schema/oxm/spring-oxm.xsd) result 200). * [ ] http://www.springframework.org/schema/tool/spring-tool.xsd with 13 occurrences migrated to: https://www.springframework.org/schema/tool/spring-tool.xsd ([https](https://www.springframework.org/schema/tool/spring-tool.xsd) result 200). * [ ] http://www.springframework.org/schema/tx/spring-tx.xsd with 7 occurrences migrated to: https://www.springframework.org/schema/tx/spring-tx.xsd ([https](https://www.springframework.org/schema/tx/spring-tx.xsd) result 200). * [ ] http://www.springframework.org/schema/util/spring-util.xsd with 1 occurrences migrated to: https://www.springframework.org/schema/util/spring-util.xsd ([https](https://www.springframework.org/schema/util/spring-util.xsd) result 200). * [ ] http://www.springframework.org/schema/websocket/spring-websocket.xsd with 11 occurrences migrated to: https://www.springframework.org/schema/websocket/spring-websocket.xsd ([https](https://www.springframework.org/schema/websocket/spring-websocket.xsd) result 200). * [ ] http://www.stuartellis.eu/articles/erb/ (301) with 2 occurrences migrated to: https://www.stuartellis.name/articles/erb/ ([https](https://www.stuartellis.eu/articles/erb/) result 200). * [ ] http://www.thymeleaf.org/ with 2 occurrences migrated to: https://www.thymeleaf.org/ ([https](https://www.thymeleaf.org/) result 200). * [ ] http://www.thymeleaf.org/documentation.html with 2 occurrences migrated to: https://www.thymeleaf.org/documentation.html ([https](https://www.thymeleaf.org/documentation.html) result 200). * [ ] http://www.w3.org/2000/xmlns/ with 5 occurrences migrated to: https://www.w3.org/2000/xmlns/ ([https](https://www.w3.org/2000/xmlns/) result 200). * [ ] http://www.w3.org/2004/08/xop/include with 2 occurrences migrated to: https://www.w3.org/2004/08/xop/include ([https](https://www.w3.org/2004/08/xop/include) result 200). * [ ] http://www.w3.org/DesignIssues/MatrixURIs.html with 2 occurrences migrated to: https://www.w3.org/DesignIssues/MatrixURIs.html ([https](https://www.w3.org/DesignIssues/MatrixURIs.html) result 200). * [ ] http://www.w3.org/TR/2004/WD-soap12-mtom-20040608/ with 2 occurrences migrated to: https://www.w3.org/TR/2004/WD-soap12-mtom-20040608/ ([https](https://www.w3.org/TR/2004/WD-soap12-mtom-20040608/) result 200). * [ ] http://www.w3.org/TR/2005/REC-xop10-20050125/ with 6 occurrences migrated to: https://www.w3.org/TR/2005/REC-xop10-20050125/ ([https](https://www.w3.org/TR/2005/REC-xop10-20050125/) result 200). * [ ] http://www.w3.org/TR/cors/ with 19 occurrences migrated to: https://www.w3.org/TR/cors/ ([https](https://www.w3.org/TR/cors/) result 200). * [ ] http://www.w3.org/TR/eventsource/ with 2 occurrences migrated to: https://www.w3.org/TR/eventsource/ ([https](https://www.w3.org/TR/eventsource/) result 200). * [ ] http://www.w3.org/TR/xhtml1/ with 1 occurrences migrated to: https://www.w3.org/TR/xhtml1/ ([https](https://www.w3.org/TR/xhtml1/) result 200). * [ ] http://www.w3.org/TR/xhtml1/diffs.html with 1 occurrences migrated to: https://www.w3.org/TR/xhtml1/diffs.html ([https](https://www.w3.org/TR/xhtml1/diffs.html) result 200). * [ ] http://www.w3.org/TR/xhtml1/dtds.html with 1 occurrences migrated to: https://www.w3.org/TR/xhtml1/dtds.html ([https](https://www.w3.org/TR/xhtml1/dtds.html) result 200). * [ ] http://www.webjars.org with 4 occurrences migrated to: https://www.webjars.org ([https](https://www.webjars.org) result 200). * [ ] http://www.webjars.org/ with 2 occurrences migrated to: https://www.webjars.org/ ([https](https://www.webjars.org/) result 200). * [ ] http://www.webjars.org/documentation with 2 occurrences migrated to: https://www.webjars.org/documentation ([https](https://www.webjars.org/documentation) result 200). * [ ] http://www.wildfly.org/ with 1 occurrences migrated to: https://www.wildfly.org/ ([https](https://www.wildfly.org/) result 200). * [ ] http://x-stream.github.io/ with 2 occurrences migrated to: https://x-stream.github.io/ ([https](https://x-stream.github.io/) result 200). * [ ] http://youtrack.jetbrains.com/issue/IDEA-53476 with 1 occurrences migrated to: https://youtrack.jetbrains.com/issue/IDEA-53476 ([https](https://youtrack.jetbrains.com/issue/IDEA-53476) result 200). * [ ] http://youtrack.jetbrains.com/issue/IDEA-64446 with 1 occurrences migrated to: https://youtrack.jetbrains.com/issue/IDEA-64446 ([https](https://youtrack.jetbrains.com/issue/IDEA-64446) result 200). * [ ] http://asm.ow2.org with 1 occurrences migrated to: https://asm.ow2.org ([https](https://asm.ow2.org) result 301). * [ ] http://bit.ly/TRlX2r with 1 occurrences migrated to: https://bit.ly/TRlX2r ([https](https://bit.ly/TRlX2r) result 301). * [ ] http://bit.ly/UzccDt with 1 occurrences migrated to: https://bit.ly/UzccDt ([https](https://bit.ly/UzccDt) result 301). * [ ] http://bit.ly/p9rIvx with 1 occurrences migrated to: https://bit.ly/p9rIvx ([https](https://bit.ly/p9rIvx) result 301). * [ ] http://bit.ly/qUwvwz with 1 occurrences migrated to: https://bit.ly/qUwvwz ([https](https://bit.ly/qUwvwz) result 301). * [ ] http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx with 1 occurrences migrated to: https://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx ([https](https://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx) result 301). * [ ] http://castor-data-binding.github.io/castor with 1 occurrences migrated to: https://castor-data-binding.github.io/castor ([https](https://castor-data-binding.github.io/castor) result 301). * [ ] http://code.google.com/p/beanshell2/ with 1 occurrences migrated to: https://code.google.com/p/beanshell2/ ([https](https://code.google.com/p/beanshell2/) result 301). * [ ] http://code.google.com/p/browsersec/wiki/Part2 with 1 occurrences migrated to: https://code.google.com/p/browsersec/wiki/Part2 ([https](https://code.google.com/p/browsersec/wiki/Part2) result 301). * [ ] http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal with 1 occurrences migrated to: https://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal ([https](https://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal) result 301). * [ ] http://code.google.com/p/xml-matchers/ with 3 occurrences migrated to: https://code.google.com/p/xml-matchers/ ([https](https://code.google.com/p/xml-matchers/) result 301). * [ ] http://commons.apache.org/proper/commons-dbcp with 2 occurrences migrated to: https://commons.apache.org/proper/commons-dbcp ([https](https://commons.apache.org/proper/commons-dbcp) result 301). * [ ] http://commons.apache.org/proper/commons-fileupload with 2 occurrences migrated to: https://commons.apache.org/proper/commons-fileupload ([https](https://commons.apache.org/proper/commons-fileupload) result 301). * [ ] http://confluence.jetbrains.net/display/IDEADEV/Gradle+integration with 1 occurrences migrated to: https://confluence.jetbrains.net/display/IDEADEV/Gradle+integration ([https](https://confluence.jetbrains.net/display/IDEADEV/Gradle+integration) result 301). * [ ] http://contributor-covenant.org with 1 occurrences migrated to: https://contributor-covenant.org ([https](https://contributor-covenant.org) result 301). * [ ] http://contributor-covenant.org/version/1/3/0/ with 1 occurrences migrated to: https://contributor-covenant.org/version/1/3/0/ ([https](https://contributor-covenant.org/version/1/3/0/) result 301). * [ ] http://db.apache.org/derby with 2 occurrences migrated to: https://db.apache.org/derby ([https](https://db.apache.org/derby) result 301). * [ ] http://dev.w3.org/html5/eventsource/ with 2 occurrences migrated to: https://dev.w3.org/html5/eventsource/ ([https](https://dev.w3.org/html5/eventsource/) result 301). * [ ] http://domain.com with 24 occurrences migrated to: https://domain.com ([https](https://domain.com) result 301). * [ ] http://domain.com/ with 1 occurrences migrated to: https://domain.com/ ([https](https://domain.com/) result 301). * [ ] http://en.wikipedia.org/wiki/Cache_ with 1 occurrences migrated to: https://en.wikipedia.org/wiki/Cache_ ([https](https://en.wikipedia.org/wiki/Cache_) result 301). * [ ] http://facebook.github.io/react/ with 2 occurrences migrated to: https://facebook.github.io/react/ ([https](https://facebook.github.io/react/) result 301). * [ ] http://forum.springframework.org/showthread.php?t=41350 with 1 occurrences migrated to: https://forum.springframework.org/showthread.php?t=41350 ([https](https://forum.springframework.org/showthread.php?t=41350) result 301). * [ ] http://github.com/SpringSource/spring-framework with 1 occurrences migrated to: https://github.com/SpringSource/spring-framework ([https](https://github.com/SpringSource/spring-framework) result 301). * [ ] http://hdiv.org/ with 1 occurrences migrated to: https://hdiv.org/ ([https](https://hdiv.org/) result 301). * [ ] http://jakarta.apache.org/commons/fileupload with 1 occurrences migrated to: https://jakarta.apache.org/commons/fileupload ([https](https://jakarta.apache.org/commons/fileupload) result 301). * [ ] http://jira.springframework.org/browse/SPR-5708 with 1 occurrences migrated to: https://jira.springframework.org/browse/SPR-5708 ([https](https://jira.springframework.org/browse/SPR-5708) result 301). * [ ] http://jira.springframework.org/browse/SPR-6124 with 1 occurrences migrated to: https://jira.springframework.org/browse/SPR-6124 ([https](https://jira.springframework.org/browse/SPR-6124) result 301). * [ ] http://jira.springframework.org/browse/SPR-6128 with 1 occurrences migrated to: https://jira.springframework.org/browse/SPR-6128 ([https](https://jira.springframework.org/browse/SPR-6128) result 301). * [ ] http://jira.springframework.org/browse/SPR-7064 with 1 occurrences migrated to: https://jira.springframework.org/browse/SPR-7064 ([https](https://jira.springframework.org/browse/SPR-7064) result 301). * [ ] http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html with 1 occurrences migrated to: https://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html ([https](https://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) result 301). * [ ] http://mockito.org/ (301) with 1 occurrences migrated to: https://mockito.github.io ([https](https://mockito.org/) result 301). * [ ] http://mydomain1.com with 36 occurrences migrated to: https://mydomain1.com ([https](https://mydomain1.com) result 301). * [ ] http://mydomain1.com/ with 2 occurrences migrated to: https://mydomain1.com/ ([https](https://mydomain1.com/) result 301). * [ ] http://mydomain1.com/path with 2 occurrences migrated to: https://mydomain1.com/path ([https](https://mydomain1.com/path) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-2660 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-2660 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-2660) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-2754 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-2754 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-2754) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-2789 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-2789 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-2789) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-3127 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-3127 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-3127) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-3775 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-3775 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-3775) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-3880 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-3880 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-3880) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-3896 with 7 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-3896 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-3896) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-3949 with 1 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-3949 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-3949) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-4008 with 2 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-4008 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-4008) result 301). * [ ] http://opensource.atlassian.com/projects/spring/browse/SPR-4040 with 2 occurrences migrated to: https://opensource.atlassian.com/projects/spring/browse/SPR-4040 ([https](https://opensource.atlassian.com/projects/spring/browse/SPR-4040) result 301). * [ ] http://projects.spring.io/spring-webflow with 1 occurrences migrated to: https://projects.spring.io/spring-webflow ([https](https://projects.spring.io/spring-webflow) result 301). * [ ] http://publib.boulder.ibm.com/infocenter/wasinfo/v5r1//topic/com.ibm.websphere.base.doc/info/aes/ae/rdat_extiapi.html with 1 occurrences migrated to: https://publib.boulder.ibm.com/infocenter/wasinfo/v5r1//topic/com.ibm.websphere.base.doc/info/aes/ae/rdat_extiapi.html ([https](https://publib.boulder.ibm.com/infocenter/wasinfo/v5r1//topic/com.ibm.websphere.base.doc/info/aes/ae/rdat_extiapi.html) result 301). * [ ] http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/topic/com.ibm.websphere.javadoc.doc/public_html/api/com/ibm/websphere/rsadapter/JDBCConnectionSpec.html with 1 occurrences migrated to: https://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/topic/com.ibm.websphere.javadoc.doc/public_html/api/com/ibm/websphere/rsadapter/JDBCConnectionSpec.html ([https](https://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/topic/com.ibm.websphere.javadoc.doc/public_html/api/com/ibm/websphere/rsadapter/JDBCConnectionSpec.html) result 301). * [ ] http://site2.com with 3 occurrences migrated to: https://site2.com ([https](https://site2.com) result 301). * [ ] http://slack.kotlinlang.org/ with 2 occurrences migrated to: https://slack.kotlinlang.org/ ([https](https://slack.kotlinlang.org/) result 301). * [ ] http://sourceforge.net/projects/c3p0 with 2 occurrences migrated to: https://sourceforge.net/projects/c3p0 ([https](https://sourceforge.net/projects/c3p0) result 301). * [ ] http://sourceforge.net/projects/javadiff with 1 occurrences migrated to: https://sourceforge.net/projects/javadiff ([https](https://sourceforge.net/projects/javadiff) result 301). * [ ] http://springframework.org with 2 occurrences migrated to: https://springframework.org ([https](https://springframework.org) result 301). * [ ] http://www-01.ibm.com/software/webservers/appserv/was/ with 1 occurrences migrated to: https://www-01.ibm.com/software/webservers/appserv/was/ ([https](https://www-01.ibm.com/software/webservers/appserv/was/) result 301). * [ ] http://www.atomenabled.org/developers/syndication/ with 1 occurrences migrated to: https://www.atomenabled.org/developers/syndication/ ([https](https://www.atomenabled.org/developers/syndication/) result 301). * [ ] http://www.eclipse.org/aspectj with 2 occurrences migrated to: https://www.eclipse.org/aspectj ([https](https://www.eclipse.org/aspectj) result 301). * [ ] http://www.google.com/ig/calculator?q=1USD=?EUR with 1 occurrences migrated to: https://www.google.com/ig/calculator?q=1USD=?EUR ([https](https://www.google.com/ig/calculator?q=1USD=?EUR) result 301). * [ ] http://www.javaworld.com/javaworld/jw-01-2009/jw-01-spring-transactions.html with 1 occurrences migrated to: https://www.javaworld.com/javaworld/jw-01-2009/jw-01-spring-transactions.html ([https](https://www.javaworld.com/javaworld/jw-01-2009/jw-01-spring-transactions.html) result 301). * [ ] http://www.jboss.org/jbossas/ with 1 occurrences migrated to: https://www.jboss.org/jbossas/ ([https](https://www.jboss.org/jbossas/) result 301). * [ ] http://www.junit.org/ with 1 occurrences migrated to: https://www.junit.org/ ([https](https://www.junit.org/) result 301). * [ ] http://www.mnot.net/cache_docs with 1 occurrences migrated to: https://www.mnot.net/cache_docs ([https](https://www.mnot.net/cache_docs) result 301). * [ ] http://www.sf.net/home.view?siteLanguage=nl with 1 occurrences migrated to: https://www.sf.net/home.view?siteLanguage=nl ([https](https://www.sf.net/home.view?siteLanguage=nl) result 301). * [ ] http://www.springframework.org with 7 occurrences migrated to: https://www.springframework.org ([https](https://www.springframework.org) result 301). * [ ] http://www.springframework.org/ with 3 occurrences migrated to: https://www.springframework.org/ ([https](https://www.springframework.org/) result 301). * [ ] http://xyz.com/path with 2 occurrences migrated to: https://xyz.com/path ([https](https://xyz.com/path) result 301). * [ ] http://commons.apache.org/codec/ with 1 occurrences migrated to: https://commons.apache.org/codec/ ([https](https://commons.apache.org/codec/) result 302). * [ ] http://commons.apache.org/logging with 2 occurrences migrated to: https://commons.apache.org/logging ([https](https://commons.apache.org/logging) result 302). * [ ] http://download.oracle.com/javaee/6/api/javax/inject/Scope.html with 1 occurrences migrated to: https://download.oracle.com/javaee/6/api/javax/inject/Scope.html ([https](https://download.oracle.com/javaee/6/api/javax/inject/Scope.html) result 302). * [ ] http://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html with 4 occurrences migrated to: https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html ([https](https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html) result 302). * [ ] http://download.oracle.com/javase/6/docs/api/java/beans/ConstructorProperties.html with 1 occurrences migrated to: https://download.oracle.com/javase/6/docs/api/java/beans/ConstructorProperties.html ([https](https://download.oracle.com/javase/6/docs/api/java/beans/ConstructorProperties.html) result 302). * [ ] http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html with 1 occurrences migrated to: https://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html ([https](https://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html) result 302). * [ ] http://download.oracle.com/javase/jndi/tutorial/beyond/misc/policy.html with 1 occurrences migrated to: https://download.oracle.com/javase/jndi/tutorial/beyond/misc/policy.html ([https](https://download.oracle.com/javase/jndi/tutorial/beyond/misc/policy.html) result 302). * [ ] http://gafter.blogspot.nl/2006/12/super-type-tokens.html with 1 occurrences migrated to: https://gafter.blogspot.nl/2006/12/super-type-tokens.html ([https](https://gafter.blogspot.nl/2006/12/super-type-tokens.html) result 302). * [ ] http://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project with 1 occurrences migrated to: https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project ([https](https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project) result 302). * [ ] http://git-scm.com/book/en/Git-Tools-Rewriting-History with 1 occurrences migrated to: https://git-scm.com/book/en/Git-Tools-Rewriting-History ([https](https://git-scm.com/book/en/Git-Tools-Rewriting-History) result 302). * [ ] http://glassfish.dev.java.net/ with 1 occurrences migrated to: https://glassfish.dev.java.net/ ([https](https://glassfish.dev.java.net/) result 302). * [ ] http://java.sun.com with 3 occurrences migrated to: https://java.sun.com ([https](https://java.sun.com) result 302). * [ ] http://java.sun.com/docs/books/jls/third_edition/html/conversions.html with 1 occurrences migrated to: https://java.sun.com/docs/books/jls/third_edition/html/conversions.html ([https](https://java.sun.com/docs/books/jls/third_edition/html/conversions.html) result 302). * [ ] http://java.sun.com/docs/books/jls/third_edition/html/expressions.html with 1 occurrences migrated to: https://java.sun.com/docs/books/jls/third_edition/html/expressions.html ([https](https://java.sun.com/docs/books/jls/third_edition/html/expressions.html) result 302). * [ ] http://java.sun.com/j2se/ with 2 occurrences migrated to: https://java.sun.com/j2se/ ([https](https://java.sun.com/j2se/) result 302). * [ ] http://java.sun.com/j2se/1.4.1/docs/tooldocs/solaris/javadoc.html with 1 occurrences migrated to: https://java.sun.com/j2se/1.4.1/docs/tooldocs/solaris/javadoc.html ([https](https://java.sun.com/j2se/1.4.1/docs/tooldocs/solaris/javadoc.html) result 302). * [ ] http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html with 1 occurrences migrated to: https://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html ([https](https://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html) result 302). * [ ] http://java.sun.com/j2se/javadoc with 1 occurrences migrated to: https://java.sun.com/j2se/javadoc ([https](https://java.sun.com/j2se/javadoc) result 302). * [ ] http://java.sun.com/j2se/javadoc/ with 1 occurrences migrated to: https://java.sun.com/j2se/javadoc/ ([https](https://java.sun.com/j2se/javadoc/) result 302). * [ ] http://java.sun.com/j2se/javadoc/faq.html with 1 occurrences migrated to: https://java.sun.com/j2se/javadoc/faq.html ([https](https://java.sun.com/j2se/javadoc/faq.html) result 302). * [ ] http://java.sun.com/webservices/jaxb/ with 1 occurrences migrated to: https://java.sun.com/webservices/jaxb/ ([https](https://java.sun.com/webservices/jaxb/) result 302). * [ ] http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd with 1 occurrences migrated to: https://java.sun.com/xml/ns/j2ee/connector_1_5.xsd ([https](https://java.sun.com/xml/ns/j2ee/connector_1_5.xsd) result 302). * [ ] http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd with 2 occurrences migrated to: https://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd ([https](https://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd) result 302). * [ ] http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd with 2 occurrences migrated to: https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd ([https](https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd) result 302). * [ ] http://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download with 1 occurrences migrated to: https://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download ([https](https://sourceforge.net/projects/cglib/files/cglib3/3.0/cglib-3.0.jar/download) result 302). * [ ] http://other.info/foo/bar (301) with 1 occurrences migrated to: https://weibo.com/otherinfo/foo/bar ([https](https://other.info/foo/bar) result 302). * [ ] http://www.iana.org/assignments/http-status-codes with 1 occurrences migrated to: https://www.iana.org/assignments/http-status-codes ([https](https://www.iana.org/assignments/http-status-codes) result 302). * [ ] http://www.iana.org/assignments/media-types/ with 1 occurrences migrated to: https://www.iana.org/assignments/media-types/ ([https](https://www.iana.org/assignments/media-types/) result 302). * [ ] http://www.sys-con.com/java with 1 occurrences migrated to: https://www.sys-con.com/java ([https](https://www.sys-con.com/java) result 302). These URLs were intentionally ignored. * http://Additional-namespace-name-URI with 1 occurrences * http://Default-namespace-name-URI with 1 occurrences * http://HOST:8080/remoting/AccountService with 1 occurrences * http://Namespace-name-URI with 1 occurrences * http://Unbound-namespace-name-URI with 1 occurrences * http://anotherHost/mvc-showcase with 1 occurrences * http://apache.org/xml/features/disallow-doctype-decl with 14 occurrences * http://barfoo:8888 with 1 occurrences * http://example/ with 1 occurrences * http://fake-service with 7 occurrences * http://foo with 3 occurrences * http://foobar:8088 with 1 occurrences * http://host:port/myApp/myEndpoint/ with 1 occurrences * http://java.sun.com/jsp/jstl/core with 1 occurrences * http://java.sun.com/xml/jaxp/properties/schemaLanguage with 1 occurrences * http://java.sun.com/xml/ns/j2ee with 6 occurrences * http://java.sun.com/xml/ns/javaee with 4 occurrences * http://java.sun.com/xml/ns/persistence with 1 occurrences * http://java.sun.com/xml/stream/properties/report-cdata-event with 1 occurrences * http://jaxws.remoting.springframework.org/ with 4 occurrences * http://localhost with 81 occurrences * http://localhost/ with 14 occurrences * http://localhost/42 with 1 occurrences * http://localhost/?cookie=foo with 1 occurrences * http://localhost/FILE.TXT with 1 occurrences * http://localhost/abc with 1 occurrences * http://localhost/abc/def with 1 occurrences * http://localhost/app%20/path/ with 1 occurrences * http://localhost/app/path%20with%20spaces/ with 1 occurrences * http://localhost/bar with 1 occurrences * http://localhost/context/a with 3 occurrences * http://localhost/contextPath/main/path with 1 occurrences * http://localhost/file.foo with 1 occurrences * http://localhost/file.txt with 1 occurrences * http://localhost/foo%20bar with 1 occurrences * http://localhost/foo/bar with 1 occurrences * http://localhost/forward with 1 occurrences * http://localhost/hotels/42/bookings/21 with 4 occurrences * http://localhost/input with 1 occurrences * http://localhost/jquery-1.11.0.min.js with 5 occurrences * http://localhost/messages/form with 1 occurrences * http://localhost/mvc-showcase with 2 occurrences * http://localhost/mvc-showcase/app with 1 occurrences * http://localhost/mvc-showcase/data/foo%20bar with 1 occurrences * http://localhost/mvc-showcase/data/param with 1 occurrences * http://localhost/mvc-showcase/data/param?foo=123 with 2 occurrences * http://localhost/myapp/main with 1 occurrences * http://localhost/myapp/people/1/addresses/DE with 1 occurrences * http://localhost/path with 3 occurrences * http://localhost/path;a=b/with/semicolon with 1 occurrences * http://localhost/persons with 1 occurrences * http://localhost/prefix with 1 occurrences * http://localhost/prefix/app with 1 occurrences * http://localhost/prefix/bar with 1 occurrences * http://localhost/prefix/mvc-showcase with 3 occurrences * http://localhost/query= with 1 occurrences * http://localhost/query=foo@bar with 1 occurrences * http://localhost/resources/child/response.txt with 1 occurrences * http://localhost/resources/foo with 1 occurrences * http://localhost/resources/response.txt with 1 occurrences * http://localhost/rest/books/6/pages/1.json with 1 occurrences * http://localhost/something/1/foo with 1 occurrences * http://localhost/something/optional-param with 1 occurrences * http://localhost/test with 4 occurrences * http://localhost/test.html with 1 occurrences * http://localhost/test/this with 2 occurrences * http://localhost:7070/example/adminhandle.vm with 1 occurrences * http://localhost:7070/example/bingo.html with 1 occurrences * http://localhost:80/jquery-1.11.0.min.js with 1 occurrences * http://localhost:8080 with 34 occurrences * http://localhost:8080/ with 4 occurrences * http://localhost:8080/OrderService with 3 occurrences * http://localhost:8080/aA/bB/cC with 1 occurrences * http://localhost:8080/context with 1 occurrences * http://localhost:8080/context/myapiresource with 1 occurrences * http://localhost:8080/gamecast/admin/index.html with 1 occurrences * http://localhost:8080/gamecast/display.html with 1 occurrences * http://localhost:8080/gamecast/displayShoppingCart.html with 1 occurrences * http://localhost:8080/jquery-1.11.0.min.js with 1 occurrences * http://localhost:8080/mvc-showcase with 1 occurrences * http://localhost:8080/myapiresource with 1 occurrences * http://localhost:8080/myapp/js/sockjs-client.js with 1 occurrences * http://localhost:8080/path with 2 occurrences * http://localhost:8080/people with 2 occurrences * http://localhost:8080/portfolio with 1 occurrences * http://localhost:8080/spring/ with 2 occurrences * http://localhost:8080/test/ with 2 occurrences * http://localhost:8080/test/print?value=%EA%B0%80+%EB%82%98 with 1 occurrences * http://localhost:8081 with 1 occurrences * http://localhost:8081/ with 1 occurrences * http://localhost:8181 with 1 occurrences * http://localhost:8888/AccountServiceEndpoint?WSDL with 1 occurrences * http://localhost:9000 with 7 occurrences * http://localhost:9090 with 18 occurrences * http://localhost:9090/jquery-1.11.0.min.js with 2 occurrences * http://localhost:9990/mail/messages/form with 1 occurrences * http://localhost:9999/ with 1 occurrences * http://localhost:9999/OrderService?wsdl with 4 occurrences * http://localhosta/testbean with 3 occurrences * http://myserver with 1 occurrences * http://myurl with 12 occurrences * http://remotehost:8080/remoting/AccountService with 2 occurrences * http://samples.springframework.org/flight with 39 occurrences * http://somethingDifferent with 1 occurrences * http://springframework.org/spring-ws with 17 occurrences * http://test.com with 1 occurrences * http://test.com/ with 1 occurrences * http://testng.org/ with 1 occurrences * http://tiles.apache.org/tags-tiles with 1 occurrences * http://www with 1 occurrences * http://www.foo.com/schema/component with 6 occurrences * http://www.mycompany.com/schema/myns with 4 occurrences * http://www.springframework.org/schema/aop with 30 occurrences * http://www.springframework.org/schema/beans with 166 occurrences * http://www.springframework.org/schema/beans/test with 2 occurrences * http://www.springframework.org/schema/c with 1 occurrences * http://www.springframework.org/schema/cache with 5 occurrences * http://www.springframework.org/schema/context with 28 occurrences * http://www.springframework.org/schema/jdbc with 6 occurrences * http://www.springframework.org/schema/jee with 8 occurrences * http://www.springframework.org/schema/jms with 5 occurrences * http://www.springframework.org/schema/lang with 10 occurrences * http://www.springframework.org/schema/mvc with 8 occurrences * http://www.springframework.org/schema/oxm with 4 occurrences * http://www.springframework.org/schema/p with 4 occurrences * http://www.springframework.org/schema/task with 2 occurrences * http://www.springframework.org/schema/tool with 27 occurrences * http://www.springframework.org/schema/tx with 17 occurrences * http://www.springframework.org/schema/tx/spring-tx.xsd__ with 1 occurrences * http://www.springframework.org/schema/util with 8 occurrences * http://www.springframework.org/schema/websocket with 24 occurrences * http://www.springframework.org/spring-ws with 1 occurrences * http://www.springframework.org/tags with 4 occurrences * http://www.springframework.org/tags/form with 2 occurrences * http://www.w3.org/1999/XSL/Transform with 8 occurrences * http://www.w3.org/2001/XMLSchema with 24 occurrences * http://www.w3.org/2001/XMLSchema-instance with 81 occurrences * http://www.w3.org/2005/Atom with 4 occurrences * http://www.w3.org/TR/html4/charset.html with 5 occurrences * http://www.w3.org/TR/html4/sgml/entities.html with 14 occurrences * http://www.w3.org/XML/1998/namespace with 5 occurrences * http://xml.org/sax/features/ with 4 occurrences * http://xml.org/sax/features/external-general-entities with 14 occurrences * http://xml.org/sax/features/is-standalone with 1 occurrences * http://xml.org/sax/features/namespace-prefixes with 13 occurrences * http://xml.org/sax/features/namespaces with 12 occurrences * http://xml.org/sax/features/namespaces-prefixes with 1 occurrences * http://xml.org/sax/properties/lexical-handler with 12 occurrences Closes gh-22679
7957 lines
298 KiB
Plaintext
7957 lines
298 KiB
Plaintext
[[spring-integration]]
|
|
= Integration
|
|
:doc-root: https://docs.spring.io
|
|
:api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework
|
|
:doc-spring-amqp: {doc-root}/spring-amqp/docs/current/reference
|
|
:doc-spring-gemfire: {doc-root}/spring-gemfire/docs/current/reference
|
|
:toc: left
|
|
:toclevels: 4
|
|
:tabsize: 4
|
|
:docinfo1:
|
|
|
|
This part of the reference documentation covers the Spring Framework's integration with
|
|
a number of Java EE (and related) technologies.
|
|
|
|
|
|
|
|
|
|
[[remoting]]
|
|
== Remoting and web services using Spring
|
|
|
|
|
|
|
|
[[remoting-introduction]]
|
|
=== Introduction
|
|
|
|
Spring features integration classes for remoting support using various technologies. The
|
|
remoting support eases the development of remote-enabled services, implemented by your
|
|
usual (Spring) POJOs. Currently, Spring supports the following remoting technologies:
|
|
|
|
* __Remote Method Invocation (RMI)__. Through the use of the `RmiProxyFactoryBean` and
|
|
the `RmiServiceExporter` Spring supports both traditional RMI (with `java.rmi.Remote`
|
|
interfaces and `java.rmi.RemoteException`) and transparent remoting via RMI invokers
|
|
(with any Java interface).
|
|
* __Spring's HTTP invoker__. Spring provides a special remoting strategy which allows
|
|
for Java serialization via HTTP, supporting any Java interface (just like the RMI
|
|
invoker). The corresponding support classes are `HttpInvokerProxyFactoryBean` and
|
|
`HttpInvokerServiceExporter`.
|
|
* __Hessian__. By using Spring's `HessianProxyFactoryBean` and the
|
|
`HessianServiceExporter` you can transparently expose your services using the
|
|
lightweight binary HTTP-based protocol provided by Caucho.
|
|
* __JAX-WS__. Spring provides remoting support for web services via JAX-WS.
|
|
* __JMS__. Remoting using JMS as the underlying protocol is supported via the
|
|
`JmsInvokerServiceExporter` and `JmsInvokerProxyFactoryBean` classes.
|
|
* __AMQP__. Remoting using AMQP as the underlying protocol is supported by the Spring
|
|
AMQP project.
|
|
|
|
While discussing the remoting capabilities of Spring, we'll use the following domain
|
|
model and corresponding services:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class Account implements Serializable{
|
|
|
|
private String name;
|
|
|
|
public String getName(){
|
|
return name;
|
|
}
|
|
|
|
public void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface AccountService {
|
|
|
|
public void insertAccount(Account account);
|
|
|
|
public List<Account> getAccounts(String name);
|
|
|
|
}
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
// the implementation doing nothing at the moment
|
|
public class AccountServiceImpl implements AccountService {
|
|
|
|
public void insertAccount(Account acc) {
|
|
// do something...
|
|
}
|
|
|
|
public List<Account> getAccounts(String name) {
|
|
// do something...
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
We will start exposing the service to a remote client by using RMI and talk a bit about
|
|
the drawbacks of using RMI. We'll then continue to show an example using Hessian as the
|
|
protocol.
|
|
|
|
|
|
|
|
[[remoting-rmi]]
|
|
=== Exposing services using RMI
|
|
|
|
Using Spring's support for RMI, you can transparently expose your services through the
|
|
RMI infrastructure. After having this set up, you basically have a configuration similar
|
|
to remote EJBs, except for the fact that there is no standard support for security
|
|
context propagation or remote transaction propagation. Spring does provide hooks for
|
|
such additional invocation context when using the RMI invoker, so you can for example
|
|
plug in security frameworks or custom security credentials here.
|
|
|
|
|
|
[[remoting-rmi-server]]
|
|
==== Exporting the service using the RmiServiceExporter
|
|
|
|
Using the `RmiServiceExporter`, we can expose the interface of our AccountService object
|
|
as RMI object. The interface can be accessed by using `RmiProxyFactoryBean`, or via
|
|
plain RMI in case of a traditional RMI service. The `RmiServiceExporter` explicitly
|
|
supports the exposing of any non-RMI services via RMI invokers.
|
|
|
|
Of course, we first have to set up our service in the Spring container:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="accountService" class="example.AccountServiceImpl">
|
|
<!-- any additional properties, maybe a DAO? -->
|
|
</bean>
|
|
----
|
|
|
|
Next we'll have to expose our service using the `RmiServiceExporter`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
|
|
<!-- does not necessarily have to be the same name as the bean to be exported -->
|
|
<property name="serviceName" value="AccountService"/>
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
<!-- defaults to 1099 -->
|
|
<property name="registryPort" value="1199"/>
|
|
</bean>
|
|
----
|
|
|
|
As you can see, we're overriding the port for the RMI registry. Often, your application
|
|
server also maintains an RMI registry and it is wise to not interfere with that one.
|
|
Furthermore, the service name is used to bind the service under. So right now, the
|
|
service will be bound at `'rmi://HOST:1199/AccountService'`. We'll use the URL later on
|
|
to link in the service at the client side.
|
|
|
|
[NOTE]
|
|
====
|
|
The `servicePort` property has been omitted (it defaults to 0). This means that an
|
|
anonymous port will be used to communicate with the service.
|
|
====
|
|
|
|
|
|
[[remoting-rmi-client]]
|
|
==== Linking in the service at the client
|
|
|
|
Our client is a simple object using the `AccountService` to manage accounts:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class SimpleObject {
|
|
|
|
private AccountService accountService;
|
|
|
|
public void setAccountService(AccountService accountService) {
|
|
this.accountService = accountService;
|
|
}
|
|
|
|
// additional methods using the accountService
|
|
|
|
}
|
|
----
|
|
|
|
To link in the service on the client, we'll create a separate Spring container,
|
|
containing the simple object and the service linking configuration bits:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="example.SimpleObject">
|
|
<property name="accountService" ref="accountService"/>
|
|
</bean>
|
|
|
|
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
|
|
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
That's all we need to do to support the remote account service on the client. Spring
|
|
will transparently create an invoker and remotely enable the account service through the
|
|
`RmiServiceExporter`. At the client we're linking it in using the `RmiProxyFactoryBean`.
|
|
|
|
|
|
|
|
[[remoting-caucho-protocols]]
|
|
=== Using Hessian to remotely call services via HTTP
|
|
|
|
Hessian offers a binary HTTP-based remoting protocol. It is developed by Caucho and more
|
|
information about Hessian itself can be found at https://www.caucho.com/[].
|
|
|
|
|
|
[[remoting-caucho-protocols-hessian]]
|
|
==== Wiring up the DispatcherServlet for Hessian and co.
|
|
|
|
Hessian communicates via HTTP and does so using a custom servlet. Using Spring's
|
|
`DispatcherServlet` principles, as known from Spring Web MVC usage, you can easily wire
|
|
up such a servlet exposing your services. First we'll have to create a new servlet in
|
|
your application (this is an excerpt from `'web.xml'`):
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<servlet>
|
|
<servlet-name>remoting</servlet-name>
|
|
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
|
<load-on-startup>1</load-on-startup>
|
|
</servlet>
|
|
|
|
<servlet-mapping>
|
|
<servlet-name>remoting</servlet-name>
|
|
<url-pattern>/remoting/*</url-pattern>
|
|
</servlet-mapping>
|
|
----
|
|
|
|
You're probably familiar with Spring's `DispatcherServlet` principles and if so, you
|
|
know that now you'll have to create a Spring container configuration resource named
|
|
`'remoting-servlet.xml'` (after the name of your servlet) in the `'WEB-INF'` directory.
|
|
The application context will be used in the next section.
|
|
|
|
Alternatively, consider the use of Spring's simpler `HttpRequestHandlerServlet`. This
|
|
allows you to embed the remote exporter definitions in your root application context (by
|
|
default in `'WEB-INF/applicationContext.xml'`), with individual servlet definitions
|
|
pointing to specific exporter beans. Each servlet name needs to match the bean name of
|
|
its target exporter in this case.
|
|
|
|
|
|
[[remoting-caucho-protocols-hessian-server]]
|
|
==== Exposing your beans by using the HessianServiceExporter
|
|
|
|
In the newly created application context called `remoting-servlet.xml`, we'll create a
|
|
`HessianServiceExporter` exporting your services:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="accountService" class="example.AccountServiceImpl">
|
|
<!-- any additional properties, maybe a DAO? -->
|
|
</bean>
|
|
|
|
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
Now we're ready to link in the service at the client. No explicit handler mapping is
|
|
specified, mapping request URLs onto services, so `BeanNameUrlHandlerMapping` will be
|
|
used: Hence, the service will be exported at the URL indicated through its bean name
|
|
within the containing ``DispatcherServlet``'s mapping (as defined above):
|
|
`'http://HOST:8080/remoting/AccountService'`.
|
|
|
|
Alternatively, create a `HessianServiceExporter` in your root application context (e.g.
|
|
in `'WEB-INF/applicationContext.xml'`):
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
In the latter case, define a corresponding servlet for this exporter in `'web.xml'`,
|
|
with the same end result: The exporter getting mapped to the request path
|
|
`/remoting/AccountService`. Note that the servlet name needs to match the bean name of
|
|
the target exporter.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<servlet>
|
|
<servlet-name>accountExporter</servlet-name>
|
|
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
|
|
</servlet>
|
|
|
|
<servlet-mapping>
|
|
<servlet-name>accountExporter</servlet-name>
|
|
<url-pattern>/remoting/AccountService</url-pattern>
|
|
</servlet-mapping>
|
|
----
|
|
|
|
|
|
[[remoting-caucho-protocols-hessian-client]]
|
|
==== Linking in the service on the client
|
|
|
|
Using the `HessianProxyFactoryBean` we can link in the service at the client. The same
|
|
principles apply as with the RMI example. We'll create a separate bean factory or
|
|
application context and mention the following beans where the `SimpleObject` is using
|
|
the `AccountService` to manage accounts:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="example.SimpleObject">
|
|
<property name="accountService" ref="accountService"/>
|
|
</bean>
|
|
|
|
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
|
|
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[remoting-caucho-protocols-security]]
|
|
==== Applying HTTP basic authentication to a service exposed through Hessian
|
|
|
|
One of the advantages of Hessian is that we can easily apply HTTP basic authentication,
|
|
because both protocols are HTTP-based. Your normal HTTP server security mechanism can
|
|
easily be applied through using the `web.xml` security features, for example. Usually,
|
|
you don't use per-user security credentials here, but rather shared credentials defined
|
|
at the `HessianProxyFactoryBean` level (similar to a JDBC `DataSource`).
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
|
|
<property name="interceptors" ref="authorizationInterceptor"/>
|
|
</bean>
|
|
|
|
<bean id="authorizationInterceptor"
|
|
class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
|
|
<property name="authorizedRoles" value="administrator,operator"/>
|
|
</bean>
|
|
----
|
|
|
|
This is an example where we explicitly mention the `BeanNameUrlHandlerMapping` and set
|
|
an interceptor allowing only administrators and operators to call the beans mentioned in
|
|
this application context.
|
|
|
|
[NOTE]
|
|
====
|
|
Of course, this example doesn't show a flexible kind of security infrastructure. For
|
|
more options as far as security is concerned, have a look at the Spring Security project
|
|
at https://projects.spring.io/spring-security/[].
|
|
====
|
|
|
|
|
|
|
|
[[remoting-httpinvoker]]
|
|
=== Exposing services using HTTP invokers
|
|
|
|
As opposed to Hessian, which are both lightweight protocols using their own slim
|
|
serialization mechanisms, Spring HTTP invokers use the standard Java serialization
|
|
mechanism to expose services through HTTP. This has a huge advantage if your arguments
|
|
and return types are complex types that cannot be serialized using the serialization
|
|
mechanisms Hessian uses (refer to the next section for more considerations when
|
|
choosing a remoting technology).
|
|
|
|
Under the hood, Spring uses either the standard facilities provided by the JDK or
|
|
Apache `HttpComponents` to perform HTTP calls. Use the latter if you need more
|
|
advanced and easier-to-use functionality. Refer to
|
|
https://hc.apache.org/httpcomponents-client-ga/[hc.apache.org/httpcomponents-client-ga/]
|
|
for more information.
|
|
|
|
[WARNING]
|
|
====
|
|
Be aware of vulnerabilities due to unsafe Java deserialization:
|
|
Manipulated input streams could lead to unwanted code execution on the server
|
|
during the deserialization step. As a consequence, do not expose HTTP invoker
|
|
endpoints to untrusted clients but rather just between your own services.
|
|
In general, we strongly recommend any other message format (e.g. JSON) instead.
|
|
|
|
If you are concerned about security vulnerabilities due to Java serialization,
|
|
consider the general-purpose serialization filter mechanism at the core JVM level,
|
|
originally developed for JDK 9 but backported to JDK 8, 7 and 6 in the meantime:
|
|
https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a
|
|
https://openjdk.java.net/jeps/290
|
|
====
|
|
|
|
|
|
[[remoting-httpinvoker-server]]
|
|
==== Exposing the service object
|
|
|
|
Setting up the HTTP invoker infrastructure for a service object resembles closely the
|
|
way you would do the same using Hessian. Just as Hessian support provides the
|
|
`HessianServiceExporter`, Spring's HttpInvoker support provides the
|
|
`org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter`.
|
|
|
|
To expose the `AccountService` (mentioned above) within a Spring Web MVC
|
|
`DispatcherServlet`, the following configuration needs to be in place in the
|
|
dispatcher's application context:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
Such an exporter definition will be exposed through the ``DispatcherServlet``'s standard
|
|
mapping facilities, as explained in the section on Hessian.
|
|
|
|
Alternatively, create an `HttpInvokerServiceExporter` in your root application context
|
|
(e.g. in `'WEB-INF/applicationContext.xml'`):
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
In addition, define a corresponding servlet for this exporter in `'web.xml'`, with the
|
|
servlet name matching the bean name of the target exporter:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<servlet>
|
|
<servlet-name>accountExporter</servlet-name>
|
|
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
|
|
</servlet>
|
|
|
|
<servlet-mapping>
|
|
<servlet-name>accountExporter</servlet-name>
|
|
<url-pattern>/remoting/AccountService</url-pattern>
|
|
</servlet-mapping>
|
|
----
|
|
|
|
If you are running outside of a servlet container and are using Oracle's Java 6, then you
|
|
can use the built-in HTTP server implementation. You can configure the
|
|
`SimpleHttpServerFactoryBean` together with a `SimpleHttpInvokerServiceExporter` as is
|
|
shown in this example:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean name="accountExporter"
|
|
class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
|
|
<property name="service" ref="accountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
|
|
<bean id="httpServer"
|
|
class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
|
|
<property name="contexts">
|
|
<util:map>
|
|
<entry key="/remoting/AccountService" value-ref="accountExporter"/>
|
|
</util:map>
|
|
</property>
|
|
<property name="port" value="8080"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[remoting-httpinvoker-client]]
|
|
==== Linking in the service at the client
|
|
|
|
Again, linking in the service from the client much resembles the way you would do it
|
|
when using Hessian. Using a proxy, Spring will be able to translate your calls to
|
|
HTTP POST requests to the URL pointing to the exported service.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
|
|
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
</bean>
|
|
----
|
|
|
|
As mentioned before, you can choose what HTTP client you want to use. By default, the
|
|
`HttpInvokerProxy` uses the JDK's HTTP functionality, but you can also use the Apache
|
|
`HttpComponents` client by setting the `httpInvokerRequestExecutor` property:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<property name="httpInvokerRequestExecutor">
|
|
<bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
|
|
</property>
|
|
----
|
|
|
|
|
|
|
|
[[remoting-web-services]]
|
|
=== Web services
|
|
|
|
Spring provides full support for standard Java web services APIs:
|
|
|
|
* Exposing web services using JAX-WS
|
|
* Accessing web services using JAX-WS
|
|
|
|
In addition to stock support for JAX-WS in Spring Core, the Spring portfolio also
|
|
features http://www.springframework.org/spring-ws[Spring Web Services], a solution for
|
|
contract-first, document-driven web services - highly recommended for building modern,
|
|
future-proof web services.
|
|
|
|
|
|
[[remoting-web-services-jaxws-export-servlet]]
|
|
==== Exposing servlet-based web services using JAX-WS
|
|
|
|
Spring provides a convenient base class for JAX-WS servlet endpoint implementations -
|
|
`SpringBeanAutowiringSupport`. To expose our `AccountService` we extend Spring's
|
|
`SpringBeanAutowiringSupport` class and implement our business logic here, usually
|
|
delegating the call to the business layer. We'll simply use Spring's `@Autowired`
|
|
annotation for expressing such dependencies on Spring-managed beans.
|
|
|
|
[source,java,indent=0]
|
|
----
|
|
/**
|
|
* JAX-WS compliant AccountService implementation that simply delegates
|
|
* to the AccountService implementation in the root web application context.
|
|
*
|
|
* This wrapper class is necessary because JAX-WS requires working with dedicated
|
|
* endpoint classes. If an existing service needs to be exported, a wrapper that
|
|
* extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
|
|
* the @Autowired annotation) is the simplest JAX-WS compliant way.
|
|
*
|
|
* This is the class registered with the server-side JAX-WS implementation.
|
|
* In the case of a Java EE 5 server, this would simply be defined as a servlet
|
|
* in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
|
|
* accordingly. The servlet name usually needs to match the specified WS service name.
|
|
*
|
|
* The web service engine manages the lifecycle of instances of this class.
|
|
* Spring bean references will just be wired in here.
|
|
*/
|
|
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
|
|
|
|
@WebService(serviceName="AccountService")
|
|
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
|
|
|
|
@Autowired
|
|
private AccountService biz;
|
|
|
|
@WebMethod
|
|
public void insertAccount(Account acc) {
|
|
biz.insertAccount(acc);
|
|
}
|
|
|
|
@WebMethod
|
|
public Account[] getAccounts(String name) {
|
|
return biz.getAccounts(name);
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
Our `AccountServiceEndpoint` needs to run in the same web application as the Spring
|
|
context to allow for access to Spring's facilities. This is the case by default in Java
|
|
EE 5 environments, using the standard contract for JAX-WS servlet endpoint deployment.
|
|
See Java EE 5 web service tutorials for details.
|
|
|
|
|
|
[[remoting-web-services-jaxws-export-standalone]]
|
|
==== Exporting standalone web services using JAX-WS
|
|
|
|
The built-in JAX-WS provider that comes with Oracle's JDK supports exposure of web
|
|
services using the built-in HTTP server that's included in the JDK as well. Spring's
|
|
`SimpleJaxWsServiceExporter` detects all `@WebService` annotated beans in the Spring
|
|
application context, exporting them through the default JAX-WS server (the JDK HTTP
|
|
server).
|
|
|
|
In this scenario, the endpoint instances are defined and managed as Spring beans
|
|
themselves; they will be registered with the JAX-WS engine but their lifecycle will be
|
|
up to the Spring application context. This means that Spring functionality like explicit
|
|
dependency injection may be applied to the endpoint instances. Of course,
|
|
annotation-driven injection through `@Autowired` will work as well.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
|
|
<property name="baseAddress" value="http://localhost:8080/"/>
|
|
</bean>
|
|
|
|
<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
|
|
...
|
|
</bean>
|
|
|
|
...
|
|
----
|
|
|
|
The `AccountServiceEndpoint` may derive from Spring's `SpringBeanAutowiringSupport` but
|
|
doesn't have to since the endpoint is a fully Spring-managed bean here. This means that
|
|
the endpoint implementation may look like as follows, without any superclass declared -
|
|
and Spring's `@Autowired` configuration annotation still being honored:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@WebService(serviceName="AccountService")
|
|
public class AccountServiceEndpoint {
|
|
|
|
@Autowired
|
|
private AccountService biz;
|
|
|
|
@WebMethod
|
|
public void insertAccount(Account acc) {
|
|
biz.insertAccount(acc);
|
|
}
|
|
|
|
@WebMethod
|
|
public List<Account> getAccounts(String name) {
|
|
return biz.getAccounts(name);
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[remoting-web-services-jaxws-export-ri]]
|
|
==== Exporting web services using the JAX-WS RI's Spring support
|
|
|
|
Oracle's JAX-WS RI, developed as part of the GlassFish project, ships Spring support as
|
|
part of its JAX-WS Commons project. This allows for defining JAX-WS endpoints as
|
|
Spring-managed beans, similar to the standalone mode discussed in the previous section -
|
|
but this time in a Servlet environment. __Note that this is not portable in a Java EE 5
|
|
environment; it is mainly intended for non-EE environments such as Tomcat, embedding the
|
|
JAX-WS RI as part of the web application.__
|
|
|
|
The difference to the standard style of exporting servlet-based endpoints is that the
|
|
lifecycle of the endpoint instances themselves will be managed by Spring here, and that
|
|
there will be only one JAX-WS servlet defined in `web.xml`. With the standard Java EE 5
|
|
style (as illustrated above), you'll have one servlet definition per service endpoint,
|
|
with each endpoint typically delegating to Spring beans (through the use of
|
|
`@Autowired`, as shown above).
|
|
|
|
Check out https://jax-ws-commons.java.net/spring/[https://jax-ws-commons.java.net/spring/]
|
|
for details on setup and usage style.
|
|
|
|
|
|
[[remoting-web-services-jaxws-access]]
|
|
==== Accessing web services using JAX-WS
|
|
|
|
Spring provides two factory beans to create JAX-WS web service proxies, namely
|
|
`LocalJaxWsServiceFactoryBean` and `JaxWsPortProxyFactoryBean`. The former can only
|
|
return a JAX-WS service class for us to work with. The latter is the full-fledged
|
|
version that can return a proxy that implements our business service interface. In this
|
|
example we use the latter to create a proxy for the `AccountService` endpoint (again):
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
|
|
<property name="serviceInterface" value="example.AccountService"/>
|
|
<property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
|
|
<property name="namespaceUri" value="http://example/"/>
|
|
<property name="serviceName" value="AccountService"/>
|
|
<property name="portName" value="AccountServiceEndpointPort"/>
|
|
</bean>
|
|
----
|
|
|
|
Where `serviceInterface` is our business interface the clients will use.
|
|
`wsdlDocumentUrl` is the URL for the WSDL file. Spring needs this a startup time to
|
|
create the JAX-WS Service. `namespaceUri` corresponds to the targetNamespace in the
|
|
.wsdl file. `serviceName` corresponds to the service name in the .wsdl file. `portName`
|
|
corresponds to the port name in the .wsdl file.
|
|
|
|
Accessing the web service is now very easy as we have a bean factory for it that will
|
|
expose it as `AccountService` interface. We can wire this up in Spring:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="client" class="example.AccountClientImpl">
|
|
...
|
|
<property name="service" ref="accountWebService"/>
|
|
</bean>
|
|
----
|
|
|
|
From the client code we can access the web service just as if it was a normal class:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class AccountClientImpl {
|
|
|
|
private AccountService service;
|
|
|
|
public void setService(AccountService service) {
|
|
this.service = service;
|
|
}
|
|
|
|
public void foo() {
|
|
service.insertAccount(...);
|
|
}
|
|
}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
The above is slightly simplified in that JAX-WS requires endpoint interfaces
|
|
and implementation classes to be annotated with `@WebService`, `@SOAPBinding` etc
|
|
annotations. This means that you cannot (easily) use plain Java interfaces and
|
|
implementation classes as JAX-WS endpoint artifacts; you need to annotate them
|
|
accordingly first. Check the JAX-WS documentation for details on those requirements.
|
|
====
|
|
|
|
|
|
|
|
[[remoting-jms]]
|
|
=== JMS
|
|
|
|
It is also possible to expose services transparently using JMS as the underlying
|
|
communication protocol. The JMS remoting support in the Spring Framework is pretty basic
|
|
- it sends and receives on the `same thread` and in the __same non-transactional__
|
|
`Session`, and as such throughput will be very implementation dependent. Note that these
|
|
single-threaded and non-transactional constraints apply only to Spring's JMS
|
|
__remoting__ support. See <<jms>> for information on Spring's rich support for JMS-based
|
|
__messaging__.
|
|
|
|
The following interface is used on both the server and the client side.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package com.foo;
|
|
|
|
public interface CheckingAccountService {
|
|
|
|
public void cancelAccount(Long accountId);
|
|
|
|
}
|
|
----
|
|
|
|
The following simple implementation of the above interface is used on the server-side.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package com.foo;
|
|
|
|
public class SimpleCheckingAccountService implements CheckingAccountService {
|
|
|
|
public void cancelAccount(Long accountId) {
|
|
System.out.println("Cancelling account [" + accountId + "]");
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
This configuration file contains the JMS-infrastructure beans that are shared on both
|
|
the client and server.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
|
|
|
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
|
|
<property name="brokerURL" value="tcp://ep-t43:61616"/>
|
|
</bean>
|
|
|
|
<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
|
|
<constructor-arg value="mmm"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
|
|
[[remoting-jms-server]]
|
|
==== Server-side configuration
|
|
|
|
On the server, you just need to expose the service object using the
|
|
`JmsInvokerServiceExporter`.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
|
|
|
<bean id="checkingAccountService"
|
|
class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
|
|
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
|
|
<property name="service">
|
|
<bean class="com.foo.SimpleCheckingAccountService"/>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destination" ref="queue"/>
|
|
<property name="concurrentConsumers" value="3"/>
|
|
<property name="messageListener" ref="checkingAccountService"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package com.foo;
|
|
|
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|
|
|
public class Server {
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[remoting-jms-client]]
|
|
==== Client-side configuration
|
|
|
|
The client merely needs to create a client-side proxy that will implement the agreed
|
|
upon interface ( `CheckingAccountService`). The resulting object created off the back of
|
|
the following bean definition can be injected into other client side objects, and the
|
|
proxy will take care of forwarding the call to the server-side object via JMS.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
|
|
|
<bean id="checkingAccountService"
|
|
class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
|
|
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="queue" ref="queue"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package com.foo;
|
|
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|
|
|
public class Client {
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
ApplicationContext ctx = new ClassPathXmlApplicationContext(
|
|
new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
|
|
CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
|
|
service.cancelAccount(new Long(10));
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[remoting-amqp]]
|
|
=== AMQP
|
|
Refer to the {doc-spring-amqp}/html/_reference.html#remoting[Spring AMQP Reference Document
|
|
'Spring Remoting with AMQP' section] for more information.
|
|
|
|
|
|
|
|
[[remoting-autodection-remote-interfaces]]
|
|
=== Auto-detection is not implemented for remote interfaces
|
|
|
|
The main reason why auto-detection of implemented interfaces does not occur for remote
|
|
interfaces is to avoid opening too many doors to remote callers. The target object might
|
|
implement internal callback interfaces like `InitializingBean` or `DisposableBean` which
|
|
one would not want to expose to callers.
|
|
|
|
Offering a proxy with all interfaces implemented by the target usually does not matter
|
|
in the local case. But when exporting a remote service, you should expose a specific
|
|
service interface, with specific operations intended for remote usage. Besides internal
|
|
callback interfaces, the target might implement multiple business interfaces, with just
|
|
one of them intended for remote exposure. For these reasons, we __require__ such a
|
|
service interface to be specified.
|
|
|
|
This is a trade-off between configuration convenience and the risk of accidental
|
|
exposure of internal methods. Always specifying a service interface is not too much
|
|
effort, and puts you on the safe side regarding controlled exposure of specific methods.
|
|
|
|
|
|
|
|
[[remoting-considerations]]
|
|
=== Considerations when choosing a technology
|
|
|
|
Each and every technology presented here has its drawbacks. You should carefully
|
|
consider your needs, the services you are exposing and the objects you'll be sending
|
|
over the wire when choosing a technology.
|
|
|
|
When using RMI, it's not possible to access the objects through the HTTP protocol,
|
|
unless you're tunneling the RMI traffic. RMI is a fairly heavy-weight protocol in that
|
|
it supports full-object serialization which is important when using a complex data model
|
|
that needs serialization over the wire. However, RMI-JRMP is tied to Java clients: It is
|
|
a Java-to-Java remoting solution.
|
|
|
|
Spring's HTTP invoker is a good choice if you need HTTP-based remoting but also rely on
|
|
Java serialization. It shares the basic infrastructure with RMI invokers, just using
|
|
HTTP as transport. Note that HTTP invokers are not only limited to Java-to-Java remoting
|
|
but also to Spring on both the client and server side. (The latter also applies to
|
|
Spring's RMI invoker for non-RMI interfaces.)
|
|
|
|
Hessian might provide significant value when operating in a heterogeneous environment,
|
|
because they explicitly allow for non-Java clients. However, non-Java support is still
|
|
limited. Known issues include the serialization of Hibernate objects in combination with
|
|
lazily-initialized collections. If you have such a data model, consider using RMI or
|
|
HTTP invokers instead of Hessian.
|
|
|
|
JMS can be useful for providing clusters of services and allowing the JMS broker to take
|
|
care of load balancing, discovery and auto-failover. By default: Java serialization is
|
|
used when using JMS remoting but the JMS provider could use a different mechanism for
|
|
the wire formatting, such as XStream to allow servers to be implemented in other
|
|
technologies.
|
|
|
|
Last but not least, EJB has an advantage over RMI in that it supports standard
|
|
role-based authentication and authorization and remote transaction propagation. It is
|
|
possible to get RMI invokers or HTTP invokers to support security context propagation as
|
|
well, although this is not provided by core Spring: There are just appropriate hooks for
|
|
plugging in third-party or custom solutions here.
|
|
|
|
|
|
|
|
[[rest-client-access]]
|
|
=== REST Endpoints
|
|
|
|
The Spring Framework provides two choices for making calls to REST endpoints:
|
|
|
|
* <<rest-resttemplate>> -- the original Spring REST client with a synchronous, template
|
|
method API.
|
|
* <<web-reactive.adoc#webflux-client,WebClient>> -- non-blocking, reactive alternative
|
|
that supports both sync and async, as well as streaming scenarios.
|
|
|
|
[NOTE]
|
|
====
|
|
As of 5.0, the non-blocking, reactive `WebClient` offers a modern alternative to the
|
|
`RestTemplate` with efficient support for both sync and async, as well as streaming
|
|
scenarios. The `RestTemplate` will be deprecated in a future version and will not have
|
|
major new features gong forward.
|
|
====
|
|
|
|
|
|
[[rest-resttemplate]]
|
|
==== RestTemplate
|
|
|
|
The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it
|
|
easy to invoke REST endpoints in a single line. It exposes the following groups of
|
|
overloaded methods:
|
|
|
|
[[rest-overview-of-resttemplate-methods-tbl]]
|
|
.RestTemplate methods
|
|
[cols="1,3"]
|
|
|===
|
|
| Method group | Description
|
|
|
|
| `getForObject`
|
|
| Retrieve a representation via GET.
|
|
|
|
| `getForEntity`
|
|
| Retrieve a `ResponseEntity`, i.e. status, headers, and body, via GET.
|
|
|
|
| `headForHeaders`
|
|
| Retrieve all headers for a resource via HEAD.
|
|
|
|
| `postForLocation`
|
|
| Create a new resource via POST and return the Location header from the response.
|
|
|
|
| `postForObject`
|
|
| Create a new resource via POST and return the representation from the response.
|
|
|
|
| `postForEntity`
|
|
| Create a new resource via POST and return the representation from the response.
|
|
|
|
| `put`
|
|
| Create or update a resource via PUT.
|
|
|
|
| `patchForObject`
|
|
| Update a resource via PATCH and return the representation from the response.
|
|
Note that the JDK `HttpURLConnection` does not support the `PATCH` but Apache
|
|
HttpComponents, and others do.
|
|
|
|
| `delete`
|
|
| Delete the resources at the specified URI via DELETE.
|
|
|
|
| `optionsForAllow`
|
|
| Retrieve allowed HTTP methods for a resource via ALLOW.
|
|
|
|
| `exchange`
|
|
| More generalized, and less opinionated version, of the above methods that provides extra
|
|
flexibility when needed. It accepts `RequestEntity`, including HTTP method, URL, headers,
|
|
and body as input, and returns a `ResponseEntity`.
|
|
|
|
These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify
|
|
a response type with generics.
|
|
|
|
| `execute`
|
|
| The most generalized way to perform a request, with full control over request
|
|
preparation and response extraction via callback interfaces.
|
|
|
|
|===
|
|
|
|
|
|
[[rest-resttemplate-create]]
|
|
===== Initialization
|
|
|
|
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
|
|
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
|
|
There is built-in support for the following:
|
|
|
|
* Apache HttpComponents
|
|
* Netty
|
|
* OkHttp
|
|
|
|
For example to switch to Apache HttpComponents use:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
|
|
----
|
|
|
|
Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying
|
|
HTTP client library, e.g. for credentials, connection pooling, etc.
|
|
|
|
[TIP]
|
|
====
|
|
Note that the `java.net` implementation for HTTP requests may raise an exception when
|
|
accessing the status of a response that represents an error (e.g. 401). If this is an
|
|
issue, switch to another HTTP client library.
|
|
====
|
|
|
|
|
|
[[rest-resttemplate-uri]]
|
|
===== URIs
|
|
|
|
Many of the `RestTemplate` methods accepts a URI template and URI template variables,
|
|
either as a `String` vararg, or as `Map<String,String>`.
|
|
|
|
For example with a `String` vararg:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
String result = restTemplate.getForObject(
|
|
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
|
|
----
|
|
|
|
Or with a `Map<String, String>`:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
Map<String, String> vars = Collections.singletonMap("hotel", "42");
|
|
|
|
String result = restTemplate.getForObject(
|
|
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
|
|
----
|
|
|
|
Keep in mind URI templates are automatically encoded. For example:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
restTemplate.getForObject("https://example.com/hotel list", String.class);
|
|
|
|
// Results in request to "https://example.com/hotel%20list"
|
|
----
|
|
|
|
You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs are
|
|
encoded. Or you can prepare a `java.net.URI` and pass it into one of the `RestTemplate`
|
|
methods that accept a `URI`.
|
|
|
|
For more details on working with and encoding URIs, see
|
|
<<web.adoc#mvc-uri-building,URI Links>>.
|
|
|
|
|
|
[[rest-template-headers]]
|
|
===== Headers
|
|
|
|
Use the `exchange()` methods to specify request headers. For example:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
String uriTemplate = "https://example.com/hotels/{hotel}";
|
|
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
|
|
|
|
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
|
|
.header(("MyRequestHeader", "MyValue")
|
|
.build();
|
|
|
|
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
|
|
|
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
|
|
String body = response.getBody();
|
|
----
|
|
|
|
Response headers can be obtained through many `RestTemplate` method variants that return
|
|
`ResponseEntity`.
|
|
|
|
|
|
[[rest-template-body]]
|
|
===== Body
|
|
|
|
Object passed into and returned from `RestTemplate` methods are converted to and from raw
|
|
content with the help of an `HttpMessageConverter`.
|
|
|
|
On a POST, an input object is serialized to the request body:
|
|
|
|
URI location = template.postForLocation("https://example.com/people", person);
|
|
|
|
The "Content-Type" header of the request does not need to be set explicitly. In most cases
|
|
a compatible message converter can be found based on the source Object type, and the chosen
|
|
message converter will set the content type accordingly. If necessary, you can use the
|
|
`exchange` methods to provide the "Content-Type" request header explicitly, and that in
|
|
turn will influence what message converter is selected.
|
|
|
|
On a GET, the body of the response is deserialized to an output Object:
|
|
|
|
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
|
|
|
|
The "Accept" header of the request does not need to be set explicitly. In most cases
|
|
a compatible message converter can be found based on the expected response type, which
|
|
then helps to populate the "Accept" header. If necessary, you can use the `exchange`
|
|
methods to provide the "Accept" header explicitly.
|
|
|
|
By default `RestTemplate` registers all built-in
|
|
<<rest-message-conversion,message converters>>, depending on classpath checks that help
|
|
to determine what optional conversion libraries are present. You can also set the message
|
|
converters to use explicitly.
|
|
|
|
|
|
[[rest-message-conversion]]
|
|
===== Message Conversion
|
|
[.small]#<<web-reactive.adoc#webflux-codecs,Same in Spring WebFlux>>#
|
|
|
|
The `spring-web` module contains the `HttpMessageConverter` contract for reading and
|
|
writing the body of HTTP requests and responses via `InputStream` and `OutputStream`.
|
|
``HttpMessageConverter``'s are used on the client side, e.g. in the `RestTemplate`, and
|
|
also on the server side, e.g. in Spring MVC REST controllers.
|
|
|
|
Concrete implementations for the main media (MIME) types are provided in the framework
|
|
and are registered by default with the `RestTemplate` on the client-side and with
|
|
`RequestMethodHandlerAdapter` on the server-side (see
|
|
<<web.adoc#mvc-config-message-converters,Configuring Message Converters>>).
|
|
|
|
The implementations of ``HttpMessageConverter``s are described in the following sections.
|
|
For all converters a default media type is used but can be overridden by setting the
|
|
`supportedMediaTypes` bean property
|
|
|
|
[[rest-message-converters-tbl]]
|
|
.HttpMessageConverter Implementations
|
|
[cols="1,3"]
|
|
|===
|
|
| MessageConverter | Description
|
|
|
|
| `StringHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write Strings from the HTTP
|
|
request and response. By default, this converter supports all text media types (
|
|
`text/{asterisk}`), and writes with a `Content-Type` of `text/plain`.
|
|
|
|
| `FormHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP
|
|
request and response. By default, this converter reads and writes the media type
|
|
`application/x-www-form-urlencoded`. Form data is read from and written into a
|
|
`MultiValueMap<String, String>`.
|
|
|
|
| `ByteArrayHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write byte arrays from the
|
|
HTTP request and response. By default, this converter supports all media types ( `{asterisk}/{asterisk}`),
|
|
and writes with a `Content-Type` of `application/octet-stream`. This can be overridden
|
|
by setting the `supportedMediaTypes` property, and overriding `getContentType(byte[])`.
|
|
|
|
| `MarshallingHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write XML using Spring's
|
|
`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package.
|
|
This converter requires a `Marshaller` and `Unmarshaller` before it can be used. These
|
|
can be injected via constructor or bean properties. By default this converter supports (
|
|
`text/xml`) and ( `application/xml`).
|
|
|
|
| `MappingJackson2HttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write JSON using Jackson's
|
|
`ObjectMapper`. JSON mapping can be customized as needed through the use of Jackson's
|
|
provided annotations. When further control is needed, a custom `ObjectMapper` can be
|
|
injected through the `ObjectMapper` property for cases where custom JSON
|
|
serializers/deserializers need to be provided for specific types. By default this
|
|
converter supports ( `application/json`).
|
|
|
|
| `MappingJackson2XmlHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write XML using
|
|
https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's
|
|
`XmlMapper`. XML mapping can be customized as needed through the use of JAXB
|
|
or Jackson's provided annotations. When further control is needed, a custom `XmlMapper`
|
|
can be injected through the `ObjectMapper` property for cases where custom XML
|
|
serializers/deserializers need to be provided for specific types. By default this
|
|
converter supports ( `application/xml`).
|
|
|
|
| `SourceHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write
|
|
`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`,
|
|
`SAXSource`, and `StreamSource` are supported. By default, this converter supports (
|
|
`text/xml`) and ( `application/xml`).
|
|
|
|
| `BufferedImageHttpMessageConverter`
|
|
| An `HttpMessageConverter` implementation that can read and write
|
|
`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads
|
|
and writes the media type supported by the Java I/O API.
|
|
|
|
|===
|
|
|
|
|
|
[[rest-template-jsonview]]
|
|
===== Jackson JSON Views
|
|
|
|
It is possible to specify a https://wiki.fasterxml.com/JacksonJsonViews[Jackson JSON View]
|
|
to serialize only a subset of the object properties. For example:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
|
|
value.setSerializationView(User.WithoutPasswordView.class);
|
|
|
|
RequestEntity<MappingJacksonValue> requestEntity =
|
|
RequestEntity.post(new URI("https://example.com/user")).body(value);
|
|
|
|
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
|
----
|
|
|
|
[[rest-template-multipart]]
|
|
===== Multipart
|
|
|
|
To send multipart data, you need to provide a `MultiValueMap<String, ?>` whose values are
|
|
either Objects representing part content, or `HttpEntity` representing the content and
|
|
headers for a part. `MultipartBodyBuilder` provides a convenient API to prepare a
|
|
multipart request:
|
|
|
|
[source,java,intent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
MultipartBodyBuilder builder = new MultipartBodyBuilder();
|
|
builder.part("fieldPart", "fieldValue");
|
|
builder.part("filePart", new FileSystemResource("...logo.png"));
|
|
builder.part("jsonPart", new Person("Jason"));
|
|
|
|
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
|
|
----
|
|
|
|
In most cases you do not have to specify the `Content-Type` for each part. The content
|
|
type is determined automatically based on the `HttpMessageConverter` chosen to serialize it,
|
|
or in the case of a `Resource` based on the file extension. If necessary you can
|
|
explicitly provide the `MediaType` to use for each part through one fo the overloaded
|
|
builder `part` methods.
|
|
|
|
Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`:
|
|
|
|
[source,java,intent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
MultipartBodyBuilder builder = ...;
|
|
template.postForObject("https://example.com/upload", builder.build(), Void.class);
|
|
----
|
|
|
|
If the `MultiValueMap` contains at least one non-String value, which could also be
|
|
represent regular form data (i.e. "application/x-www-form-urlencoded"), you don't have to
|
|
set the `Content-Type` to "multipart/form-data". This is always the case when using
|
|
`MultipartBodyBuilder` which ensures an `HttpEntity` wrapper.
|
|
|
|
|
|
[[rest-async-resttemplate]]
|
|
==== Async RestTemplate
|
|
|
|
The `AsyncRestTemplate` is deprecated. For all use cases where the `AsyncRestTemplate`
|
|
is considered for use, please use the <<web-reactive.adoc#webflux-client,WebClient>> instead.
|
|
|
|
|
|
|
|
|
|
[[ejb]]
|
|
== Enterprise JavaBeans (EJB) integration
|
|
|
|
|
|
|
|
[[ejb-introduction]]
|
|
=== Introduction
|
|
|
|
As a lightweight container, Spring is often considered an EJB replacement. We do believe
|
|
that for many if not most applications and use cases, Spring as a container, combined
|
|
with its rich supporting functionality in the area of transactions, ORM and JDBC access,
|
|
is a better choice than implementing equivalent functionality via an EJB container and
|
|
EJBs.
|
|
|
|
However, it is important to note that using Spring does not prevent you from using EJBs.
|
|
In fact, Spring makes it much easier to access EJBs and implement EJBs and functionality
|
|
within them. Additionally, using Spring to access services provided by EJBs allows the
|
|
implementation of those services to later transparently be switched between local EJB,
|
|
remote EJB, or POJO (plain old Java object) variants, without the client code having to
|
|
be changed.
|
|
|
|
In this chapter, we look at how Spring can help you access and implement EJBs. Spring
|
|
provides particular value when accessing stateless session beans (SLSBs), so we'll begin
|
|
by discussing this.
|
|
|
|
|
|
|
|
[[ejb-access]]
|
|
=== Accessing EJBs
|
|
|
|
|
|
[[ejb-access-concepts]]
|
|
==== Concepts
|
|
|
|
To invoke a method on a local or remote stateless session bean, client code must
|
|
normally perform a JNDI lookup to obtain the (local or remote) EJB Home object, then use
|
|
a 'create' method call on that object to obtain the actual (local or remote) EJB object.
|
|
One or more methods are then invoked on the EJB.
|
|
|
|
To avoid repeated low-level code, many EJB applications use the Service Locator and
|
|
Business Delegate patterns. These are better than spraying JNDI lookups throughout
|
|
client code, but their usual implementations have significant disadvantages. For example:
|
|
|
|
* Typically code using EJBs depends on Service Locator or Business Delegate singletons,
|
|
making it hard to test.
|
|
* In the case of the Service Locator pattern used without a Business Delegate,
|
|
application code still ends up having to invoke the create() method on an EJB home,
|
|
and deal with the resulting exceptions. Thus it remains tied to the EJB API and the
|
|
complexity of the EJB programming model.
|
|
* Implementing the Business Delegate pattern typically results in significant code
|
|
duplication, where we have to write numerous methods that simply call the same method
|
|
on the EJB.
|
|
|
|
The Spring approach is to allow the creation and use of proxy objects, normally
|
|
configured inside a Spring container, which act as codeless business delegates. You do
|
|
not need to write another Service Locator, another JNDI lookup, or duplicate methods in
|
|
a hand-coded Business Delegate unless you are actually adding real value in such code.
|
|
|
|
|
|
[[ejb-access-local]]
|
|
==== Accessing local SLSBs
|
|
|
|
Assume that we have a web controller that needs to use a local EJB. We'll follow best
|
|
practice and use the EJB Business Methods Interface pattern, so that the EJB's local
|
|
interface extends a non EJB-specific business methods interface. Let's call this
|
|
business methods interface `MyComponent`.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface MyComponent {
|
|
...
|
|
}
|
|
----
|
|
|
|
One of the main reasons to use the Business Methods Interface pattern is to ensure that
|
|
synchronization between method signatures in local interface and bean implementation
|
|
class is automatic. Another reason is that it later makes it much easier for us to
|
|
switch to a POJO (plain old Java object) implementation of the service if it makes sense
|
|
to do so. Of course we'll also need to implement the local home interface and provide an
|
|
implementation class that implements `SessionBean` and the `MyComponent` business
|
|
methods interface. Now the only Java coding we'll need to do to hook up our web tier
|
|
controller to the EJB implementation is to expose a setter method of type `MyComponent`
|
|
on the controller. This will save the reference as an instance variable in the
|
|
controller:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
private MyComponent myComponent;
|
|
|
|
public void setMyComponent(MyComponent myComponent) {
|
|
this.myComponent = myComponent;
|
|
}
|
|
----
|
|
|
|
We can subsequently use this instance variable in any business method in the controller.
|
|
Now assuming we are obtaining our controller object out of a Spring container, we can
|
|
(in the same context) configure a `LocalStatelessSessionProxyFactoryBean` instance,
|
|
which will be the EJB proxy object. The configuration of the proxy, and setting of the
|
|
`myComponent` property of the controller is done with a configuration entry such as:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="myComponent"
|
|
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
|
|
<property name="jndiName" value="ejb/myBean"/>
|
|
<property name="businessInterface" value="com.mycom.MyComponent"/>
|
|
</bean>
|
|
|
|
<bean id="myController" class="com.mycom.myController">
|
|
<property name="myComponent" ref="myComponent"/>
|
|
</bean>
|
|
----
|
|
|
|
There's a lot of work happening behind the scenes, courtesy of the Spring AOP framework,
|
|
although you aren't forced to work with AOP concepts to enjoy the results. The
|
|
`myComponent` bean definition creates a proxy for the EJB, which implements the business
|
|
method interface. The EJB local home is cached on startup, so there's only a single JNDI
|
|
lookup. Each time the EJB is invoked, the proxy invokes the `classname` method on the
|
|
local EJB and invokes the corresponding business method on the EJB.
|
|
|
|
The `myController` bean definition sets the `myComponent` property of the controller
|
|
class to the EJB proxy.
|
|
|
|
Alternatively (and preferably in case of many such proxy definitions), consider using
|
|
the `<jee:local-slsb>` configuration element in Spring's "jee" namespace:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
|
|
business-interface="com.mycom.MyComponent"/>
|
|
|
|
<bean id="myController" class="com.mycom.myController">
|
|
<property name="myComponent" ref="myComponent"/>
|
|
</bean>
|
|
----
|
|
|
|
This EJB access mechanism delivers huge simplification of application code: the web tier
|
|
code (or other EJB client code) has no dependence on the use of EJB. If we want to
|
|
replace this EJB reference with a POJO or a mock object or other test stub, we could
|
|
simply change the `myComponent` bean definition without changing a line of Java code.
|
|
Additionally, we haven't had to write a single line of JNDI lookup or other EJB plumbing
|
|
code as part of our application.
|
|
|
|
Benchmarks and experience in real applications indicate that the performance overhead of
|
|
this approach (which involves reflective invocation of the target EJB) is minimal, and
|
|
is typically undetectable in typical use. Remember that we don't want to make
|
|
fine-grained calls to EJBs anyway, as there's a cost associated with the EJB
|
|
infrastructure in the application server.
|
|
|
|
There is one caveat with regards to the JNDI lookup. In a bean container, this class is
|
|
normally best used as a singleton (there simply is no reason to make it a prototype).
|
|
However, if that bean container pre-instantiates singletons (as do the various XML
|
|
`ApplicationContext` variants) you may have a problem if the bean container is loaded
|
|
before the EJB container loads the target EJB. That is because the JNDI lookup will be
|
|
performed in the `init()` method of this class and then cached, but the EJB will not
|
|
have been bound at the target location yet. The solution is to not pre-instantiate this
|
|
factory object, but allow it to be created on first use. In the XML containers, this is
|
|
controlled via the `lazy-init` attribute.
|
|
|
|
Although this will not be of interest to the majority of Spring users, those doing
|
|
programmatic AOP work with EJBs may want to look at `LocalSlsbInvokerInterceptor`.
|
|
|
|
|
|
[[ejb-access-remote]]
|
|
==== Accessing remote SLSBs
|
|
|
|
Accessing remote EJBs is essentially identical to accessing local EJBs, except that the
|
|
`SimpleRemoteStatelessSessionProxyFactoryBean` or `<jee:remote-slsb>` configuration
|
|
element is used. Of course, with or without Spring, remote invocation semantics apply; a
|
|
call to a method on an object in another VM in another computer does sometimes have to
|
|
be treated differently in terms of usage scenarios and failure handling.
|
|
|
|
Spring's EJB client support adds one more advantage over the non-Spring approach.
|
|
Normally it is problematic for EJB client code to be easily switched back and forth
|
|
between calling EJBs locally or remotely. This is because the remote interface methods
|
|
must declare that they throw `RemoteException`, and client code must deal with this,
|
|
while the local interface methods don't. Client code written for local EJBs which needs
|
|
to be moved to remote EJBs typically has to be modified to add handling for the remote
|
|
exceptions, and client code written for remote EJBs which needs to be moved to local
|
|
EJBs, can either stay the same but do a lot of unnecessary handling of remote
|
|
exceptions, or needs to be modified to remove that code. With the Spring remote EJB
|
|
proxy, you can instead not declare any thrown `RemoteException` in your Business Method
|
|
Interface and implementing EJB code, have a remote interface which is identical except
|
|
that it does throw `RemoteException`, and rely on the proxy to dynamically treat the two
|
|
interfaces as if they were the same. That is, client code does not have to deal with the
|
|
checked `RemoteException` class. Any actual `RemoteException` that is thrown during the
|
|
EJB invocation will be re-thrown as the non-checked `RemoteAccessException` class, which
|
|
is a subclass of `RuntimeException`. The target service can then be switched at will
|
|
between a local EJB or remote EJB (or even plain Java object) implementation, without
|
|
the client code knowing or caring. Of course, this is optional; there is nothing
|
|
stopping you from declaring `RemoteExceptions` in your business interface.
|
|
|
|
|
|
[[ejb-access-ejb2-ejb3]]
|
|
==== Accessing EJB 2.x SLSBs versus EJB 3 SLSBs
|
|
|
|
Accessing EJB 2.x Session Beans and EJB 3 Session Beans via Spring is largely
|
|
transparent. Spring's EJB accessors, including the `<jee:local-slsb>` and
|
|
`<jee:remote-slsb>` facilities, transparently adapt to the actual component at runtime.
|
|
They handle a home interface if found (EJB 2.x style), or perform straight component
|
|
invocations if no home interface is available (EJB 3 style).
|
|
|
|
Note: For EJB 3 Session Beans, you could effectively use a `JndiObjectFactoryBean` /
|
|
`<jee:jndi-lookup>` as well, since fully usable component references are exposed for
|
|
plain JNDI lookups there. Defining explicit `<jee:local-slsb>` / `<jee:remote-slsb>`
|
|
lookups simply provides consistent and more explicit EJB access configuration.
|
|
|
|
|
|
|
|
|
|
[[jms]]
|
|
== JMS (Java Message Service)
|
|
|
|
|
|
|
|
[[jms-introduction]]
|
|
=== Introduction
|
|
|
|
Spring provides a JMS integration framework that simplifies the use of the JMS API much
|
|
like Spring's integration does for the JDBC API.
|
|
|
|
JMS can be roughly divided into two areas of functionality, namely the production and
|
|
consumption of messages. The `JmsTemplate` class is used for message production and
|
|
synchronous message reception. For asynchronous reception similar to Java EE's
|
|
message-driven bean style, Spring provides a number of message listener containers that
|
|
are used to create Message-Driven POJOs (MDPs). Spring also provides a declarative way
|
|
of creating message listeners.
|
|
|
|
The package `org.springframework.jms.core` provides the core functionality for using
|
|
JMS. It contains JMS template classes that simplify the use of the JMS by handling the
|
|
creation and release of resources, much like the `JdbcTemplate` does for JDBC. The
|
|
design principle common to Spring template classes is to provide helper methods to
|
|
perform common operations and for more sophisticated usage, delegate the essence of the
|
|
processing task to user implemented callback interfaces. The JMS template follows the
|
|
same design. The classes offer various convenience methods for the sending of messages,
|
|
consuming a message synchronously, and exposing the JMS session and message producer to
|
|
the user.
|
|
|
|
The package `org.springframework.jms.support` provides `JMSException` translation
|
|
functionality. The translation converts the checked `JMSException` hierarchy to a
|
|
mirrored hierarchy of unchecked exceptions. If there are any provider specific
|
|
subclasses of the checked `javax.jms.JMSException`, this exception is wrapped in the
|
|
unchecked `UncategorizedJmsException`.
|
|
|
|
The package `org.springframework.jms.support.converter` provides a `MessageConverter`
|
|
abstraction to convert between Java objects and JMS messages.
|
|
|
|
The package `org.springframework.jms.support.destination` provides various strategies
|
|
for managing JMS destinations, such as providing a service locator for destinations
|
|
stored in JNDI.
|
|
|
|
The package `org.springframework.jms.annotation` provides the necessary infrastructure
|
|
to support annotation-driven listener endpoints using `@JmsListener`.
|
|
|
|
The package `org.springframework.jms.config` provides the parser implementation for the
|
|
`jms` namespace as well the java config support to configure listener containers and
|
|
create listener endpoints.
|
|
|
|
Finally, the package `org.springframework.jms.connection` provides an implementation of
|
|
the `ConnectionFactory` suitable for use in standalone applications. It also contains an
|
|
implementation of Spring's `PlatformTransactionManager` for JMS (the cunningly named
|
|
`JmsTransactionManager`). This allows for seamless integration of JMS as a transactional
|
|
resource into Spring's transaction management mechanisms.
|
|
|
|
[NOTE]
|
|
====
|
|
As of Spring Framework 5, Spring's JMS package fully supports JMS 2.0 and requires the
|
|
JMS 2.0 API to be present at runtime. We recommend the use of a JMS 2.0 compatible provider.
|
|
|
|
If you happen to use an older message broker in your system, you may try upgrading to a
|
|
JMS 2.0 compatible driver for your existing broker generation. Alternatively, you may also
|
|
try to run against a JMS 1.1 based driver, simply putting the JMS 2.0 API jar on the
|
|
classpath but only using JMS 1.1 compatible API against your driver. Spring's JMS support
|
|
adheres to JMS 1.1 conventions by default, so with corresponding configuration it does
|
|
support such a scenario. However, please consider this for transition scenarios only.
|
|
====
|
|
|
|
|
|
|
|
[[jms-using]]
|
|
=== Using Spring JMS
|
|
|
|
|
|
[[jms-jmstemplate]]
|
|
==== JmsTemplate
|
|
|
|
The `JmsTemplate` class is the central class in the JMS core package. It simplifies the
|
|
use of JMS since it handles the creation and release of resources when sending or
|
|
synchronously receiving messages.
|
|
|
|
Code that uses the `JmsTemplate` only needs to implement callback interfaces giving them
|
|
a clearly defined high level contract. The `MessageCreator` callback interface creates a
|
|
message given a `Session` provided by the calling code in `JmsTemplate`. In order to
|
|
allow for more complex usage of the JMS API, the callback `SessionCallback` provides the
|
|
user with the JMS session and the callback `ProducerCallback` exposes a `Session` and
|
|
`MessageProducer` pair.
|
|
|
|
The JMS API exposes two types of send methods, one that takes delivery mode, priority,
|
|
and time-to-live as Quality of Service (QOS) parameters and one that takes no QOS
|
|
parameters which uses default values. Since there are many send methods in
|
|
`JmsTemplate`, the setting of the QOS parameters have been exposed as bean properties to
|
|
avoid duplication in the number of send methods. Similarly, the timeout value for
|
|
synchronous receive calls is set using the property `setReceiveTimeout`.
|
|
|
|
Some JMS providers allow the setting of default QOS values administratively through the
|
|
configuration of the `ConnectionFactory`. This has the effect that a call to
|
|
``MessageProducer``'s send method `send(Destination destination, Message message)` will
|
|
use different QOS default values than those specified in the JMS specification. In order
|
|
to provide consistent management of QOS values, the `JmsTemplate` must therefore be
|
|
specifically enabled to use its own QOS values by setting the boolean property
|
|
`isExplicitQosEnabled` to `true`.
|
|
|
|
For convenience, `JmsTemplate` also exposes a basic request-reply operation that allows
|
|
to send a message and wait for a reply on a temporary queue that is created as part of
|
|
the operation.
|
|
|
|
[NOTE]
|
|
====
|
|
Instances of the `JmsTemplate` class are __thread-safe once configured__. This is
|
|
important because it means that you can configure a single instance of a `JmsTemplate`
|
|
and then safely inject this __shared__ reference into multiple collaborators. To be
|
|
clear, the `JmsTemplate` is stateful, in that it maintains a reference to a
|
|
`ConnectionFactory`, but this state is __not__ conversational state.
|
|
====
|
|
|
|
As of Spring Framework 4.1, `JmsMessagingTemplate` is built on top of `JmsTemplate`
|
|
and provides an integration with the messaging abstraction, i.e.
|
|
`org.springframework.messaging.Message`. This allows you to create the message to
|
|
send in generic manner.
|
|
|
|
|
|
[[jms-connections]]
|
|
==== Connections
|
|
|
|
The `JmsTemplate` requires a reference to a `ConnectionFactory`. The `ConnectionFactory`
|
|
is part of the JMS specification and serves as the entry point for working with JMS. It
|
|
is used by the client application as a factory to create connections with the JMS
|
|
provider and encapsulates various configuration parameters, many of which are vendor
|
|
specific such as SSL configuration options.
|
|
|
|
When using JMS inside an EJB, the vendor provides implementations of the JMS interfaces
|
|
so that they can participate in declarative transaction management and perform pooling
|
|
of connections and sessions. In order to use this implementation, Java EE containers
|
|
typically require that you declare a JMS connection factory as a `resource-ref` inside
|
|
the EJB or servlet deployment descriptors. To ensure the use of these features with the
|
|
`JmsTemplate` inside an EJB, the client application should ensure that it references the
|
|
managed implementation of the `ConnectionFactory`.
|
|
|
|
[[jms-caching-resources]]
|
|
===== Caching Messaging Resources
|
|
|
|
The standard API involves creating many intermediate objects. To send a message the
|
|
following 'API' walk is performed
|
|
|
|
[literal]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
ConnectionFactory->Connection->Session->MessageProducer->send
|
|
----
|
|
|
|
Between the ConnectionFactory and the Send operation there are three intermediate
|
|
objects that are created and destroyed. To optimise the resource usage and increase
|
|
performance two implementations of `ConnectionFactory` are provided.
|
|
|
|
[[jms-connection-factory]]
|
|
===== SingleConnectionFactory
|
|
|
|
Spring provides an implementation of the `ConnectionFactory` interface,
|
|
`SingleConnectionFactory`, that will return the same `Connection` on all
|
|
`createConnection()` calls and ignore calls to `close()`. This is useful for testing and
|
|
standalone environments so that the same connection can be used for multiple
|
|
`JmsTemplate` calls that may span any number of transactions. `SingleConnectionFactory`
|
|
takes a reference to a standard `ConnectionFactory` that would typically come from JNDI.
|
|
|
|
[[jdbc-connection-factory-caching]]
|
|
===== CachingConnectionFactory
|
|
|
|
The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory`
|
|
and adds the caching of Sessions, MessageProducers, and MessageConsumers. The initial
|
|
cache size is set to 1, use the property `sessionCacheSize` to increase the number of
|
|
cached sessions. Note that the number of actual cached sessions will be more than that
|
|
number as sessions are cached based on their acknowledgment mode, so there can be up to
|
|
4 cached session instances when `sessionCacheSize` is set to one, one for each
|
|
acknowledgment mode. MessageProducers and MessageConsumers are cached within their
|
|
owning session and also take into account the unique properties of the producers and
|
|
consumers when caching. MessageProducers are cached based on their destination.
|
|
MessageConsumers are cached based on a key composed of the destination, selector,
|
|
noLocal delivery flag, and the durable subscription name (if creating durable consumers).
|
|
|
|
|
|
[[jms-destinations]]
|
|
==== Destination Management
|
|
|
|
Destinations, like ConnectionFactories, are JMS administered objects that can be stored
|
|
and retrieved in JNDI. When configuring a Spring application context you can use the
|
|
JNDI factory class `JndiObjectFactoryBean` / `<jee:jndi-lookup>` to perform dependency
|
|
injection on your object's references to JMS destinations. However, often this strategy
|
|
is cumbersome if there are a large number of destinations in the application or if there
|
|
are advanced destination management features unique to the JMS provider. Examples of
|
|
such advanced destination management would be the creation of dynamic destinations or
|
|
support for a hierarchical namespace of destinations. The `JmsTemplate` delegates the
|
|
resolution of a destination name to a JMS destination object to an implementation of the
|
|
interface `DestinationResolver`. `DynamicDestinationResolver` is the default
|
|
implementation used by `JmsTemplate` and accommodates resolving dynamic destinations. A
|
|
`JndiDestinationResolver` is also provided that acts as a service locator for
|
|
destinations contained in JNDI and optionally falls back to the behavior contained in
|
|
`DynamicDestinationResolver`.
|
|
|
|
Quite often the destinations used in a JMS application are only known at runtime and
|
|
therefore cannot be administratively created when the application is deployed. This is
|
|
often because there is shared application logic between interacting system components
|
|
that create destinations at runtime according to a well-known naming convention. Even
|
|
though the creation of dynamic destinations is not part of the JMS specification, most
|
|
vendors have provided this functionality. Dynamic destinations are created with a name
|
|
defined by the user which differentiates them from temporary destinations and are often
|
|
not registered in JNDI. The API used to create dynamic destinations varies from provider
|
|
to provider since the properties associated with the destination are vendor specific.
|
|
However, a simple implementation choice that is sometimes made by vendors is to
|
|
disregard the warnings in the JMS specification and to use the `TopicSession` method
|
|
`createTopic(String topicName)` or the `QueueSession` method `createQueue(String
|
|
queueName)` to create a new destination with default destination properties. Depending
|
|
on the vendor implementation, `DynamicDestinationResolver` may then also create a
|
|
physical destination instead of only resolving one.
|
|
|
|
The boolean property `pubSubDomain` is used to configure the `JmsTemplate` with
|
|
knowledge of what JMS domain is being used. By default the value of this property is
|
|
false, indicating that the point-to-point domain, Queues, will be used. This property
|
|
used by `JmsTemplate` determines the behavior of dynamic destination resolution via
|
|
implementations of the `DestinationResolver` interface.
|
|
|
|
You can also configure the `JmsTemplate` with a default destination via the property
|
|
`defaultDestination`. The default destination will be used with send and receive
|
|
operations that do not refer to a specific destination.
|
|
|
|
|
|
[[jms-mdp]]
|
|
==== Message Listener Containers
|
|
|
|
One of the most common uses of JMS messages in the EJB world is to drive message-driven
|
|
beans (MDBs). Spring offers a solution to create message-driven POJOs (MDPs) in a way
|
|
that does not tie a user to an EJB container. (See <<jms-receiving-async>> for detailed
|
|
coverage of Spring's MDP support.) As from Spring Framework 4.1, endpoint methods can
|
|
be simply annotated using `@JmsListener` see <<jms-annotated>> for more details.
|
|
|
|
A message listener container is used to receive messages from a JMS message queue and
|
|
drive the `MessageListener` that is injected into it. The listener container is
|
|
responsible for all threading of message reception and dispatches into the listener for
|
|
processing. A message listener container is the intermediary between an MDP and a
|
|
messaging provider, and takes care of registering to receive messages, participating in
|
|
transactions, resource acquisition and release, exception conversion and suchlike. This
|
|
allows you as an application developer to write the (possibly complex) business logic
|
|
associated with receiving a message (and possibly responding to it), and delegates
|
|
boilerplate JMS infrastructure concerns to the framework.
|
|
|
|
There are two standard JMS message listener containers packaged with Spring, each with
|
|
its specialised feature set.
|
|
|
|
[[jms-mdp-simple]]
|
|
===== SimpleMessageListenerContainer
|
|
|
|
This message listener container is the simpler of the two standard flavors. It creates a
|
|
fixed number of JMS sessions and consumers at startup, registers the listener using the
|
|
standard JMS `MessageConsumer.setMessageListener()` method, and leaves it up the JMS
|
|
provider to perform listener callbacks. This variant does not allow for dynamic adaption
|
|
to runtime demands or for participation in externally managed transactions.
|
|
Compatibility-wise, it stays very close to the spirit of the standalone JMS
|
|
specification - but is generally not compatible with Java EE's JMS restrictions.
|
|
|
|
[NOTE]
|
|
====
|
|
While `SimpleMessageListenerContainer` does not allow for the participation in externally
|
|
managed transactions, it does support native JMS transactions: simply switch the
|
|
'sessionTransacted' flag to 'true' or, in the namespace, set the 'acknowledge' attribute
|
|
to 'transacted': Exceptions thrown from your listener will lead to a rollback then, with
|
|
the message getting redelivered. Alternatively, consider using 'CLIENT_ACKNOWLEDGE' mode
|
|
which provides redelivery in case of an exception as well but does not use transacted
|
|
Sessions and therefore does not include any other Session operations (such as sending
|
|
response messages) in the transaction protocol.
|
|
|
|
**The default 'AUTO_ACKNOWLEDGE' mode does not provide proper reliability guarantees.**
|
|
Messages may get lost when listener execution fails (since the provider will automatically
|
|
acknowledge each message after listener invocation, with no exceptions to be propagated to
|
|
the provider) or when the listener container shuts down (this may be configured through
|
|
the 'acceptMessagesWhileStopping' flag). Make sure to use transacted sessions in case of
|
|
reliability needs, e.g. for reliable queue handling and durable topic subscriptions.
|
|
====
|
|
|
|
[[jms-mdp-default]]
|
|
===== DefaultMessageListenerContainer
|
|
|
|
This message listener container is the one used in most cases. In contrast to
|
|
`SimpleMessageListenerContainer`, this container variant allows for dynamic adaptation
|
|
to runtime demands and is able to participate in externally managed transactions. Each
|
|
received message is registered with an XA transaction when configured with a
|
|
`JtaTransactionManager`; so processing may take advantage of XA transaction semantics.
|
|
This listener container strikes a good balance between low requirements on the JMS
|
|
provider, advanced functionality such as the participation in externally managed
|
|
transactions, and compatibility with Java EE environments.
|
|
|
|
The cache level of the container can be customized. Note that when no caching is enabled,
|
|
a new connection and a new session is created for each message reception. Combining this
|
|
with a non durable subscription with high loads may lead to message lost. Make sure to
|
|
use a proper cache level in such case.
|
|
|
|
This container also has recoverable capabilities when the broker goes down. By default,
|
|
a simple `BackOff` implementation retries every 5 seconds. It is possible to specify
|
|
a custom `BackOff` implementation for more fine-grained recovery options, see
|
|
`ExponentialBackOff` for an example.
|
|
|
|
[NOTE]
|
|
====
|
|
Like its sibling `SimpleMessageListenerContainer`, `DefaultMessageListenerContainer`
|
|
supports native JMS transactions and also allows for customizing the acknowledgment mode.
|
|
This is strongly recommended over externally managed transactions if feasible for your
|
|
scenario: that is, if you can live with occasional duplicate messages in case of the
|
|
JVM dying. Custom duplicate message detection steps in your business logic may cover
|
|
such situations, e.g. in the form of a business entity existence check or a protocol
|
|
table check. Any such arrangements will be significantly more efficient than the
|
|
alternative: wrapping your entire processing with an XA transaction (through configuring
|
|
your `DefaultMessageListenerContainer` with an `JtaTransactionManager`), covering the
|
|
reception of the JMS message as well as the execution of the business logic in your
|
|
message listener (including database operations etc).
|
|
|
|
**The default 'AUTO_ACKNOWLEDGE' mode does not provide proper reliability guarantees.**
|
|
Messages may get lost when listener execution fails (since the provider will automatically
|
|
acknowledge each message before listener invocation) or when the listener container shuts
|
|
down (this may be configured through the 'acceptMessagesWhileStopping' flag). Make sure
|
|
to use transacted sessions in case of reliability needs, e.g. for reliable queue handling
|
|
and durable topic subscriptions.
|
|
====
|
|
|
|
|
|
[[jms-tx]]
|
|
==== Transaction management
|
|
|
|
Spring provides a `JmsTransactionManager` that manages transactions for a single JMS
|
|
`ConnectionFactory`. This allows JMS applications to leverage the managed transaction
|
|
features of Spring as described in <<data-access.adoc#transaction,Transaction Management>>.
|
|
The `JmsTransactionManager` performs local resource transactions, binding a JMS
|
|
Connection/Session pair from the specified `ConnectionFactory` to the thread.
|
|
`JmsTemplate` automatically detects such transactional resources and operates
|
|
on them accordingly.
|
|
|
|
In a Java EE environment, the `ConnectionFactory` will pool Connections and Sessions, so
|
|
those resources are efficiently reused across transactions. In a standalone environment,
|
|
using Spring's `SingleConnectionFactory` will result in a shared JMS `Connection`, with
|
|
each transaction having its own independent `Session`. Alternatively, consider the use
|
|
of a provider-specific pooling adapter such as ActiveMQ's `PooledConnectionFactory`
|
|
class.
|
|
|
|
`JmsTemplate` can also be used with the `JtaTransactionManager` and an XA-capable JMS
|
|
`ConnectionFactory` for performing distributed transactions. Note that this requires the
|
|
use of a JTA transaction manager as well as a properly XA-configured ConnectionFactory!
|
|
(Check your Java EE server's / JMS provider's documentation.)
|
|
|
|
Reusing code across a managed and unmanaged transactional environment can be confusing
|
|
when using the JMS API to create a `Session` from a `Connection`. This is because the
|
|
JMS API has only one factory method to create a `Session` and it requires values for the
|
|
transaction and acknowledgment modes. In a managed environment, setting these values is
|
|
the responsibility of the environment's transactional infrastructure, so these values
|
|
are ignored by the vendor's wrapper to the JMS Connection. When using the `JmsTemplate`
|
|
in an unmanaged environment you can specify these values through the use of the
|
|
properties `sessionTransacted` and `sessionAcknowledgeMode`. When using a
|
|
`PlatformTransactionManager` with `JmsTemplate`, the template will always be given a
|
|
transactional JMS `Session`.
|
|
|
|
|
|
|
|
[[jms-sending]]
|
|
=== Sending a Message
|
|
|
|
The `JmsTemplate` contains many convenience methods to send a message. There are send
|
|
methods that specify the destination using a `javax.jms.Destination` object and those
|
|
that specify the destination using a string for use in a JNDI lookup. The send method
|
|
that takes no destination argument uses the default destination.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
import javax.jms.ConnectionFactory;
|
|
import javax.jms.JMSException;
|
|
import javax.jms.Message;
|
|
import javax.jms.Queue;
|
|
import javax.jms.Session;
|
|
|
|
import org.springframework.jms.core.MessageCreator;
|
|
import org.springframework.jms.core.JmsTemplate;
|
|
|
|
public class JmsQueueSender {
|
|
|
|
private JmsTemplate jmsTemplate;
|
|
private Queue queue;
|
|
|
|
public void setConnectionFactory(ConnectionFactory cf) {
|
|
this.jmsTemplate = new JmsTemplate(cf);
|
|
}
|
|
|
|
public void setQueue(Queue queue) {
|
|
this.queue = queue;
|
|
}
|
|
|
|
public void simpleSend() {
|
|
this.jmsTemplate.send(this.queue, new MessageCreator() {
|
|
public Message createMessage(Session session) throws JMSException {
|
|
return session.createTextMessage("hello queue world");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
----
|
|
|
|
This example uses the `MessageCreator` callback to create a text message from the
|
|
supplied `Session` object. The `JmsTemplate` is constructed by passing a reference to a
|
|
`ConnectionFactory`. As an alternative, a zero argument constructor and
|
|
`connectionFactory` is provided and can be used for constructing the instance in
|
|
JavaBean style (using a BeanFactory or plain Java code). Alternatively, consider
|
|
deriving from Spring's `JmsGatewaySupport` convenience base class, which provides
|
|
pre-built bean properties for JMS configuration.
|
|
|
|
The method `send(String destinationName, MessageCreator creator)` lets you send a
|
|
message using the string name of the destination. If these names are registered in JNDI,
|
|
you should set the `destinationResolver` property of the template to an instance of
|
|
`JndiDestinationResolver`.
|
|
|
|
If you created the `JmsTemplate` and specified a default destination, the
|
|
`send(MessageCreator c)` sends a message to that destination.
|
|
|
|
|
|
[[jms-msg-conversion]]
|
|
==== Using Message Converters
|
|
|
|
In order to facilitate the sending of domain model objects, the `JmsTemplate` has
|
|
various send methods that take a Java object as an argument for a message's data
|
|
content. The overloaded methods `convertAndSend()` and `receiveAndConvert()` in
|
|
`JmsTemplate` delegate the conversion process to an instance of the `MessageConverter`
|
|
interface. This interface defines a simple contract to convert between Java objects and
|
|
JMS messages. The default implementation `SimpleMessageConverter` supports conversion
|
|
between `String` and `TextMessage`, `byte[]` and `BytesMesssage`, and `java.util.Map`
|
|
and `MapMessage`. By using the converter, you and your application code can focus on the
|
|
business object that is being sent or received via JMS and not be concerned with the
|
|
details of how it is represented as a JMS message.
|
|
|
|
The sandbox currently includes a `MapMessageConverter` which uses reflection to convert
|
|
between a JavaBean and a `MapMessage`. Other popular implementation choices you might
|
|
implement yourself are Converters that use an existing XML marshalling package, such as
|
|
JAXB, Castor or XStream, to create a `TextMessage` representing the object.
|
|
|
|
To accommodate the setting of a message's properties, headers, and body that can not be
|
|
generically encapsulated inside a converter class, the `MessagePostProcessor` interface
|
|
gives you access to the message after it has been converted, but before it is sent. The
|
|
example below demonstrates how to modify a message header and a property after a
|
|
`java.util.Map` is converted to a message.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public void sendWithConversion() {
|
|
Map map = new HashMap();
|
|
map.put("Name", "Mark");
|
|
map.put("Age", new Integer(47));
|
|
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
|
|
public Message postProcessMessage(Message message) throws JMSException {
|
|
message.setIntProperty("AccountID", 1234);
|
|
message.setJMSCorrelationID("123-00001");
|
|
return message;
|
|
}
|
|
});
|
|
}
|
|
----
|
|
|
|
This results in a message of the form:
|
|
|
|
[literal]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
MapMessage={
|
|
Header={
|
|
... standard headers ...
|
|
CorrelationID={123-00001}
|
|
}
|
|
Properties={
|
|
AccountID={Integer:1234}
|
|
}
|
|
Fields={
|
|
Name={String:Mark}
|
|
Age={Integer:47}
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
[[jms-callbacks]]
|
|
==== SessionCallback and ProducerCallback
|
|
|
|
While the send operations cover many common usage scenarios, there are cases when you
|
|
want to perform multiple operations on a JMS `Session` or `MessageProducer`. The
|
|
`SessionCallback` and `ProducerCallback` expose the JMS `Session` and `Session` /
|
|
`MessageProducer` pair respectively. The `execute()` methods on `JmsTemplate` execute
|
|
these callback methods.
|
|
|
|
|
|
|
|
[[jms-receiving]]
|
|
=== Receiving a message
|
|
|
|
|
|
[[jms-receiving-sync]]
|
|
==== Synchronous reception
|
|
|
|
While JMS is typically associated with asynchronous processing, it is possible to
|
|
consume messages synchronously. The overloaded `receive(..)` methods provide this
|
|
functionality. During a synchronous receive, the calling thread blocks until a message
|
|
becomes available. This can be a dangerous operation since the calling thread can
|
|
potentially be blocked indefinitely. The property `receiveTimeout` specifies how long
|
|
the receiver should wait before giving up waiting for a message.
|
|
|
|
|
|
[[jms-receiving-async]]
|
|
==== Asynchronous reception: Message-Driven POJOs
|
|
|
|
[NOTE]
|
|
====
|
|
Spring also supports annotated-listener endpoints through the use of the `@JmsListener`
|
|
annotation and provides an open infrastructure to register endpoints programmatically. This
|
|
is by far the most convenient way to setup an asynchronous receiver, see
|
|
<<jms-annotated-support>> for more details.
|
|
====
|
|
|
|
In a fashion similar to a Message-Driven Bean (MDB) in the EJB world, the Message-Driven
|
|
POJO (MDP) acts as a receiver for JMS messages. The one restriction (but see also below
|
|
for the discussion of the `MessageListenerAdapter` class) on an MDP is that it must
|
|
implement the `javax.jms.MessageListener` interface. Please also be aware that in the
|
|
case where your POJO will be receiving messages on multiple threads, it is important to
|
|
ensure that your implementation is thread-safe.
|
|
|
|
Below is a simple implementation of an MDP:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
import javax.jms.JMSException;
|
|
import javax.jms.Message;
|
|
import javax.jms.MessageListener;
|
|
import javax.jms.TextMessage;
|
|
|
|
public class ExampleListener implements MessageListener {
|
|
|
|
public void onMessage(Message message) {
|
|
if (message instanceof TextMessage) {
|
|
try {
|
|
System.out.println(((TextMessage) message).getText());
|
|
}
|
|
catch (JMSException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
else {
|
|
throw new IllegalArgumentException("Message must be of type TextMessage");
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
Once you've implemented your `MessageListener`, it's time to create a message listener
|
|
container.
|
|
|
|
Find below an example of how to define and configure one of the message listener
|
|
containers that ships with Spring (in this case the `DefaultMessageListenerContainer`).
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<!-- this is the Message Driven POJO (MDP) -->
|
|
<bean id="messageListener" class="jmsexample.ExampleListener"/>
|
|
|
|
<!-- and this is the message listener container -->
|
|
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destination" ref="destination"/>
|
|
**<property name="messageListener" ref="messageListener"/>**
|
|
</bean>
|
|
----
|
|
|
|
Please refer to the Spring javadocs of the various message listener containers for a full
|
|
description of the features supported by each implementation.
|
|
|
|
|
|
[[jms-receiving-async-session-aware-message-listener]]
|
|
==== SessionAwareMessageListener interface
|
|
|
|
The `SessionAwareMessageListener` interface is a Spring-specific interface that provides
|
|
a similar contract to the JMS `MessageListener` interface, but also provides the message
|
|
handling method with access to the JMS `Session` from which the `Message` was received.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package org.springframework.jms.listener;
|
|
|
|
public interface SessionAwareMessageListener {
|
|
|
|
void onMessage(Message message, Session session) throws JMSException;
|
|
}
|
|
----
|
|
|
|
You can choose to have your MDPs implement this interface (in preference to the standard
|
|
JMS `MessageListener` interface) if you want your MDPs to be able to respond to any
|
|
received messages (using the `Session` supplied in the `onMessage(Message, Session)`
|
|
method). All of the message listener container implementations that ship with Spring
|
|
have support for MDPs that implement either the `MessageListener` or
|
|
`SessionAwareMessageListener` interface. Classes that implement the
|
|
`SessionAwareMessageListener` come with the caveat that they are then tied to Spring
|
|
through the interface. The choice of whether or not to use it is left entirely up to you
|
|
as an application developer or architect.
|
|
|
|
Please note that the `'onMessage(..)'` method of the `SessionAwareMessageListener`
|
|
interface throws `JMSException`. In contrast to the standard JMS `MessageListener`
|
|
interface, when using the `SessionAwareMessageListener` interface, it is the
|
|
responsibility of the client code to handle any exceptions thrown.
|
|
|
|
|
|
[[jms-receiving-async-message-listener-adapter]]
|
|
==== MessageListenerAdapter
|
|
|
|
The `MessageListenerAdapter` class is the final component in Spring's asynchronous
|
|
messaging support: in a nutshell, it allows you to expose almost __any__ class as a MDP
|
|
(there are of course some constraints).
|
|
|
|
Consider the following interface definition. Notice that although the interface extends
|
|
neither the `MessageListener` nor `SessionAwareMessageListener` interfaces, it can still
|
|
be used as a MDP via the use of the `MessageListenerAdapter` class. Notice also how the
|
|
various message handling methods are strongly typed according to the __contents__ of the
|
|
various `Message` types that they can receive and handle.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface MessageDelegate {
|
|
|
|
void handleMessage(String message);
|
|
|
|
void handleMessage(Map message);
|
|
|
|
void handleMessage(byte[] message);
|
|
|
|
void handleMessage(Serializable message);
|
|
}
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class DefaultMessageDelegate implements MessageDelegate {
|
|
// implementation elided for clarity...
|
|
}
|
|
----
|
|
|
|
In particular, note how the above implementation of the `MessageDelegate` interface (the
|
|
above `DefaultMessageDelegate` class) has __no__ JMS dependencies at all. It truly is a
|
|
POJO that we will make into an MDP via the following configuration.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<!-- this is the Message Driven POJO (MDP) -->
|
|
**<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
|
<constructor-arg>
|
|
<bean class="jmsexample.DefaultMessageDelegate"/>
|
|
</constructor-arg>
|
|
</bean>**
|
|
|
|
<!-- and this is the message listener container... -->
|
|
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destination" ref="destination"/>
|
|
**<property name="messageListener" ref="messageListener"/>**
|
|
</bean>
|
|
----
|
|
|
|
Below is an example of another MDP that can only handle the receiving of JMS
|
|
`TextMessage` messages. Notice how the message handling method is actually called
|
|
`'receive'` (the name of the message handling method in a `MessageListenerAdapter`
|
|
defaults to `'handleMessage'`), but it is configurable (as you will see below). Notice
|
|
also how the `'receive(..)'` method is strongly typed to receive and respond only to JMS
|
|
`TextMessage` messages.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface TextMessageDelegate {
|
|
|
|
void receive(TextMessage message);
|
|
}
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class DefaultTextMessageDelegate implements TextMessageDelegate {
|
|
// implementation elided for clarity...
|
|
}
|
|
----
|
|
|
|
The configuration of the attendant `MessageListenerAdapter` would look like this:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
|
<constructor-arg>
|
|
<bean class="jmsexample.DefaultTextMessageDelegate"/>
|
|
</constructor-arg>
|
|
<property name="defaultListenerMethod" value="receive"/>
|
|
<!-- we don't want automatic message context extraction -->
|
|
<property name="messageConverter">
|
|
<null/>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
Please note that if the above `'messageListener'` receives a JMS `Message` of a type
|
|
other than `TextMessage`, an `IllegalStateException` will be thrown (and subsequently
|
|
swallowed). Another of the capabilities of the `MessageListenerAdapter` class is the
|
|
ability to automatically send back a response `Message` if a handler method returns a
|
|
non-void value. Consider the interface and class:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface ResponsiveTextMessageDelegate {
|
|
|
|
// notice the return type...
|
|
String receive(TextMessage message);
|
|
}
|
|
----
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
|
|
// implementation elided for clarity...
|
|
}
|
|
----
|
|
|
|
If the above `DefaultResponsiveTextMessageDelegate` is used in conjunction with a
|
|
`MessageListenerAdapter` then any non-null value that is returned from the execution of
|
|
the `'receive(..)'` method will (in the default configuration) be converted into a
|
|
`TextMessage`. The resulting `TextMessage` will then be sent to the `Destination` (if
|
|
one exists) defined in the JMS Reply-To property of the original `Message`, or the
|
|
default `Destination` set on the `MessageListenerAdapter` (if one has been configured);
|
|
if no `Destination` is found then an `InvalidDestinationException` will be thrown (and
|
|
please note that this exception __will not__ be swallowed and __will__ propagate up the
|
|
call stack).
|
|
|
|
|
|
[[jms-tx-participation]]
|
|
==== Processing messages within transactions
|
|
|
|
Invoking a message listener within a transaction only requires reconfiguration of the
|
|
listener container.
|
|
|
|
Local resource transactions can simply be activated through the `sessionTransacted` flag
|
|
on the listener container definition. Each message listener invocation will then operate
|
|
within an active JMS transaction, with message reception rolled back in case of listener
|
|
execution failure. Sending a response message (via `SessionAwareMessageListener`) will
|
|
be part of the same local transaction, but any other resource operations (such as
|
|
database access) will operate independently. This usually requires duplicate message
|
|
detection in the listener implementation, covering the case where database processing
|
|
has committed but message processing failed to commit.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destination" ref="destination"/>
|
|
<property name="messageListener" ref="messageListener"/>
|
|
**<property name="sessionTransacted" value="true"/>**
|
|
</bean>
|
|
----
|
|
|
|
For participating in an externally managed transaction, you will need to configure a
|
|
transaction manager and use a listener container which supports externally managed
|
|
transactions: typically `DefaultMessageListenerContainer`.
|
|
|
|
To configure a message listener container for XA transaction participation, you'll want
|
|
to configure a `JtaTransactionManager` (which, by default, delegates to the Java EE
|
|
server's transaction subsystem). Note that the underlying JMS ConnectionFactory needs to
|
|
be XA-capable and properly registered with your JTA transaction coordinator! (Check your
|
|
Java EE server's configuration of JNDI resources.) This allows message reception as well
|
|
as e.g. database access to be part of the same transaction (with unified commit
|
|
semantics, at the expense of XA transaction log overhead).
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
|
|
----
|
|
|
|
Then you just need to add it to our earlier container configuration. The container will
|
|
take care of the rest.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destination" ref="destination"/>
|
|
<property name="messageListener" ref="messageListener"/>
|
|
**<property name="transactionManager" ref="transactionManager"/>**
|
|
</bean>
|
|
----
|
|
|
|
|
|
|
|
[[jms-jca-message-endpoint-manager]]
|
|
=== Support for JCA Message Endpoints
|
|
|
|
Beginning with version 2.5, Spring also provides support for a JCA-based
|
|
`MessageListener` container. The `JmsMessageEndpointManager` will attempt to
|
|
automatically determine the `ActivationSpec` class name from the provider's
|
|
`ResourceAdapter` class name. Therefore, it is typically possible to just provide
|
|
Spring's generic `JmsActivationSpecConfig` as shown in the following example.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
|
<property name="resourceAdapter" ref="resourceAdapter"/>
|
|
<property name="activationSpecConfig">
|
|
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
|
|
<property name="destinationName" value="myQueue"/>
|
|
</bean>
|
|
</property>
|
|
<property name="messageListener" ref="myMessageListener"/>
|
|
</bean>
|
|
----
|
|
|
|
Alternatively, you may set up a `JmsMessageEndpointManager` with a given
|
|
`ActivationSpec` object. The `ActivationSpec` object may also come from a JNDI lookup
|
|
(using `<jee:jndi-lookup>`).
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
|
<property name="resourceAdapter" ref="resourceAdapter"/>
|
|
<property name="activationSpec">
|
|
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
|
|
<property name="destination" value="myQueue"/>
|
|
<property name="destinationType" value="javax.jms.Queue"/>
|
|
</bean>
|
|
</property>
|
|
<property name="messageListener" ref="myMessageListener"/>
|
|
</bean>
|
|
----
|
|
|
|
Using Spring's `ResourceAdapterFactoryBean`, the target `ResourceAdapter` may be
|
|
configured locally as depicted in the following example.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
|
|
<property name="resourceAdapter">
|
|
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
|
|
<property name="serverUrl" value="tcp://localhost:61616"/>
|
|
</bean>
|
|
</property>
|
|
<property name="workManager">
|
|
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
The specified `WorkManager` may also point to an environment-specific thread pool -
|
|
typically through ``SimpleTaskWorkManager``'s "asyncTaskExecutor" property. Consider
|
|
defining a shared thread pool for all your `ResourceAdapter` instances if you happen to
|
|
use multiple adapters.
|
|
|
|
In some environments (e.g. WebLogic 9 or above), the entire `ResourceAdapter` object may
|
|
be obtained from JNDI instead (using `<jee:jndi-lookup>`). The Spring-based message
|
|
listeners can then interact with the server-hosted `ResourceAdapter`, also using the
|
|
server's built-in `WorkManager`.
|
|
|
|
Please consult the javadoc for `JmsMessageEndpointManager`, `JmsActivationSpecConfig`,
|
|
and `ResourceAdapterFactoryBean` for more details.
|
|
|
|
Spring also provides a generic JCA message endpoint manager which is not tied to JMS:
|
|
`org.springframework.jca.endpoint.GenericMessageEndpointManager`. This component allows
|
|
for using any message listener type (e.g. a CCI MessageListener) and any
|
|
provider-specific ActivationSpec object. Check out your JCA provider's documentation to
|
|
find out about the actual capabilities of your connector, and consult
|
|
``GenericMessageEndpointManager``'s javadoc for the Spring-specific configuration details.
|
|
|
|
[NOTE]
|
|
====
|
|
JCA-based message endpoint management is very analogous to EJB 2.1 Message-Driven Beans;
|
|
it uses the same underlying resource provider contract. Like with EJB 2.1 MDBs, any
|
|
message listener interface supported by your JCA provider can be used in the Spring
|
|
context as well. Spring nevertheless provides explicit 'convenience' support for JMS,
|
|
simply because JMS is the most common endpoint API used with the JCA endpoint management
|
|
contract.
|
|
====
|
|
|
|
|
|
|
|
[[jms-annotated]]
|
|
=== Annotation-driven listener endpoints
|
|
|
|
The easiest way to receive a message asynchronously is to use the annotated listener
|
|
endpoint infrastructure. In a nutshell, it allows you to expose a method of a managed
|
|
bean as a JMS listener endpoint.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Component
|
|
public class MyService {
|
|
|
|
@JmsListener(destination = "myDestination")
|
|
public void processOrder(String data) { ... }
|
|
}
|
|
----
|
|
|
|
The idea of the example above is that whenever a message is available on the
|
|
`javax.jms.Destination` "myDestination", the `processOrder` method is invoked
|
|
accordingly (in this case, with the content of the JMS message similarly to
|
|
what the <<jms-receiving-async-message-listener-adapter, `MessageListenerAdapter`>>
|
|
provides).
|
|
|
|
The annotated endpoint infrastructure creates a message listener container
|
|
behind the scenes for each annotated method, using a `JmsListenerContainerFactory`.
|
|
Such a container is not registered against the application context but can be easily
|
|
located for management purposes using the `JmsListenerEndpointRegistry` bean.
|
|
|
|
[TIP]
|
|
====
|
|
`@JmsListener` is a _repeatable_ annotation on Java 8, so it is possible to associate
|
|
several JMS destinations to the same method by adding additional `@JmsListener`
|
|
declarations to it.
|
|
====
|
|
|
|
|
|
[[jms-annotated-support]]
|
|
==== Enable listener endpoint annotations
|
|
|
|
To enable support for `@JmsListener` annotations add `@EnableJms` to one of
|
|
your `@Configuration` classes.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableJms
|
|
public class AppConfig {
|
|
|
|
@Bean
|
|
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
|
|
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
|
factory.setConnectionFactory(connectionFactory());
|
|
factory.setDestinationResolver(destinationResolver());
|
|
factory.setSessionTransacted(true);
|
|
factory.setConcurrency("3-10");
|
|
return factory;
|
|
}
|
|
}
|
|
----
|
|
|
|
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
|
|
as the source for the factory to use to create message listener containers. In this
|
|
case, and ignoring the JMS infrastructure setup, the `processOrder` method can be
|
|
invoked with a core poll size of 3 threads and a maximum pool size of 10 threads.
|
|
|
|
It is possible to customize the listener container factory to use per annotation or
|
|
an explicit default can be configured by implementing the `JmsListenerConfigurer`
|
|
interface. The default is only required if at least one endpoint is registered
|
|
without a specific container factory. See the javadoc for full details and examples.
|
|
|
|
If you prefer <<jms-namespace,XML configuration>> use the `<jms:annotation-driven>`
|
|
element.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jms:annotation-driven/>
|
|
|
|
<bean id="jmsListenerContainerFactory"
|
|
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
<property name="destinationResolver" ref="destinationResolver"/>
|
|
<property name="sessionTransacted" value="true"/>
|
|
<property name="concurrency" value="3-10"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[jms-annotated-programmatic-registration]]
|
|
==== Programmatic endpoints registration
|
|
|
|
`JmsListenerEndpoint` provides a model of an JMS endpoint and is responsible for configuring
|
|
the container for that model. The infrastructure allows you to configure endpoints
|
|
programmatically in addition to the ones that are detected by the `JmsListener` annotation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableJms
|
|
public class AppConfig implements JmsListenerConfigurer {
|
|
|
|
@Override
|
|
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
|
|
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
|
|
endpoint.setId("myJmsEndpoint");
|
|
endpoint.setDestination("anotherQueue");
|
|
endpoint.setMessageListener(message -> {
|
|
// processing
|
|
});
|
|
registrar.registerEndpoint(endpoint);
|
|
}
|
|
}
|
|
----
|
|
|
|
In the example above, we used `SimpleJmsListenerEndpoint` which provides the actual
|
|
`MessageListener` to invoke but you could just as well build your own endpoint variant
|
|
describing a custom invocation mechanism.
|
|
|
|
It should be noted that you could just as well skip the use of `@JmsListener` altogether
|
|
and only register your endpoints programmatically through `JmsListenerConfigurer`.
|
|
|
|
|
|
[[jms-annotated-method-signature]]
|
|
==== Annotated endpoint method signature
|
|
|
|
So far, we have been injecting a simple `String` in our endpoint but it can actually
|
|
have a very flexible method signature. Let's rewrite it to inject the `Order` with
|
|
a custom header:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Component
|
|
public class MyService {
|
|
|
|
@JmsListener(destination = "myDestination")
|
|
public void processOrder(Order order, @Header("order_type") String orderType) {
|
|
...
|
|
}
|
|
}
|
|
----
|
|
|
|
These are the main elements you can inject in JMS listener endpoints:
|
|
|
|
* The raw `javax.jms.Message` or any of its subclasses (provided of course that it
|
|
matches the incoming message type).
|
|
* The `javax.jms.Session` for optional access to the native JMS API e.g. for sending
|
|
a custom reply.
|
|
* The `org.springframework.messaging.Message` representing the incoming JMS message.
|
|
Note that this message holds both the custom and the standard headers (as defined
|
|
by `JmsHeaders`).
|
|
* `@Header`-annotated method arguments to extract a specific header value, including
|
|
standard JMS headers.
|
|
* `@Headers`-annotated argument that must also be assignable to `java.util.Map` for
|
|
getting access to all headers.
|
|
* A non-annotated element that is not one of the supported types (i.e. `Message` and
|
|
`Session`) is considered to be the payload. You can make that explicit by annotating
|
|
the parameter with `@Payload`. You can also turn on validation by adding an extra
|
|
`@Valid`.
|
|
|
|
The ability to inject Spring's `Message` abstraction is particularly useful to benefit
|
|
from all the information stored in the transport-specific message without relying on
|
|
transport-specific API.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@JmsListener(destination = "myDestination")
|
|
public void processOrder(Message<Order> order) { ... }
|
|
----
|
|
|
|
Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory` which can be
|
|
further customized to support additional method arguments. The conversion and validation
|
|
support can be customized there as well.
|
|
|
|
For instance, if we want to make sure our `Order` is valid before processing it, we can
|
|
annotate the payload with `@Valid` and configure the necessary validator as follows:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableJms
|
|
public class AppConfig implements JmsListenerConfigurer {
|
|
|
|
@Override
|
|
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
|
|
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
|
|
}
|
|
|
|
@Bean
|
|
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
|
|
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
|
|
factory.setValidator(myValidator());
|
|
return factory;
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
[[jms-annotated-response]]
|
|
==== Response management
|
|
|
|
The existing support in <<jms-receiving-async-message-listener-adapter,MessageListenerAdapter>>
|
|
already allows your method to have a non-`void` return type. When that's the case, the result of
|
|
the invocation is encapsulated in a `javax.jms.Message` sent either in the destination specified
|
|
in the `JMSReplyTo` header of the original message or in the default destination configured on
|
|
the listener. That default destination can now be set using the `@SendTo` annotation of the
|
|
messaging abstraction.
|
|
|
|
Assuming our `processOrder` method should now return an `OrderStatus`, it is possible to write it
|
|
as follow to automatically send a response:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@JmsListener(destination = "myDestination")
|
|
@SendTo("status")
|
|
public OrderStatus processOrder(Order order) {
|
|
// order processing
|
|
return status;
|
|
}
|
|
----
|
|
|
|
[TIP]
|
|
====
|
|
If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo`
|
|
annotation at the class level to share a default reply destination.
|
|
====
|
|
|
|
If you need to set additional headers in a transport-independent manner, you could return a
|
|
`Message` instead, something like:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@JmsListener(destination = "myDestination")
|
|
@SendTo("status")
|
|
public Message<OrderStatus> processOrder(Order order) {
|
|
// order processing
|
|
return MessageBuilder
|
|
.withPayload(status)
|
|
.setHeader("code", 1234)
|
|
.build();
|
|
}
|
|
----
|
|
|
|
If you need to compute the response destination at runtime, you can encapsulate your response
|
|
in a `JmsResponse` instance that also provides the destination to use at runtime. The previous
|
|
example can be rewritten as follows:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@JmsListener(destination = "myDestination")
|
|
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
|
|
// order processing
|
|
Message<OrderStatus> response = MessageBuilder
|
|
.withPayload(status)
|
|
.setHeader("code", 1234)
|
|
.build();
|
|
return JmsResponse.forQueue(response, "status");
|
|
}
|
|
----
|
|
|
|
Finally if you need to specify some QoS values for the response such as the priority or
|
|
the time to live, you can configure the `JmsListenerContainerFactory` accordingly:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableJms
|
|
public class AppConfig {
|
|
|
|
@Bean
|
|
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
|
|
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
|
factory.setConnectionFactory(connectionFactory());
|
|
QosSettings replyQosSettings = new QosSettings();
|
|
replyQosSettings.setPriority(2);
|
|
replyQosSettings.setTimeToLive(10000);
|
|
factory.setReplyQosSettings(replyQosSettings);
|
|
return factory;
|
|
}
|
|
}
|
|
----
|
|
|
|
|
|
|
|
[[jms-namespace]]
|
|
=== JMS namespace support
|
|
|
|
Spring provides an XML namespace for simplifying JMS configuration. To use the JMS
|
|
namespace elements you will need to reference the JMS schema:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
**xmlns:jms="http://www.springframework.org/schema/jms"**
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
|
**http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd**">
|
|
|
|
<!-- bean definitions here -->
|
|
|
|
</beans>
|
|
----
|
|
|
|
The namespace consists of three top-level elements: `<annotation-driven/>`, `<listener-container/>`
|
|
and `<jca-listener-container/>`. `<annotation-driven` enables the use of <<jms-annotated,
|
|
annotation-driven listener endpoints>>. `<listener-container/>` and `<jca-listener-container/>`
|
|
defines shared listener container configuration and may contain `<listener/>` child elements. Here
|
|
is an example of a basic configuration for two listeners.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jms:listener-container>
|
|
|
|
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
|
|
|
|
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
|
|
|
|
</jms:listener-container>
|
|
----
|
|
|
|
The example above is equivalent to creating two distinct listener container bean
|
|
definitions and two distinct `MessageListenerAdapter` bean definitions as demonstrated
|
|
in <<jms-receiving-async-message-listener-adapter>>. In addition to the attributes shown
|
|
above, the `listener` element may contain several optional ones. The following table
|
|
describes all available attributes:
|
|
|
|
[[jms-namespace-listener-tbl]]
|
|
.Attributes of the JMS <listener> element
|
|
[cols="1,6"]
|
|
|===
|
|
| Attribute | Description
|
|
|
|
| id
|
|
| A bean name for the hosting listener container. If not specified, a bean name will be
|
|
automatically generated.
|
|
|
|
| destination __(required)__
|
|
| The destination name for this listener, resolved through the `DestinationResolver`
|
|
strategy.
|
|
|
|
| ref __(required)__
|
|
| The bean name of the handler object.
|
|
|
|
| method
|
|
| The name of the handler method to invoke. If the `ref` points to a `MessageListener`
|
|
or Spring `SessionAwareMessageListener`, this attribute may be omitted.
|
|
|
|
| response-destination
|
|
| The name of the default response destination to send response messages to. This will
|
|
be applied in case of a request message that does not carry a "JMSReplyTo" field. The
|
|
type of this destination will be determined by the listener-container's
|
|
"response-destination-type" attribute. Note: This only applies to a listener method with a
|
|
return value, for which each result object will be converted into a response message.
|
|
|
|
| subscription
|
|
| The name of the durable subscription, if any.
|
|
|
|
| selector
|
|
| An optional message selector for this listener.
|
|
|
|
| concurrency
|
|
| The number of concurrent sessions/consumers to start for this listener. Can either be
|
|
a simple number indicating the maximum number (e.g. "5") or a range indicating the lower
|
|
as well as the upper limit (e.g. "3-5"). Note that a specified minimum is just a hint
|
|
and might be ignored at runtime. Default is the value provided by the container
|
|
|===
|
|
|
|
The `<listener-container/>` element also accepts several optional attributes. This
|
|
allows for customization of the various strategies (for example, `taskExecutor` and
|
|
`destinationResolver`) as well as basic JMS settings and resource references. Using
|
|
these attributes, it is possible to define highly-customized listener containers while
|
|
still benefiting from the convenience of the namespace.
|
|
|
|
Such settings can be automatically exposed as a `JmsListenerContainerFactory` by
|
|
specifying the id of the bean to expose through the `factory-id` attribute.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jms:listener-container connection-factory="myConnectionFactory"
|
|
task-executor="myTaskExecutor"
|
|
destination-resolver="myDestinationResolver"
|
|
transaction-manager="myTransactionManager"
|
|
concurrency="10">
|
|
|
|
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
|
|
|
|
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
|
|
|
|
</jms:listener-container>
|
|
----
|
|
|
|
The following table describes all available attributes. Consult the class-level javadocs
|
|
of the `AbstractMessageListenerContainer` and its concrete subclasses for more details
|
|
on the individual properties. The javadocs also provide a discussion of transaction
|
|
choices and message redelivery scenarios.
|
|
|
|
[[jms-namespace-listener-container-tbl]]
|
|
.Attributes of the JMS <listener-container> element
|
|
[cols="1,6"]
|
|
|===
|
|
| Attribute | Description
|
|
|
|
| container-type
|
|
| The type of this listener container. Available options are: `default`, `simple`,
|
|
`default102`, or `simple102` (the default value is `'default'`).
|
|
|
|
| container-class
|
|
| A custom listener container implementation class as fully qualified class name.
|
|
Default is Spring's standard `DefaultMessageListenerContainer` or
|
|
`SimpleMessageListenerContainer`, according to the "container-type" attribute.
|
|
|
|
| factory-id
|
|
| Exposes the settings defined by this element as a `JmsListenerContainerFactory`
|
|
with the specified id so that they can be reused with other endpoints.
|
|
|
|
| connection-factory
|
|
| A reference to the JMS `ConnectionFactory` bean (the default bean name is
|
|
`'connectionFactory'`).
|
|
|
|
| task-executor
|
|
| A reference to the Spring `TaskExecutor` for the JMS listener invokers.
|
|
|
|
| destination-resolver
|
|
| A reference to the `DestinationResolver` strategy for resolving JMS `Destinations`.
|
|
|
|
| message-converter
|
|
| A reference to the `MessageConverter` strategy for converting JMS Messages to listener
|
|
method arguments. Default is a `SimpleMessageConverter`.
|
|
|
|
| error-handler
|
|
| A reference to an `ErrorHandler` strategy for handling any uncaught Exceptions that
|
|
may occur during the execution of the `MessageListener`.
|
|
|
|
| destination-type
|
|
| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`
|
|
or `sharedDurableTopic`. This enables potentially the `pubSubDomain`, `subscriptionDurable`
|
|
and `subscriptionShared` properties of the container. The default is `queue` (i.e. disabling
|
|
those 3 properties).
|
|
|
|
| response-destination-type
|
|
| The JMS destination type for responses: "queue", "topic". Default is the value of the
|
|
"destination-type" attribute.
|
|
|
|
| client-id
|
|
| The JMS client id for this listener container. Needs to be specified when using
|
|
durable subscriptions.
|
|
|
|
| cache
|
|
| The cache level for JMS resources: `none`, `connection`, `session`, `consumer` or
|
|
`auto`. By default ( `auto`), the cache level will effectively be "consumer", unless
|
|
an external transaction manager has been specified - in which case the effective
|
|
default will be `none` (assuming Java EE-style transaction management where the given
|
|
ConnectionFactory is an XA-aware pool).
|
|
|
|
| acknowledge
|
|
| The native JMS acknowledge mode: `auto`, `client`, `dups-ok` or `transacted`. A value
|
|
of `transacted` activates a locally transacted `Session`. As an alternative, specify
|
|
the `transaction-manager` attribute described below. Default is `auto`.
|
|
|
|
| transaction-manager
|
|
| A reference to an external `PlatformTransactionManager` (typically an XA-based
|
|
transaction coordinator, e.g. Spring's `JtaTransactionManager`). If not specified,
|
|
native acknowledging will be used (see "acknowledge" attribute).
|
|
|
|
| concurrency
|
|
| The number of concurrent sessions/consumers to start for each listener. Can either be
|
|
a simple number indicating the maximum number (e.g. "5") or a range indicating the
|
|
lower as well as the upper limit (e.g. "3-5"). Note that a specified minimum is just a
|
|
hint and might be ignored at runtime. Default is 1; keep concurrency limited to 1 in
|
|
case of a topic listener or if queue ordering is important; consider raising it for
|
|
general queues.
|
|
|
|
| prefetch
|
|
| The maximum number of messages to load into a single session. Note that raising this
|
|
number might lead to starvation of concurrent consumers!
|
|
|
|
| receive-timeout
|
|
| The timeout to use for receive calls (in milliseconds). The default is `1000` ms (1
|
|
sec); `-1` indicates no timeout at all.
|
|
|
|
| back-off
|
|
| Specify the `BackOff` instance to use to compute the interval between recovery
|
|
attempts. If the `BackOffExecution` implementation returns `BackOffExecution#STOP`,
|
|
the listener container will not further attempt to recover. The `recovery-interval`
|
|
value is ignored when this property is set. The default is a `FixedBackOff` with
|
|
an interval of 5000 ms, that is 5 seconds.
|
|
|
|
| recovery-interval
|
|
| Specify the interval between recovery attempts, in milliseconds. Convenience
|
|
way to create a `FixedBackOff` with the specified interval. For more recovery
|
|
options, consider specifying a BackOff instance instead. The default is 5000 ms,
|
|
that is 5 seconds.
|
|
|
|
| phase
|
|
| The lifecycle phase within which this container should start and stop. The lower the
|
|
value the earlier this container will start and the later it will stop. The default is
|
|
`Integer.MAX_VALUE` meaning the container will start as late as possible and stop as
|
|
soon as possible.
|
|
|===
|
|
|
|
Configuring a JCA-based listener container with the "jms" schema support is very similar.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jms:jca-listener-container resource-adapter="myResourceAdapter"
|
|
destination-resolver="myDestinationResolver"
|
|
transaction-manager="myTransactionManager"
|
|
concurrency="10">
|
|
|
|
<jms:listener destination="queue.orders" ref="myMessageListener"/>
|
|
|
|
</jms:jca-listener-container>
|
|
----
|
|
|
|
The available configuration options for the JCA variant are described in the following
|
|
table:
|
|
|
|
[[jms-namespace-jca-listener-container-tbl]]
|
|
.Attributes of the JMS <jca-listener-container/> element
|
|
[cols="1,6"]
|
|
|===
|
|
| Attribute | Description
|
|
|
|
| factory-id
|
|
| Exposes the settings defined by this element as a `JmsListenerContainerFactory`
|
|
with the specified id so that they can be reused with other endpoints.
|
|
|
|
| resource-adapter
|
|
| A reference to the JCA `ResourceAdapter` bean (the default bean name is
|
|
`'resourceAdapter'`).
|
|
|
|
| activation-spec-factory
|
|
| A reference to the `JmsActivationSpecFactory`. The default is to autodetect the JMS
|
|
provider and its `ActivationSpec` class (see `DefaultJmsActivationSpecFactory`)
|
|
|
|
| destination-resolver
|
|
| A reference to the `DestinationResolver` strategy for resolving JMS `Destinations`.
|
|
|
|
| message-converter
|
|
| A reference to the `MessageConverter` strategy for converting JMS Messages to listener
|
|
method arguments. Default is a `SimpleMessageConverter`.
|
|
|
|
| destination-type
|
|
| The JMS destination type for this listener: `queue`, `topic`, `durableTopic`, `sharedTopic`
|
|
or `sharedDurableTopic`. This enables potentially the `pubSubDomain`, `subscriptionDurable`
|
|
and `subscriptionShared` properties of the container. The default is `queue` (i.e. disabling
|
|
those 3 properties).
|
|
|
|
| response-destination-type
|
|
| The JMS destination type for responses: "queue", "topic". Default is the value of the
|
|
"destination-type" attribute.
|
|
|
|
| client-id
|
|
| The JMS client id for this listener container. Needs to be specified when using
|
|
durable subscriptions.
|
|
|
|
| acknowledge
|
|
| The native JMS acknowledge mode: `auto`, `client`, `dups-ok` or `transacted`. A value
|
|
of `transacted` activates a locally transacted `Session`. As an alternative, specify
|
|
the `transaction-manager` attribute described below. Default is `auto`.
|
|
|
|
| transaction-manager
|
|
| A reference to a Spring `JtaTransactionManager` or a
|
|
`javax.transaction.TransactionManager` for kicking off an XA transaction for each
|
|
incoming message. If not specified, native acknowledging will be used (see the
|
|
"acknowledge" attribute).
|
|
|
|
| concurrency
|
|
| The number of concurrent sessions/consumers to start for each listener. Can either be
|
|
a simple number indicating the maximum number (e.g. "5") or a range indicating the
|
|
lower as well as the upper limit (e.g. "3-5"). Note that a specified minimum is just a
|
|
hint and will typically be ignored at runtime when using a JCA listener container.
|
|
Default is 1.
|
|
|
|
| prefetch
|
|
| The maximum number of messages to load into a single session. Note that raising this
|
|
number might lead to starvation of concurrent consumers!
|
|
|===
|
|
|
|
|
|
|
|
|
|
[[jmx]]
|
|
== JMX
|
|
|
|
|
|
|
|
[[jmx-introduction]]
|
|
=== Introduction
|
|
|
|
The JMX support in Spring provides you with the features to easily and transparently
|
|
integrate your Spring application into a JMX infrastructure.
|
|
|
|
.JMX?
|
|
****
|
|
This chapter is not an introduction to JMX... it doesn't try to explain the motivations
|
|
of why one might want to use JMX (or indeed what the letters JMX actually stand for). If
|
|
you are new to JMX, check out <<jmx-resources>> at the end of this chapter.
|
|
****
|
|
|
|
Specifically, Spring's JMX support provides four core features:
|
|
|
|
* The automatic registration of __any__ Spring bean as a JMX MBean
|
|
* A flexible mechanism for controlling the management interface of your beans
|
|
* The declarative exposure of MBeans over remote, JSR-160 connectors
|
|
* The simple proxying of both local and remote MBean resources
|
|
|
|
These features are designed to work without coupling your application components to
|
|
either Spring or JMX interfaces and classes. Indeed, for the most part your application
|
|
classes need not be aware of either Spring or JMX in order to take advantage of the
|
|
Spring JMX features.
|
|
|
|
|
|
|
|
[[jmx-exporting]]
|
|
=== Exporting your beans to JMX
|
|
|
|
The core class in Spring's JMX framework is the `MBeanExporter`. This class is
|
|
responsible for taking your Spring beans and registering them with a JMX `MBeanServer`.
|
|
For example, consider the following class:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package org.springframework.jmx;
|
|
|
|
public class JmxTestBean implements IJmxTestBean {
|
|
|
|
private String name;
|
|
private int age;
|
|
private boolean isSuperman;
|
|
|
|
public int getAge() {
|
|
return age;
|
|
}
|
|
|
|
public void setAge(int age) {
|
|
this.age = age;
|
|
}
|
|
|
|
public void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
public int add(int x, int y) {
|
|
return x + y;
|
|
}
|
|
|
|
public void dontExposeMe() {
|
|
throw new RuntimeException();
|
|
}
|
|
}
|
|
----
|
|
|
|
To expose the properties and methods of this bean as attributes and operations of an
|
|
MBean you simply configure an instance of the `MBeanExporter` class in your
|
|
configuration file and pass in the bean as shown below:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
<!-- this bean must not be lazily initialized if the exporting is to happen -->
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
</bean>
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
</beans>
|
|
----
|
|
|
|
The pertinent bean definition from the above configuration snippet is the `exporter`
|
|
bean. The `beans` property tells the `MBeanExporter` exactly which of your beans must be
|
|
exported to the JMX `MBeanServer`. In the default configuration, the key of each entry
|
|
in the `beans` `Map` is used as the `ObjectName` for the bean referenced by the
|
|
corresponding entry value. This behavior can be changed as described in <<jmx-naming>>.
|
|
|
|
With this configuration the `testBean` bean is exposed as an MBean under the
|
|
`ObjectName` `bean:name=testBean1`. By default, all __public__ properties of the bean
|
|
are exposed as attributes and all __public__ methods (bar those inherited from the
|
|
`Object` class) are exposed as operations.
|
|
|
|
[NOTE]
|
|
====
|
|
`MBeanExporter` is a `Lifecycle` bean (see <<core.adoc#beans-factory-lifecycle-processor,
|
|
Startup and shutdown callbacks>>) and MBeans are exported as late as possible during
|
|
the application lifecycle by default. It is possible to configure the `phase` at which
|
|
the export happens or disable automatic registration by setting the `autoStartup` flag.
|
|
====
|
|
|
|
|
|
[[jmx-exporting-mbeanserver]]
|
|
==== Creating an MBeanServer
|
|
|
|
The above configuration assumes that the application is running in an environment that
|
|
has one (and only one) `MBeanServer` already running. In this case, Spring will attempt
|
|
to locate the running `MBeanServer` and register your beans with that server (if any).
|
|
This behavior is useful when your application is running inside a container such as
|
|
Tomcat or IBM WebSphere that has its own `MBeanServer`.
|
|
|
|
However, this approach is of no use in a standalone environment, or when running inside
|
|
a container that does not provide an `MBeanServer`. To address this you can create an
|
|
`MBeanServer` instance declaratively by adding an instance of the
|
|
`org.springframework.jmx.support.MBeanServerFactoryBean` class to your configuration.
|
|
You can also ensure that a specific `MBeanServer` is used by setting the value of the
|
|
``MBeanExporter``'s `server` property to the `MBeanServer` value returned by an
|
|
`MBeanServerFactoryBean`; for example:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
|
|
|
|
<!--
|
|
this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
|
|
this means that it must not be marked as lazily initialized
|
|
-->
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="server" ref="mbeanServer"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
Here an instance of `MBeanServer` is created by the `MBeanServerFactoryBean` and is
|
|
supplied to the `MBeanExporter` via the server property. When you supply your own
|
|
`MBeanServer` instance, the `MBeanExporter` will not attempt to locate a running
|
|
`MBeanServer` and will use the supplied `MBeanServer` instance. For this to work
|
|
correctly, you must (of course) have a JMX implementation on your classpath.
|
|
|
|
|
|
[[jmx-mbean-server]]
|
|
==== Reusing an existing MBeanServer
|
|
|
|
If no server is specified, the `MBeanExporter` tries to automatically detect a running
|
|
`MBeanServer`. This works in most environment where only one `MBeanServer` instance is
|
|
used, however when multiple instances exist, the exporter might pick the wrong server.
|
|
In such cases, one should use the `MBeanServer` `agentId` to indicate which instance to
|
|
be used:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
|
|
<!-- indicate to first look for a server -->
|
|
<property name="locateExistingServerIfPossible" value="true"/>
|
|
<!-- search for the MBeanServer instance with the given agentId -->
|
|
<property name="agentId" value="MBeanServer_instance_agentId>"/>
|
|
</bean>
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="server" ref="mbeanServer"/>
|
|
...
|
|
</bean>
|
|
</beans>
|
|
----
|
|
|
|
For platforms/cases where the existing `MBeanServer` has a dynamic (or unknown)
|
|
`agentId` which is retrieved through lookup methods, one should use
|
|
<<core.adoc#beans-factory-class-static-factory-method,factory-method>>:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="server">
|
|
<!-- Custom MBeanServerLocator -->
|
|
<bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
|
|
</property>
|
|
</bean>
|
|
|
|
<!-- other beans here -->
|
|
|
|
</beans>
|
|
----
|
|
|
|
|
|
[[jmx-exporting-lazy]]
|
|
==== Lazy-initialized MBeans
|
|
|
|
If you configure a bean with the `MBeanExporter` that is also configured for lazy
|
|
initialization, then the `MBeanExporter` will __not__ break this contract and will avoid
|
|
instantiating the bean. Instead, it will register a proxy with the `MBeanServer` and
|
|
will defer obtaining the bean from the container until the first invocation on the proxy
|
|
occurs.
|
|
|
|
|
|
[[jmx-exporting-auto]]
|
|
==== Automatic registration of MBeans
|
|
|
|
Any beans that are exported through the `MBeanExporter` and are already valid MBeans are
|
|
registered as-is with the `MBeanServer` without further intervention from Spring. MBeans
|
|
can be automatically detected by the `MBeanExporter` by setting the `autodetect`
|
|
property to `true`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="autodetect" value="true"/>
|
|
</bean>
|
|
|
|
<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>
|
|
----
|
|
|
|
Here, the bean called `spring:mbean=true` is already a valid JMX MBean and will be
|
|
automatically registered by Spring. By default, beans that are autodetected for JMX
|
|
registration have their bean name used as the `ObjectName`. This behavior can be
|
|
overridden as detailed in <<jmx-naming>>.
|
|
|
|
|
|
[[jmx-exporting-registration-behavior]]
|
|
==== Controlling the registration behavior
|
|
|
|
Consider the scenario where a Spring `MBeanExporter` attempts to register an `MBean`
|
|
with an `MBeanServer` using the `ObjectName` `'bean:name=testBean1'`. If an `MBean`
|
|
instance has already been registered under that same `ObjectName`, the default behavior
|
|
is to fail (and throw an `InstanceAlreadyExistsException`).
|
|
|
|
It is possible to control the behavior of exactly what happens when an `MBean` is
|
|
registered with an `MBeanServer`. Spring's JMX support allows for three different
|
|
registration behaviors to control the registration behavior when the registration
|
|
process finds that an `MBean` has already been registered under the same `ObjectName`;
|
|
these registration behaviors are summarized on the following table:
|
|
|
|
[[jmx-registration-behaviors]]
|
|
.Registration Behaviors
|
|
[cols="1,4"]
|
|
|===
|
|
| Registration behavior | Explanation
|
|
|
|
| `FAIL_ON_EXISTING`
|
|
| This is the default registration behavior. If an `MBean` instance has already been
|
|
registered under the same `ObjectName`, the `MBean` that is being registered will not
|
|
be registered and an `InstanceAlreadyExistsException` will be thrown. The existing
|
|
`MBean` is unaffected.
|
|
|
|
| `IGNORE_EXISTING`
|
|
| If an `MBean` instance has already been registered under the same `ObjectName`, the
|
|
`MBean` that is being registered will __not__ be registered. The existing `MBean` is
|
|
unaffected, and no `Exception` will be thrown. This is useful in settings where
|
|
multiple applications want to share a common `MBean` in a shared `MBeanServer`.
|
|
|
|
| `REPLACE_EXISTING`
|
|
| If an `MBean` instance has already been registered under the same `ObjectName`, the
|
|
existing `MBean` that was previously registered will be unregistered and the new
|
|
`MBean` will be registered in its place (the new `MBean` effectively replaces the
|
|
previous instance).
|
|
|===
|
|
|
|
The above values are defined as enums on the `RegistrationPolicy` class.
|
|
If you want to change the default registration behavior, you simply need to set the value of the
|
|
`registrationPolicy` property on your `MBeanExporter` definition to one of those
|
|
values.
|
|
|
|
The following example illustrates how to effect a change from the default registration
|
|
behavior to the `REPLACE_EXISTING` behavior:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="registrationPolicy" value="REPLACE_EXISTING"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
|
|
|
|
[[jmx-interface]]
|
|
=== Controlling the management interface of your beans
|
|
|
|
In the previous example, you had little control over the management interface of your
|
|
bean; __all__ of the __public__ properties and methods of each exported bean was exposed
|
|
as JMX attributes and operations respectively. To exercise finer-grained control over
|
|
exactly which properties and methods of your exported beans are actually exposed as JMX
|
|
attributes and operations, Spring JMX provides a comprehensive and extensible mechanism
|
|
for controlling the management interfaces of your beans.
|
|
|
|
|
|
[[jmx-interface-assembler]]
|
|
==== MBeanInfoAssembler interface
|
|
|
|
Behind the scenes, the `MBeanExporter` delegates to an implementation of the
|
|
`org.springframework.jmx.export.assembler.MBeanInfoAssembler` interface which is
|
|
responsible for defining the management interface of each bean that is being exposed.
|
|
The default implementation,
|
|
`org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler`, simply
|
|
defines a management interface that exposes all public properties and methods (as you
|
|
saw in the previous examples). Spring provides two additional implementations of the
|
|
`MBeanInfoAssembler` interface that allow you to control the generated management
|
|
interface using either source-level metadata or any arbitrary interface.
|
|
|
|
|
|
[[jmx-interface-metadata]]
|
|
==== Using source-level metadata: Java annotations
|
|
|
|
Using the `MetadataMBeanInfoAssembler` you can define the management interfaces for your
|
|
beans using source level metadata. The reading of metadata is encapsulated by the
|
|
`org.springframework.jmx.export.metadata.JmxAttributeSource` interface. Spring JMX
|
|
provides a default implementation which uses Java annotations, namely
|
|
`org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource`. The
|
|
`MetadataMBeanInfoAssembler` __must__ be configured with an implementation instance of
|
|
the `JmxAttributeSource` interface for it to function correctly (there is __no__
|
|
default).
|
|
|
|
To mark a bean for export to JMX, you should annotate the bean class with the
|
|
`ManagedResource` annotation. Each method you wish to expose as an operation must be
|
|
marked with the `ManagedOperation` annotation and each property you wish to expose must
|
|
be marked with the `ManagedAttribute` annotation. When marking properties you can omit
|
|
either the annotation of the getter or the setter to create a write-only or read-only
|
|
attribute respectively.
|
|
|
|
NOTE: A `ManagedResource` annotated bean must be public as well as the methods exposing
|
|
an operation or an attribute.
|
|
|
|
The example below shows the annotated version of the `JmxTestBean` class that you saw
|
|
earlier:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package org.springframework.jmx;
|
|
|
|
import org.springframework.jmx.export.annotation.ManagedResource;
|
|
import org.springframework.jmx.export.annotation.ManagedOperation;
|
|
import org.springframework.jmx.export.annotation.ManagedAttribute;
|
|
|
|
@ManagedResource(
|
|
objectName="bean:name=testBean4",
|
|
description="My Managed Bean",
|
|
log=true,
|
|
logFile="jmx.log",
|
|
currencyTimeLimit=15,
|
|
persistPolicy="OnUpdate",
|
|
persistPeriod=200,
|
|
persistLocation="foo",
|
|
persistName="bar")
|
|
public class AnnotationTestBean implements IJmxTestBean {
|
|
|
|
private String name;
|
|
private int age;
|
|
|
|
@ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
|
|
public int getAge() {
|
|
return age;
|
|
}
|
|
|
|
public void setAge(int age) {
|
|
this.age = age;
|
|
}
|
|
|
|
@ManagedAttribute(description="The Name Attribute",
|
|
currencyTimeLimit=20,
|
|
defaultValue="bar",
|
|
persistPolicy="OnUpdate")
|
|
public void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
@ManagedAttribute(defaultValue="foo", persistPeriod=300)
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
@ManagedOperation(description="Add two numbers")
|
|
@ManagedOperationParameters({
|
|
@ManagedOperationParameter(name = "x", description = "The first number"),
|
|
@ManagedOperationParameter(name = "y", description = "The second number")})
|
|
public int add(int x, int y) {
|
|
return x + y;
|
|
}
|
|
|
|
public void dontExposeMe() {
|
|
throw new RuntimeException();
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
Here you can see that the `JmxTestBean` class is marked with the `ManagedResource`
|
|
annotation and that this `ManagedResource` annotation is configured with a set of
|
|
properties. These properties can be used to configure various aspects of the MBean that
|
|
is generated by the `MBeanExporter`, and are explained in greater detail later in
|
|
section entitled <<jmx-interface-metadata-types>>.
|
|
|
|
You will also notice that both the `age` and `name` properties are annotated with the
|
|
`ManagedAttribute` annotation, but in the case of the `age` property, only the getter is
|
|
marked. This will cause both of these properties to be included in the management
|
|
interface as attributes, but the `age` attribute will be read-only.
|
|
|
|
Finally, you will notice that the `add(int, int)` method is marked with the
|
|
`ManagedOperation` attribute whereas the `dontExposeMe()` method is not. This will cause
|
|
the management interface to contain only one operation, `add(int, int)`, when using the
|
|
`MetadataMBeanInfoAssembler`.
|
|
|
|
The configuration below shows how you configure the `MBeanExporter` to use the
|
|
`MetadataMBeanInfoAssembler`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="assembler" ref="assembler"/>
|
|
<property name="namingStrategy" ref="namingStrategy"/>
|
|
<property name="autodetect" value="true"/>
|
|
</bean>
|
|
|
|
<bean id="jmxAttributeSource"
|
|
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
|
|
|
|
<!-- will create management interface using annotation metadata -->
|
|
<bean id="assembler"
|
|
class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
|
|
<property name="attributeSource" ref="jmxAttributeSource"/>
|
|
</bean>
|
|
|
|
<!-- will pick up the ObjectName from the annotation -->
|
|
<bean id="namingStrategy"
|
|
class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
|
|
<property name="attributeSource" ref="jmxAttributeSource"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
</beans>
|
|
----
|
|
|
|
Here you can see that an `MetadataMBeanInfoAssembler` bean has been configured with an
|
|
instance of the `AnnotationJmxAttributeSource` class and passed to the `MBeanExporter`
|
|
through the assembler property. This is all that is required to take advantage of
|
|
metadata-driven management interfaces for your Spring-exposed MBeans.
|
|
|
|
|
|
[[jmx-interface-metadata-types]]
|
|
==== Source-level metadata types
|
|
|
|
The following source level metadata types are available for use in Spring JMX:
|
|
|
|
[[jmx-metadata-types]]
|
|
.Source-level metadata types
|
|
|===
|
|
| Purpose| Annotation| Annotation Type
|
|
|
|
| Mark all instances of a `Class` as JMX managed resources
|
|
| `@ManagedResource`
|
|
| Class
|
|
|
|
| Mark a method as a JMX operation
|
|
| `@ManagedOperation`
|
|
| Method
|
|
|
|
| Mark a getter or setter as one half of a JMX attribute
|
|
| `@ManagedAttribute`
|
|
| Method (only getters and setters)
|
|
|
|
| Define descriptions for operation parameters
|
|
| `@ManagedOperationParameter` and `@ManagedOperationParameters`
|
|
| Method
|
|
|===
|
|
|
|
The following configuration parameters are available for use on these source-level
|
|
metadata types:
|
|
|
|
[[jmx-metadata-parameters]]
|
|
.Source-level metadata parameters
|
|
[cols="1,3,1"]
|
|
|===
|
|
| Parameter | Description | Applies to
|
|
|
|
| `ObjectName`
|
|
| Used by `MetadataNamingStrategy` to determine the `ObjectName` of a managed resource
|
|
| `ManagedResource`
|
|
|
|
| `description`
|
|
| Sets the friendly description of the resource, attribute or operation
|
|
| `ManagedResource`, `ManagedAttribute`, `ManagedOperation`, `ManagedOperationParameter`
|
|
|
|
| `currencyTimeLimit`
|
|
| Sets the value of the `currencyTimeLimit` descriptor field
|
|
| `ManagedResource`, `ManagedAttribute`
|
|
|
|
| `defaultValue`
|
|
| Sets the value of the `defaultValue` descriptor field
|
|
| `ManagedAttribute`
|
|
|
|
| `log`
|
|
| Sets the value of the `log` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `logFile`
|
|
| Sets the value of the `logFile` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `persistPolicy`
|
|
| Sets the value of the `persistPolicy` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `persistPeriod`
|
|
| Sets the value of the `persistPeriod` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `persistLocation`
|
|
| Sets the value of the `persistLocation` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `persistName`
|
|
| Sets the value of the `persistName` descriptor field
|
|
| `ManagedResource`
|
|
|
|
| `name`
|
|
| Sets the display name of an operation parameter
|
|
| `ManagedOperationParameter`
|
|
|
|
| `index`
|
|
| Sets the index of an operation parameter
|
|
| `ManagedOperationParameter`
|
|
|===
|
|
|
|
|
|
[[jmx-interface-autodetect]]
|
|
==== AutodetectCapableMBeanInfoAssembler interface
|
|
|
|
To simplify configuration even further, Spring introduces the
|
|
`AutodetectCapableMBeanInfoAssembler` interface which extends the `MBeanInfoAssembler`
|
|
interface to add support for autodetection of MBean resources. If you configure the
|
|
`MBeanExporter` with an instance of `AutodetectCapableMBeanInfoAssembler` then it is
|
|
allowed to "vote" on the inclusion of beans for exposure to JMX.
|
|
|
|
Out of the box, the only implementation of the `AutodetectCapableMBeanInfo` interface is
|
|
the `MetadataMBeanInfoAssembler` which will vote to include any bean which is marked
|
|
with the `ManagedResource` attribute. The default approach in this case is to use the
|
|
bean name as the `ObjectName` which results in a configuration like this:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<!-- notice how no 'beans' are explicitly configured here -->
|
|
<property name="autodetect" value="true"/>
|
|
<property name="assembler" ref="assembler"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
|
|
<property name="attributeSource">
|
|
<bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
|
|
</property>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
Notice that in this configuration no beans are passed to the `MBeanExporter`; however,
|
|
the `JmxTestBean` will still be registered since it is marked with the `ManagedResource`
|
|
attribute and the `MetadataMBeanInfoAssembler` detects this and votes to include it. The
|
|
only problem with this approach is that the name of the `JmxTestBean` now has business
|
|
meaning. You can address this issue by changing the default behavior for `ObjectName`
|
|
creation as defined in <<jmx-naming>>.
|
|
|
|
|
|
[[jmx-interface-java]]
|
|
==== Defining management interfaces using Java interfaces
|
|
|
|
In addition to the `MetadataMBeanInfoAssembler`, Spring also includes the
|
|
`InterfaceBasedMBeanInfoAssembler` which allows you to constrain the methods and
|
|
properties that are exposed based on the set of methods defined in a collection of
|
|
interfaces.
|
|
|
|
Although the standard mechanism for exposing MBeans is to use interfaces and a simple
|
|
naming scheme, the `InterfaceBasedMBeanInfoAssembler` extends this functionality by
|
|
removing the need for naming conventions, allowing you to use more than one interface
|
|
and removing the need for your beans to implement the MBean interfaces.
|
|
|
|
Consider this interface that is used to define a management interface for the
|
|
`JmxTestBean` class that you saw earlier:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface IJmxTestBean {
|
|
|
|
public int add(int x, int y);
|
|
|
|
public long myOperation();
|
|
|
|
public int getAge();
|
|
|
|
public void setAge(int age);
|
|
|
|
public void setName(String name);
|
|
|
|
public String getName();
|
|
|
|
}
|
|
----
|
|
|
|
This interface defines the methods and properties that will be exposed as operations and
|
|
attributes on the JMX MBean. The code below shows how to configure Spring JMX to use
|
|
this interface as the definition for the management interface:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean5" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="assembler">
|
|
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
|
|
<property name="managedInterfaces">
|
|
<value>org.springframework.jmx.IJmxTestBean</value>
|
|
</property>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
Here you can see that the `InterfaceBasedMBeanInfoAssembler` is configured to use the
|
|
`IJmxTestBean` interface when constructing the management interface for any bean. It is
|
|
important to understand that beans processed by the `InterfaceBasedMBeanInfoAssembler`
|
|
are __not__ required to implement the interface used to generate the JMX management
|
|
interface.
|
|
|
|
In the case above, the `IJmxTestBean` interface is used to construct all management
|
|
interfaces for all beans. In many cases this is not the desired behavior and you may
|
|
want to use different interfaces for different beans. In this case, you can pass
|
|
`InterfaceBasedMBeanInfoAssembler` a `Properties` instance via the `interfaceMappings`
|
|
property, where the key of each entry is the bean name and the value of each entry is a
|
|
comma-separated list of interface names to use for that bean.
|
|
|
|
If no management interface is specified through either the `managedInterfaces` or
|
|
`interfaceMappings` properties, then the `InterfaceBasedMBeanInfoAssembler` will reflect
|
|
on the bean and use all of the interfaces implemented by that bean to create the
|
|
management interface.
|
|
|
|
|
|
[[jmx-interface-methodnames]]
|
|
==== Using MethodNameBasedMBeanInfoAssembler
|
|
|
|
The `MethodNameBasedMBeanInfoAssembler` allows you to specify a list of method names
|
|
that will be exposed to JMX as attributes and operations. The code below shows a sample
|
|
configuration for this:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean5" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="assembler">
|
|
<bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
|
|
<property name="managedMethods">
|
|
<value>add,myOperation,getName,setName,getAge</value>
|
|
</property>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
Here you can see that the methods `add` and `myOperation` will be exposed as JMX
|
|
operations and `getName()`, `setName(String)` and `getAge()` will be exposed as the
|
|
appropriate half of a JMX attribute. In the code above, the method mappings apply to
|
|
beans that are exposed to JMX. To control method exposure on a bean-by-bean basis, use
|
|
the `methodMappings` property of `MethodNameMBeanInfoAssembler` to map bean names to
|
|
lists of method names.
|
|
|
|
|
|
|
|
[[jmx-naming]]
|
|
=== Controlling the ObjectNames for your beans
|
|
|
|
Behind the scenes, the `MBeanExporter` delegates to an implementation of the
|
|
`ObjectNamingStrategy` to obtain ``ObjectName``s for each of the beans it is registering.
|
|
The default implementation, `KeyNamingStrategy`, will, by default, use the key of the
|
|
`beans` `Map` as the `ObjectName`. In addition, the `KeyNamingStrategy` can map the key
|
|
of the `beans` `Map` to an entry in a `Properties` file (or files) to resolve the
|
|
`ObjectName`. In addition to the `KeyNamingStrategy`, Spring provides two additional
|
|
`ObjectNamingStrategy` implementations: the `IdentityNamingStrategy` that builds an
|
|
`ObjectName` based on the JVM identity of the bean and the `MetadataNamingStrategy` that
|
|
uses source level metadata to obtain the `ObjectName`.
|
|
|
|
|
|
[[jmx-naming-properties]]
|
|
==== Reading ObjectNames from Properties
|
|
|
|
You can configure your own `KeyNamingStrategy` instance and configure it to read
|
|
``ObjectName``s from a `Properties` instance rather than use bean key. The
|
|
`KeyNamingStrategy` will attempt to locate an entry in the `Properties` with a key
|
|
corresponding to the bean key. If no entry is found or if the `Properties` instance is
|
|
`null` then the bean key itself is used.
|
|
|
|
The code below shows a sample configuration for the `KeyNamingStrategy`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="testBean" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="namingStrategy" ref="namingStrategy"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
|
|
<property name="mappings">
|
|
<props>
|
|
<prop key="testBean">bean:name=testBean1</prop>
|
|
</props>
|
|
</property>
|
|
<property name="mappingLocations">
|
|
<value>names1.properties,names2.properties</value>
|
|
</property>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
Here an instance of `KeyNamingStrategy` is configured with a `Properties` instance that
|
|
is merged from the `Properties` instance defined by the mapping property and the
|
|
properties files located in the paths defined by the mappings property. In this
|
|
configuration, the `testBean` bean will be given the `ObjectName` `bean:name=testBean1`
|
|
since this is the entry in the `Properties` instance that has a key corresponding to the
|
|
bean key.
|
|
|
|
If no entry in the `Properties` instance can be found then the bean key name is used as
|
|
the `ObjectName`.
|
|
|
|
|
|
[[jmx-naming-metadata]]
|
|
==== Using the MetadataNamingStrategy
|
|
|
|
The `MetadataNamingStrategy` uses the `objectName` property of the `ManagedResource`
|
|
attribute on each bean to create the `ObjectName`. The code below shows the
|
|
configuration for the `MetadataNamingStrategy`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="testBean" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="namingStrategy" ref="namingStrategy"/>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
|
|
<property name="attributeSource" ref="attributeSource"/>
|
|
</bean>
|
|
|
|
<bean id="attributeSource"
|
|
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
|
|
|
|
</beans>
|
|
----
|
|
|
|
If no `objectName` has been provided for the `ManagedResource` attribute, then an
|
|
`ObjectName` will be created with the following
|
|
format:__[fully-qualified-package-name]:type=[short-classname],name=[bean-name]__. For
|
|
example, the generated `ObjectName` for the following bean would be:
|
|
__com.foo:type=MyClass,name=myBean__.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="myBean" class="com.foo.MyClass"/>
|
|
----
|
|
|
|
|
|
[[jmx-context-mbeanexport]]
|
|
==== Configuring annotation based MBean export
|
|
|
|
If you prefer using <<jmx-interface-metadata,the annotation based approach>> to define
|
|
your management interfaces, then a convenience subclass of `MBeanExporter` is available:
|
|
`AnnotationMBeanExporter`. When defining an instance of this subclass, the
|
|
`namingStrategy`, `assembler`, and `attributeSource` configuration is no longer needed,
|
|
since it will always use standard Java annotation-based metadata (autodetection is
|
|
always enabled as well). In fact, rather than defining an `MBeanExporter` bean, an even
|
|
simpler syntax is supported by the `@EnableMBeanExport` `@Configuration` annotation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableMBeanExport
|
|
public class AppConfig {
|
|
|
|
}
|
|
----
|
|
|
|
If you prefer XML based configuration the `'context:mbean-export'` element serves the
|
|
same purpose.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<context:mbean-export/>
|
|
----
|
|
|
|
You can provide a reference to a particular MBean `server` if necessary, and the
|
|
`defaultDomain` attribute (a property of `AnnotationMBeanExporter`) accepts an alternate
|
|
value for the generated MBean `ObjectNames`' domains. This would be used in place of the
|
|
fully qualified package name as described in the previous section on
|
|
<<jmx-naming-metadata, MetadataNamingStrategy>>.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
|
|
@Configuration
|
|
ContextConfiguration {
|
|
|
|
}
|
|
----
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
Do not use interface-based AOP proxies in combination with autodetection of JMX
|
|
annotations in your bean classes. Interface-based proxies 'hide' the target class, which
|
|
also hides the JMX managed resource annotations. Hence, use target-class proxies in that
|
|
case: through setting the 'proxy-target-class' flag on `<aop:config/>`,
|
|
`<tx:annotation-driven/>`, etc. Otherwise, your JMX beans might be silently ignored at
|
|
startup...
|
|
====
|
|
|
|
|
|
|
|
[[jmx-jsr160]]
|
|
=== JSR-160 Connectors
|
|
|
|
For remote access, Spring JMX module offers two `FactoryBean` implementations inside the
|
|
`org.springframework.jmx.support` package for creating both server- and client-side
|
|
connectors.
|
|
|
|
|
|
[[jmx-jsr160-server]]
|
|
==== Server-side connectors
|
|
|
|
To have Spring JMX create, start and expose a JSR-160 `JMXConnectorServer` use the
|
|
following configuration:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
|
|
----
|
|
|
|
By default `ConnectorServerFactoryBean` creates a `JMXConnectorServer` bound to
|
|
`"service:jmx:jmxmp://localhost:9875"`. The `serverConnector` bean thus exposes the
|
|
local `MBeanServer` to clients through the JMXMP protocol on localhost, port 9875. Note
|
|
that the JMXMP protocol is marked as optional by the JSR 160 specification: currently,
|
|
the main open-source JMX implementation, MX4J, and the one provided with the JDK
|
|
do __not__ support JMXMP.
|
|
|
|
To specify another URL and register the `JMXConnectorServer` itself with the
|
|
`MBeanServer` use the `serviceUrl` and `ObjectName` properties respectively:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="serverConnector"
|
|
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
|
|
<property name="objectName" value="connector:name=rmi"/>
|
|
<property name="serviceUrl"
|
|
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
|
|
</bean>
|
|
----
|
|
|
|
If the `ObjectName` property is set Spring will automatically register your connector
|
|
with the `MBeanServer` under that `ObjectName`. The example below shows the full set of
|
|
parameters which you can pass to the `ConnectorServerFactoryBean` when creating a
|
|
JMXConnector:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="serverConnector"
|
|
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
|
|
<property name="objectName" value="connector:name=iiop"/>
|
|
<property name="serviceUrl"
|
|
value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
|
|
<property name="threaded" value="true"/>
|
|
<property name="daemon" value="true"/>
|
|
<property name="environment">
|
|
<map>
|
|
<entry key="someKey" value="someValue"/>
|
|
</map>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
Note that when using a RMI-based connector you need the lookup service (tnameserv or
|
|
rmiregistry) to be started in order for the name registration to complete. If you are
|
|
using Spring to export remote services for you via RMI, then Spring will already have
|
|
constructed an RMI registry. If not, you can easily start a registry using the following
|
|
snippet of configuration:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
|
|
<property name="port" value="1099"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[jmx-jsr160-client]]
|
|
==== Client-side connectors
|
|
|
|
To create an `MBeanServerConnection` to a remote JSR-160 enabled `MBeanServer` use the
|
|
`MBeanServerConnectionFactoryBean` as shown below:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
|
|
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[jmx-jsr160-protocols]]
|
|
==== JMX over Hessian or SOAP
|
|
|
|
JSR-160 permits extensions to the way in which communication is done between the client
|
|
and the server. The examples above are using the mandatory RMI-based implementation
|
|
required by the JSR-160 specification (IIOP and JRMP) and the (optional) JMXMP. By using
|
|
other providers or JMX implementations (such as http://mx4j.sourceforge.net[MX4J]) you
|
|
can take advantage of protocols like SOAP or Hessian over simple HTTP or SSL and others:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
|
|
<property name="objectName" value="connector:name=burlap"/>
|
|
<property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
|
|
</bean>
|
|
----
|
|
|
|
In the case of the above example, MX4J 3.0.0 was used; see the official MX4J
|
|
documentation for more information.
|
|
|
|
|
|
|
|
[[jmx-proxy]]
|
|
=== Accessing MBeans via proxies
|
|
|
|
Spring JMX allows you to create proxies that re-route calls to MBeans registered in a
|
|
local or remote `MBeanServer`. These proxies provide you with a standard Java interface
|
|
through which you can interact with your MBeans. The code below shows how to configure a
|
|
proxy for an MBean running in a local `MBeanServer`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
|
|
<property name="objectName" value="bean:name=testBean"/>
|
|
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
|
|
</bean>
|
|
----
|
|
|
|
Here you can see that a proxy is created for the MBean registered under the
|
|
`ObjectName`: `bean:name=testBean`. The set of interfaces that the proxy will implement
|
|
is controlled by the `proxyInterfaces` property and the rules for mapping methods and
|
|
properties on these interfaces to operations and attributes on the MBean are the same
|
|
rules used by the `InterfaceBasedMBeanInfoAssembler`.
|
|
|
|
The `MBeanProxyFactoryBean` can create a proxy to any MBean that is accessible via an
|
|
`MBeanServerConnection`. By default, the local `MBeanServer` is located and used, but
|
|
you can override this and provide an `MBeanServerConnection` pointing to a remote
|
|
`MBeanServer` to cater for proxies pointing to remote MBeans:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="clientConnector"
|
|
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
|
|
<property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
|
|
</bean>
|
|
|
|
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
|
|
<property name="objectName" value="bean:name=testBean"/>
|
|
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
|
|
<property name="server" ref="clientConnector"/>
|
|
</bean>
|
|
----
|
|
|
|
Here you can see that we create an `MBeanServerConnection` pointing to a remote machine
|
|
using the `MBeanServerConnectionFactoryBean`. This `MBeanServerConnection` is then
|
|
passed to the `MBeanProxyFactoryBean` via the `server` property. The proxy that is
|
|
created will forward all invocations to the `MBeanServer` via this
|
|
`MBeanServerConnection`.
|
|
|
|
|
|
|
|
[[jmx-notifications]]
|
|
=== Notifications
|
|
|
|
Spring's JMX offering includes comprehensive support for JMX notifications.
|
|
|
|
|
|
[[jmx-notifications-listeners]]
|
|
==== Registering listeners for notifications
|
|
|
|
Spring's JMX support makes it very easy to register any number of
|
|
`NotificationListeners` with any number of MBeans (this includes MBeans exported by
|
|
Spring's `MBeanExporter` and MBeans registered via some other mechanism). By way of an
|
|
example, consider the scenario where one would like to be informed (via a
|
|
`Notification`) each and every time an attribute of a target MBean changes.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package com.example;
|
|
|
|
import javax.management.AttributeChangeNotification;
|
|
import javax.management.Notification;
|
|
import javax.management.NotificationFilter;
|
|
import javax.management.NotificationListener;
|
|
|
|
public class ConsoleLoggingNotificationListener
|
|
implements NotificationListener, NotificationFilter {
|
|
|
|
public void handleNotification(Notification notification, Object handback) {
|
|
System.out.println(notification);
|
|
System.out.println(handback);
|
|
}
|
|
|
|
public boolean isNotificationEnabled(Notification notification) {
|
|
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="notificationListenerMappings">
|
|
<map>
|
|
<entry key="bean:name=testBean1">
|
|
<bean class="com.example.ConsoleLoggingNotificationListener"/>
|
|
</entry>
|
|
</map>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
With the above configuration in place, every time a JMX `Notification` is broadcast from
|
|
the target MBean ( `bean:name=testBean1`), the `ConsoleLoggingNotificationListener` bean
|
|
that was registered as a listener via the `notificationListenerMappings` property will
|
|
be notified. The `ConsoleLoggingNotificationListener` bean can then take whatever action
|
|
it deems appropriate in response to the `Notification`.
|
|
|
|
You can also use straight bean names as the link between exported beans and listeners:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="notificationListenerMappings">
|
|
<map>
|
|
<entry key="__testBean__">
|
|
<bean class="com.example.ConsoleLoggingNotificationListener"/>
|
|
</entry>
|
|
</map>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="__testBean__" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
If one wants to register a single `NotificationListener` instance for all of the beans
|
|
that the enclosing `MBeanExporter` is exporting, one can use the special wildcard `'{asterisk}'`
|
|
(sans quotes) as the key for an entry in the `notificationListenerMappings` property
|
|
map; for example:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<property name="notificationListenerMappings">
|
|
<map>
|
|
<entry key="*">
|
|
<bean class="com.example.ConsoleLoggingNotificationListener"/>
|
|
</entry>
|
|
</map>
|
|
</property>
|
|
----
|
|
|
|
If one needs to do the inverse (that is, register a number of distinct listeners against
|
|
an MBean), then one has to use the `notificationListeners` list property instead (and in
|
|
preference to the `notificationListenerMappings` property). This time, instead of
|
|
configuring simply a `NotificationListener` for a single MBean, one configures
|
|
`NotificationListenerBean` instances... a `NotificationListenerBean` encapsulates a
|
|
`NotificationListener` and the `ObjectName` (or `ObjectNames`) that it is to be
|
|
registered against in an `MBeanServer`. The `NotificationListenerBean` also encapsulates
|
|
a number of other properties such as a `NotificationFilter` and an arbitrary handback
|
|
object that can be used in advanced JMX notification scenarios.
|
|
|
|
The configuration when using `NotificationListenerBean` instances is not wildly
|
|
different to what was presented previously:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
|
</map>
|
|
</property>
|
|
<property name="notificationListeners">
|
|
<list>
|
|
<bean class="org.springframework.jmx.export.NotificationListenerBean">
|
|
<constructor-arg>
|
|
<bean class="com.example.ConsoleLoggingNotificationListener"/>
|
|
</constructor-arg>
|
|
<property name="mappedObjectNames">
|
|
<list>
|
|
<value>bean:name=testBean1</value>
|
|
</list>
|
|
</property>
|
|
</bean>
|
|
</list>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
The above example is equivalent to the first notification example. Lets assume then that
|
|
we want to be given a handback object every time a `Notification` is raised, and that
|
|
additionally we want to filter out extraneous `Notifications` by supplying a
|
|
`NotificationFilter`. (For a full discussion of just what a handback object is, and
|
|
indeed what a `NotificationFilter` is, please do consult that section of the JMX
|
|
specification (1.2) entitled 'The JMX Notification Model'.)
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans>
|
|
|
|
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
|
|
<property name="beans">
|
|
<map>
|
|
<entry key="bean:name=testBean1" value-ref="testBean1"/>
|
|
<entry key="bean:name=testBean2" value-ref="testBean2"/>
|
|
</map>
|
|
</property>
|
|
<property name="notificationListeners">
|
|
<list>
|
|
<bean class="org.springframework.jmx.export.NotificationListenerBean">
|
|
<constructor-arg ref="customerNotificationListener"/>
|
|
<property name="mappedObjectNames">
|
|
<list>
|
|
<!-- handles notifications from two distinct MBeans -->
|
|
<value>bean:name=testBean1</value>
|
|
<value>bean:name=testBean2</value>
|
|
</list>
|
|
</property>
|
|
<property name="handback">
|
|
<bean class="java.lang.String">
|
|
<constructor-arg value="This could be anything..."/>
|
|
</bean>
|
|
</property>
|
|
<property name="notificationFilter" ref="customerNotificationListener"/>
|
|
</bean>
|
|
</list>
|
|
</property>
|
|
</bean>
|
|
|
|
<!-- implements both the NotificationListener and NotificationFilter interfaces -->
|
|
<bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
|
|
|
|
<bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="TEST"/>
|
|
<property name="age" value="100"/>
|
|
</bean>
|
|
|
|
<bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
|
|
<property name="name" value="ANOTHER TEST"/>
|
|
<property name="age" value="200"/>
|
|
</bean>
|
|
|
|
</beans>
|
|
----
|
|
|
|
|
|
[[jmx-notifications-publishing]]
|
|
==== Publishing Notifications
|
|
|
|
Spring provides support not just for registering to receive `Notifications`, but also
|
|
for publishing `Notifications`.
|
|
|
|
[NOTE]
|
|
====
|
|
Please note that this section is really only relevant to Spring managed beans that have
|
|
been exposed as MBeans via an `MBeanExporter`; any existing, user-defined MBeans should
|
|
use the standard JMX APIs for notification publication.
|
|
====
|
|
|
|
The key interface in Spring's JMX notification publication support is the
|
|
`NotificationPublisher` interface (defined in the
|
|
`org.springframework.jmx.export.notification` package). Any bean that is going to be
|
|
exported as an MBean via an `MBeanExporter` instance can implement the related
|
|
`NotificationPublisherAware` interface to gain access to a `NotificationPublisher`
|
|
instance. The `NotificationPublisherAware` interface simply supplies an instance of a
|
|
`NotificationPublisher` to the implementing bean via a simple setter method, which the
|
|
bean can then use to publish `Notifications`.
|
|
|
|
As stated in the javadocs of the `NotificationPublisher` class, managed beans that are
|
|
publishing events via the `NotificationPublisher` mechanism are __not__ responsible for
|
|
the state management of any notification listeners and the like ... Spring's JMX support
|
|
will take care of handling all the JMX infrastructure issues. All one need do as an
|
|
application developer is implement the `NotificationPublisherAware` interface and start
|
|
publishing events using the supplied `NotificationPublisher` instance. Note that the
|
|
`NotificationPublisher` will be set __after__ the managed bean has been registered with
|
|
an `MBeanServer`.
|
|
|
|
Using a `NotificationPublisher` instance is quite straightforward... one simply creates
|
|
a JMX `Notification` instance (or an instance of an appropriate `Notification`
|
|
subclass), populates the notification with the data pertinent to the event that is to be
|
|
published, and one then invokes the `sendNotification(Notification)` on the
|
|
`NotificationPublisher` instance, passing in the `Notification`.
|
|
|
|
Find below a simple example... in this scenario, exported instances of the `JmxTestBean`
|
|
are going to publish a `NotificationEvent` every time the `add(int, int)` operation is
|
|
invoked.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
package org.springframework.jmx;
|
|
|
|
import org.springframework.jmx.export.notification.NotificationPublisherAware;
|
|
import org.springframework.jmx.export.notification.NotificationPublisher;
|
|
import javax.management.Notification;
|
|
|
|
public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
|
|
|
|
private String name;
|
|
private int age;
|
|
private boolean isSuperman;
|
|
private NotificationPublisher publisher;
|
|
|
|
// other getters and setters omitted for clarity
|
|
|
|
public int add(int x, int y) {
|
|
int answer = x + y;
|
|
this.publisher.sendNotification(new Notification("add", this, 0));
|
|
return answer;
|
|
}
|
|
|
|
public void dontExposeMe() {
|
|
throw new RuntimeException();
|
|
}
|
|
|
|
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
|
|
this.publisher = notificationPublisher;
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
The `NotificationPublisher` interface and the machinery to get it all working is one of
|
|
the nicer features of Spring's JMX support. It does however come with the price tag of
|
|
coupling your classes to both Spring and JMX; as always, the advice here is to be
|
|
pragmatic... if you need the functionality offered by the `NotificationPublisher` and
|
|
you can accept the coupling to both Spring and JMX, then do so.
|
|
|
|
|
|
|
|
[[jmx-resources]]
|
|
=== Further resources
|
|
|
|
This section contains links to further resources about JMX.
|
|
|
|
* The https://www.oracle.com/technetwork/java/javase/tech/javamanagement-140525.html[JMX
|
|
homepage] at Oracle
|
|
* The https://jcp.org/aboutJava/communityprocess/final/jsr003/index3.html[JMX
|
|
specification] (JSR-000003)
|
|
* The https://jcp.org/aboutJava/communityprocess/final/jsr160/index.html[JMX Remote API
|
|
specification] (JSR-000160)
|
|
* The http://mx4j.sourceforge.net/[MX4J homepage] (an Open Source implementation of
|
|
various JMX specs)
|
|
|
|
|
|
|
|
|
|
[[cci]]
|
|
== JCA CCI
|
|
|
|
|
|
|
|
[[cci-introduction]]
|
|
=== Introduction
|
|
|
|
Java EE provides a specification to standardize access to enterprise information systems
|
|
(EIS): the JCA (Java EE Connector Architecture). This specification is divided into
|
|
several different parts:
|
|
|
|
* SPI (Service provider interfaces) that the connector provider must implement. These
|
|
interfaces constitute a resource adapter which can be deployed on a Java EE
|
|
application server. In such a scenario, the server manages connection pooling,
|
|
transaction and security (managed mode). The application server is also responsible
|
|
for managing the configuration, which is held outside the client application. A
|
|
connector can be used without an application server as well; in this case, the
|
|
application must configure it directly (non-managed mode).
|
|
* CCI (Common Client Interface) that an application can use to interact with the
|
|
connector and thus communicate with an EIS. An API for local transaction demarcation
|
|
is provided as well.
|
|
|
|
The aim of the Spring CCI support is to provide classes to access a CCI connector in
|
|
typical Spring style, leveraging the Spring Framework's general resource and transaction
|
|
management facilities.
|
|
|
|
[NOTE]
|
|
====
|
|
The client side of connectors doesn't alway use CCI. Some connectors expose their own
|
|
APIs, only providing JCA resource adapter to use the system contracts of a Java EE
|
|
container (connection pooling, global transactions, security). Spring does not offer
|
|
special support for such connector-specific APIs.
|
|
====
|
|
|
|
|
|
|
|
[[cci-config]]
|
|
=== Configuring CCI
|
|
|
|
|
|
[[cci-config-connector]]
|
|
==== Connector configuration
|
|
|
|
The base resource to use JCA CCI is the `ConnectionFactory` interface. The connector
|
|
used must provide an implementation of this interface.
|
|
|
|
To use your connector, you can deploy it on your application server and fetch the
|
|
`ConnectionFactory` from the server's JNDI environment (managed mode). The connector
|
|
must be packaged as a RAR file (resource adapter archive) and contain a `ra.xml` file to
|
|
describe its deployment characteristics. The actual name of the resource is specified
|
|
when you deploy it. To access it within Spring, simply use Spring's
|
|
`JndiObjectFactoryBean` / `<jee:jndi-lookup>` fetch the factory by its JNDI name.
|
|
|
|
Another way to use a connector is to embed it in your application (non-managed mode),
|
|
not using an application server to deploy and configure it. Spring offers the
|
|
possibility to configure a connector as a bean, through a provided `FactoryBean` (
|
|
`LocalConnectionFactoryBean`). In this manner, you only need the connector library in
|
|
the classpath (no RAR file and no `ra.xml` descriptor needed). The library must be
|
|
extracted from the connector's RAR file, if necessary.
|
|
|
|
Once you have got access to your `ConnectionFactory` instance, you can inject it into
|
|
your components. These components can either be coded against the plain CCI API or
|
|
leverage Spring's support classes for CCI access (e.g. `CciTemplate`).
|
|
|
|
[NOTE]
|
|
====
|
|
When you use a connector in non-managed mode, you can't use global transactions because
|
|
the resource is never enlisted / delisted in the current global transaction of the
|
|
current thread. The resource is simply not aware of any global Java EE transactions that
|
|
might be running.
|
|
====
|
|
|
|
|
|
[[cci-config-connectionfactory]]
|
|
==== ConnectionFactory configuration in Spring
|
|
|
|
In order to make connections to the EIS, you need to obtain a `ConnectionFactory` from
|
|
the application server if you are in a managed mode, or directly from Spring if you are
|
|
in a non-managed mode.
|
|
|
|
In a managed mode, you access a `ConnectionFactory` from JNDI; its properties will be
|
|
configured in the application server.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
|
|
----
|
|
|
|
In non-managed mode, you must configure the `ConnectionFactory` you want to use in the
|
|
configuration of Spring as a JavaBean. The `LocalConnectionFactoryBean` class offers
|
|
this setup style, passing in the `ManagedConnectionFactory` implementation of your
|
|
connector, exposing the application-level CCI `ConnectionFactory`.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
|
|
<property name="serverName" value="TXSERIES"/>
|
|
<property name="connectionURL" value="tcp://localhost/"/>
|
|
<property name="portNumber" value="2006"/>
|
|
</bean>
|
|
|
|
<bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
You can't directly instantiate a specific `ConnectionFactory`. You need to go through
|
|
the corresponding implementation of the `ManagedConnectionFactory` interface for your
|
|
connector. This interface is part of the JCA SPI specification.
|
|
====
|
|
|
|
|
|
[[cci-config-cci-connections]]
|
|
==== Configuring CCI connections
|
|
|
|
JCA CCI allow the developer to configure the connections to the EIS using the
|
|
`ConnectionSpec` implementation of your connector. In order to configure its properties,
|
|
you need to wrap the target connection factory with a dedicated adapter,
|
|
`ConnectionSpecConnectionFactoryAdapter`. So, the dedicated `ConnectionSpec` can be
|
|
configured with the property `connectionSpec` (as an inner bean).
|
|
|
|
This property is not mandatory because the CCI `ConnectionFactory` interface defines two
|
|
different methods to obtain a CCI connection. Some of the `ConnectionSpec` properties
|
|
can often be configured in the application server (in managed mode) or on the
|
|
corresponding local `ManagedConnectionFactory` implementation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface ConnectionFactory implements Serializable, Referenceable {
|
|
...
|
|
Connection getConnection() throws ResourceException;
|
|
Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
|
|
...
|
|
}
|
|
----
|
|
|
|
Spring provides a `ConnectionSpecConnectionFactoryAdapter` that allows for specifying a
|
|
`ConnectionSpec` instance to use for all operations on a given factory. If the adapter's
|
|
`connectionSpec` property is specified, the adapter uses the `getConnection` variant
|
|
with the `ConnectionSpec` argument, otherwise the variant without argument.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="managedConnectionFactory"
|
|
class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
|
|
<property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
|
|
<property name="driverName" value="org.hsqldb.jdbcDriver"/>
|
|
</bean>
|
|
|
|
<bean id="targetConnectionFactory"
|
|
class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
|
|
</bean>
|
|
|
|
<bean id="connectionFactory"
|
|
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
|
|
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
|
|
<property name="connectionSpec">
|
|
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
|
|
<property name="user" value="sa"/>
|
|
<property name="password" value=""/>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[cci-config-single-connection]]
|
|
==== Using a single CCI connection
|
|
|
|
If you want to use a single CCI connection, Spring provides a further
|
|
`ConnectionFactory` adapter to manage this. The `SingleConnectionFactory` adapter class
|
|
will open a single connection lazily and close it when this bean is destroyed at
|
|
application shutdown. This class will expose special `Connection` proxies that behave
|
|
accordingly, all sharing the same underlying physical connection.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="eciManagedConnectionFactory"
|
|
class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
|
|
<property name="serverName" value="TEST"/>
|
|
<property name="connectionURL" value="tcp://localhost/"/>
|
|
<property name="portNumber" value="2006"/>
|
|
</bean>
|
|
|
|
<bean id="targetEciConnectionFactory"
|
|
class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
|
|
</bean>
|
|
|
|
<bean id="eciConnectionFactory"
|
|
class="org.springframework.jca.cci.connection.SingleConnectionFactory">
|
|
<property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
This `ConnectionFactory` adapter cannot directly be configured with a `ConnectionSpec`.
|
|
Use an intermediary `ConnectionSpecConnectionFactoryAdapter` that the
|
|
`SingleConnectionFactory` talks to if you require a single connection for a specific
|
|
`ConnectionSpec`.
|
|
====
|
|
|
|
|
|
|
|
[[cci-using]]
|
|
=== Using Spring's CCI access support
|
|
|
|
|
|
[[cci-record-creator]]
|
|
==== Record conversion
|
|
|
|
One of the aims of the JCA CCI support is to provide convenient facilities for
|
|
manipulating CCI records. The developer can specify the strategy to create records and
|
|
extract datas from records, for use with Spring's `CciTemplate`. The following
|
|
interfaces will configure the strategy to use input and output records if you don't want
|
|
to work with records directly in your application.
|
|
|
|
In order to create an input `Record`, the developer can use a dedicated implementation
|
|
of the `RecordCreator` interface.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface RecordCreator {
|
|
|
|
Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;
|
|
|
|
}
|
|
----
|
|
|
|
As you can see, the `createRecord(..)` method receives a `RecordFactory` instance as
|
|
parameter, which corresponds to the `RecordFactory` of the `ConnectionFactory` used.
|
|
This reference can be used to create `IndexedRecord` or `MappedRecord` instances. The
|
|
following sample shows how to use the `RecordCreator` interface and indexed/mapped
|
|
records.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyRecordCreator implements RecordCreator {
|
|
|
|
public Record createRecord(RecordFactory recordFactory) throws ResourceException {
|
|
IndexedRecord input = recordFactory.createIndexedRecord("input");
|
|
input.add(new Integer(id));
|
|
return input;
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
An output `Record` can be used to receive data back from the EIS. Hence, a specific
|
|
implementation of the `RecordExtractor` interface can be passed to Spring's
|
|
`CciTemplate` for extracting data from the output `Record`.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface RecordExtractor {
|
|
|
|
Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;
|
|
|
|
}
|
|
----
|
|
|
|
The following sample shows how to use the `RecordExtractor` interface.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyRecordExtractor implements RecordExtractor {
|
|
|
|
public Object extractData(Record record) throws ResourceException {
|
|
CommAreaRecord commAreaRecord = (CommAreaRecord) record;
|
|
String str = new String(commAreaRecord.toByteArray());
|
|
String field1 = string.substring(0,6);
|
|
String field2 = string.substring(6,1);
|
|
return new OutputObject(Long.parseLong(field1), field2);
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[cci-using-template]]
|
|
==== CciTemplate
|
|
|
|
The `CciTemplate` is the central class of the core CCI support package (
|
|
`org.springframework.jca.cci.core`). It simplifies the use of CCI since it handles the
|
|
creation and release of resources. This helps to avoid common errors like forgetting to
|
|
always close the connection. It cares for the lifecycle of connection and interaction
|
|
objects, letting application code focus on generating input records from application
|
|
data and extracting application data from output records.
|
|
|
|
The JCA CCI specification defines two distinct methods to call operations on an EIS. The
|
|
CCI `Interaction` interface provides two execute method signatures:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface javax.resource.cci.Interaction {
|
|
|
|
...
|
|
|
|
boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;
|
|
|
|
Record execute(InteractionSpec spec, Record input) throws ResourceException;
|
|
|
|
...
|
|
|
|
}
|
|
----
|
|
|
|
Depending on the template method called, `CciTemplate` will know which `execute` method
|
|
to call on the interaction. In any case, a correctly initialized `InteractionSpec`
|
|
instance is mandatory.
|
|
|
|
`CciTemplate.execute(..)` can be used in two ways:
|
|
|
|
* With direct `Record` arguments. In this case, you simply need to pass the CCI input
|
|
record in, and the returned object be the corresponding CCI output record.
|
|
* With application objects, using record mapping. In this case, you need to provide
|
|
corresponding `RecordCreator` and `RecordExtractor` instances.
|
|
|
|
With the first approach, the following methods of the template will be used. These
|
|
methods directly correspond to those on the `Interaction` interface.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class CciTemplate implements CciOperations {
|
|
|
|
public Record execute(InteractionSpec spec, Record inputRecord)
|
|
throws DataAccessException { ... }
|
|
|
|
public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
|
|
throws DataAccessException { ... }
|
|
|
|
}
|
|
----
|
|
|
|
With the second approach, we need to specify the record creation and record extraction
|
|
strategies as arguments. The interfaces used are those describe in the previous section
|
|
on record conversion. The corresponding `CciTemplate` methods are the following:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class CciTemplate implements CciOperations {
|
|
|
|
public Record execute(InteractionSpec spec,
|
|
RecordCreator inputCreator) throws DataAccessException {
|
|
// ...
|
|
}
|
|
|
|
public Object execute(InteractionSpec spec, Record inputRecord,
|
|
RecordExtractor outputExtractor) throws DataAccessException {
|
|
// ...
|
|
}
|
|
|
|
public Object execute(InteractionSpec spec, RecordCreator creator,
|
|
RecordExtractor extractor) throws DataAccessException {
|
|
// ...
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
Unless the `outputRecordCreator` property is set on the template (see the following
|
|
section), every method will call the corresponding `execute` method of the CCI
|
|
`Interaction` with two parameters: `InteractionSpec` and input `Record`, receiving an
|
|
output `Record` as return value.
|
|
|
|
`CciTemplate` also provides methods to create `IndexRecord` and `MappedRecord` outside a
|
|
`RecordCreator` implementation, through its `createIndexRecord(..)` and
|
|
`createMappedRecord(..)` methods. This can be used within DAO implementations to create
|
|
`Record` instances to pass into corresponding `CciTemplate.execute(..)` methods.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class CciTemplate implements CciOperations {
|
|
|
|
public IndexedRecord createIndexedRecord(String name) throws DataAccessException { ... }
|
|
|
|
public MappedRecord createMappedRecord(String name) throws DataAccessException { ... }
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[cci-using-dao]]
|
|
==== DAO support
|
|
|
|
Spring's CCI support provides a abstract class for DAOs, supporting injection of a
|
|
`ConnectionFactory` or a `CciTemplate` instances. The name of the class is
|
|
`CciDaoSupport`: It provides simple `setConnectionFactory` and `setCciTemplate` methods.
|
|
Internally, this class will create a `CciTemplate` instance for a passed-in
|
|
`ConnectionFactory`, exposing it to concrete data access implementations in subclasses.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public abstract class CciDaoSupport {
|
|
|
|
public void setConnectionFactory(ConnectionFactory connectionFactory) {
|
|
// ...
|
|
}
|
|
|
|
public ConnectionFactory getConnectionFactory() {
|
|
// ...
|
|
}
|
|
|
|
public void setCciTemplate(CciTemplate cciTemplate) {
|
|
// ...
|
|
}
|
|
|
|
public CciTemplate getCciTemplate() {
|
|
// ...
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[automatic-output-generation]]
|
|
==== Automatic output record generation
|
|
|
|
If the connector used only supports the `Interaction.execute(..)` method with input and
|
|
output records as parameters (that is, it requires the desired output record to be
|
|
passed in instead of returning an appropriate output record), you can set the
|
|
`outputRecordCreator` property of the `CciTemplate` to automatically generate an output
|
|
record to be filled by the JCA connector when the response is received. This record will
|
|
be then returned to the caller of the template.
|
|
|
|
This property simply holds an implementation of the `RecordCreator` interface, used for
|
|
that purpose. The `RecordCreator` interface has already been discussed in
|
|
<<cci-record-creator>>. The `outputRecordCreator` property must be directly specified on
|
|
the `CciTemplate`. This could be done in the application code like so:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
cciTemplate.setOutputRecordCreator(new EciOutputRecordCreator());
|
|
----
|
|
|
|
Or (recommended) in the Spring configuration, if the `CciTemplate` is configured as a
|
|
dedicated bean instance:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="eciOutputRecordCreator" class="eci.EciOutputRecordCreator"/>
|
|
|
|
<bean id="cciTemplate" class="org.springframework.jca.cci.core.CciTemplate">
|
|
<property name="connectionFactory" ref="eciConnectionFactory"/>
|
|
<property name="outputRecordCreator" ref="eciOutputRecordCreator"/>
|
|
</bean>
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
As the `CciTemplate` class is thread-safe, it will usually be configured as a shared
|
|
instance.
|
|
====
|
|
|
|
|
|
[[template-summary]]
|
|
==== Summary
|
|
|
|
The following table summarizes the mechanisms of the `CciTemplate` class and the
|
|
corresponding methods called on the CCI `Interaction` interface:
|
|
|
|
[[cci-interaction-execute-methods]]
|
|
.Usage of Interaction execute methods
|
|
[cols="3,1,3"]
|
|
|===
|
|
| CciTemplate method signature | CciTemplate outputRecordCreator property | execute method called on the CCI Interaction
|
|
|
|
| Record execute(InteractionSpec, Record)
|
|
| not set
|
|
| Record execute(InteractionSpec, Record)
|
|
|
|
| Record execute(InteractionSpec, Record)
|
|
| set
|
|
| boolean execute(InteractionSpec, Record, Record)
|
|
|
|
| void execute(InteractionSpec, Record, Record)
|
|
| not set
|
|
| void execute(InteractionSpec, Record, Record)
|
|
|
|
| void execute(InteractionSpec, Record, Record)
|
|
| set
|
|
| void execute(InteractionSpec, Record, Record)
|
|
|
|
| Record execute(InteractionSpec, RecordCreator)
|
|
| not set
|
|
| Record execute(InteractionSpec, Record)
|
|
|
|
| Record execute(InteractionSpec, RecordCreator)
|
|
| set
|
|
| void execute(InteractionSpec, Record, Record)
|
|
|
|
| Record execute(InteractionSpec, Record, RecordExtractor)
|
|
| not set
|
|
| Record execute(InteractionSpec, Record)
|
|
|
|
| Record execute(InteractionSpec, Record, RecordExtractor)
|
|
| set
|
|
| void execute(InteractionSpec, Record, Record)
|
|
|
|
| Record execute(InteractionSpec, RecordCreator, RecordExtractor)
|
|
| not set
|
|
| Record execute(InteractionSpec, Record)
|
|
|
|
| Record execute(InteractionSpec, RecordCreator, RecordExtractor)
|
|
| set
|
|
| void execute(InteractionSpec, Record, Record)
|
|
|===
|
|
|
|
|
|
[[cci-straight]]
|
|
==== Using a CCI Connection and Interaction directly
|
|
|
|
`CciTemplate` also offers the possibility to work directly with CCI connections and
|
|
interactions, in the same manner as `JdbcTemplate` and `JmsTemplate`. This is useful
|
|
when you want to perform multiple operations on a CCI connection or interaction, for
|
|
example.
|
|
|
|
The interface `ConnectionCallback` provides a CCI `Connection` as argument, in order to
|
|
perform custom operations on it, plus the CCI `ConnectionFactory` which the `Connection`
|
|
was created with. The latter can be useful for example to get an associated
|
|
`RecordFactory` instance and create indexed/mapped records, for example.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface ConnectionCallback {
|
|
|
|
Object doInConnection(Connection connection, ConnectionFactory connectionFactory)
|
|
throws ResourceException, SQLException, DataAccessException;
|
|
|
|
}
|
|
----
|
|
|
|
The interface `InteractionCallback` provides the CCI `Interaction`, in order to perform
|
|
custom operations on it, plus the corresponding CCI `ConnectionFactory`.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface InteractionCallback {
|
|
|
|
Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
|
|
throws ResourceException, SQLException, DataAccessException;
|
|
|
|
}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
`InteractionSpec` objects can either be shared across multiple template calls or newly
|
|
created inside every callback method. This is completely up to the DAO implementation.
|
|
====
|
|
|
|
|
|
[[cci-template-example]]
|
|
==== Example for CciTemplate usage
|
|
|
|
In this section, the usage of the `CciTemplate` will be shown to acces to a CICS with
|
|
ECI mode, with the IBM CICS ECI connector.
|
|
|
|
Firstly, some initializations on the CCI `InteractionSpec` must be done to specify which
|
|
CICS program to access and how to interact with it.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
ECIInteractionSpec interactionSpec = new ECIInteractionSpec();
|
|
interactionSpec.setFunctionName("MYPROG");
|
|
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
|
|
----
|
|
|
|
Then the program can use CCI via Spring's template and specify mappings between custom
|
|
objects and CCI `Records`.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyDaoImpl extends CciDaoSupport implements MyDao {
|
|
|
|
public OutputObject getData(InputObject input) {
|
|
ECIInteractionSpec interactionSpec = ...;
|
|
|
|
OutputObject output = (ObjectOutput) getCciTemplate().execute(interactionSpec,
|
|
new RecordCreator() {
|
|
public Record createRecord(RecordFactory recordFactory) throws ResourceException {
|
|
return new CommAreaRecord(input.toString().getBytes());
|
|
}
|
|
},
|
|
new RecordExtractor() {
|
|
public Object extractData(Record record) throws ResourceException {
|
|
CommAreaRecord commAreaRecord = (CommAreaRecord)record;
|
|
String str = new String(commAreaRecord.toByteArray());
|
|
String field1 = string.substring(0,6);
|
|
String field2 = string.substring(6,1);
|
|
return new OutputObject(Long.parseLong(field1), field2);
|
|
}
|
|
});
|
|
|
|
return output;
|
|
}
|
|
}
|
|
----
|
|
|
|
As discussed previously, callbacks can be used to work directly on CCI connections or
|
|
interactions.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyDaoImpl extends CciDaoSupport implements MyDao {
|
|
|
|
public OutputObject getData(InputObject input) {
|
|
ObjectOutput output = (ObjectOutput) getCciTemplate().execute(
|
|
new ConnectionCallback() {
|
|
public Object doInConnection(Connection connection,
|
|
ConnectionFactory factory) throws ResourceException {
|
|
|
|
// do something...
|
|
|
|
}
|
|
});
|
|
}
|
|
return output;
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
With a `ConnectionCallback`, the `Connection` used will be managed and closed by the
|
|
`CciTemplate`, but any interactions created on the connection must be managed by the
|
|
callback implementation.
|
|
====
|
|
|
|
For a more specific callback, you can implement an `InteractionCallback`. The passed-in
|
|
`Interaction` will be managed and closed by the `CciTemplate` in this case.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyDaoImpl extends CciDaoSupport implements MyDao {
|
|
|
|
public String getData(String input) {
|
|
ECIInteractionSpec interactionSpec = ...;
|
|
String output = (String) getCciTemplate().execute(interactionSpec,
|
|
new InteractionCallback() {
|
|
public Object doInInteraction(Interaction interaction,
|
|
ConnectionFactory factory) throws ResourceException {
|
|
Record input = new CommAreaRecord(inputString.getBytes());
|
|
Record output = new CommAreaRecord();
|
|
interaction.execute(holder.getInteractionSpec(), input, output);
|
|
return new String(output.toByteArray());
|
|
}
|
|
});
|
|
return output;
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
For the examples above, the corresponding configuration of the involved Spring beans
|
|
could look like this in non-managed mode:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
|
|
<property name="serverName" value="TXSERIES"/>
|
|
<property name="connectionURL" value="local:"/>
|
|
<property name="userName" value="CICSUSER"/>
|
|
<property name="password" value="CICS"/>
|
|
</bean>
|
|
|
|
<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
|
|
</bean>
|
|
|
|
<bean id="component" class="mypackage.MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
In managed mode (that is, in a Java EE environment), the configuration could look as
|
|
follows:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>
|
|
|
|
<bean id="component" class="MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
|
|
[[cci-object]]
|
|
=== Modeling CCI access as operation objects
|
|
|
|
The `org.springframework.jca.cci.object` package contains support classes that allow you
|
|
to access the EIS in a different style: through reusable operation objects, analogous to
|
|
Spring's JDBC operation objects (see JDBC chapter). This will usually encapsulate the
|
|
CCI API: an application-level input object will be passed to the operation object, so it
|
|
can construct the input record and then convert the received record data to an
|
|
application-level output object and return it.
|
|
|
|
[NOTE]
|
|
====
|
|
This approach is internally based on the `CciTemplate` class and the
|
|
`RecordCreator` / `RecordExtractor` interfaces, reusing the machinery of Spring's core
|
|
CCI support.
|
|
====
|
|
|
|
|
|
[[cci-object-mapping-record]]
|
|
==== MappingRecordOperation
|
|
|
|
`MappingRecordOperation` essentially performs the same work as `CciTemplate`, but
|
|
represents a specific, pre-configured operation as an object. It provides two template
|
|
methods to specify how to convert an input object to a input record, and how to convert
|
|
an output record to an output object (record mapping):
|
|
|
|
* `createInputRecord(..)` to specify how to convert an input object to an input `Record`
|
|
* `extractOutputData(..)` to specify how to extract an output object from an output
|
|
`Record`
|
|
|
|
Here are the signatures of these methods:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public abstract class MappingRecordOperation extends EisOperation {
|
|
|
|
...
|
|
|
|
protected abstract Record createInputRecord(RecordFactory recordFactory,
|
|
Object inputObject) throws ResourceException, DataAccessException {
|
|
// ...
|
|
}
|
|
|
|
protected abstract Object extractOutputData(Record outputRecord)
|
|
throws ResourceException, SQLException, DataAccessException {
|
|
// ...
|
|
}
|
|
|
|
...
|
|
|
|
}
|
|
----
|
|
|
|
Thereafter, in order to execute an EIS operation, you need to use a single execute
|
|
method, passing in an application-level input object and receiving an application-level
|
|
output object as result:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public abstract class MappingRecordOperation extends EisOperation {
|
|
|
|
...
|
|
|
|
public Object execute(Object inputObject) throws DataAccessException {
|
|
}
|
|
|
|
...
|
|
}
|
|
----
|
|
|
|
As you can see, contrary to the `CciTemplate` class, this `execute(..)` method does not
|
|
have an `InteractionSpec` as argument. Instead, the `InteractionSpec` is global to the
|
|
operation. The following constructor must be used to instantiate an operation object
|
|
with a specific `InteractionSpec`:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
InteractionSpec spec = ...;
|
|
MyMappingRecordOperation eisOperation = new MyMappingRecordOperation(getConnectionFactory(), spec);
|
|
...
|
|
----
|
|
|
|
|
|
[[cci-object-mapping-comm-area]]
|
|
==== MappingCommAreaOperation
|
|
|
|
Some connectors use records based on a COMMAREA which represents an array of bytes
|
|
containing parameters to send to the EIS and data returned by it. Spring provides a
|
|
special operation class for working directly on COMMAREA rather than on records. The
|
|
`MappingCommAreaOperation` class extends the `MappingRecordOperation` class to provide
|
|
such special COMMAREA support. It implicitly uses the `CommAreaRecord` class as input
|
|
and output record type, and provides two new methods to convert an input object into an
|
|
input COMMAREA and the output COMMAREA into an output object.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public abstract class MappingCommAreaOperation extends MappingRecordOperation {
|
|
|
|
...
|
|
|
|
protected abstract byte[] objectToBytes(Object inObject)
|
|
throws IOException, DataAccessException;
|
|
|
|
protected abstract Object bytesToObject(byte[] bytes)
|
|
throws IOException, DataAccessException;
|
|
|
|
...
|
|
|
|
}
|
|
----
|
|
|
|
|
|
[[cci-automatic-record-gen]]
|
|
==== Automatic output record generation
|
|
|
|
As every `MappingRecordOperation` subclass is based on CciTemplate internally, the same
|
|
way to automatically generate output records as with `CciTemplate` is available. Every
|
|
operation object provides a corresponding `setOutputRecordCreator(..)` method. For
|
|
further information, see <<automatic-output-generation>>.
|
|
|
|
|
|
[[cci-object-summary]]
|
|
==== Summary
|
|
|
|
The operation object approach uses records in the same manner as the `CciTemplate` class.
|
|
|
|
[[cci-interaction-methods]]
|
|
.Usage of Interaction execute methods
|
|
[cols="3,1,3"]
|
|
|===
|
|
| MappingRecordOperation method signature | MappingRecordOperation outputRecordCreator property | execute method called on the CCI Interaction
|
|
|
|
| Object execute(Object)
|
|
| not set
|
|
| Record execute(InteractionSpec, Record)
|
|
|
|
| Object execute(Object)
|
|
| set
|
|
| boolean execute(InteractionSpec, Record, Record)
|
|
|===
|
|
|
|
|
|
[[cci-objects-mappring-record-example]]
|
|
==== Example for MappingRecordOperation usage
|
|
|
|
In this section, the usage of the `MappingRecordOperation` will be shown to access a
|
|
database with the Blackbox CCI connector.
|
|
|
|
[NOTE]
|
|
====
|
|
The original version of this connector is provided by the Java EE SDK (version 1.3),
|
|
available from Oracle.
|
|
====
|
|
|
|
Firstly, some initializations on the CCI `InteractionSpec` must be done to specify which
|
|
SQL request to execute. In this sample, we directly define the way to convert the
|
|
parameters of the request to a CCI record and the way to convert the CCI result record
|
|
to an instance of the `Person` class.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class PersonMappingOperation extends MappingRecordOperation {
|
|
|
|
public PersonMappingOperation(ConnectionFactory connectionFactory) {
|
|
setConnectionFactory(connectionFactory);
|
|
CciInteractionSpec interactionSpec = new CciConnectionSpec();
|
|
interactionSpec.setSql("select * from person where person_id=?");
|
|
setInteractionSpec(interactionSpec);
|
|
}
|
|
|
|
protected Record createInputRecord(RecordFactory recordFactory,
|
|
Object inputObject) throws ResourceException {
|
|
Integer id = (Integer) inputObject;
|
|
IndexedRecord input = recordFactory.createIndexedRecord("input");
|
|
input.add(new Integer(id));
|
|
return input;
|
|
}
|
|
|
|
protected Object extractOutputData(Record outputRecord)
|
|
throws ResourceException, SQLException {
|
|
ResultSet rs = (ResultSet) outputRecord;
|
|
Person person = null;
|
|
if (rs.next()) {
|
|
Person person = new Person();
|
|
person.setId(rs.getInt("person_id"));
|
|
person.setLastName(rs.getString("person_last_name"));
|
|
person.setFirstName(rs.getString("person_first_name"));
|
|
}
|
|
return person;
|
|
}
|
|
}
|
|
----
|
|
|
|
Then the application can execute the operation object, with the person identifier as
|
|
argument. Note that operation object could be set up as shared instance, as it is
|
|
thread-safe.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyDaoImpl extends CciDaoSupport implements MyDao {
|
|
|
|
public Person getPerson(int id) {
|
|
PersonMappingOperation query = new PersonMappingOperation(getConnectionFactory());
|
|
Person person = (Person) query.execute(new Integer(id));
|
|
return person;
|
|
}
|
|
}
|
|
----
|
|
|
|
The corresponding configuration of Spring beans could look as follows in non-managed mode:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="managedConnectionFactory"
|
|
class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
|
|
<property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
|
|
<property name="driverName" value="org.hsqldb.jdbcDriver"/>
|
|
</bean>
|
|
|
|
<bean id="targetConnectionFactory"
|
|
class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
|
|
</bean>
|
|
|
|
<bean id="connectionFactory"
|
|
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
|
|
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
|
|
<property name="connectionSpec">
|
|
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
|
|
<property name="user" value="sa"/>
|
|
<property name="password" value=""/>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="component" class="MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
In managed mode (that is, in a Java EE environment), the configuration could look as
|
|
follows:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:jndi-lookup id="targetConnectionFactory" jndi-name="eis/blackbox"/>
|
|
|
|
<bean id="connectionFactory"
|
|
class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
|
|
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
|
|
<property name="connectionSpec">
|
|
<bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
|
|
<property name="user" value="sa"/>
|
|
<property name="password" value=""/>
|
|
</bean>
|
|
</property>
|
|
</bean>
|
|
|
|
<bean id="component" class="MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[cci-objects-mapping-comm-area-example]]
|
|
==== Example for MappingCommAreaOperation usage
|
|
|
|
In this section, the usage of the `MappingCommAreaOperation` will be shown: accessing a
|
|
CICS with ECI mode with the IBM CICS ECI connector.
|
|
|
|
Firstly, the CCI `InteractionSpec` needs to be initialized to specify which CICS program
|
|
to access and how to interact with it.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public abstract class EciMappingOperation extends MappingCommAreaOperation {
|
|
|
|
public EciMappingOperation(ConnectionFactory connectionFactory, String programName) {
|
|
setConnectionFactory(connectionFactory);
|
|
ECIInteractionSpec interactionSpec = new ECIInteractionSpec(),
|
|
interactionSpec.setFunctionName(programName);
|
|
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
|
|
interactionSpec.setCommareaLength(30);
|
|
setInteractionSpec(interactionSpec);
|
|
setOutputRecordCreator(new EciOutputRecordCreator());
|
|
}
|
|
|
|
private static class EciOutputRecordCreator implements RecordCreator {
|
|
public Record createRecord(RecordFactory recordFactory) throws ResourceException {
|
|
return new CommAreaRecord();
|
|
}
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
The abstract `EciMappingOperation` class can then be subclassed to specify mappings
|
|
between custom objects and `Records`.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyDaoImpl extends CciDaoSupport implements MyDao {
|
|
|
|
public OutputObject getData(Integer id) {
|
|
EciMappingOperation query = new EciMappingOperation(getConnectionFactory(), "MYPROG") {
|
|
|
|
protected abstract byte[] objectToBytes(Object inObject) throws IOException {
|
|
Integer id = (Integer) inObject;
|
|
return String.valueOf(id);
|
|
}
|
|
|
|
protected abstract Object bytesToObject(byte[] bytes) throws IOException;
|
|
String str = new String(bytes);
|
|
String field1 = str.substring(0,6);
|
|
String field2 = str.substring(6,1);
|
|
String field3 = str.substring(7,1);
|
|
return new OutputObject(field1, field2, field3);
|
|
}
|
|
});
|
|
|
|
return (OutputObject) query.execute(new Integer(id));
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
The corresponding configuration of Spring beans could look as follows in non-managed mode:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
|
|
<property name="serverName" value="TXSERIES"/>
|
|
<property name="connectionURL" value="local:"/>
|
|
<property name="userName" value="CICSUSER"/>
|
|
<property name="password" value="CICS"/>
|
|
</bean>
|
|
|
|
<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
|
|
<property name="managedConnectionFactory" ref="managedConnectionFactory"/>
|
|
</bean>
|
|
|
|
<bean id="component" class="MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
In managed mode (that is, in a Java EE environment), the configuration could look as
|
|
follows:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>
|
|
|
|
<bean id="component" class="MyDaoImpl">
|
|
<property name="connectionFactory" ref="connectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
|
|
[[cci-tx]]
|
|
=== Transactions
|
|
|
|
JCA specifies several levels of transaction support for resource adapters. The kind of
|
|
transactions that your resource adapter supports is specified in its `ra.xml` file.
|
|
There are essentially three options: none (for example with CICS EPI connector), local
|
|
transactions (for example with a CICS ECI connector), global transactions (for example
|
|
with an IMS connector).
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<connector>
|
|
<resourceadapter>
|
|
<!-- <transaction-support>NoTransaction</transaction-support> -->
|
|
<!-- <transaction-support>LocalTransaction</transaction-support> -->
|
|
<transaction-support>XATransaction</transaction-support>
|
|
<resourceadapter>
|
|
<connector>
|
|
----
|
|
|
|
For global transactions, you can use Spring's generic transaction infrastructure to
|
|
demarcate transactions, with `JtaTransactionManager` as backend (delegating to the Java
|
|
EE server's distributed transaction coordinator underneath).
|
|
|
|
For local transactions on a single CCI `ConnectionFactory`, Spring provides a specific
|
|
transaction management strategy for CCI, analogous to the `DataSourceTransactionManager`
|
|
for JDBC. The CCI API defines a local transaction object and corresponding local
|
|
transaction demarcation methods. Spring's `CciLocalTransactionManager` executes such
|
|
local CCI transactions, fully compliant with Spring's generic
|
|
`PlatformTransactionManager` abstraction.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
|
|
|
|
<bean id="eciTransactionManager"
|
|
class="org.springframework.jca.cci.connection.CciLocalTransactionManager">
|
|
<property name="connectionFactory" ref="eciConnectionFactory"/>
|
|
</bean>
|
|
----
|
|
|
|
Both transaction strategies can be used with any of Spring's transaction demarcation
|
|
facilities, be it declarative or programmatic. This is a consequence of Spring's generic
|
|
`PlatformTransactionManager` abstraction, which decouples transaction demarcation from
|
|
the actual execution strategy. Simply switch between `JtaTransactionManager` and
|
|
`CciLocalTransactionManager` as needed, keeping your transaction demarcation as-is.
|
|
|
|
For more information on Spring's transaction facilities, see the chapter entitled
|
|
<<data-access.adoc#transaction,Transaction Management>>.
|
|
|
|
|
|
|
|
|
|
[[mail]]
|
|
== Email
|
|
|
|
|
|
|
|
[[mail-introduction]]
|
|
=== Introduction
|
|
|
|
.Library dependencies
|
|
****
|
|
The following JAR needs to be on the classpath of your application in order to use
|
|
the Spring Framework's email library.
|
|
|
|
* The https://java.net/projects/javamail/pages/Home[JavaMail] library
|
|
|
|
This library is freely available on the web -- for example, in Maven Central as
|
|
`com.sun.mail:javax.mail`.
|
|
****
|
|
|
|
The Spring Framework provides a helpful utility library for sending email that shields
|
|
the user from the specifics of the underlying mailing system and is responsible for low
|
|
level resource handling on behalf of the client.
|
|
|
|
The `org.springframework.mail` package is the root level package for the Spring
|
|
Framework's email support. The central interface for sending emails is the `MailSender`
|
|
interface; a simple value object encapsulating the properties of a simple mail such as
|
|
__from__ and __to__ (plus many others) is the `SimpleMailMessage` class. This package
|
|
also contains a hierarchy of checked exceptions which provide a higher level of
|
|
abstraction over the lower level mail system exceptions with the root exception being
|
|
`MailException`. Please refer to the javadocs for more information on the rich mail
|
|
exception hierarchy.
|
|
|
|
The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized
|
|
__JavaMail__ features such as MIME message support to the `MailSender` interface (from
|
|
which it inherits). `JavaMailSender` also provides a callback interface for preparing
|
|
a 'MimeMessage', called `org.springframework.mail.javamail.MimeMessagePreparator`.
|
|
|
|
|
|
|
|
[[mail-usage]]
|
|
=== Usage
|
|
|
|
Let's assume there is a business interface called `OrderManager`:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface OrderManager {
|
|
|
|
void placeOrder(Order order);
|
|
|
|
}
|
|
----
|
|
|
|
Let us also assume that there is a requirement stating that an email message with an
|
|
order number needs to be generated and sent to a customer placing the relevant order.
|
|
|
|
|
|
[[mail-usage-simple]]
|
|
==== Basic MailSender and SimpleMailMessage usage
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
import org.springframework.mail.MailException;
|
|
import org.springframework.mail.MailSender;
|
|
import org.springframework.mail.SimpleMailMessage;
|
|
|
|
public class SimpleOrderManager implements OrderManager {
|
|
|
|
private MailSender mailSender;
|
|
private SimpleMailMessage templateMessage;
|
|
|
|
public void setMailSender(MailSender mailSender) {
|
|
this.mailSender = mailSender;
|
|
}
|
|
|
|
public void setTemplateMessage(SimpleMailMessage templateMessage) {
|
|
this.templateMessage = templateMessage;
|
|
}
|
|
|
|
public void placeOrder(Order order) {
|
|
|
|
// Do the business calculations...
|
|
|
|
// Call the collaborators to persist the order...
|
|
|
|
// Create a thread safe "copy" of the template message and customize it
|
|
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
|
msg.setTo(order.getCustomer().getEmailAddress());
|
|
msg.setText(
|
|
"Dear " + order.getCustomer().getFirstName()
|
|
+ order.getCustomer().getLastName()
|
|
+ ", thank you for placing order. Your order number is "
|
|
+ order.getOrderNumber());
|
|
try{
|
|
this.mailSender.send(msg);
|
|
}
|
|
catch (MailException ex) {
|
|
// simply log it and go on...
|
|
System.err.println(ex.getMessage());
|
|
}
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
Find below the bean definitions for the above code:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
|
<property name="host" value="mail.mycompany.com"/>
|
|
</bean>
|
|
|
|
<!-- this is a template message that we can pre-load with default state -->
|
|
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
|
|
<property name="from" value="customerservice@mycompany.com"/>
|
|
<property name="subject" value="Your order"/>
|
|
</bean>
|
|
|
|
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
|
|
<property name="mailSender" ref="mailSender"/>
|
|
<property name="templateMessage" ref="templateMessage"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
[[mail-usage-mime]]
|
|
==== Using the JavaMailSender and the MimeMessagePreparator
|
|
|
|
Here is another implementation of `OrderManager` using the `MimeMessagePreparator`
|
|
callback interface. Please note in this case that the `mailSender` property is of type
|
|
`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
import javax.mail.Message;
|
|
import javax.mail.MessagingException;
|
|
import javax.mail.internet.InternetAddress;
|
|
import javax.mail.internet.MimeMessage;
|
|
|
|
import javax.mail.internet.MimeMessage;
|
|
import org.springframework.mail.MailException;
|
|
import org.springframework.mail.javamail.JavaMailSender;
|
|
import org.springframework.mail.javamail.MimeMessagePreparator;
|
|
|
|
public class SimpleOrderManager implements OrderManager {
|
|
|
|
private JavaMailSender mailSender;
|
|
|
|
public void setMailSender(JavaMailSender mailSender) {
|
|
this.mailSender = mailSender;
|
|
}
|
|
|
|
public void placeOrder(final Order order) {
|
|
// Do the business calculations...
|
|
// Call the collaborators to persist the order...
|
|
|
|
MimeMessagePreparator preparator = new MimeMessagePreparator() {
|
|
public void prepare(MimeMessage mimeMessage) throws Exception {
|
|
mimeMessage.setRecipient(Message.RecipientType.TO,
|
|
new InternetAddress(order.getCustomer().getEmailAddress()));
|
|
mimeMessage.setFrom(new InternetAddress("mail@mycompany.com"));
|
|
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
|
|
order.getCustomer().getLastName() + ", thanks for your order. " +
|
|
"Your order number is " + order.getOrderNumber() + ".");
|
|
}
|
|
};
|
|
|
|
try {
|
|
this.mailSender.send(preparator);
|
|
}
|
|
catch (MailException ex) {
|
|
// simply log it and go on...
|
|
System.err.println(ex.getMessage());
|
|
}
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
The mail code is a crosscutting concern and could well be a candidate for refactoring
|
|
into a <<core.adoc#aop,custom Spring AOP aspect>>, which then could be executed at appropriate
|
|
joinpoints on the `OrderManager` target.
|
|
====
|
|
|
|
The Spring Framework's mail support ships with the standard JavaMail implementation.
|
|
Please refer to the relevant javadocs for more information.
|
|
|
|
|
|
|
|
[[mail-javamail-mime]]
|
|
=== Using the JavaMail MimeMessageHelper
|
|
|
|
A class that comes in pretty handy when dealing with JavaMail messages is the
|
|
`org.springframework.mail.javamail.MimeMessageHelper` class, which shields you from
|
|
having to use the verbose JavaMail API. Using the `MimeMessageHelper` it is pretty easy
|
|
to create a `MimeMessage`:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
// of course you would use DI in any real-world cases
|
|
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
|
sender.setHost("mail.host.com");
|
|
|
|
MimeMessage message = sender.createMimeMessage();
|
|
MimeMessageHelper helper = new MimeMessageHelper(message);
|
|
helper.setTo("test@host.com");
|
|
helper.setText("Thank you for ordering!");
|
|
|
|
sender.send(message);
|
|
----
|
|
|
|
|
|
[[mail-javamail-mime-attachments]]
|
|
==== Sending attachments and inline resources
|
|
|
|
Multipart email messages allow for both attachments and inline resources. Examples of
|
|
inline resources would be images or a stylesheet you want to use in your message, but
|
|
that you don't want displayed as an attachment.
|
|
|
|
[[mail-javamail-mime-attachments-attachment]]
|
|
===== Attachments
|
|
|
|
The following example shows you how to use the `MimeMessageHelper` to send an email
|
|
along with a single JPEG image attachment.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
|
sender.setHost("mail.host.com");
|
|
|
|
MimeMessage message = sender.createMimeMessage();
|
|
|
|
// use the true flag to indicate you need a multipart message
|
|
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
|
helper.setTo("test@host.com");
|
|
|
|
helper.setText("Check out this image!");
|
|
|
|
// let's attach the infamous windows Sample file (this time copied to c:/)
|
|
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
|
|
helper.addAttachment("CoolImage.jpg", file);
|
|
|
|
sender.send(message);
|
|
----
|
|
|
|
[[mail-javamail-mime-attachments-inline]]
|
|
===== Inline resources
|
|
|
|
The following example shows you how to use the `MimeMessageHelper` to send an email
|
|
along with an inline image.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
|
sender.setHost("mail.host.com");
|
|
|
|
MimeMessage message = sender.createMimeMessage();
|
|
|
|
// use the true flag to indicate you need a multipart message
|
|
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
|
helper.setTo("test@host.com");
|
|
|
|
// use the true flag to indicate the text included is HTML
|
|
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
|
|
|
|
// let's include the infamous windows Sample file (this time copied to c:/)
|
|
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
|
|
helper.addInline("identifier1234", res);
|
|
|
|
sender.send(message);
|
|
----
|
|
|
|
[WARNING]
|
|
====
|
|
Inline resources are added to the `MimeMessage` using the specified `Content-ID`
|
|
(`identifier1234` in the above example). The order in which you are adding the text
|
|
and the resource are __very__ important. Be sure to __first add the text__ and after
|
|
that the resources. If you are doing it the other way around, it won't work!
|
|
====
|
|
|
|
|
|
[[mail-templates]]
|
|
==== Creating email content using a templating library
|
|
|
|
The code in the previous examples explicitly created the content of the email message,
|
|
using methods calls such as `message.setText(..)`. This is fine for simple cases, and it
|
|
is okay in the context of the aforementioned examples, where the intent was to show you
|
|
the very basics of the API.
|
|
|
|
In your typical enterprise application though, you are not going to create the content
|
|
of your emails using the above approach for a number of reasons.
|
|
|
|
* Creating HTML-based email content in Java code is tedious and error prone
|
|
* There is no clear separation between display logic and business logic
|
|
* Changing the display structure of the email content requires writing Java code,
|
|
recompiling, redeploying...
|
|
|
|
Typically the approach taken to address these issues is to use a template library such
|
|
as FreeMarker to define the display structure of email content. This leaves your code
|
|
tasked only with creating the data that is to be rendered in the email template and
|
|
sending the email. It is definitely a best practice for when the content of your emails
|
|
becomes even moderately complex, and with the Spring Framework's support classes for
|
|
FreeMarker becomes quite easy to do.
|
|
|
|
|
|
|
|
|
|
[[scheduling]]
|
|
== Task Execution and Scheduling
|
|
|
|
|
|
|
|
[[scheduling-introduction]]
|
|
=== Introduction
|
|
|
|
The Spring Framework provides abstractions for asynchronous execution and scheduling of
|
|
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
|
|
features implementations of those interfaces that support thread pools or delegation to
|
|
CommonJ within an application server environment. Ultimately the use of these
|
|
implementations behind the common interfaces abstracts away the differences between Java
|
|
SE 5, Java SE 6 and Java EE environments.
|
|
|
|
Spring also features integration classes for supporting scheduling with the `Timer`,
|
|
part of the JDK since 1.3, and the Quartz Scheduler ( https://www.quartz-scheduler.org/[]).
|
|
Both of those schedulers are set up using a `FactoryBean` with optional references to
|
|
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
|
|
the Quartz Scheduler and the `Timer` is available that allows you to invoke a method of
|
|
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
|
|
operation).
|
|
|
|
|
|
|
|
[[scheduling-task-executor]]
|
|
=== The Spring TaskExecutor abstraction
|
|
|
|
Executors are the JDK name for the concept of thread pools. The "executor" naming is
|
|
due to the fact that there is no guarantee that the underlying implementation is
|
|
actually a pool; an executor may be single-threaded or even synchronous. Spring's
|
|
abstraction hides implementation details between Java SE and Java EE environments.
|
|
|
|
Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor`
|
|
interface. In fact, originally, its primary reason for existence was to abstract away
|
|
the need for Java 5 when using thread pools. The interface has a single method
|
|
`execute(Runnable task)` that accepts a task for execution based on the semantics
|
|
and configuration of the thread pool.
|
|
|
|
The `TaskExecutor` was originally created to give other Spring components an abstraction
|
|
for thread pooling where needed. Components such as the `ApplicationEventMulticaster`,
|
|
JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the
|
|
`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling
|
|
behavior, it is possible to use this abstraction for your own needs.
|
|
|
|
|
|
[[scheduling-task-executor-types]]
|
|
==== TaskExecutor types
|
|
|
|
There are a number of pre-built implementations of `TaskExecutor` included with the
|
|
Spring distribution. In all likelihood, you should never need to implement your own.
|
|
The common out-of-the-box variants are:
|
|
|
|
* `SyncTaskExecutor`
|
|
This implementation does not execute invocations asynchronously. Instead, each
|
|
invocation takes place in the calling thread. It is primarily used in situations
|
|
where multi-threading is not necessary such as in simple test cases.
|
|
* `SimpleAsyncTaskExecutor`
|
|
This implementation does not reuse any threads, rather it starts up a new thread
|
|
for each invocation. However, it does support a concurrency limit which will block
|
|
any invocations that are over the limit until a slot has been freed up. If you
|
|
are looking for true pooling, see `ThreadPoolTaskExecutor` below.
|
|
* `ConcurrentTaskExecutor`
|
|
This implementation is an adapter for a `java.util.concurrent.Executor` instance.
|
|
There is an alternative, `ThreadPoolTaskExecutor`, that exposes the `Executor`
|
|
configuration parameters as bean properties. There is rarely a need to use
|
|
`ConcurrentTaskExecutor` directly, but if the `ThreadPoolTaskExecutor` is not
|
|
flexible enough for your needs, then `ConcurrentTaskExecutor` is an alternative.
|
|
* `ThreadPoolTaskExecutor`
|
|
This implementation is the most commonly used one. It exposes bean properties for
|
|
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
|
|
If you need to adapt to a different kind of `java.util.concurrent.Executor`, it is
|
|
recommended that you use a `ConcurrentTaskExecutor` instead.
|
|
* `WorkManagerTaskExecutor`
|
|
This implementation uses a CommonJ `WorkManager` as its backing service provider
|
|
and is the central convenience class for setting up CommonJ-based thread pool
|
|
integration on WebLogic/WebSphere within a Spring application context.
|
|
* `DefaultManagedTaskExecutor`
|
|
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
|
|
compatible runtime environment such as a Java EE 7+ application server,
|
|
replacing a CommonJ WorkManager for that purpose.
|
|
|
|
|
|
[[scheduling-task-executor-usage]]
|
|
==== Using a TaskExecutor
|
|
|
|
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the example
|
|
below, we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
|
|
out a set of messages.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
import org.springframework.core.task.TaskExecutor;
|
|
|
|
public class TaskExecutorExample {
|
|
|
|
private class MessagePrinterTask implements Runnable {
|
|
|
|
private String message;
|
|
|
|
public MessagePrinterTask(String message) {
|
|
this.message = message;
|
|
}
|
|
|
|
public void run() {
|
|
System.out.println(message);
|
|
}
|
|
}
|
|
|
|
private TaskExecutor taskExecutor;
|
|
|
|
public TaskExecutorExample(TaskExecutor taskExecutor) {
|
|
this.taskExecutor = taskExecutor;
|
|
}
|
|
|
|
public void printMessages() {
|
|
for(int i = 0; i < 25; i++) {
|
|
taskExecutor.execute(new MessagePrinterTask("Message" + i));
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
As you can see, rather than retrieving a thread from the pool and executing yourself,
|
|
you add your `Runnable` to the queue and the `TaskExecutor` uses its internal rules to
|
|
decide when the task gets executed.
|
|
|
|
To configure the rules that the `TaskExecutor` will use, simple bean properties have
|
|
been exposed.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
|
|
<property name="corePoolSize" value="5"/>
|
|
<property name="maxPoolSize" value="10"/>
|
|
<property name="queueCapacity" value="25"/>
|
|
</bean>
|
|
|
|
<bean id="taskExecutorExample" class="TaskExecutorExample">
|
|
<constructor-arg ref="taskExecutor"/>
|
|
</bean>
|
|
----
|
|
|
|
|
|
|
|
[[scheduling-task-scheduler]]
|
|
=== The Spring TaskScheduler abstraction
|
|
|
|
In addition to the `TaskExecutor` abstraction, Spring 3.0 introduces a `TaskScheduler`
|
|
with a variety of methods for scheduling tasks to run at some point in the future.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface TaskScheduler {
|
|
|
|
ScheduledFuture schedule(Runnable task, Trigger trigger);
|
|
|
|
ScheduledFuture schedule(Runnable task, Instant startTime);
|
|
|
|
ScheduledFuture schedule(Runnable task, Date startTime);
|
|
|
|
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
|
|
|
|
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
|
|
|
|
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
|
|
|
|
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
|
|
|
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
|
|
|
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
|
|
|
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
|
|
|
|
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
|
|
}
|
|
----
|
|
|
|
The simplest method is the one named 'schedule' that takes a `Runnable` and `Date` only.
|
|
That will cause the task to run once after the specified time. All of the other methods
|
|
are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay
|
|
methods are for simple, periodic execution, but the method that accepts a Trigger is
|
|
much more flexible.
|
|
|
|
|
|
[[scheduling-trigger-interface]]
|
|
==== Trigger interface
|
|
|
|
The `Trigger` interface is essentially inspired by JSR-236 which, as of Spring 3.0,
|
|
was not yet officially implemented. The basic idea of the `Trigger` is that execution
|
|
times may be determined based on past execution outcomes or even arbitrary conditions.
|
|
If these determinations do take into account the outcome of the preceding execution,
|
|
that information is available within a `TriggerContext`. The `Trigger` interface itself
|
|
is quite simple:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface Trigger {
|
|
|
|
Date nextExecutionTime(TriggerContext triggerContext);
|
|
}
|
|
----
|
|
|
|
As you can see, the `TriggerContext` is the most important part. It encapsulates all of
|
|
the relevant data, and is open for extension in the future if necessary. The
|
|
`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by
|
|
default). Here you can see what methods are available for `Trigger` implementations.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public interface TriggerContext {
|
|
|
|
Date lastScheduledExecutionTime();
|
|
|
|
Date lastActualExecutionTime();
|
|
|
|
Date lastCompletionTime();
|
|
}
|
|
----
|
|
|
|
|
|
[[scheduling-trigger-implementations]]
|
|
==== Trigger implementations
|
|
|
|
Spring provides two implementations of the `Trigger` interface. The most interesting one
|
|
is the `CronTrigger`. It enables the scheduling of tasks based on cron expressions. For
|
|
example, the following task is being scheduled to run 15 minutes past each hour but only
|
|
during the 9-to-5 "business hours" on weekdays.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
|
|
----
|
|
|
|
The other out-of-the-box implementation is a `PeriodicTrigger` that accepts a fixed
|
|
period, an optional initial delay value, and a boolean to indicate whether the period
|
|
should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler`
|
|
interface already defines methods for scheduling tasks at a fixed-rate or with a
|
|
fixed-delay, those methods should be used directly whenever possible. The value of the
|
|
`PeriodicTrigger` implementation is that it can be used within components that rely on
|
|
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
|
|
cron-based triggers, and even custom trigger implementations to be used interchangeably.
|
|
Such a component could take advantage of dependency injection so that such `Triggers`
|
|
could be configured externally and therefore easily modified or extended.
|
|
|
|
|
|
[[scheduling-task-scheduler-implementations]]
|
|
==== TaskScheduler implementations
|
|
|
|
As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler`
|
|
arrangement is that an application's scheduling needs are decoupled from the deployment
|
|
environment. This abstraction level is particularly relevant when deploying to an
|
|
application server environment where threads should not be created directly by the
|
|
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
|
|
delegating to a CommonJ TimerManager on WebLogic/WebSphere as well as a more recent
|
|
`DefaultManagedTaskScheduler` delegating to a JSR-236 `ManagedScheduledExecutorService`
|
|
in a Java EE 7+ environment, both typically configured with a JNDI lookup.
|
|
|
|
Whenever external thread management is not a requirement, a simpler alternative is
|
|
a local `ScheduledExecutorService` setup within the application which can be adapted
|
|
through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a
|
|
`ThreadPoolTaskScheduler` which internally delegates to a `ScheduledExecutorService`,
|
|
providing common bean-style configuration along the lines of `ThreadPoolTaskExecutor`.
|
|
These variants work perfectly fine for locally embedded thread pool setups in lenient
|
|
application server environments as well, in particular on Tomcat and Jetty.
|
|
|
|
|
|
|
|
[[scheduling-annotation-support]]
|
|
=== Annotation Support for Scheduling and Asynchronous Execution
|
|
|
|
Spring provides annotation support for both task scheduling and asynchronous method
|
|
execution.
|
|
|
|
|
|
[[scheduling-enable-annotation-support]]
|
|
==== Enable scheduling annotations
|
|
|
|
To enable support for `@Scheduled` and `@Async` annotations add `@EnableScheduling` and
|
|
`@EnableAsync` to one of your `@Configuration` classes:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableAsync
|
|
@EnableScheduling
|
|
public class AppConfig {
|
|
}
|
|
----
|
|
|
|
You are free to pick and choose the relevant annotations for your application. For
|
|
example, if you only need support for `@Scheduled`, simply omit `@EnableAsync`. For more
|
|
fine-grained control you can additionally implement the `SchedulingConfigurer` and/or
|
|
`AsyncConfigurer` interfaces. See the javadocs for full details.
|
|
|
|
If you prefer XML configuration use the `<task:annotation-driven>` element.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
|
|
<task:executor id="myExecutor" pool-size="5"/>
|
|
<task:scheduler id="myScheduler" pool-size="10"/>
|
|
----
|
|
|
|
Notice with the above XML that an executor reference is provided for handling those
|
|
tasks that correspond to methods with the `@Async` annotation, and the scheduler
|
|
reference is provided for managing those methods annotated with `@Scheduled`.
|
|
|
|
[NOTE]
|
|
====
|
|
The default advice mode for processing `@Async` annotations is "proxy" which allows
|
|
for interception of calls through the proxy only; local calls within the same class
|
|
cannot get intercepted that way. For a more advanced mode of interception, consider
|
|
switching to "aspectj" mode in combination with compile-time or load-time weaving.
|
|
====
|
|
|
|
|
|
[[scheduling-annotation-support-scheduled]]
|
|
==== The @Scheduled annotation
|
|
|
|
The `@Scheduled` annotation can be added to a method along with trigger metadata. For
|
|
example, the following method would be invoked every 5 seconds with a fixed delay,
|
|
meaning that the period will be measured from the completion time of each preceding
|
|
invocation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Scheduled(fixedDelay=5000)
|
|
public void doSomething() {
|
|
// something that should execute periodically
|
|
}
|
|
----
|
|
|
|
If a fixed rate execution is desired, simply change the property name specified within
|
|
the annotation. The following would be executed every 5 seconds measured between the
|
|
successive start times of each invocation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Scheduled(fixedRate=5000)
|
|
public void doSomething() {
|
|
// something that should execute periodically
|
|
}
|
|
----
|
|
|
|
For fixed-delay and fixed-rate tasks, an initial delay may be specified indicating the
|
|
number of milliseconds to wait before the first execution of the method.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Scheduled(initialDelay=1000, fixedRate=5000)
|
|
public void doSomething() {
|
|
// something that should execute periodically
|
|
}
|
|
----
|
|
|
|
If simple periodic scheduling is not expressive enough, then a cron expression may be
|
|
provided. For example, the following will only execute on weekdays.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
@Scheduled(cron="*/5 * * * * MON-FRI")
|
|
public void doSomething() {
|
|
// something that should execute on weekdays only
|
|
}
|
|
----
|
|
|
|
[TIP]
|
|
====
|
|
You can additionally use the `zone` attribute to specify the time zone in which the cron
|
|
expression will be resolved.
|
|
====
|
|
|
|
Notice that the methods to be scheduled must have void returns and must not expect any
|
|
arguments. If the method needs to interact with other objects from the Application
|
|
Context, then those would typically have been provided through dependency injection.
|
|
|
|
[NOTE]
|
|
====
|
|
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
|
|
|
|
Make sure that you are not initializing multiple instances of the same `@Scheduled`
|
|
annotation class at runtime, unless you do want to schedule callbacks to each such
|
|
instance. Related to this, make sure that you do not use `@Configurable` on bean
|
|
classes which are annotated with `@Scheduled` and registered as regular Spring beans
|
|
with the container: You would get double initialization otherwise, once through the
|
|
container and once through the `@Configurable` aspect, with the consequence of each
|
|
`@Scheduled` method being invoked twice.
|
|
====
|
|
|
|
|
|
[[scheduling-annotation-support-async]]
|
|
==== The @Async annotation
|
|
|
|
The `@Async` annotation can be provided on a method so that invocation of that method
|
|
will occur asynchronously. In other words, the caller will return immediately upon
|
|
invocation and the actual execution of the method will occur in a task that has been
|
|
submitted to a Spring `TaskExecutor`. In the simplest case, the annotation may be
|
|
applied to a `void`-returning method.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Async
|
|
void doSomething() {
|
|
// this will be executed asynchronously
|
|
}
|
|
----
|
|
|
|
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
|
|
arguments, because they will be invoked in the "normal" way by callers at runtime rather
|
|
than from a scheduled task being managed by the container. For example, the following is
|
|
a legitimate application of the `@Async` annotation.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Async
|
|
void doSomething(String s) {
|
|
// this will be executed asynchronously
|
|
}
|
|
----
|
|
|
|
Even methods that return a value can be invoked asynchronously. However, such methods
|
|
are required to have a `Future` typed return value. This still provides the benefit of
|
|
asynchronous execution so that the caller can perform other tasks prior to calling
|
|
`get()` on that Future.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Async
|
|
Future<String> returnSomething(int i) {
|
|
// this will be executed asynchronously
|
|
}
|
|
----
|
|
|
|
[TIP]
|
|
====
|
|
`@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
|
|
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
|
|
4.2, JDK 8's `java.util.concurrent.CompletableFuture`: for richer interaction with the
|
|
asynchronous task and for immediate composition with further processing steps.
|
|
====
|
|
|
|
`@Async` can not be used in conjunction with lifecycle callbacks such as
|
|
`@PostConstruct`. To asynchronously initialize Spring beans you currently have to use
|
|
a separate initializing Spring bean that invokes the `@Async` annotated method on the
|
|
target then.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class SampleBeanImpl implements SampleBean {
|
|
|
|
@Async
|
|
void doSomething() {
|
|
// ...
|
|
}
|
|
|
|
}
|
|
|
|
public class SampleBeanInitializer {
|
|
|
|
private final SampleBean bean;
|
|
|
|
public SampleBeanInitializer(SampleBean bean) {
|
|
this.bean = bean;
|
|
}
|
|
|
|
@PostConstruct
|
|
public void initialize() {
|
|
bean.doSomething();
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
There is no direct XML equivalent for `@Async` since such methods should be designed
|
|
for asynchronous execution in the first place, not externally re-declared to be async.
|
|
However, you may manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP,
|
|
in combination with a custom pointcut.
|
|
====
|
|
|
|
|
|
[[scheduling-annotation-support-qualification]]
|
|
==== Executor qualification with @Async
|
|
|
|
By default when specifying `@Async` on a method, the executor that will be used is the
|
|
one supplied to the 'annotation-driven' element as described above. However, the `value`
|
|
attribute of the `@Async` annotation can be used when needing to indicate that an
|
|
executor other than the default should be used when executing a given method.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Async("otherExecutor")
|
|
void doSomething(String s) {
|
|
// this will be executed asynchronously by "otherExecutor"
|
|
}
|
|
----
|
|
|
|
In this case, "otherExecutor" may be the name of any `Executor` bean in the Spring
|
|
container, or may be the name of a __qualifier__ associated with any `Executor`, e.g. as
|
|
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation.
|
|
|
|
|
|
[[scheduling-annotation-support-exception]]
|
|
==== Exception management with @Async
|
|
|
|
When an `@Async` method has a `Future` typed return value, it is easy to manage
|
|
an exception that was thrown during the method execution as this exception will
|
|
be thrown when calling `get` on the `Future` result. With a void return type
|
|
however, the exception is uncaught and cannot be transmitted. For those cases, an
|
|
`AsyncUncaughtExceptionHandler` can be provided to handle such exceptions.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
|
|
|
|
@Override
|
|
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
|
// handle exception
|
|
}
|
|
}
|
|
----
|
|
|
|
By default, the exception is simply logged. A custom `AsyncUncaughtExceptionHandler` can
|
|
be defined _via_ `AsyncConfigurer` or the `task:annotation-driven` XML element.
|
|
|
|
|
|
|
|
[[scheduling-task-namespace]]
|
|
=== The task namespace
|
|
|
|
Beginning with Spring 3.0, there is an XML namespace for configuring `TaskExecutor` and
|
|
`TaskScheduler` instances. It also provides a convenient way to configure tasks to be
|
|
scheduled with a trigger.
|
|
|
|
|
|
[[scheduling-task-namespace-scheduler]]
|
|
==== The 'scheduler' element
|
|
|
|
The following element will create a `ThreadPoolTaskScheduler` instance with the
|
|
specified thread pool size.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:scheduler id="scheduler" pool-size="10"/>
|
|
----
|
|
|
|
The value provided for the 'id' attribute will be used as the prefix for thread names
|
|
within the pool. The 'scheduler' element is relatively straightforward. If you do not
|
|
provide a 'pool-size' attribute, the default thread pool will only have a single thread.
|
|
There are no other configuration options for the scheduler.
|
|
|
|
|
|
[[scheduling-task-namespace-executor]]
|
|
==== The 'executor' element
|
|
|
|
The following will create a `ThreadPoolTaskExecutor` instance:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:executor id="executor" pool-size="10"/>
|
|
----
|
|
|
|
As with the scheduler above, the value provided for the 'id' attribute will be used as
|
|
the prefix for thread names within the pool. As far as the pool size is concerned, the
|
|
'executor' element supports more configuration options than the 'scheduler' element. For
|
|
one thing, the thread pool for a `ThreadPoolTaskExecutor` is itself more configurable.
|
|
Rather than just a single size, an executor's thread pool may have different values for
|
|
the __core__ and the __max__ size. If a single value is provided then the executor will
|
|
have a fixed-size thread pool (the core and max sizes are the same). However, the
|
|
'executor' element's 'pool-size' attribute also accepts a range in the form of "min-max".
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:executor
|
|
id="executorWithPoolSizeRange"
|
|
pool-size="5-25"
|
|
queue-capacity="100"/>
|
|
----
|
|
|
|
As you can see from that configuration, a 'queue-capacity' value has also been provided.
|
|
The configuration of the thread pool should also be considered in light of the
|
|
executor's queue capacity. For the full description of the relationship between pool
|
|
size and queue capacity, consult the documentation for
|
|
https://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html[ThreadPoolExecutor].
|
|
The main idea is that when a task is submitted, the executor will first try to use a
|
|
free thread if the number of active threads is currently less than the core size. If the
|
|
core size has been reached, then the task will be added to the queue as long as its
|
|
capacity has not yet been reached. Only then, if the queue's capacity __has__ been
|
|
reached, will the executor create a new thread beyond the core size. If the max size has
|
|
also been reached, then the executor will reject the task.
|
|
|
|
By default, the queue is __unbounded__, but this is rarely the desired configuration,
|
|
because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while
|
|
all pool threads are busy. Furthermore, if the queue is unbounded, then the max size has
|
|
no effect at all. Since the executor will always try the queue before creating a new
|
|
thread beyond the core size, a queue must have a finite capacity for the thread pool to
|
|
grow beyond the core size (this is why a __fixed size__ pool is the only sensible case
|
|
when using an unbounded queue).
|
|
|
|
In a moment, we will review the effects of the keep-alive setting which adds yet another
|
|
factor to consider when providing a pool size configuration. First, let's consider the
|
|
case, as mentioned above, when a task is rejected. By default, when a task is rejected,
|
|
a thread pool executor will throw a `TaskRejectedException`. However, the rejection
|
|
policy is actually configurable. The exception is thrown when using the default
|
|
rejection policy which is the `AbortPolicy` implementation. For applications where some
|
|
tasks can be skipped under heavy load, either the `DiscardPolicy` or
|
|
`DiscardOldestPolicy` may be configured instead. Another option that works well for
|
|
applications that need to throttle the submitted tasks under heavy load is the
|
|
`CallerRunsPolicy`. Instead of throwing an exception or discarding tasks, that policy
|
|
will simply force the thread that is calling the submit method to run the task itself.
|
|
The idea is that such a caller will be busy while running that task and not able to
|
|
submit other tasks immediately. Therefore it provides a simple way to throttle the
|
|
incoming load while maintaining the limits of the thread pool and queue. Typically this
|
|
allows the executor to "catch up" on the tasks it is handling and thereby frees up some
|
|
capacity on the queue, in the pool, or both. Any of these options can be chosen from an
|
|
enumeration of values available for the 'rejection-policy' attribute on the 'executor'
|
|
element.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:executor
|
|
id="executorWithCallerRunsPolicy"
|
|
pool-size="5-25"
|
|
queue-capacity="100"
|
|
rejection-policy="CALLER_RUNS"/>
|
|
----
|
|
|
|
Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads
|
|
may remain idle before being terminated. If there are more than the core number of threads
|
|
currently in the pool, after waiting this amount of time without processing a task, excess
|
|
threads will get terminated. A time value of zero will cause excess threads to terminate
|
|
immediately after executing a task without remaining follow-up work in the task queue.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:executor
|
|
id="executorWithKeepAlive"
|
|
pool-size="5-25"
|
|
keep-alive="120"/>
|
|
----
|
|
|
|
|
|
[[scheduling-task-namespace-scheduled-tasks]]
|
|
==== The 'scheduled-tasks' element
|
|
|
|
The most powerful feature of Spring's task namespace is the support for configuring
|
|
tasks to be scheduled within a Spring Application Context. This follows an approach
|
|
similar to other "method-invokers" in Spring, such as that provided by the JMS namespace
|
|
for configuring Message-driven POJOs. Basically a "ref" attribute can point to any
|
|
Spring-managed object, and the "method" attribute provides the name of a method to be
|
|
invoked on that object. Here is a simple example.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<task:scheduled-tasks scheduler="myScheduler">
|
|
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
|
|
</task:scheduled-tasks>
|
|
|
|
<task:scheduler id="myScheduler" pool-size="10"/>
|
|
----
|
|
|
|
As you can see, the scheduler is referenced by the outer element, and each individual
|
|
task includes the configuration of its trigger metadata. In the preceding example, that
|
|
metadata defines a periodic trigger with a fixed delay indicating the number of
|
|
milliseconds to wait after each task execution has completed. Another option is
|
|
'fixed-rate', indicating how often the method should be executed regardless of how long
|
|
any previous execution takes. Additionally, for both fixed-delay and fixed-rate tasks an
|
|
'initial-delay' parameter may be specified indicating the number of milliseconds to wait
|
|
before the first execution of the method. For more control, a "cron" attribute may be
|
|
provided instead. Here is an example demonstrating these other options.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
<task:scheduled-tasks scheduler="myScheduler">
|
|
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
|
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
|
|
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
|
|
</task:scheduled-tasks>
|
|
|
|
<task:scheduler id="myScheduler" pool-size="10"/>
|
|
----
|
|
|
|
|
|
|
|
[[scheduling-quartz]]
|
|
=== Using the Quartz Scheduler
|
|
|
|
Quartz uses `Trigger`, `Job` and `JobDetail` objects to realize scheduling of all kinds
|
|
of jobs. For the basic concepts behind Quartz, have a look at
|
|
https://www.quartz-scheduler.org/[]. For convenience purposes, Spring offers a couple of
|
|
classes that simplify the usage of Quartz within Spring-based applications.
|
|
|
|
|
|
[[scheduling-quartz-jobdetail]]
|
|
==== Using the JobDetailFactoryBean
|
|
|
|
Quartz `JobDetail` objects contain all information needed to run a job. Spring provides a
|
|
`JobDetailFactoryBean` which provides bean-style properties for XML configuration purposes.
|
|
Let's have a look at an example:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
|
<property name="jobClass" value="example.ExampleJob"/>
|
|
<property name="jobDataAsMap">
|
|
<map>
|
|
<entry key="timeout" value="5"/>
|
|
</map>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
The job detail configuration has all information it needs to run the job (`ExampleJob`).
|
|
The timeout is specified in the job data map. The job data map is available through the
|
|
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
|
|
its properties from the job data mapped to properties of the job instance. So in this
|
|
case, if the `ExampleJob` contains a bean property named `timeout`, the `JobDetail`
|
|
will have it applied automatically:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
package example;
|
|
|
|
public class ExampleJob extends QuartzJobBean {
|
|
|
|
private int timeout;
|
|
|
|
/**
|
|
* Setter called after the ExampleJob is instantiated
|
|
* with the value from the JobDetailFactoryBean (5)
|
|
*/
|
|
public void setTimeout(int timeout) {
|
|
this.timeout = timeout;
|
|
}
|
|
|
|
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
|
|
// do the actual work
|
|
}
|
|
|
|
}
|
|
----
|
|
|
|
All additional properties from the job data map are of course available to you as well.
|
|
|
|
[NOTE]
|
|
====
|
|
Using the `name` and `group` properties, you can modify the name and the group
|
|
of the job, respectively. By default, the name of the job matches the bean name
|
|
of the `JobDetailFactoryBean` (in the example above, this is `exampleJob`).
|
|
====
|
|
|
|
|
|
[[scheduling-quartz-method-invoking-job]]
|
|
==== Using the MethodInvokingJobDetailFactoryBean
|
|
|
|
Often you just need to invoke a method on a specific object. Using the
|
|
`MethodInvokingJobDetailFactoryBean` you can do exactly this:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
|
<property name="targetObject" ref="exampleBusinessObject"/>
|
|
<property name="targetMethod" value="doIt"/>
|
|
</bean>
|
|
----
|
|
|
|
The above example will result in the `doIt` method being called on the
|
|
`exampleBusinessObject` method (see below):
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
public class ExampleBusinessObject {
|
|
|
|
// properties and collaborators
|
|
|
|
public void doIt() {
|
|
// do the actual work
|
|
}
|
|
}
|
|
----
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
|
|
----
|
|
|
|
Using the `MethodInvokingJobDetailFactoryBean`, you don't need to create one-line jobs
|
|
that just invoke a method, and you only need to create the actual business object and
|
|
wire up the detail object.
|
|
|
|
By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering
|
|
with each other. If you specify two triggers for the same `JobDetail`, it might be
|
|
possible that before the first job has finished, the second one will start. If
|
|
`JobDetail` classes implement the `Stateful` interface, this won't happen. The second
|
|
job will not start before the first one has finished. To make jobs resulting from the
|
|
`MethodInvokingJobDetailFactoryBean` non-concurrent, set the `concurrent` flag to
|
|
`false`.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
|
<property name="targetObject" ref="exampleBusinessObject"/>
|
|
<property name="targetMethod" value="doIt"/>
|
|
<property name="concurrent" value="false"/>
|
|
</bean>
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
By default, jobs will run in a concurrent fashion.
|
|
====
|
|
|
|
|
|
[[scheduling-quartz-cron]]
|
|
==== Wiring up jobs using triggers and the SchedulerFactoryBean
|
|
|
|
We've created job details and jobs. We've also reviewed the convenience bean that allows
|
|
you to invoke a method on a specific object. Of course, we still need to schedule the
|
|
jobs themselves. This is done using triggers and a `SchedulerFactoryBean`. Several
|
|
triggers are available within Quartz and Spring offers two Quartz `FactoryBean`
|
|
implementations with convenient defaults: `CronTriggerFactoryBean` and
|
|
`SimpleTriggerFactoryBean`.
|
|
|
|
Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes
|
|
triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with
|
|
those triggers.
|
|
|
|
Find below a couple of examples:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
|
<!-- see the example of method invoking job above -->
|
|
<property name="jobDetail" ref="jobDetail"/>
|
|
<!-- 10 seconds -->
|
|
<property name="startDelay" value="10000"/>
|
|
<!-- repeat every 50 seconds -->
|
|
<property name="repeatInterval" value="50000"/>
|
|
</bean>
|
|
|
|
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
|
<property name="jobDetail" ref="exampleJob"/>
|
|
<!-- run every morning at 6 AM -->
|
|
<property name="cronExpression" value="0 0 6 * * ?"/>
|
|
</bean>
|
|
----
|
|
|
|
Now we've set up two triggers, one running every 50 seconds with a starting delay of 10
|
|
seconds and one every morning at 6 AM. To finalize everything, we need to set up the
|
|
`SchedulerFactoryBean`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
|
<property name="triggers">
|
|
<list>
|
|
<ref bean="cronTrigger"/>
|
|
<ref bean="simpleTrigger"/>
|
|
</list>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
More properties are available for the `SchedulerFactoryBean` for you to set, such as the
|
|
calendars used by the job details, properties to customize Quartz with, etc. Have a look
|
|
at the
|
|
{api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`
|
|
javadocs] for more information.
|
|
|
|
|
|
|
|
|
|
[[cache]]
|
|
== Cache Abstraction
|
|
|
|
|
|
|
|
[[cache-introduction]]
|
|
=== Introduction
|
|
|
|
Since version 3.1, Spring Framework provides support for transparently adding caching
|
|
into an existing Spring application. Similar to the <<data-access.adoc#transaction,transaction>>
|
|
support, the caching abstraction allows consistent use of various caching solutions with minimal
|
|
impact on the code.
|
|
|
|
As from Spring 4.1, the cache abstraction has been significantly improved with the
|
|
support of <<cache-jsr-107,JSR-107 annotations>> and more customization options.
|
|
|
|
|
|
|
|
[[cache-strategies]]
|
|
=== Understanding the cache abstraction
|
|
|
|
.Cache vs Buffer
|
|
****
|
|
|
|
The terms "buffer" and "cache" tend to be used interchangeably; note however they
|
|
represent different things. A buffer is used traditionally as an intermediate temporary
|
|
store for data between a fast and a slow entity. As one party would have to __wait__ for
|
|
the other affecting performance, the buffer alleviates this by allowing entire blocks of
|
|
data to move at once rather then in small chunks. The data is written and read only once
|
|
from the buffer. Furthermore, the buffers are __visible__ to at least one party which is
|
|
aware of it.
|
|
|
|
A cache on the other hand by definition is hidden and neither party is aware that
|
|
caching occurs.It as well improves performance but does that by allowing the same data
|
|
to be read multiple times in a fast fashion.
|
|
|
|
A further explanation of the differences between two can be found
|
|
https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache[here].
|
|
****
|
|
|
|
At its core, the abstraction applies caching to Java methods, reducing thus the number
|
|
of executions based on the information available in the cache. That is, each time a
|
|
__targeted__ method is invoked, the abstraction will apply a caching behavior checking
|
|
whether the method has been already executed for the given arguments. If it has, then
|
|
the cached result is returned without having to execute the actual method; if it has
|
|
not, then method is executed, the result cached and returned to the user so that, the
|
|
next time the method is invoked, the cached result is returned. This way, expensive
|
|
methods (whether CPU or IO bound) can be executed only once for a given set of
|
|
parameters and the result reused without having to actually execute the method again.
|
|
The caching logic is applied transparently without any interference to the invoker.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
|
|
Obviously this approach works only for methods that are guaranteed to return the same
|
|
output (result) for a given input (or arguments) no matter how many times it is being
|
|
executed.
|
|
====
|
|
|
|
Other cache-related operations are provided by the abstraction such as the ability
|
|
to update the content of the cache or remove one of all entries. These are useful if
|
|
the cache deals with data that can change during the course of the application.
|
|
|
|
Just like other services in the Spring Framework, the caching service is an
|
|
abstraction (not a cache implementation) and requires the use of an actual storage to
|
|
store the cache data - that is, the abstraction frees the developer from having to write
|
|
the caching logic but does not provide the actual stores. This abstraction is
|
|
materialized by the `org.springframework.cache.Cache` and
|
|
`org.springframework.cache.CacheManager` interfaces.
|
|
|
|
There are <<cache-store-configuration,a few implementations>> of that abstraction
|
|
available out of the box: JDK `java.util.concurrent.ConcurrentMap` based caches,
|
|
https://www.ehcache.org/[Ehcache 2.x], Gemfire cache,
|
|
https://github.com/ben-manes/caffeine/wiki[Caffeine] and JSR-107 compliant
|
|
caches (e.g. Ehcache 3.x). See <<cache-plug>> for more information on plugging in
|
|
other cache stores/providers.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
The caching abstraction has no special handling of multi-threaded and multi-process
|
|
environments as such features are handled by the cache implementation. .
|
|
====
|
|
|
|
If you have a multi-process environment (i.e. an application deployed on several nodes),
|
|
you will need to configure your cache provider accordingly. Depending on your use cases,
|
|
a copy of the same data on several nodes may be enough but if you change the data during
|
|
the course of the application, you may need to enable other propagation mechanisms.
|
|
|
|
Caching a particular item is a direct equivalent of the typical get-if-not-found-then-
|
|
proceed-and-put-eventually code blocks found with programmatic cache interaction: no locks
|
|
are applied and several threads may try to load the same item concurrently. The same applies
|
|
to eviction: if several threads are trying to update or evict data concurrently, you may
|
|
use stale data. Certain cache providers offer advanced features in that area, refer to
|
|
the documentation of the cache provider that you are using for more details.
|
|
|
|
To use the cache abstraction, the developer needs to take care of two aspects:
|
|
|
|
* caching declaration - identify the methods that need to be cached and their policy
|
|
* cache configuration - the backing cache where the data is stored and read from
|
|
|
|
|
|
|
|
[[cache-annotations]]
|
|
=== Declarative annotation-based caching
|
|
|
|
For caching declaration, the abstraction provides a set of Java annotations:
|
|
|
|
* `@Cacheable` triggers cache population
|
|
* `@CacheEvict` triggers cache eviction
|
|
* `@CachePut` updates the cache without interfering with the method execution
|
|
* `@Caching` regroups multiple cache operations to be applied on a method
|
|
* `@CacheConfig` shares some common cache-related settings at class-level
|
|
|
|
Let us take a closer look at each annotation:
|
|
|
|
|
|
[[cache-annotations-cacheable]]
|
|
==== @Cacheable annotation
|
|
|
|
As the name implies, `@Cacheable` is used to demarcate methods that are cacheable - that
|
|
is, methods for whom the result is stored into the cache so on subsequent invocations
|
|
(with the same arguments), the value in the cache is returned without having to actually
|
|
execute the method. In its simplest form, the annotation declaration requires the name
|
|
of the cache associated with the annotated method:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable("books")
|
|
public Book findBook(ISBN isbn) {...}
|
|
----
|
|
|
|
In the snippet above, the method `findBook` is associated with the cache named `books`.
|
|
Each time the method is called, the cache is checked to see whether the invocation has
|
|
been already executed and does not have to be repeated. While in most cases, only one
|
|
cache is declared, the annotation allows multiple names to be specified so that more
|
|
than one cache are being used. In this case, each of the caches will be checked before
|
|
executing the method - if at least one cache is hit, then the associated value will be
|
|
returned:
|
|
|
|
[NOTE]
|
|
====
|
|
All the other caches that do not contain the value will be updated as well even though
|
|
the cached method was not actually executed.
|
|
====
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable({"books", "isbns"})
|
|
public Book findBook(ISBN isbn) {...}
|
|
----
|
|
|
|
[[cache-annotations-cacheable-default-key]]
|
|
===== Default Key Generation
|
|
|
|
Since caches are essentially key-value stores, each invocation of a cached method needs
|
|
to be translated into a suitable key for cache access. Out of the box, the caching
|
|
abstraction uses a simple `KeyGenerator` based on the following algorithm:
|
|
|
|
* If no params are given, return `SimpleKey.EMPTY`.
|
|
* If only one param is given, return that instance.
|
|
* If more the one param is given, return a `SimpleKey` containing all parameters.
|
|
|
|
This approach works well for most use-cases; As long as parameters have __natural keys__
|
|
and implement valid `hashCode()` and `equals()` methods. If that is not the case then the
|
|
strategy needs to be changed.
|
|
|
|
To provide a different __default__ key generator, one needs to implement the
|
|
`org.springframework.cache.interceptor.KeyGenerator` interface.
|
|
|
|
|
|
[NOTE]
|
|
====
|
|
The default key generation strategy changed with the release of Spring 4.0. Earlier
|
|
versions of Spring used a key generation strategy that, for multiple key parameters,
|
|
only considered the `hashCode()` of parameters and not `equals()`; this could cause
|
|
unexpected key collisions (see https://jira.spring.io/browse/SPR-10237[SPR-10237]
|
|
for background). The new 'SimpleKeyGenerator' uses a compound key for such scenarios.
|
|
|
|
If you want to keep using the previous key strategy, you can configure the deprecated
|
|
`org.springframework.cache.interceptor.DefaultKeyGenerator` class or create a custom
|
|
hash-based 'KeyGenerator' implementation.
|
|
====
|
|
|
|
[[cache-annotations-cacheable-key]]
|
|
===== Custom Key Generation Declaration
|
|
|
|
Since caching is generic, it is quite likely the target methods have various signatures
|
|
that cannot be simply mapped on top of the cache structure. This tends to become obvious
|
|
when the target method has multiple arguments out of which only some are suitable for
|
|
caching (while the rest are used only by the method logic). For example:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable("books")
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
At first glance, while the two `boolean` arguments influence the way the book is found,
|
|
they are no use for the cache. Further more what if only one of the two is important
|
|
while the other is not?
|
|
|
|
For such cases, the `@Cacheable` annotation allows the user to specify how the key is
|
|
generated through its `key` attribute. The developer can use <<core.adoc#expressions,SpEL>> to
|
|
pick the arguments of interest (or their nested properties), perform operations or even
|
|
invoke arbitrary methods without having to write any code or implement any interface.
|
|
This is the recommended approach over the
|
|
<<cache-annotations-cacheable-default-key,default generator>> since methods tend to be
|
|
quite different in signatures as the code base grows; while the default strategy might
|
|
work for some methods, it rarely does for all methods.
|
|
|
|
Below are some examples of various SpEL declarations - if you are not familiar with it,
|
|
do yourself a favor and read <<core.adoc#expressions,Spring Expression Language>>:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="books", **key="#isbn"**)
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
|
|
@Cacheable(cacheNames="books", **key="#isbn.rawNumber"**)
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
|
|
@Cacheable(cacheNames="books", **key="T(someType).hash(#isbn)"**)
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
The snippets above show how easy it is to select a certain argument, one of its
|
|
properties or even an arbitrary (static) method.
|
|
|
|
If the algorithm responsible to generate the key is too specific or if it needs
|
|
to be shared, you may define a custom `keyGenerator` on the operation. To do
|
|
this, specify the name of the `KeyGenerator` bean implementation to use:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="books", **keyGenerator="myKeyGenerator"**)
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
The `key` and `keyGenerator` parameters are mutually exclusive and an operation
|
|
specifying both will result in an exception.
|
|
====
|
|
|
|
[[cache-annotations-cacheable-default-cache-resolver]]
|
|
===== Default Cache Resolution
|
|
|
|
Out of the box, the caching abstraction uses a simple `CacheResolver` that
|
|
retrieves the cache(s) defined at the operation level using the configured
|
|
`CacheManager`.
|
|
|
|
To provide a different __default__ cache resolver, one needs to implement the
|
|
`org.springframework.cache.interceptor.CacheResolver` interface.
|
|
|
|
[[cache-annotations-cacheable-cache-resolver]]
|
|
===== Custom cache resolution
|
|
|
|
The default cache resolution fits well for applications working with a
|
|
single `CacheManager` and with no complex cache resolution requirements.
|
|
|
|
For applications working with several cache managers, it is possible
|
|
to set the `cacheManager` to use per operation:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="books", **cacheManager="anotherCacheManager"**)
|
|
public Book findBook(ISBN isbn) {...}
|
|
----
|
|
|
|
It is also possible to replace the `CacheResolver` entirely in a similar
|
|
fashion as for <<cache-annotations-cacheable-key,key generation>>. The
|
|
resolution is requested for every cache operation, giving a chance to
|
|
the implementation to actually resolve the cache(s) to use based on
|
|
runtime arguments:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(**cacheResolver="runtimeCacheResolver"**)
|
|
public Book findBook(ISBN isbn) {...}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
Since Spring 4.1, the `value` attribute of the cache annotations are no longer
|
|
mandatory since this particular information can be provided by the `CacheResolver`
|
|
regardless of the content of the annotation.
|
|
|
|
Similarly to `key` and `keyGenerator`, the `cacheManager` and `cacheResolver`
|
|
parameters are mutually exclusive and an operation specifying both will
|
|
result in an exception as a custom `CacheManager` will be ignored by the
|
|
`CacheResolver` implementation. This is probably not what you expect.
|
|
====
|
|
|
|
[[cache-annotations-cacheable-synchronized]]
|
|
===== Synchronized caching
|
|
|
|
In a multi-threaded environment, certain operations might be concurrently invoked for
|
|
the same argument (typically on startup). By default, the cache abstraction does not
|
|
lock anything and the same value may be computed several times, defeating the purpose
|
|
of caching.
|
|
|
|
For those particular cases, the `sync` attribute can be used to instruct the underlying
|
|
cache provider to _lock_ the cache entry while the value is being computed. As a result,
|
|
only one thread will be busy computing the value while the others are blocked until the
|
|
entry is updated in the cache.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="foos", **sync=true**)
|
|
public Foo executeExpensiveOperation(String id) {...}
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
This is an optional feature and your favorite cache library may not support it. All
|
|
`CacheManager` implementations provided by the core framework support it. Check the
|
|
documentation of your cache provider for more details.
|
|
====
|
|
|
|
[[cache-annotations-cacheable-condition]]
|
|
===== Conditional caching
|
|
|
|
Sometimes, a method might not be suitable for caching all the time (for example, it
|
|
might depend on the given arguments). The cache annotations support such functionality
|
|
through the `condition` parameter which takes a `SpEL` expression that is evaluated to
|
|
either `true` or `false`. If `true`, the method is cached - if not, it behaves as if the
|
|
method is not cached, that is executed every time no matter what values are in the cache
|
|
or what arguments are used. A quick example - the following method will be cached only
|
|
if the argument `name` has a length shorter than 32:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="book", **condition="#name.length() < 32"**)
|
|
public Book findBook(String name)
|
|
----
|
|
|
|
In addition the `condition` parameter, the `unless` parameter can be used to veto the
|
|
adding of a value to the cache. Unlike `condition`, `unless` expressions are evaluated
|
|
__after__ the method has been called. Expanding on the previous example - perhaps we
|
|
only want to cache paperback books:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="book", condition="#name.length() < 32", **unless="#result.hardback"**)
|
|
public Book findBook(String name)
|
|
----
|
|
|
|
The cache abstraction supports `java.util.Optional`, using its content as cached value
|
|
only if it present. `#result` always refers to the business entity and never on a
|
|
supported wrapper so the previous example can be rewritten as follows:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="book", condition="#name.length() < 32", **unless="#result?.hardback"**)
|
|
public Optional<Book> findBook(String name)
|
|
----
|
|
|
|
Note that `result` still refers to `Book` and not `Optional`. As it might be `null`, we
|
|
should use the safe navigation operator.
|
|
|
|
[[cache-spel-context]]
|
|
===== Available caching SpEL evaluation context
|
|
|
|
Each `SpEL` expression evaluates again a dedicated
|
|
<<core.adoc#expressions-language-ref,`context`>>. In addition to the build in parameters, the
|
|
framework provides dedicated caching related metadata such as the argument names. The
|
|
next table lists the items made available to the context so one can use them for key and
|
|
conditional computations:
|
|
|
|
[[cache-spel-context-tbl]]
|
|
.Cache SpEL available metadata
|
|
|===
|
|
| Name| Location| Description| Example
|
|
|
|
| methodName
|
|
| root object
|
|
| The name of the method being invoked
|
|
| `#root.methodName`
|
|
|
|
| method
|
|
| root object
|
|
| The method being invoked
|
|
| `#root.method.name`
|
|
|
|
| target
|
|
| root object
|
|
| The target object being invoked
|
|
| `#root.target`
|
|
|
|
| targetClass
|
|
| root object
|
|
| The class of the target being invoked
|
|
| `#root.targetClass`
|
|
|
|
| args
|
|
| root object
|
|
| The arguments (as array) used for invoking the target
|
|
| `#root.args[0]`
|
|
|
|
| caches
|
|
| root object
|
|
| Collection of caches against which the current method is executed
|
|
| `#root.caches[0].name`
|
|
|
|
| __argument name__
|
|
| evaluation context
|
|
| Name of any of the method arguments. If for some reason the names are not available
|
|
(e.g. no debug information), the argument names are also available under the `#a<#arg>`
|
|
where __#arg__ stands for the argument index (starting from 0).
|
|
| `#iban` or `#a0` (one can also use `#p0` or `#p<#arg>` notation as an alias).
|
|
|
|
| result
|
|
| evaluation context
|
|
| The result of the method call (the value to be cached). Only available in `unless`
|
|
expressions, `cache put` expressions (to compute the `key`), or `cache evict`
|
|
expressions (when `beforeInvocation` is `false`). For supported wrappers such as
|
|
`Optional`, `#result` refers to the actual object, not the wrapper.
|
|
| `#result`
|
|
|===
|
|
|
|
|
|
[[cache-annotations-put]]
|
|
==== @CachePut annotation
|
|
|
|
For cases where the cache needs to be updated without interfering with the method
|
|
execution, one can use the `@CachePut` annotation. That is, the method will always be
|
|
executed and its result placed into the cache (according to the `@CachePut` options). It
|
|
supports the same options as `@Cacheable` and should be used for cache population rather
|
|
than method flow optimization:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@CachePut(cacheNames="book", key="#isbn")
|
|
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
|
|
----
|
|
|
|
[IMPORTANT]
|
|
====
|
|
Note that using `@CachePut` and `@Cacheable` annotations on the same method is generally
|
|
strongly discouraged because they have different behaviors. While the latter causes the
|
|
method execution to be skipped by using the cache, the former forces the execution in
|
|
order to execute a cache update. This leads to unexpected behavior and with the exception of
|
|
specific corner-cases (such as annotations having conditions that exclude them from each
|
|
other), such declaration should be avoided. Note also that such condition should not rely
|
|
on the result object (i.e. the `#result` variable) as these are validated upfront to confirm
|
|
the exclusion.
|
|
====
|
|
|
|
|
|
[[cache-annotations-evict]]
|
|
==== @CacheEvict annotation
|
|
|
|
The cache abstraction allows not just population of a cache store but also eviction.
|
|
This process is useful for removing stale or unused data from the cache. Opposed to
|
|
`@Cacheable`, annotation `@CacheEvict` demarcates methods that perform cache
|
|
__eviction__, that is methods that act as triggers for removing data from the cache.
|
|
Just like its sibling, `@CacheEvict` requires specifying one (or multiple) caches
|
|
that are affected by the action, allows a custom cache and key resolution or a
|
|
condition to be specified but in addition, features an extra parameter
|
|
`allEntries` which indicates whether a cache-wide eviction needs to be performed
|
|
rather then just an entry one (based on the key):
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@CacheEvict(cacheNames="books", **allEntries=true**)
|
|
public void loadBooks(InputStream batch)
|
|
----
|
|
|
|
This option comes in handy when an entire cache region needs to be cleared out - rather
|
|
then evicting each entry (which would take a long time since it is inefficient), all the
|
|
entries are removed in one operation as shown above. Note that the framework will ignore
|
|
any key specified in this scenario as it does not apply (the entire cache is evicted not
|
|
just one entry).
|
|
|
|
One can also indicate whether the eviction should occur after (the default) or before
|
|
the method executes through the `beforeInvocation` attribute. The former provides the
|
|
same semantics as the rest of the annotations - once the method completes successfully,
|
|
an action (in this case eviction) on the cache is executed. If the method does not
|
|
execute (as it might be cached) or an exception is thrown, the eviction does not occur.
|
|
The latter ( `beforeInvocation=true`) causes the eviction to occur always, before the
|
|
method is invoked - this is useful in cases where the eviction does not need to be tied
|
|
to the method outcome.
|
|
|
|
It is important to note that void methods can be used with `@CacheEvict` - as the
|
|
methods act as triggers, the return values are ignored (as they don't interact with the
|
|
cache) - this is not the case with `@Cacheable` which adds/updates data into the cache
|
|
and thus requires a result.
|
|
|
|
|
|
[[cache-annotations-caching]]
|
|
==== @Caching annotation
|
|
|
|
There are cases when multiple annotations of the same type, such as `@CacheEvict` or
|
|
`@CachePut` need to be specified, for example because the condition or the key
|
|
expression is different between different caches. `@Caching` allows multiple nested
|
|
`@Cacheable`, `@CachePut` and `@CacheEvict` to be used on the same method:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
|
|
public Book importBooks(String deposit, Date date)
|
|
----
|
|
|
|
|
|
[[cache-annotations-config]]
|
|
==== @CacheConfig annotation
|
|
|
|
So far we have seen that caching operations offered many customization options and
|
|
these can be set on an operation basis. However, some of the customization options
|
|
can be tedious to configure if they apply to all operations of the class. For
|
|
instance, specifying the name of the cache to use for every cache operation of the
|
|
class could be replaced by a single class-level definition. This is where `@CacheConfig`
|
|
comes into play.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
**@CacheConfig("books")**
|
|
public class BookRepositoryImpl implements BookRepository {
|
|
|
|
@Cacheable
|
|
public Book findBook(ISBN isbn) {...}
|
|
}
|
|
----
|
|
|
|
`@CacheConfig` is a class-level annotation that allows to share the cache names, the custom
|
|
`KeyGenerator`, the custom `CacheManager` and finally the custom `CacheResolver`. Placing
|
|
this annotation on the class does not turn on any caching operation.
|
|
|
|
An operation-level customization will always override a customization set on `@CacheConfig`. This
|
|
gives therefore three levels of customizations per cache operation:
|
|
|
|
* Globally configured, available for `CacheManager`, `KeyGenerator`
|
|
* At class level, using `@CacheConfig`
|
|
* At the operation level
|
|
|
|
|
|
[[cache-annotation-enable]]
|
|
==== Enable caching annotations
|
|
|
|
It is important to note that even though declaring the cache annotations does not
|
|
automatically trigger their actions - like many things in Spring, the feature has to be
|
|
declaratively enabled (which means if you ever suspect caching is to blame, you can
|
|
disable it by removing only one configuration line rather than all the annotations in
|
|
your code).
|
|
|
|
To enable caching annotations add the annotation `@EnableCaching` to one of your
|
|
`@Configuration` classes:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableCaching
|
|
public class AppConfig {
|
|
}
|
|
----
|
|
|
|
Alternatively for XML configuration use the `cache:annotation-driven` element:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:cache="http://www.springframework.org/schema/cache"
|
|
xsi:schemaLocation="
|
|
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
|
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
|
|
|
|
<cache:annotation-driven/>
|
|
</beans>
|
|
----
|
|
|
|
Both the `cache:annotation-driven` element and `@EnableCaching` annotation allow various
|
|
options to be specified that influence the way the caching behavior is added to the
|
|
application through AOP. The configuration is intentionally similar with that of
|
|
<<data-access.adoc#tx-annotation-driven-settings, `@Transactional`>>:
|
|
|
|
[NOTE]
|
|
====
|
|
The default advice mode for processing caching annotations is "proxy" which allows
|
|
for interception of calls through the proxy only; local calls within the same class
|
|
cannot get intercepted that way. For a more advanced mode of interception, consider
|
|
switching to "aspectj" mode in combination with compile-time or load-time weaving.
|
|
====
|
|
|
|
[NOTE]
|
|
====
|
|
Advanced customizations using Java config require to implement `CachingConfigurer`:
|
|
Please refer to {api-spring-framework}/cache/annotation/CachingConfigurer.html[the
|
|
javadoc for more details].
|
|
====
|
|
|
|
[[cache-annotation-driven-settings]]
|
|
.Cache annotation settings
|
|
[cols="1,1,1,3"]
|
|
|===
|
|
| XML Attribute | Annotation Attribute | Default | Description
|
|
|
|
| `cache-manager`
|
|
| N/A (See `CachingConfigurer` javadocs)
|
|
| cacheManager
|
|
| Name of cache manager to use. A default `CacheResolver` will be initialized behind
|
|
the scenes with this cache manager (or `cacheManager`if not set). For more
|
|
fine-grained management of the cache resolution, consider setting the 'cache-resolver'
|
|
attribute.
|
|
|
|
| `cache-resolver`
|
|
| N/A (See `CachingConfigurer` javadocs)
|
|
| A `SimpleCacheResolver` using the configured `cacheManager`.
|
|
| The bean name of the CacheResolver that is to be used to resolve the backing caches.
|
|
This attribute is not required, and only needs to be specified as an alternative to
|
|
the 'cache-manager' attribute.
|
|
|
|
| `key-generator`
|
|
| N/A (See `CachingConfigurer` javadocs)
|
|
| `SimpleKeyGenerator`
|
|
| Name of the custom key generator to use.
|
|
|
|
| `error-handler`
|
|
| N/A (See `CachingConfigurer` javadocs)
|
|
| `SimpleCacheErrorHandler`
|
|
| Name of the custom cache error handler to use. By default, any exception throw during
|
|
a cache related operations are thrown back at the client.
|
|
|
|
| `mode`
|
|
| `mode`
|
|
| proxy
|
|
| The default mode "proxy" processes annotated beans to be proxied using Spring's AOP
|
|
framework (following proxy semantics, as discussed above, applying to method calls
|
|
coming in through the proxy only). The alternative mode "aspectj" instead weaves the
|
|
affected classes with Spring's AspectJ caching aspect, modifying the target class byte
|
|
code to apply to any kind of method call. AspectJ weaving requires spring-aspects.jar
|
|
in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See
|
|
<<core.adoc#aop-aj-ltw-spring, Spring configuration>> for details on how to set up
|
|
load-time weaving.)
|
|
|
|
| `proxy-target-class`
|
|
| `proxyTargetClass`
|
|
| false
|
|
| Applies to proxy mode only. Controls what type of caching proxies are created for
|
|
classes annotated with the `@Cacheable` or `@CacheEvict` annotations. If the
|
|
`proxy-target-class` attribute is set to `true`, then class-based proxies are created.
|
|
If `proxy-target-class` is `false` or if the attribute is omitted, then standard JDK
|
|
interface-based proxies are created. (See <<core.adoc#aop-proxying,Proxying mechanisms>>
|
|
for a detailed examination of the different proxy types.)
|
|
|
|
| `order`
|
|
| `order`
|
|
| Ordered.LOWEST_PRECEDENCE
|
|
| Defines the order of the cache advice that is applied to beans annotated with
|
|
`@Cacheable` or `@CacheEvict`. (For more information about the rules related to
|
|
ordering of AOP advice, see <<core.adoc#aop-ataspectj-advice-ordering,Advice ordering>>.)
|
|
No specified ordering means that the AOP subsystem determines the order of the advice.
|
|
|===
|
|
|
|
[NOTE]
|
|
====
|
|
`<cache:annotation-driven/>` only looks for `@Cacheable/@CachePut/@CacheEvict/@Caching`
|
|
on beans in the same application context it is defined in. This means that, if you put
|
|
`<cache:annotation-driven/>` in a `WebApplicationContext` for a `DispatcherServlet`, it
|
|
only checks for beans in your controllers, and not your services.
|
|
See <<web.adoc#mvc-servlet,the MVC section>> for more information.
|
|
====
|
|
|
|
.Method visibility and cache annotations
|
|
****
|
|
When using proxies, you should apply the cache annotations only to methods with
|
|
__public__ visibility. If you do annotate protected, private or package-visible methods
|
|
with these annotations, no error is raised, but the annotated method does not exhibit
|
|
the configured caching settings. Consider the use of AspectJ (see below) if you need to
|
|
annotate non-public methods as it changes the bytecode itself.
|
|
****
|
|
|
|
[TIP]
|
|
====
|
|
Spring recommends that you only annotate concrete classes (and methods of concrete
|
|
classes) with the `@Cache{asterisk}` annotation, as opposed to annotating interfaces. You
|
|
certainly can place the `@Cache{asterisk}` annotation on an interface (or an interface method),
|
|
but this works only as you would expect it to if you are using interface-based proxies.
|
|
The fact that Java annotations are __not inherited from interfaces__ means that if you
|
|
are using class-based proxies ( `proxy-target-class="true"`) or the weaving-based aspect
|
|
( `mode="aspectj"`), then the caching settings are not recognized by the proxying and
|
|
weaving infrastructure, and the object will not be wrapped in a caching proxy, which
|
|
would be decidedly __bad__.
|
|
====
|
|
|
|
[NOTE]
|
|
====
|
|
In proxy mode (which is the default), only external method calls coming in through the
|
|
proxy are intercepted. This means that self-invocation, in effect, a method within the
|
|
target object calling another method of the target object, will not lead to an actual
|
|
caching at runtime even if the invoked method is marked with `@Cacheable` - considering
|
|
using the aspectj mode in this case. Also, the proxy must be fully initialized to provide
|
|
the expected behaviour so you should not rely on this feature in your initialization
|
|
code, i.e. `@PostConstruct`.
|
|
====
|
|
|
|
|
|
[[cache-annotation-stereotype]]
|
|
==== Using custom annotations
|
|
|
|
.Custom annotation and AspectJ
|
|
****
|
|
This feature only works out-of-the-box with the proxy-based approach but can be enabled
|
|
with a bit of extra effort using AspectJ.
|
|
|
|
The `spring-aspects` module defines an aspect for the standard annotations only. If you
|
|
have defined your own annotations, you also need to define an aspect for those. Check
|
|
`AnnotationCacheAspect` for an example.
|
|
****
|
|
|
|
The caching abstraction allows you to use your own annotations to identify what method
|
|
triggers cache population or eviction. This is quite handy as a template mechanism as it
|
|
eliminates the need to duplicate cache annotation declarations (especially useful if the
|
|
key or condition are specified) or if the foreign imports (`org.springframework`) are
|
|
not allowed in your code base. Similar to the rest of the
|
|
<<core.adoc#beans-stereotype-annotations,stereotype>> annotations, `@Cacheable`, `@CachePut`,
|
|
`@CacheEvict` and `@CacheConfig` can be used as <<core.adoc#beans-meta-annotations,meta-annotations>>,
|
|
that is annotations that can annotate other annotations. To wit, let us replace a common
|
|
`@Cacheable` declaration with our own, custom annotation:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@Target({ElementType.METHOD})
|
|
@Cacheable(cacheNames="books", key="#isbn")
|
|
public @interface SlowService {
|
|
}
|
|
----
|
|
|
|
Above, we have defined our own `SlowService` annotation which itself is annotated with
|
|
`@Cacheable` - now we can replace the following code:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="books", key="#isbn")
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
with:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@SlowService
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
Even though `@SlowService` is not a Spring annotation, the container automatically picks
|
|
up its declaration at runtime and understands its meaning. Note that as mentioned
|
|
<<cache-annotation-enable,above>>, the annotation-driven behavior needs to be enabled.
|
|
|
|
|
|
|
|
[[cache-jsr-107]]
|
|
=== JCache (JSR-107) annotations
|
|
|
|
Since the Spring Framework 4.1, the caching abstraction fully supports the JCache
|
|
standard annotations: these are `@CacheResult`, `@CachePut`, `@CacheRemove` and
|
|
`@CacheRemoveAll` as well as the `@CacheDefaults`, `@CacheKey` and `@CacheValue`
|
|
companions. These annotations can be used right the way without migrating your
|
|
cache store to JSR-107: the internal implementation uses Spring's caching abstraction
|
|
and provides default `CacheResolver` and `KeyGenerator` implementations that are
|
|
compliant with the specification. In other words, if you are already using Spring's
|
|
caching abstraction, you can switch to these standard annotations without changing
|
|
your cache storage (or configuration, for that matter).
|
|
|
|
|
|
[[cache-jsr-107-summary]]
|
|
==== Feature summary
|
|
|
|
For those who are familiar with Spring's caching annotations, the following table
|
|
describes the main differences between the Spring annotations and the JSR-107
|
|
counterpart:
|
|
|
|
.Spring vs. JSR-107 caching annotations
|
|
[cols="1,1,3"]
|
|
|===
|
|
| Spring | JSR-107 | Remark
|
|
|
|
| `@Cacheable`
|
|
| `@CacheResult`
|
|
| Fairly similar. `@CacheResult` can cache specific exceptions and force the
|
|
execution of the method regardless of the content of the cache.
|
|
|
|
| `@CachePut`
|
|
| `@CachePut`
|
|
| While Spring updates the cache with the result of the method invocation, JCache
|
|
requires to pass it as an argument that is annotated with `@CacheValue`. Due
|
|
to this difference, JCache allows to update the cache before or after the
|
|
actual method invocation.
|
|
|
|
| `@CacheEvict`
|
|
| `@CacheRemove`
|
|
| Fairly similar. `@CacheRemove` supports a conditional evict in case the
|
|
method invocation results in an exception.
|
|
|
|
| `@CacheEvict(allEntries=true)`
|
|
| `@CacheRemoveAll`
|
|
| See `@CacheRemove`.
|
|
|
|
| `@CacheConfig`
|
|
| `@CacheDefaults`
|
|
| Allows to configure the same concepts, in a similar fashion.
|
|
|===
|
|
|
|
JCache has the notion of `javax.cache.annotation.CacheResolver` that is identical
|
|
to the Spring's `CacheResolver` interface, except that JCache only supports a single
|
|
cache. By default, a simple implementation retrieves the cache to use based on
|
|
the name declared on the annotation. It should be noted that if no cache name
|
|
is specified on the annotation, a default is automatically generated, check the
|
|
javadoc of `@CacheResult#cacheName()` for more information.
|
|
|
|
`CacheResolver` instances are retrieved by a `CacheResolverFactory`. It is
|
|
possible to customize the factory per cache operation:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@CacheResult(cacheNames="books", *cacheResolverFactory=MyCacheResolverFactory.class*)
|
|
public Book findBook(ISBN isbn)
|
|
----
|
|
|
|
[NOTE]
|
|
====
|
|
For all referenced _classes_, Spring tries to locate a bean with the given type. If
|
|
more than one match exists, a new instance is created and can use the regular
|
|
bean lifecycle callbacks such as dependency injection.
|
|
====
|
|
|
|
Keys are generated by a `javax.cache.annotation.CacheKeyGenerator` that serves the
|
|
same purpose as Spring's `KeyGenerator`. By default, all method arguments are taken
|
|
into account unless at least one parameter is annotated with `@CacheKey`. This is
|
|
similar to Spring's <<cache-annotations-cacheable-key,custom key generation
|
|
declaration>>. For instance these are identical operations, one using Spring's
|
|
abstraction and the other with JCache:
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@Cacheable(cacheNames="books", **key="#isbn"**)
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
|
|
@CacheResult(cacheName="books")
|
|
public Book findBook(**@CacheKey** ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
|
----
|
|
|
|
The `CacheKeyResolver` to use can also be specified on the operation, in a similar
|
|
fashion as the `CacheResolverFactory`.
|
|
|
|
JCache can manage exceptions thrown by annotated methods: this can prevent an update of
|
|
the cache but it can also cache the exception as an indicator of the failure instead of
|
|
calling the method again. Let's assume that `InvalidIsbnNotFoundException` is thrown if
|
|
the structure of the ISBN is invalid. This is a permanent failure, no book could ever be
|
|
retrieved with such parameter. The following caches the exception so that further calls
|
|
with the same, invalid ISBN, throws the cached exception directly instead of invoking
|
|
the method again.
|
|
|
|
[source,java,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
@CacheResult(cacheName="books", **exceptionCacheName="failures"**
|
|
**cachedExceptions = InvalidIsbnNotFoundException.class**)
|
|
public Book findBook(ISBN isbn)
|
|
----
|
|
|
|
|
|
==== Enabling JSR-107 support
|
|
|
|
Nothing specific needs to be done to enable the JSR-107 support alongside Spring's
|
|
declarative annotation support. Both `@EnableCaching` and the
|
|
`cache:annotation-driven` element will enable automatically the JCache support
|
|
if both the JSR-107 API and the `spring-context-support` module are present in
|
|
the classpath.
|
|
|
|
[NOTE]
|
|
====
|
|
Depending of your use case, the choice is basically yours. You can even mix
|
|
and match services using the JSR-107 API and others using Spring's own
|
|
annotations. Be aware however that if these services are impacting the same
|
|
caches, a consistent and identical key generation implementation should be used.
|
|
====
|
|
|
|
|
|
|
|
[[cache-declarative-xml]]
|
|
=== Declarative XML-based caching
|
|
|
|
If annotations are not an option (no access to the sources or no external code), one can
|
|
use XML for declarative caching. So instead of annotating the methods for caching, one
|
|
specifies the target method and the caching directives externally (similar to the
|
|
declarative transaction management <<data-access.adoc#transaction-declarative-first-example,advice>>).
|
|
The previous example can be translated into:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim"]
|
|
----
|
|
<!-- the service we want to make cacheable -->
|
|
<bean id="bookService" class="x.y.service.DefaultBookService"/>
|
|
|
|
<!-- cache definitions -->
|
|
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
|
|
<cache:caching cache="books">
|
|
<cache:cacheable method="findBook" key="#isbn"/>
|
|
<cache:cache-evict method="loadBooks" all-entries="true"/>
|
|
</cache:caching>
|
|
</cache:advice>
|
|
|
|
<!-- apply the cacheable behavior to all BookService interfaces -->
|
|
<aop:config>
|
|
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
|
|
</aop:config>
|
|
|
|
<!-- cache manager definition omitted -->
|
|
----
|
|
|
|
In the configuration above, the `bookService` is made cacheable. The caching semantics
|
|
to apply are encapsulated in the `cache:advice` definition which instructs method
|
|
`findBooks` to be used for putting data into the cache while method `loadBooks` for
|
|
evicting data. Both definitions are working against the `books` cache.
|
|
|
|
The `aop:config` definition applies the cache advice to the appropriate points in the
|
|
program by using the AspectJ pointcut expression (more information is available in
|
|
<<core.adoc#aop, Aspect Oriented Programming with Spring>>).
|
|
In the example above, all methods from the `BookService` are considered and
|
|
the cache advice applied to them.
|
|
|
|
The declarative XML caching supports all of the annotation-based model so moving between
|
|
the two should be fairly easy - further more both can be used inside the same
|
|
application. The XML based approach does not touch the target code however it is
|
|
inherently more verbose; when dealing with classes with overloaded methods that are
|
|
targeted for caching, identifying the proper methods does take an extra effort since the
|
|
`method` argument is not a good discriminator - in these cases, the AspectJ pointcut can
|
|
be used to cherry pick the target methods and apply the appropriate caching
|
|
functionality. However through XML, it is easier to apply a package/group/interface-wide
|
|
caching (again due to the AspectJ pointcut) and to create template-like definitions (as
|
|
we did in the example above by defining the target cache through the `cache:definitions`
|
|
`cache` attribute).
|
|
|
|
|
|
|
|
[[cache-store-configuration]]
|
|
=== Configuring the cache storage
|
|
|
|
Out of the box, the cache abstraction provides several storage integration. To use
|
|
them, one needs to simply declare an appropriate `CacheManager` - an entity that
|
|
controls and manages ``Cache``s and can be used to retrieve these for storage.
|
|
|
|
|
|
[[cache-store-configuration-jdk]]
|
|
==== JDK ConcurrentMap-based Cache
|
|
|
|
The JDK-based `Cache` implementation resides under
|
|
`org.springframework.cache.concurrent` package. It allows one to use `ConcurrentHashMap`
|
|
as a backing `Cache` store.
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<!-- simple cache manager -->
|
|
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
|
|
<property name="caches">
|
|
<set>
|
|
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
|
|
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
|
|
</set>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
The snippet above uses the `SimpleCacheManager` to create a `CacheManager` for the two
|
|
nested `ConcurrentMapCache` instances named __default__ and __books__. Note that the
|
|
names are configured directly for each cache.
|
|
|
|
As the cache is created by the application, it is bound to its lifecycle, making it
|
|
suitable for basic use cases, tests or simple applications. The cache scales well and is
|
|
very fast but it does not provide any management or persistence capabilities nor
|
|
eviction contracts.
|
|
|
|
|
|
[[cache-store-configuration-ehcache]]
|
|
==== Ehcache-based Cache
|
|
|
|
NOTE: Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it.
|
|
|
|
The Ehcache 2.x implementation is located under `org.springframework.cache.ehcache` package.
|
|
Again, to use it, one simply needs to declare the appropriate `CacheManager`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="cacheManager"
|
|
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
|
|
|
|
<!-- EhCache library setup -->
|
|
<bean id="ehcache"
|
|
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
|
|
----
|
|
|
|
This setup bootstraps the ehcache library inside Spring IoC (through the `ehcache` bean) which
|
|
is then wired into the dedicated `CacheManager` implementation. Note the entire
|
|
ehcache-specific configuration is read from `ehcache.xml`.
|
|
|
|
|
|
[[cache-store-configuration-caffeine]]
|
|
==== Caffeine Cache
|
|
|
|
Caffeine is a Java 8 rewrite of Guava's cache and its implementation is located under
|
|
`org.springframework.cache.caffeine` package and provides access to several features
|
|
of Caffeine.
|
|
|
|
Configuring a `CacheManager` that creates the cache on demand is straightforward:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="cacheManager"
|
|
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
|
|
----
|
|
|
|
It is also possible to provide the caches to use explicitly. In that case, only those
|
|
will be made available by the manager:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
|
<property name="caches">
|
|
<set>
|
|
<value>default</value>
|
|
<value>books</value>
|
|
</set>
|
|
</property>
|
|
</bean>
|
|
----
|
|
|
|
The Caffeine `CacheManager` also supports customs `Caffeine` and `CacheLoader`. See
|
|
the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation] for more
|
|
information about those.
|
|
|
|
|
|
[[cache-store-configuration-gemfire]]
|
|
==== GemFire-based Cache
|
|
|
|
GemFire is a memory-oriented/disk-backed, elastically scalable, continuously available,
|
|
active (with built-in pattern-based subscription notifications), globally replicated
|
|
database and provides fully-featured edge caching. For further information on how to use
|
|
GemFire as a CacheManager (and more), please refer to the
|
|
{doc-spring-gemfire}/html/[Spring Data GemFire reference documentation].
|
|
|
|
|
|
[[cache-store-configuration-jsr107]]
|
|
==== JSR-107 Cache
|
|
|
|
JSR-107 compliant caches can also be used by Spring's caching abstraction. The JCache
|
|
implementation is located under `org.springframework.cache.jcache` package.
|
|
|
|
Again, to use it, one simply needs to declare the appropriate `CacheManager`:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="cacheManager"
|
|
class="org.springframework.cache.jcache.JCacheCacheManager"
|
|
p:cache-manager-ref="jCacheManager"/>
|
|
|
|
<!-- JSR-107 cache manager setup -->
|
|
<bean id="jCacheManager" .../>
|
|
----
|
|
|
|
|
|
[[cache-store-configuration-noop]]
|
|
==== Dealing with caches without a backing store
|
|
|
|
Sometimes when switching environments or doing testing, one might have cache
|
|
declarations without an actual backing cache configured. As this is an invalid
|
|
configuration, at runtime an exception will be thrown since the caching infrastructure
|
|
is unable to find a suitable store. In situations like this, rather then removing the
|
|
cache declarations (which can prove tedious), one can wire in a simple, dummy cache that
|
|
performs no caching - that is, forces the cached methods to be executed every time:
|
|
|
|
[source,xml,indent=0]
|
|
[subs="verbatim,quotes"]
|
|
----
|
|
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
|
|
<property name="cacheManagers">
|
|
<list>
|
|
<ref bean="jdkCache"/>
|
|
<ref bean="gemfireCache"/>
|
|
</list>
|
|
</property>
|
|
<property name="fallbackToNoOpCache" value="true"/>
|
|
</bean>
|
|
----
|
|
|
|
The `CompositeCacheManager` above chains multiple ``CacheManager``s and additionally,
|
|
through the `fallbackToNoOpCache` flag, adds a __no op__ cache that for all the
|
|
definitions not handled by the configured cache managers. That is, every cache
|
|
definition not found in either `jdkCache` or `gemfireCache` (configured above) will be
|
|
handled by the no op cache, which will not store any information causing the target
|
|
method to be executed every time.
|
|
|
|
|
|
|
|
[[cache-plug]]
|
|
=== Plugging-in different back-end caches
|
|
|
|
Clearly there are plenty of caching products out there that can be used as a backing
|
|
store. To plug them in, one needs to provide a `CacheManager` and `Cache` implementation
|
|
since unfortunately there is no available standard that we can use instead. This may
|
|
sound harder than it is since in practice, the classes tend to be simple
|
|
https://en.wikipedia.org/wiki/Adapter_pattern[adapter]s that map the caching abstraction
|
|
framework on top of the storage API as the `ehcache` classes can show. Most
|
|
`CacheManager` classes can use the classes in `org.springframework.cache.support`
|
|
package, such as `AbstractCacheManager` which takes care of the boiler-plate code
|
|
leaving only the actual __mapping__ to be completed. We hope that in time, the libraries
|
|
that provide integration with Spring can fill in this small configuration gap.
|
|
|
|
|
|
|
|
[[cache-specific-config]]
|
|
=== How can I set the TTL/TTI/Eviction policy/XXX feature?
|
|
|
|
Directly through your cache provider. The cache abstraction is... well, an abstraction
|
|
not a cache implementation. The solution you are using might support various data
|
|
policies and different topologies which other solutions do not (take for example the JDK
|
|
`ConcurrentHashMap`) - exposing that in the cache abstraction would be useless simply
|
|
because there would no backing support. Such functionality should be controlled directly
|
|
through the backing cache, when configuring it or through its native API.
|
|
|
|
|
|
|
|
|
|
include::integration-appendix.adoc[leveloffset=+1]
|