diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index 3e8c838704c..8692371d3a8 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -45,9 +45,10 @@ import org.springframework.core.NativeDetector; * @see AdvisedSupport#setProxyTargetClass * @see AdvisedSupport#setInterfaces */ -@SuppressWarnings("serial") public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { + private static final long serialVersionUID = 7930414337282325166L; + @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java index 5f3d3d17e41..27dbd3ec180 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java @@ -421,11 +421,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport * are unaffected by such changes. */ private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { - if (this.advisorChainInitialized) { - return; - } - - if (!ObjectUtils.isEmpty(this.interceptorNames)) { + if (!this.advisorChainInitialized && !ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); @@ -464,9 +460,9 @@ public class ProxyFactoryBean extends ProxyCreatorSupport addAdvisorOnChainCreation(advice); } } - } - this.advisorChainInitialized = true; + this.advisorChainInitialized = true; + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java index 389f19c9e48..2642e72d7ae 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -353,9 +353,31 @@ public interface ListableBeanFactory extends BeanFactory { * @since 3.0 * @see #getBeanNamesForAnnotation * @see #getBeansWithAnnotation + * @see #getType(String) */ @Nullable A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException; + /** + * Find an {@link Annotation} of {@code annotationType} on the specified bean, + * traversing its interfaces and super classes if no annotation can be found on + * the given class itself, as well as checking the bean's factory method (if any). + * @param beanName the name of the bean to look for annotations on + * @param annotationType the type of annotation to look for + * (at class, interface or factory method level of the specified bean) + * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized + * just for the purpose of determining its object type + * @return the annotation of the given type if found, or {@code null} otherwise + * @throws NoSuchBeanDefinitionException if there is no bean with the given name + * @since 5.3.14 + * @see #getBeanNamesForAnnotation + * @see #getBeansWithAnnotation + * @see #getType(String, boolean) + */ + @Nullable + A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException; + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 6848c734774..99561a063b1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -719,14 +719,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - return findMergedAnnotationOnBean(beanName, annotationType) + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return findMergedAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit) .synthesize(MergedAnnotation::isPresent).orElse(null); } private MergedAnnotation findMergedAnnotationOnBean( - String beanName, Class annotationType) { + String beanName, Class annotationType, boolean allowFactoryBeanInit) { - Class beanType = getType(beanName); + Class beanType = getType(beanName, allowFactoryBeanInit); if (beanType != null) { MergedAnnotation annotation = MergedAnnotations.from(beanType, SearchStrategy.TYPE_HIERARCHY).get(annotationType); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index b9994f6f763..e6c0eeee032 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -459,7 +459,16 @@ public class StaticListableBeanFactory implements ListableBeanFactory { public A findAnnotationOnBean(String beanName, Class annotationType) throws NoSuchBeanDefinitionException { - Class beanType = getType(beanName); + return findAnnotationOnBean(beanName, annotationType, true); + } + + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + Class beanType = getType(beanName, allowFactoryBeanInit); return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null); } 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 e0b12f443f9..e4b09c0798f 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 @@ import org.springframework.lang.Nullable; * 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 @@ import org.springframework.lang.Nullable; * @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 e0982a2e5ff..15185bba6c3 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 class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe /** * 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/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 1bb789a46e9..17bc9a7330e 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1309,6 +1309,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return getBeanFactory().findAnnotationOnBean(beanName, annotationType); } + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + assertBeanFactoryActive(); + return getBeanFactory().findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java index 57df369b68e..91d85d35dad 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -65,10 +65,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIOException; /** - * @since 13.03.2003 * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams + * @since 13.03.2003 */ public class ProxyFactoryBeanTests { @@ -633,20 +633,50 @@ public class ProxyFactoryBeanTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(FROZEN_CONTEXT, CLASS)); - Advised advised = (Advised)bf.getBean("frozen"); + Advised advised = (Advised) bf.getBean("frozen"); assertThat(advised.isFrozen()).as("The proxy should be frozen").isTrue(); } @Test - public void testDetectsInterfaces() throws Exception { + public void testDetectsInterfaces() { ProxyFactoryBean fb = new ProxyFactoryBean(); fb.setTarget(new TestBean()); fb.addAdvice(new DebugInterceptor()); fb.setBeanFactory(new DefaultListableBeanFactory()); + ITestBean proxy = (ITestBean) fb.getObject(); assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue(); } + @Test + public void testWithInterceptorNames() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerSingleton("debug", new DebugInterceptor()); + + ProxyFactoryBean fb = new ProxyFactoryBean(); + fb.setTarget(new TestBean()); + fb.setInterceptorNames("debug"); + fb.setBeanFactory(bf); + + Advised proxy = (Advised) fb.getObject(); + assertThat(proxy.getAdvisorCount()).isEqualTo(1); + } + + @Test + public void testWithLateInterceptorNames() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerSingleton("debug", new DebugInterceptor()); + + ProxyFactoryBean fb = new ProxyFactoryBean(); + fb.setTarget(new TestBean()); + fb.setBeanFactory(bf); + fb.getObject(); + + fb.setInterceptorNames("debug"); + Advised proxy = (Advised) fb.getObject(); + assertThat(proxy.getAdvisorCount()).isEqualTo(1); + } + /** * Fires only on void methods. Saves list of methods intercepted. 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 f9154f4776f..39a636298eb 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 @@ -96,6 +96,19 @@ public class EnableCachingIntegrationTests { 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); @@ -223,6 +236,36 @@ public class EnableCachingIntegrationTests { } } + @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/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java index d350341a8c4..8f81b987c8a 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java @@ -76,6 +76,7 @@ class ThreadPoolExecutorFactoryBeanTests { verify(threadPoolExecutor).prestartAllCoreThreads(); } + @Configuration static class ExecutorConfig { @@ -83,9 +84,10 @@ class ThreadPoolExecutorFactoryBeanTests { ThreadPoolExecutorFactoryBean executor() { return new ThreadPoolExecutorFactoryBean(); } - } + + @SuppressWarnings("serial") private static class TestThreadPoolExecutorFactoryBean extends ThreadPoolExecutorFactoryBean { @Override diff --git a/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java b/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java index 9a2693e2ce6..550a202255c 100644 --- a/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java +++ b/spring-core/src/main/java/org/springframework/core/log/LogFormatUtils.java @@ -22,6 +22,7 @@ import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; /** * Utility methods for formatting and logging messages. @@ -71,10 +72,10 @@ public abstract class LogFormatUtils { } String result; try { - result = value.toString(); + result = ObjectUtils.nullSafeToString(value); } catch (Throwable ex) { - result = ex.toString(); + result = ObjectUtils.nullSafeToString(ex); } if (maxLength != -1) { result = (result.length() > maxLength ? result.substring(0, maxLength) + " (truncated)..." : result); diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index ba58532c74f..3ca17918b53 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -1159,6 +1159,20 @@ class ResolvableTypeTests { assertThatResolvableType(complex4).isNotAssignableFrom(complex3); } + @Test + void identifyTypeVariable() throws Exception { + Method method = ClassArguments.class.getMethod("typedArgumentFirst", Class.class, Class.class, Class.class); + ResolvableType returnType = ResolvableType.forMethodReturnType(method, ClassArguments.class); + + ResolvableType arg0 = ResolvableType.forMethodParameter(method, 0, ClassArguments.class); + ResolvableType arg1 = ResolvableType.forMethodParameter(method, 1, ClassArguments.class); + ResolvableType arg2 = ResolvableType.forMethodParameter(method, 2, ClassArguments.class); + + assertThat(returnType.getType().equals(arg0.as(Class.class).getGeneric(0).getType())).isTrue(); + assertThat(returnType.getType().equals(arg1.as(Class.class).getGeneric(0).getType())).isFalse(); + assertThat(returnType.getType().equals(arg2.as(Class.class).getGeneric(0).getType())).isFalse(); + } + @Test void hashCodeAndEquals() throws Exception { ResolvableType forClass = ResolvableType.forClass(List.class); @@ -1427,6 +1441,10 @@ class ResolvableTypeTests { } + interface TypedMethods extends Methods { + } + + static class AssignmentBase { public O o; @@ -1479,7 +1497,9 @@ class ResolvableTypeTests { } - interface TypedMethods extends Methods { + interface ClassArguments { + + T typedArgumentFirst(Class arg0, Class arg1, Class arg2); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index 368bd0bede2..02dbfa93bad 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -314,6 +314,15 @@ class StubWebApplicationContext implements WebApplicationContext { return this.beanFactory.findAnnotationOnBean(beanName, annotationType); } + @Override + @Nullable + public A findAnnotationOnBean( + String beanName, Class annotationType, boolean allowFactoryBeanInit) + throws NoSuchBeanDefinitionException { + + return this.beanFactory.findAnnotationOnBean(beanName, annotationType, allowFactoryBeanInit); + } + //--------------------------------------------------------------------- // Implementation of HierarchicalBeanFactory interface diff --git a/src/docs/asciidoc/integration.adoc b/src/docs/asciidoc/integration.adoc index 9d058c316ab..b28d062315f 100644 --- a/src/docs/asciidoc/integration.adoc +++ b/src/docs/asciidoc/integration.adoc @@ -5385,6 +5385,7 @@ You can use these macros instead of the six-digit value, thus: `@Scheduled(cron |=== + [[scheduling-quartz]] === Using the Quartz Scheduler @@ -5440,7 +5441,6 @@ has it applied automatically: protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { // do the actual work } - } ---- @@ -5560,11 +5560,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`. + @@ -5866,7 +5874,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. @@ -6024,7 +6031,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. @@ -6083,7 +6089,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. @@ -6224,11 +6229,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 the proxy mode (`mode="proxy"`). -If you use the weaving-based aspect (`mode="aspectj"`), the caching settings are not -recognized by weaving infrastructure. +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 @@ -6365,7 +6370,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.