From d7b9270672a141cd5b046fbdeb8a22f269c73a6f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Dec 2021 16:46:13 +0100 Subject: [PATCH] Clarify SchedulerFactoryBean's LocalDataSourceJobStore overriding Includes clarification of interface-level cache annotations for target-class proxies. Closes gh-27709 See gh-27726 --- .../quartz/LocalDataSourceJobStore.java | 3 ++ .../quartz/SchedulerFactoryBean.java | 4 +- .../config/EnableCachingIntegrationTests.java | 43 +++++++++++++++++++ src/docs/asciidoc/integration.adoc | 32 +++++++------- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java index e0b12f443f91..e4b09c0798fb 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/LocalDataSourceJobStore.java @@ -39,6 +39,7 @@ * Subclass of Quartz's {@link JobStoreCMT} class that delegates to a Spring-managed * {@link DataSource} instead of using a Quartz-managed JDBC connection pool. * This JobStore will be used if SchedulerFactoryBean's "dataSource" property is set. + * You may also configure it explicitly, possibly as a custom subclass of this class. * *

Supports both transactional and non-transactional DataSource access. * With a non-XA DataSource and local Spring transactions, a single DataSource @@ -58,6 +59,8 @@ * @since 1.1 * @see SchedulerFactoryBean#setDataSource * @see SchedulerFactoryBean#setNonTransactionalDataSource + * @see SchedulerFactoryBean#getConfigTimeDataSource() + * @see SchedulerFactoryBean#getConfigTimeNonTransactionalDataSource() * @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection */ diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index e0982a2e5ff9..15185bba6c37 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -310,9 +310,11 @@ public void setTaskExecutor(Executor taskExecutor) { /** * Set the default {@link DataSource} to be used by the Scheduler. - * If set, this will override corresponding settings in Quartz properties. *

Note: If this is set, the Quartz settings should not define * a job store "dataSource" to avoid meaningless double configuration. + * Also, do not define a "org.quartz.jobStore.class" property at all. + * (You may explicitly define Spring's {@link LocalDataSourceJobStore} + * but that's the default when using this method anyway.) *

A Spring-specific subclass of Quartz' JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java index 6e10f5d04c64..6a7e1127aecd 100644 --- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingIntegrationTests.java @@ -83,6 +83,19 @@ private void fooGetSimple(FooService service) { assertCacheHit(key, value, cache); } + @Test + public void barServiceWithCacheableInterfaceCglib() { + this.context = new AnnotationConfigApplicationContext(BarConfigCglib.class); + BarService service = this.context.getBean(BarService.class); + Cache cache = getCache(); + + Object key = new Object(); + assertCacheMiss(key, cache); + + Object value = service.getSimple(key); + assertCacheHit(key, value, cache); + } + @Test public void beanConditionOff() { this.context = new AnnotationConfigApplicationContext(BeanConditionConfig.class); @@ -185,6 +198,36 @@ public Object getWithCondition(Object key) { } + @Configuration + @Import(SharedConfig.class) + @EnableCaching(proxyTargetClass = true) + static class BarConfigCglib { + + @Bean + public BarService barService() { + return new BarServiceImpl(); + } + } + + + interface BarService { + + @Cacheable(cacheNames = "testCache") + Object getSimple(Object key); + } + + + static class BarServiceImpl implements BarService { + + private final AtomicLong counter = new AtomicLong(); + + @Override + public Object getSimple(Object key) { + return this.counter.getAndIncrement(); + } + } + + @Configuration @Import(FooConfig.class) @EnableCaching diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index f2ec53ad0aed..9ea8f646d7d5 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -5396,6 +5396,7 @@ You can use these macros instead of the six-digit value, thus: `@Scheduled(cron |=== + [[scheduling-quartz]] === Using the Quartz Scheduler @@ -5451,7 +5452,6 @@ has it applied automatically: protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { // do the actual work } - } ---- @@ -5571,11 +5571,19 @@ seconds and one running every morning at 6 AM. To finalize everything, we need t ---- -More properties are available for the `SchedulerFactoryBean`, such as the calendars -used by the job details, properties to customize Quartz with, and others. See the -{api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] +More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the +job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See +the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`] javadoc for more information. +NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath, +based on Quartz property keys, as with regular Quartz configuration. Please note that many +`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file; +it is therefore not recommended to specify values at both levels. For example, do not set +an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource, +or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which +is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`. + @@ -5877,7 +5885,6 @@ is updated in the cache. The following example shows how to use the `sync` attri ---- <1> Using the `sync` attribute. - 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. See the documentation of your cache provider for more details. @@ -6035,7 +6042,6 @@ all entries from the `books` cache: ---- <1> Using the `allEntries` attribute to evict all entries from the cache. - This option comes in handy when an entire cache region needs to be cleared out. Rather than evicting each entry (which would take a long time, since it is inefficient), all the entries are removed in one operation, as the preceding example shows. @@ -6094,7 +6100,6 @@ comes into play. The following examples uses `@CacheConfig` to set the name of t ---- <1> Using `@CacheConfig` to set the name of the cache. - `@CacheConfig` is a class-level annotation that allows sharing the cache names, the custom `KeyGenerator`, the custom `CacheManager`, and the custom `CacheResolver`. Placing this annotation on the class does not turn on any caching operation. @@ -6235,13 +6240,11 @@ 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 use interface-based proxies. -The fact that Java annotations are not inherited from interfaces means that, if you use -class-based proxies (`proxy-target-class="true"`) or the weaving-based aspect -(`mode="aspectj"`), the caching settings are not recognized by the proxying and weaving -infrastructure, and the object is not wrapped in a caching proxy. +classes) with the `@Cache{asterisk}` annotations, as opposed to annotating interfaces. +You certainly can place an `@Cache{asterisk}` annotation on an interface (or an interface +method), but this works only if you use the proxy mode (`mode="proxy"`). If you use the +weaving-based aspect (`mode="aspectj"`), the caching settings are not recognized on +interface-level declarations by the weaving infrastructure. NOTE: In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the @@ -6378,7 +6381,6 @@ to customize the factory for each cache operation, as the following example show ---- <1> Customizing the factory for this operation. - 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.