diff --git a/spring-boot-samples/spring-boot-sample-cache/README.adoc b/spring-boot-samples/spring-boot-sample-cache/README.adoc new file mode 100644 index 0000000000..91b1b0551c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/README.adoc @@ -0,0 +1,72 @@ += Spring Boot Cache Sample + +This sample demonstrates the caching auto-configuration support. Spring's caching +abstraction is supported by many caching libraries, including: + +* Any compliant `JSR-107` (JCache) provider +* `EhCache` +* `Hazelcast` +* `Infinispan` +* `Redis` +* `Guava` +* Simple provider based on `ConcurrentHashMap` + +The sample defines a simple `CountryService` that caches countries by ISO code. When +the application starts a client invokes the service with a random code every 500ms. You +can look at the `/metrics` endpoint to review the cache statistics if your chosen +caching provider is supported. + +== Using a different cache provider + +Initially, the project does not define any caching library so the abstraction works +on simple `ConcurrentHashMap`-based caches. You can try out your favorite caching library +as explained below. + +=== EhCache 2.x + +Simply add the `net.sf.ehcache:ehcache` dependency to the project. Since there is a +default `ehcache.xml` configuration file at the root of the classpath, it is automatically +used to configure the underlying `CacheManager`. + +=== Hazelcast + +Both `com.hazelcast:hazelcast` and `com.hazelcast:hazelcast-spring` should be added to +the project to enable support for Hazelcast. Since there is a default `hazelcast.xml` +configuration file at the root of the classpath, it is used to automatically configure +the underlying `HazelcastInstance`. + +=== Infinispan + +Simply add the `org.infinispan:infinispan-spring4` dependency to enable support for +Infinispan. There is no default location that Infinispan uses to look for a config +file so if you don't specify anything it will bootstrap on a hardcoded default. You +can set the `spring.cache.infinispan.config` property to use the provided +`infinispan.xml` configuration instead. + +=== JCache (JSR-107) + +You do not need to use a JSR-107 compliant `CacheManager` to use the standard +annotations. As a matter of a fact, this sample uses by default the standard annotations +with a simple map-based `CacheManager` by default. If you want to configure your cache +infrastructure via the standard, you need a compliant implementation. You could try +the following: + +* `Hazelcast`: add `com.hazelcast:hazelcast` +* `Infinispan`: add `org.infinispan:infinispan-jcache` + + +Since Spring Boot supports the native cache library and the JCache wrapper, you +should set the `spring.cache.type` property to `jcache` to specify that you want the +cache manager to be auto-configured that way. + +=== Redis + +Add the `spring-boot-starter-redis` and make sure it is configured properly (by default, +a redis instance with the default settings is expected on your local box). + +=== Guava + +Spring Boot does not provide any dependency management for _Guava_ so you'll have to add +the `com.google.guava:guava` dependency with a version. You can customize how caches are +created in different ways, see `application.properties` for an example and the +documentation for more details. \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-cache/pom.xml b/spring-boot-samples/spring-boot-sample-cache/pom.xml index 4d0f28cab5..2d17131dfc 100644 --- a/spring-boot-samples/spring-boot-sample-cache/pom.xml +++ b/spring-boot-samples/spring-boot-sample-cache/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -21,16 +22,54 @@ org.springframework.boot - spring-boot-starter + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator org.springframework spring-context-support + + javax.cache + cache-api + + + + + org.springframework.boot spring-boot-starter-test diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleEhCacheCacheApplication.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Country.java similarity index 56% rename from spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleEhCacheCacheApplication.java rename to spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Country.java index b033f53b0e..9ec55fdcd8 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleEhCacheCacheApplication.java +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Country.java @@ -16,20 +16,34 @@ package sample; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cache.annotation.EnableCaching; +import java.io.Serializable; -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ -@EnableCaching -@SpringBootApplication -public class SampleEhCacheCacheApplication { +@SuppressWarnings("serial") +public class Country implements Serializable { - public static void main(String[] args) { - SpringApplication.run(SampleEhCacheCacheApplication.class); + private final String code; + + public Country(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Country country = (Country) o; + + return code.equals(country.code); + } + + @Override + public int hashCode() { + return code.hashCode(); } } diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryService.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryRepository.java similarity index 67% rename from spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryService.java rename to spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryRepository.java index 8c17927f3e..165bc7e560 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryService.java +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/CountryRepository.java @@ -16,23 +16,19 @@ package sample; -import org.springframework.cache.annotation.Cacheable; +import javax.cache.annotation.CacheDefaults; +import javax.cache.annotation.CacheResult; + import org.springframework.stereotype.Component; -import java.util.Arrays; -import java.util.List; - -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ @Component -public class CountryService { +@CacheDefaults(cacheName = "countries") +public class CountryRepository { - @Cacheable("countries") - public List countries() { - System.out.println("Loading countries"); - return Arrays.asList("Perú", "United States"); + @CacheResult + public Country findByCode(String code) { + System.out.println("---> Loading country with code '" + code + "'"); + return new Country(code); } } diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleCacheApplication.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleCacheApplication.java new file mode 100644 index 0000000000..61c6ea80b7 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleCacheApplication.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; + +@EnableCaching +@EnableScheduling +@SpringBootApplication +public class SampleCacheApplication { + + private static final Logger logger = LoggerFactory.getLogger(SampleCacheApplication.class); + + public static void main(String[] args) { + new SpringApplicationBuilder() + .sources(SampleCacheApplication.class) + .profiles("app") + .run(args); + } + + @Component + static class CacheManagerCheck implements CommandLineRunner { + + private final CacheManager cacheManager; + + @Autowired + CacheManagerCheck(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + public void run(String... strings) throws Exception { + logger.info("\n\n" + "=========================================================\n" + + "Using cache manager: " + this.cacheManager.getClass().getName() + "\n" + + "=========================================================\n\n"); + } + } + +} diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleClient.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleClient.java new file mode 100644 index 0000000000..cdf0cbfed0 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/SampleClient.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@Profile("app") +class SampleClient { + + private static final List SAMPLE_COUNTRY_CODES = Arrays.asList("AF", "AX", "AL", "DZ", "AS", + "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", + "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", + "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", + "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", + "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", + "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", + "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", + "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", + "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", + "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", + "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", + "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", + "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", + "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", + "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", + "EH", "YE", "ZM", "ZW"); + + private final CountryRepository countryService; + + private final Random random; + + @Autowired + public SampleClient(CountryRepository countryService) { + this.countryService = countryService; + this.random = new Random(); + } + + @Scheduled(fixedDelay = 500) + public void retrieveCountry() { + String randomCode = SAMPLE_COUNTRY_CODES.get(random.nextInt(SAMPLE_COUNTRY_CODES.size())); + System.out.println("Looking for country with code '" + randomCode + "'"); + this.countryService.findByCode(randomCode); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Startup.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Startup.java deleted file mode 100644 index 7cdd3f9149..0000000000 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/Startup.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ -@Component -public class Startup implements CommandLineRunner { - - @Autowired - private CountryService countryService; - - @Override - public void run(String... args) throws Exception { - System.out.println("---- calling country service"); - System.out.println(countryService.countries()); - System.out.println("---- calling country service again from cache."); - System.out.println(countryService.countries()); - } - -} diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application-override.properties b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application-override.properties deleted file mode 100644 index 5255d6360f..0000000000 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application-override.properties +++ /dev/null @@ -1 +0,0 @@ -spring.cache.config=/cache/ehcache-override.xml diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties new file mode 100644 index 0000000000..b28030ed8b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/application.properties @@ -0,0 +1,19 @@ + + +# +# Infinispan configuration file location. +# +#spring.cache.infinispan.config=infinispan.xml + + +# +# JCache configuration (example with hazelcast). +# +#spring.cache.type=jcache +#spring.cache.jcache.config=hazelcast.xml + + +# +# Guava configuration +# +#spring.cache.guava.spec=maximumSize=200,expireAfterAccess=600s \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/cache/ehcache-override.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/cache/ehcache-override.xml deleted file mode 100644 index 62948a72f2..0000000000 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/cache/ehcache-override.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/ehcache.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/ehcache.xml index 62948a72f2..4b6e3692fd 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/ehcache.xml +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/ehcache.xml @@ -1,8 +1,7 @@ + xsi:noNamespaceSchemaLocation="ehcache.xsd"> + maxEntriesLocalHeap="200" + timeToLiveSeconds="600"> diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/hazelcast.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/hazelcast.xml new file mode 100644 index 0000000000..a191842d45 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/hazelcast.xml @@ -0,0 +1,17 @@ + + + + 600 + 200 + + + + + + true + true + + + \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml new file mode 100644 index 0000000000..acf90677df --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/AbstractEhCacheCacheTests.java b/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleCacheApplicationTests.java similarity index 56% rename from spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/AbstractEhCacheCacheTests.java rename to spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleCacheApplicationTests.java index 5e9c672337..fe9e6195a2 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/AbstractEhCacheCacheTests.java +++ b/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleCacheApplicationTests.java @@ -16,48 +16,39 @@ package sample; -import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.core.IsInstanceOf.instanceOf; -import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.core.IsNull.notNullValue; -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = {SampleEhCacheCacheApplication.class}) -public abstract class AbstractEhCacheCacheTests { +@SpringApplicationConfiguration(classes = {SampleCacheApplication.class}) +public class SampleCacheApplicationTests { + @Autowired private CacheManager cacheManager; @Autowired - private EhCacheCacheManager ehCacheManager; - - @After - public void tearDown() { - if (this.ehCacheManager != null) { - this.ehCacheManager.getCacheManager().shutdown(); - } - } + private CountryRepository countryRepository; @Test - public void testCacheManagerInstance() { - assertThat(this.cacheManager, instanceOf(EhCacheCacheManager.class)); - } - - @Test - public void testCacheManager() { - assertThat(this.cacheManager.getCacheNames(), contains("countries")); + public void validateCache() { + Cache countries = this.cacheManager.getCache("countries"); + assertThat(countries, is(notNullValue())); + countries.clear(); // Simple test assuming the cache is empty + assertThat(countries.get("BE"), is(nullValue())); + Country be = this.countryRepository.findByCode("BE"); + assertThat((Country) countries.get("BE").get(), is(be)); } } diff --git a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationOverrideTests.java b/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationOverrideTests.java deleted file mode 100644 index 5b5e72d253..0000000000 --- a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationOverrideTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = {SampleEhCacheCacheApplication.class}) -@ActiveProfiles("override") -public class SampleEhCacheCacheApplicationOverrideTests extends AbstractEhCacheCacheTests { - -} diff --git a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationTests.java b/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationTests.java deleted file mode 100644 index 5346848c36..0000000000 --- a/spring-boot-samples/spring-boot-sample-cache/src/test/java/sample/SampleEhCacheCacheApplicationTests.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package sample; - -import org.junit.runner.RunWith; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * @author Eddú Meléndez - * @since 1.3.0 - */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = {SampleEhCacheCacheApplication.class}) -public class SampleEhCacheCacheApplicationTests extends AbstractEhCacheCacheTests { - -}