diff --git a/.gitignore b/.gitignore index d713eae13a511a57c2fdc3ac266f8b06ac6e8d14..b69cb116254113448412d4e8ac32b0c2f9dc150b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,6 @@ classes/ buildSrc/build /spring-*/build /src/asciidoc/build -target/ # Projects not in this branch integration-tests/ diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..4fd173f2893eb34fd1b24a20716a168936a48318 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.framework.autoproxy.target; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.aop.framework.autoproxy.TargetSourceCreator; +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.lang.Nullable; + +/** + * Convenient superclass for + * {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator} + * implementations that require creating multiple instances of a prototype bean. + * + *

Uses an internal BeanFactory to manage the target instances, + * copying the original bean definition to this internal factory. + * This is necessary because the original BeanFactory will just + * contain the proxy instance created through auto-proxying. + * + *

Requires running in an + * {@link org.springframework.beans.factory.support.AbstractBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource + * @see org.springframework.beans.factory.support.AbstractBeanFactory + */ +public abstract class AbstractBeanFactoryBasedTargetSourceCreator + implements TargetSourceCreator, BeanFactoryAware, DisposableBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + private ConfigurableBeanFactory beanFactory; + + /** Internally used DefaultListableBeanFactory instances, keyed by bean name. */ + private final Map internalBeanFactories = + new HashMap<>(); + + + @Override + public final void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ConfigurableBeanFactory)) { + throw new IllegalStateException("Cannot do auto-TargetSource creation with a BeanFactory " + + "that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass()); + } + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } + + /** + * Return the BeanFactory that this TargetSourceCreators runs in. + */ + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + //--------------------------------------------------------------------- + // Implementation of the TargetSourceCreator interface + //--------------------------------------------------------------------- + + @Override + @Nullable + public final TargetSource getTargetSource(Class beanClass, String beanName) { + AbstractBeanFactoryBasedTargetSource targetSource = + createBeanFactoryBasedTargetSource(beanClass, beanName); + if (targetSource == null) { + return null; + } + + if (logger.isDebugEnabled()) { + logger.debug("Configuring AbstractBeanFactoryBasedTargetSource: " + targetSource); + } + + DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName); + + // We need to override just this bean definition, as it may reference other beans + // and we're happy to take the parent's definition for those. + // Always use prototype scope if demanded. + BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); + GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd); + if (isPrototypeBased()) { + bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE); + } + internalBeanFactory.registerBeanDefinition(beanName, bdCopy); + + // Complete configuring the PrototypeTargetSource. + targetSource.setTargetBeanName(beanName); + targetSource.setBeanFactory(internalBeanFactory); + + return targetSource; + } + + /** + * Return the internal BeanFactory to be used for the specified bean. + * @param beanName the name of the target bean + * @return the internal BeanFactory to be used + */ + protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) { + synchronized (this.internalBeanFactories) { + DefaultListableBeanFactory internalBeanFactory = this.internalBeanFactories.get(beanName); + if (internalBeanFactory == null) { + internalBeanFactory = buildInternalBeanFactory(this.beanFactory); + this.internalBeanFactories.put(beanName, internalBeanFactory); + } + return internalBeanFactory; + } + } + + /** + * Build an internal BeanFactory for resolving target beans. + * @param containingFactory the containing BeanFactory that originally defines the beans + * @return an independent internal BeanFactory to hold copies of some target beans + */ + protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) { + // Set parent so that references (up container hierarchies) are correctly resolved. + DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory); + + // Required so that all BeanPostProcessors, Scopes, etc become available. + internalBeanFactory.copyConfigurationFrom(containingFactory); + + // Filter out BeanPostProcessors that are part of the AOP infrastructure, + // since those are only meant to apply to beans defined in the original factory. + internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor -> + beanPostProcessor instanceof AopInfrastructureBean); + + return internalBeanFactory; + } + + /** + * Destroys the internal BeanFactory on shutdown of the TargetSourceCreator. + * @see #getInternalBeanFactoryForBean + */ + @Override + public void destroy() { + synchronized (this.internalBeanFactories) { + for (DefaultListableBeanFactory bf : this.internalBeanFactories.values()) { + bf.destroySingletons(); + } + } + } + + + //--------------------------------------------------------------------- + // Template methods to be implemented by subclasses + //--------------------------------------------------------------------- + + /** + * Return whether this TargetSourceCreator is prototype-based. + * The scope of the target bean definition will be set accordingly. + *

Default is "true". + * @see org.springframework.beans.factory.config.BeanDefinition#isSingleton() + */ + protected boolean isPrototypeBased() { + return true; + } + + /** + * Subclasses must implement this method to return a new AbstractPrototypeBasedTargetSource + * if they wish to create a custom TargetSource for this bean, or {@code null} if they are + * not interested it in, in which case no special target source will be created. + * Subclasses should not call {@code setTargetBeanName} or {@code setBeanFactory} + * on the AbstractPrototypeBasedTargetSource: This class' implementation of + * {@code getTargetSource()} will do that. + * @param beanClass the class of the bean to create a TargetSource for + * @param beanName the name of the bean + * @return the AbstractPrototypeBasedTargetSource, or {@code null} if we don't match this + */ + @Nullable + protected abstract AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName); + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..5761068e8aa22b38db822014650fb3b71cd3e958 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2014 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 + * + * https://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 org.springframework.aop.framework.autoproxy.target; + +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.aop.target.LazyInitTargetSource; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.lang.Nullable; + +/** + * TargetSourceCreator that enforces a LazyInitTargetSource for each bean + * that is defined as "lazy-init". This will lead to a proxy created for + * each of those beans, allowing to fetch a reference to such a bean + * without actually initializing the target bean instance. + * + *

To be registered as custom TargetSourceCreator for an auto-proxy creator, + * in combination with custom interceptors for specific beans or for the + * creation of lazy-init proxies only. For example, as autodetected + * infrastructure bean in an XML application context definition: + * + *

+ * <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
+ *   <property name="customTargetSourceCreators">
+ *     <list>
+ *       <bean class="org.springframework.aop.framework.autoproxy.target.LazyInitTargetSourceCreator"/>
+ *     </list>
+ *   </property>
+ * </bean>
+ *
+ * <bean id="myLazyInitBean" class="mypackage.MyBeanClass" lazy-init="true">
+ *   ...
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.beans.factory.config.BeanDefinition#isLazyInit + * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#setCustomTargetSourceCreators + * @see org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator + */ +public class LazyInitTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator { + + @Override + protected boolean isPrototypeBased() { + return false; + } + + @Override + @Nullable + protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName) { + + if (getBeanFactory() instanceof ConfigurableListableBeanFactory) { + BeanDefinition definition = + ((ConfigurableListableBeanFactory) getBeanFactory()).getBeanDefinition(beanName); + if (definition.isLazyInit()) { + return new LazyInitTargetSource(); + } + } + return null; + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..a6a741e0f879defeafaf7fd127211a232e323a06 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.framework.autoproxy.target; + +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.aop.target.CommonsPool2TargetSource; +import org.springframework.aop.target.PrototypeTargetSource; +import org.springframework.aop.target.ThreadLocalTargetSource; +import org.springframework.lang.Nullable; + +/** + * Convenient TargetSourceCreator using bean name prefixes to create one of three + * well-known TargetSource types: + *
  • : CommonsPool2TargetSource + *
  • % ThreadLocalTargetSource + *
  • ! PrototypeTargetSource + * + * @author Rod Johnson + * @author Stephane Nicoll + * @see org.springframework.aop.target.CommonsPool2TargetSource + * @see org.springframework.aop.target.ThreadLocalTargetSource + * @see org.springframework.aop.target.PrototypeTargetSource + */ +public class QuickTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator { + + /** + * The CommonsPool2TargetSource prefix. + */ + public static final String PREFIX_COMMONS_POOL = ":"; + + /** + * The ThreadLocalTargetSource prefix. + */ + public static final String PREFIX_THREAD_LOCAL = "%"; + + /** + * The PrototypeTargetSource prefix. + */ + public static final String PREFIX_PROTOTYPE = "!"; + + @Override + @Nullable + protected final AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName) { + + if (beanName.startsWith(PREFIX_COMMONS_POOL)) { + CommonsPool2TargetSource cpts = new CommonsPool2TargetSource(); + cpts.setMaxSize(25); + return cpts; + } + else if (beanName.startsWith(PREFIX_THREAD_LOCAL)) { + return new ThreadLocalTargetSource(); + } + else if (beanName.startsWith(PREFIX_PROTOTYPE)) { + return new PrototypeTargetSource(); + } + else { + // No match. Don't create a custom target source. + return null; + } + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..cf32c396d847949c539478828b2ee7d9d4149e34 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.io.Serializable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link org.springframework.aop.TargetSource} implementations + * that are based on a Spring {@link org.springframework.beans.factory.BeanFactory}, + * delegating to Spring-managed bean instances. + * + *

    Subclasses can create prototype instances or lazily access a + * singleton target, for example. See {@link LazyInitTargetSource} and + * {@link AbstractPrototypeBasedTargetSource}'s subclasses for concrete strategies. + * + *

    BeanFactory-based TargetSources are serializable. This involves + * disconnecting the current target and turning into a {@link SingletonTargetSource}. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 1.1.4 + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see LazyInitTargetSource + * @see PrototypeTargetSource + * @see ThreadLocalTargetSource + * @see CommonsPool2TargetSource + */ +public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSource, BeanFactoryAware, Serializable { + + /** use serialVersionUID from Spring 1.2.7 for interoperability. */ + private static final long serialVersionUID = -4721607536018568393L; + + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Name of the target bean we will create on each invocation. */ + private String targetBeanName; + + /** Class of the target. */ + private volatile Class targetClass; + + /** + * BeanFactory that owns this TargetSource. We need to hold onto this + * reference so that we can create new prototype instances as necessary. + */ + private BeanFactory beanFactory; + + + /** + * Set the name of the target bean in the factory. + *

    The target bean should not be a singleton, else the same instance will + * always be obtained from the factory, resulting in the same behavior as + * provided by {@link SingletonTargetSource}. + * @param targetBeanName name of the target bean in the BeanFactory + * that owns this interceptor + * @see SingletonTargetSource + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + /** + * Return the name of the target bean in the factory. + */ + public String getTargetBeanName() { + return this.targetBeanName; + } + + /** + * Specify the target class explicitly, to avoid any kind of access to the + * target bean (for example, to avoid initialization of a FactoryBean instance). + *

    Default is to detect the type automatically, through a {@code getType} + * call on the BeanFactory (or even a full {@code getBean} call as fallback). + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + /** + * Set the owning BeanFactory. We need to save a reference so that we can + * use the {@code getBean} method on every invocation. + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) { + if (this.targetBeanName == null) { + throw new IllegalStateException("Property 'targetBeanName' is required"); + } + this.beanFactory = beanFactory; + } + + /** + * Return the owning BeanFactory. + */ + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + @Override + public Class getTargetClass() { + Class targetClass = this.targetClass; + if (targetClass != null) { + return targetClass; + } + synchronized (this) { + // Full check within synchronization, entering the BeanFactory interaction algorithm only once... + targetClass = this.targetClass; + if (targetClass == null && this.beanFactory != null) { + // Determine type of the target bean. + targetClass = this.beanFactory.getType(this.targetBeanName); + if (targetClass == null) { + if (logger.isTraceEnabled()) { + logger.trace("Getting bean with name '" + this.targetBeanName + "' for type determination"); + } + Object beanInstance = this.beanFactory.getBean(this.targetBeanName); + targetClass = beanInstance.getClass(); + } + this.targetClass = targetClass; + } + return targetClass; + } + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public void releaseTarget(Object target) throws Exception { + // Nothing to do here. + } + + + /** + * Copy configuration from the other AbstractBeanFactoryBasedTargetSource object. + * Subclasses should override this if they wish to expose it. + * @param other object to copy configuration from + */ + protected void copyFrom(AbstractBeanFactoryBasedTargetSource other) { + this.targetBeanName = other.targetBeanName; + this.targetClass = other.targetClass; + this.beanFactory = other.beanFactory; + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + AbstractBeanFactoryBasedTargetSource otherTargetSource = (AbstractBeanFactoryBasedTargetSource) other; + return (ObjectUtils.nullSafeEquals(this.beanFactory, otherTargetSource.beanFactory) && + ObjectUtils.nullSafeEquals(this.targetBeanName, otherTargetSource.targetBeanName)); + } + + @Override + public int hashCode() { + int hashCode = getClass().hashCode(); + hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.beanFactory); + hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.targetBeanName); + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(getClass().getSimpleName()); + sb.append(" for target bean '").append(this.targetBeanName).append("'"); + if (this.targetClass != null) { + sb.append(" of type [").append(this.targetClass.getName()).append("]"); + } + return sb.toString(); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..f742a106bab8ce3b5cf3c8fe45d881afad1826e8 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.lang.Nullable; + +/** + * {@link org.springframework.aop.TargetSource} implementation that will + * lazily create a user-managed object. + * + *

    Creation of the lazy target object is controlled by the user by implementing + * the {@link #createObject()} method. This {@code TargetSource} will invoke + * this method the first time the proxy is accessed. + * + *

    Useful when you need to pass a reference to some dependency to an object + * but you don't actually want the dependency to be created until it is first used. + * A typical scenario for this is a connection to a remote resource. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2.4 + * @see #isInitialized() + * @see #createObject() + */ +public abstract class AbstractLazyCreationTargetSource implements TargetSource { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** The lazily initialized target object. */ + private Object lazyTarget; + + + /** + * Return whether the lazy target object of this TargetSource + * has already been fetched. + */ + public synchronized boolean isInitialized() { + return (this.lazyTarget != null); + } + + /** + * This default implementation returns {@code null} if the + * target is {@code null} (it is hasn't yet been initialized), + * or the target class if the target has already been initialized. + *

    Subclasses may wish to override this method in order to provide + * a meaningful value when the target is still {@code null}. + * @see #isInitialized() + */ + @Override + @Nullable + public synchronized Class getTargetClass() { + return (this.lazyTarget != null ? this.lazyTarget.getClass() : null); + } + + @Override + public boolean isStatic() { + return false; + } + + /** + * Returns the lazy-initialized target object, + * creating it on-the-fly if it doesn't exist already. + * @see #createObject() + */ + @Override + public synchronized Object getTarget() throws Exception { + if (this.lazyTarget == null) { + logger.debug("Initializing lazy target object"); + this.lazyTarget = createObject(); + } + return this.lazyTarget; + } + + @Override + public void releaseTarget(Object target) throws Exception { + // nothing to do + } + + + /** + * Subclasses should implement this method to return the lazy initialized object. + * Called the first time the proxy is invoked. + * @return the created object + * @throws Exception if creation failed + */ + protected abstract Object createObject() throws Exception; + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..59017bfabeba9e61311d1ef6ccb31a5d0ba4f198 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.lang.Nullable; + +/** + * Abstract base class for pooling {@link org.springframework.aop.TargetSource} + * implementations which maintain a pool of target instances, acquiring and + * releasing a target object from the pool for each method invocation. + * This abstract base class is independent of concrete pooling technology; + * see the subclass {@link CommonsPool2TargetSource} for a concrete example. + * + *

    Subclasses must implement the {@link #getTarget} and + * {@link #releaseTarget} methods based on their chosen object pool. + * The {@link #newPrototypeInstance()} method inherited from + * {@link AbstractPrototypeBasedTargetSource} can be used to create objects + * in order to put them into the pool. + * + *

    Subclasses must also implement some of the monitoring methods from the + * {@link PoolingConfig} interface. The {@link #getPoolingConfigMixin()} method + * makes these stats available on proxied objects through an IntroductionAdvisor. + * + *

    This class implements the {@link org.springframework.beans.factory.DisposableBean} + * interface in order to force subclasses to implement a {@link #destroy()} + * method, closing down their object pool. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #getTarget + * @see #releaseTarget + * @see #destroy + */ +@SuppressWarnings("serial") +public abstract class AbstractPoolingTargetSource extends AbstractPrototypeBasedTargetSource + implements PoolingConfig, DisposableBean { + + /** The maximum size of the pool. */ + private int maxSize = -1; + + + /** + * Set the maximum size of the pool. + * Default is -1, indicating no size limit. + */ + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Return the maximum size of the pool. + */ + @Override + public int getMaxSize() { + return this.maxSize; + } + + + @Override + public final void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + try { + createPool(); + } + catch (Throwable ex) { + throw new BeanInitializationException("Could not create instance pool for TargetSource", ex); + } + } + + + /** + * Create the pool. + * @throws Exception to avoid placing constraints on pooling APIs + */ + protected abstract void createPool() throws Exception; + + /** + * Acquire an object from the pool. + * @return an object from the pool + * @throws Exception we may need to deal with checked exceptions from pool + * APIs, so we're forgiving with our exception signature + */ + @Override + @Nullable + public abstract Object getTarget() throws Exception; + + /** + * Return the given object to the pool. + * @param target object that must have been acquired from the pool + * via a call to {@code getTarget()} + * @throws Exception to allow pooling APIs to throw exception + * @see #getTarget + */ + @Override + public abstract void releaseTarget(Object target) throws Exception; + + + /** + * Return an IntroductionAdvisor that providing a mixin + * exposing statistics about the pool maintained by this object. + */ + public DefaultIntroductionAdvisor getPoolingConfigMixin() { + DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor(this); + return new DefaultIntroductionAdvisor(dii, PoolingConfig.class); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..b3c564743b8df13147d8eca5a621c69dd43f0160 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java @@ -0,0 +1,128 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; + +/** + * Base class for dynamic {@link org.springframework.aop.TargetSource} implementations + * that create new prototype bean instances to support a pooling or + * new-instance-per-invocation strategy. + * + *

    Such TargetSources must run in a {@link BeanFactory}, as it needs to + * call the {@code getBean} method to create a new prototype instance. + * Therefore, this base class extends {@link AbstractBeanFactoryBasedTargetSource}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see PrototypeTargetSource + * @see ThreadLocalTargetSource + * @see CommonsPool2TargetSource + */ +@SuppressWarnings("serial") +public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource { + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + + // Check whether the target bean is defined as prototype. + if (!beanFactory.isPrototype(getTargetBeanName())) { + throw new BeanDefinitionStoreException( + "Cannot use prototype-based TargetSource against non-prototype bean with name '" + + getTargetBeanName() + "': instances would not be independent"); + } + } + + /** + * Subclasses should call this method to create a new prototype instance. + * @throws BeansException if bean creation failed + */ + protected Object newPrototypeInstance() throws BeansException { + if (logger.isDebugEnabled()) { + logger.debug("Creating new instance of bean '" + getTargetBeanName() + "'"); + } + return getBeanFactory().getBean(getTargetBeanName()); + } + + /** + * Subclasses should call this method to destroy an obsolete prototype instance. + * @param target the bean instance to destroy + */ + protected void destroyPrototypeInstance(Object target) { + if (logger.isDebugEnabled()) { + logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'"); + } + if (getBeanFactory() instanceof ConfigurableBeanFactory) { + ((ConfigurableBeanFactory) getBeanFactory()).destroyBean(getTargetBeanName(), target); + } + else if (target instanceof DisposableBean) { + try { + ((DisposableBean) target).destroy(); + } + catch (Throwable ex) { + logger.warn("Destroy method on bean with name '" + getTargetBeanName() + "' threw an exception", ex); + } + } + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + throw new NotSerializableException("A prototype-based TargetSource itself is not deserializable - " + + "just a disconnected SingletonTargetSource or EmptyTargetSource is"); + } + + /** + * Replaces this object with a SingletonTargetSource on serialization. + * Protected as otherwise it won't be invoked for subclasses. + * (The {@code writeReplace()} method must be visible to the class + * being serialized.) + *

    With this implementation of this method, there is no need to mark + * non-serializable fields in this class or subclasses as transient. + */ + protected Object writeReplace() throws ObjectStreamException { + if (logger.isDebugEnabled()) { + logger.debug("Disconnecting TargetSource [" + this + "]"); + } + try { + // Create disconnected SingletonTargetSource/EmptyTargetSource. + Object target = getTarget(); + return (target != null ? new SingletonTargetSource(target) : + EmptyTargetSource.forClass(getTargetClass())); + } + catch (Exception ex) { + String msg = "Cannot get target for disconnecting TargetSource [" + this + "]"; + logger.error(msg, ex); + throw new NotSerializableException(msg + ": " + ex); + } + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/CommonsPool2TargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPool2TargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..4733d02696fd30a0f9cad74d9eb4e59b5c9391ee --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/CommonsPool2TargetSource.java @@ -0,0 +1,298 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import org.apache.commons.pool2.ObjectPool; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.PooledObjectFactory; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.aop.TargetSource} implementation that holds + * objects in a configurable Apache Commons2 Pool. + * + *

    By default, an instance of {@code GenericObjectPool} is created. + * Subclasses may change the type of {@code ObjectPool} used by + * overriding the {@code createObjectPool()} method. + * + *

    Provides many configuration properties mirroring those of the Commons Pool + * {@code GenericObjectPool} class; these properties are passed to the + * {@code GenericObjectPool} during construction. If creating a subclass of this + * class to change the {@code ObjectPool} implementation type, pass in the values + * of configuration properties that are relevant to your chosen implementation. + * + *

    The {@code testOnBorrow}, {@code testOnReturn} and {@code testWhileIdle} + * properties are explicitly not mirrored because the implementation of + * {@code PoolableObjectFactory} used by this class does not implement + * meaningful validation. All exposed Commons Pool properties use the + * corresponding Commons Pool defaults. + * + *

    Compatible with Apache Commons Pool 2.4, as of Spring 4.2. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @author Stephane Nicoll + * @author Kazuki Shimizu + * @since 4.2 + * @see GenericObjectPool + * @see #createObjectPool() + * @see #setMaxSize + * @see #setMaxIdle + * @see #setMinIdle + * @see #setMaxWait + * @see #setTimeBetweenEvictionRunsMillis + * @see #setMinEvictableIdleTimeMillis + */ +@SuppressWarnings({"rawtypes", "unchecked", "serial"}) +public class CommonsPool2TargetSource extends AbstractPoolingTargetSource implements PooledObjectFactory { + + private int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE; + + private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE; + + private long maxWait = GenericObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS; + + private long timeBetweenEvictionRunsMillis = GenericObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; + + private long minEvictableIdleTimeMillis = GenericObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; + + private boolean blockWhenExhausted = GenericObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED; + + /** + * The Apache Commons {@code ObjectPool} used to pool target objects. + */ + @Nullable + private ObjectPool pool; + + + /** + * Create a CommonsPoolTargetSource with default settings. + * Default maximum size of the pool is 8. + * @see #setMaxSize + * @see GenericObjectPoolConfig#setMaxTotal + */ + public CommonsPool2TargetSource() { + setMaxSize(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL); + } + + + /** + * Set the maximum number of idle objects in the pool. + * Default is 8. + * @see GenericObjectPool#setMaxIdle + */ + public void setMaxIdle(int maxIdle) { + this.maxIdle = maxIdle; + } + + /** + * Return the maximum number of idle objects in the pool. + */ + public int getMaxIdle() { + return this.maxIdle; + } + + /** + * Set the minimum number of idle objects in the pool. + * Default is 0. + * @see GenericObjectPool#setMinIdle + */ + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + + /** + * Return the minimum number of idle objects in the pool. + */ + public int getMinIdle() { + return this.minIdle; + } + + /** + * Set the maximum waiting time for fetching an object from the pool. + * Default is -1, waiting forever. + * @see GenericObjectPool#setMaxWaitMillis + */ + public void setMaxWait(long maxWait) { + this.maxWait = maxWait; + } + + /** + * Return the maximum waiting time for fetching an object from the pool. + */ + public long getMaxWait() { + return this.maxWait; + } + + /** + * Set the time between eviction runs that check idle objects whether + * they have been idle for too long or have become invalid. + * Default is -1, not performing any eviction. + * @see GenericObjectPool#setTimeBetweenEvictionRunsMillis + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + /** + * Return the time between eviction runs that check idle objects. + */ + public long getTimeBetweenEvictionRunsMillis() { + return this.timeBetweenEvictionRunsMillis; + } + + /** + * Set the minimum time that an idle object can sit in the pool before + * it becomes subject to eviction. Default is 1800000 (30 minutes). + *

    Note that eviction runs need to be performed to take this + * setting into effect. + * @see #setTimeBetweenEvictionRunsMillis + * @see GenericObjectPool#setMinEvictableIdleTimeMillis + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + /** + * Return the minimum time that an idle object can sit in the pool. + */ + public long getMinEvictableIdleTimeMillis() { + return this.minEvictableIdleTimeMillis; + } + + /** + * Set whether the call should bock when the pool is exhausted. + */ + public void setBlockWhenExhausted(boolean blockWhenExhausted) { + this.blockWhenExhausted = blockWhenExhausted; + } + + /** + * Specify if the call should block when the pool is exhausted. + */ + public boolean isBlockWhenExhausted() { + return this.blockWhenExhausted; + } + + + /** + * Creates and holds an ObjectPool instance. + * @see #createObjectPool() + */ + @Override + protected final void createPool() { + logger.debug("Creating Commons object pool"); + this.pool = createObjectPool(); + } + + /** + * Subclasses can override this if they want to return a specific Commons pool. + * They should apply any configuration properties to the pool here. + *

    Default is a GenericObjectPool instance with the given pool size. + * @return an empty Commons {@code ObjectPool}. + * @see GenericObjectPool + * @see #setMaxSize + */ + protected ObjectPool createObjectPool() { + GenericObjectPoolConfig config = new GenericObjectPoolConfig(); + config.setMaxTotal(getMaxSize()); + config.setMaxIdle(getMaxIdle()); + config.setMinIdle(getMinIdle()); + config.setMaxWaitMillis(getMaxWait()); + config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis()); + config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); + config.setBlockWhenExhausted(isBlockWhenExhausted()); + return new GenericObjectPool(this, config); + } + + + /** + * Borrows an object from the {@code ObjectPool}. + */ + @Override + public Object getTarget() throws Exception { + Assert.state(this.pool != null, "No Commons ObjectPool available"); + return this.pool.borrowObject(); + } + + /** + * Returns the specified object to the underlying {@code ObjectPool}. + */ + @Override + public void releaseTarget(Object target) throws Exception { + if (this.pool != null) { + this.pool.returnObject(target); + } + } + + @Override + public int getActiveCount() throws UnsupportedOperationException { + return (this.pool != null ? this.pool.getNumActive() : 0); + } + + @Override + public int getIdleCount() throws UnsupportedOperationException { + return (this.pool != null ? this.pool.getNumIdle() : 0); + } + + + /** + * Closes the underlying {@code ObjectPool} when destroying this object. + */ + @Override + public void destroy() throws Exception { + if (this.pool != null) { + logger.debug("Closing Commons ObjectPool"); + this.pool.close(); + } + } + + + //---------------------------------------------------------------------------- + // Implementation of org.apache.commons.pool2.PooledObjectFactory interface + //---------------------------------------------------------------------------- + + @Override + public PooledObject makeObject() throws Exception { + return new DefaultPooledObject<>(newPrototypeInstance()); + } + + @Override + public void destroyObject(PooledObject p) throws Exception { + destroyPrototypeInstance(p.getObject()); + } + + @Override + public boolean validateObject(PooledObject p) { + return true; + } + + @Override + public void activateObject(PooledObject p) throws Exception { + } + + @Override + public void passivateObject(PooledObject p) throws Exception { + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..2ebcc216dce7537c6bd3138828e92b7119972183 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * Canonical {@code TargetSource} when there is no target + * (or just the target class known), and behavior is supplied + * by interfaces and advisors only. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public final class EmptyTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability. */ + private static final long serialVersionUID = 3680494563553489691L; + + + //--------------------------------------------------------------------- + // Static factory methods + //--------------------------------------------------------------------- + + /** + * The canonical (Singleton) instance of this {@link EmptyTargetSource}. + */ + public static final EmptyTargetSource INSTANCE = new EmptyTargetSource(null, true); + + + /** + * Return an EmptyTargetSource for the given target Class. + * @param targetClass the target Class (may be {@code null}) + * @see #getTargetClass() + */ + public static EmptyTargetSource forClass(@Nullable Class targetClass) { + return forClass(targetClass, true); + } + + /** + * Return an EmptyTargetSource for the given target Class. + * @param targetClass the target Class (may be {@code null}) + * @param isStatic whether the TargetSource should be marked as static + * @see #getTargetClass() + */ + public static EmptyTargetSource forClass(@Nullable Class targetClass, boolean isStatic) { + return (targetClass == null && isStatic ? INSTANCE : new EmptyTargetSource(targetClass, isStatic)); + } + + + //--------------------------------------------------------------------- + // Instance implementation + //--------------------------------------------------------------------- + + private final Class targetClass; + + private final boolean isStatic; + + + /** + * Create a new instance of the {@link EmptyTargetSource} class. + *

    This constructor is {@code private} to enforce the + * Singleton pattern / factory method pattern. + * @param targetClass the target class to expose (may be {@code null}) + * @param isStatic whether the TargetSource is marked as static + */ + private EmptyTargetSource(@Nullable Class targetClass, boolean isStatic) { + this.targetClass = targetClass; + this.isStatic = isStatic; + } + + + /** + * Always returns the specified target Class, or {@code null} if none. + */ + @Override + @Nullable + public Class getTargetClass() { + return this.targetClass; + } + + /** + * Always returns {@code true}. + */ + @Override + public boolean isStatic() { + return this.isStatic; + } + + /** + * Always returns {@code null}. + */ + @Override + @Nullable + public Object getTarget() { + return null; + } + + /** + * Nothing to release. + */ + @Override + public void releaseTarget(Object target) { + } + + + /** + * Returns the canonical instance on deserialization in case + * of no target class, thus protecting the Singleton pattern. + */ + private Object readResolve() { + return (this.targetClass == null && this.isStatic ? INSTANCE : this); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof EmptyTargetSource)) { + return false; + } + EmptyTargetSource otherTs = (EmptyTargetSource) other; + return (ObjectUtils.nullSafeEquals(this.targetClass, otherTs.targetClass) && this.isStatic == otherTs.isStatic); + } + + @Override + public int hashCode() { + return EmptyTargetSource.class.hashCode() * 13 + ObjectUtils.nullSafeHashCode(this.targetClass); + } + + @Override + public String toString() { + return "EmptyTargetSource: " + + (this.targetClass != null ? "target class [" + this.targetClass.getName() + "]" : "no target class") + + ", " + (this.isStatic ? "static" : "dynamic"); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..5d85f50438634340e98d7c77b4abb9de6a691ab1 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.aop.TargetSource} implementation that + * caches a local target object, but allows the target to be swapped + * while the application is running. + * + *

    If configuring an object of this class in a Spring IoC container, + * use constructor injection. + * + *

    This TargetSource is serializable if the target is at the time + * of serialization. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class HotSwappableTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability. */ + private static final long serialVersionUID = 7497929212653839187L; + + + /** The current target object. */ + private Object target; + + + /** + * Create a new HotSwappableTargetSource with the given initial target object. + * @param initialTarget the initial target object + */ + public HotSwappableTargetSource(Object initialTarget) { + Assert.notNull(initialTarget, "Target object must not be null"); + this.target = initialTarget; + } + + + /** + * Return the type of the current target object. + *

    The returned type should usually be constant across all target objects. + */ + @Override + public synchronized Class getTargetClass() { + return this.target.getClass(); + } + + @Override + public final boolean isStatic() { + return false; + } + + @Override + public synchronized Object getTarget() { + return this.target; + } + + @Override + public void releaseTarget(Object target) { + // nothing to do + } + + + /** + * Swap the target, returning the old target object. + * @param newTarget the new target object + * @return the old target object + * @throws IllegalArgumentException if the new target is invalid + */ + public synchronized Object swap(Object newTarget) throws IllegalArgumentException { + Assert.notNull(newTarget, "Target object must not be null"); + Object old = this.target; + this.target = newTarget; + return old; + } + + + /** + * Two HotSwappableTargetSources are equal if the current target + * objects are equal. + */ + @Override + public boolean equals(Object other) { + return (this == other || (other instanceof HotSwappableTargetSource && + this.target.equals(((HotSwappableTargetSource) other).target))); + } + + @Override + public int hashCode() { + return HotSwappableTargetSource.class.hashCode(); + } + + @Override + public String toString() { + return "HotSwappableTargetSource for target: " + this.target; + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..ff0b1d1d9ae1e8a144d857d1ffe2ed13b42fd809 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target; + +import org.springframework.beans.BeansException; +import org.springframework.lang.Nullable; + +/** + * {@link org.springframework.aop.TargetSource} that lazily accesses a + * singleton bean from a {@link org.springframework.beans.factory.BeanFactory}. + * + *

    Useful when a proxy reference is needed on initialization but + * the actual target object should not be initialized until first use. + * When the target bean is defined in an + * {@link org.springframework.context.ApplicationContext} (or a + * {@code BeanFactory} that is eagerly pre-instantiating singleton beans) + * it must be marked as "lazy-init" too, else it will be instantiated by said + * {@code ApplicationContext} (or {@code BeanFactory}) on startup. + *

    For example: + * + *

    + * <bean id="serviceTarget" class="example.MyService" lazy-init="true">
    + *   ...
    + * </bean>
    + *
    + * <bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">
    + *   <property name="targetSource">
    + *     <bean class="org.springframework.aop.target.LazyInitTargetSource">
    + *       <property name="targetBeanName"><idref local="serviceTarget"/></property>
    + *     </bean>
    + *   </property>
    + * </bean>
    + * + * The "serviceTarget" bean will not get initialized until a method on the + * "service" proxy gets invoked. + * + *

    Subclasses can extend this class and override the {@link #postProcessTargetObject(Object)} to + * perform some additional processing with the target object when it is first loaded. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1.4 + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see #postProcessTargetObject + */ +@SuppressWarnings("serial") +public class LazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource { + + @Nullable + private Object target; + + + @Override + @Nullable + public synchronized Object getTarget() throws BeansException { + if (this.target == null) { + this.target = getBeanFactory().getBean(getTargetBeanName()); + postProcessTargetObject(this.target); + } + return this.target; + } + + /** + * Subclasses may override this method to perform additional processing on + * the target object when it is first loaded. + * @param targetObject the target object that has just been instantiated (and configured) + */ + protected void postProcessTargetObject(Object targetObject) { + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/PoolingConfig.java b/spring-aop/src/main/java/org/springframework/aop/target/PoolingConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..bd439d95d7280a3a6881eae29dc9f77c4b69cc12 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/PoolingConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target; + +/** + * Config interface for a pooling target source. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface PoolingConfig { + + /** + * Return the maximum size of the pool. + */ + int getMaxSize(); + + /** + * Return the number of active objects in the pool. + * @throws UnsupportedOperationException if not supported by the pool + */ + int getActiveCount() throws UnsupportedOperationException; + + /** + * Return the number of idle objects in the pool. + * @throws UnsupportedOperationException if not supported by the pool + */ + int getIdleCount() throws UnsupportedOperationException; + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..e43fb6a12ee9c18f77c685897085ecd0a9a4216c --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2014 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 + * + * https://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 org.springframework.aop.target; + +import org.springframework.beans.BeansException; + +/** + * {@link org.springframework.aop.TargetSource} implementation that + * creates a new instance of the target bean for each request, + * destroying each instance on release (after each request). + * + *

    Obtains bean instances from its containing + * {@link org.springframework.beans.factory.BeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setBeanFactory + * @see #setTargetBeanName + */ +@SuppressWarnings("serial") +public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource { + + /** + * Obtain a new prototype instance for every call. + * @see #newPrototypeInstance() + */ + @Override + public Object getTarget() throws BeansException { + return newPrototypeInstance(); + } + + /** + * Destroy the given independent instance. + * @see #destroyPrototypeInstance + */ + @Override + public void releaseTarget(Object target) { + destroyPrototypeInstance(target); + } + + @Override + public String toString() { + return "PrototypeTargetSource for target bean with name '" + getTargetBeanName() + "'"; + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..6446df2d65fc41567b2ad8d4e79eaacab2f23371 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target; + +/** + * Simple {@link org.springframework.aop.TargetSource} implementation, + * freshly obtaining the specified target bean from its containing + * Spring {@link org.springframework.beans.factory.BeanFactory}. + * + *

    Can obtain any kind of target bean: singleton, scoped, or prototype. + * Typically used for scoped beans. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +@SuppressWarnings("serial") +public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { + + @Override + public Object getTarget() throws Exception { + return getBeanFactory().getBean(getTargetBeanName()); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..24ae9865a6db8a94dc4bde437daf4c4230971725 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Implementation of the {@link org.springframework.aop.TargetSource} interface + * that holds a given object. This is the default implementation of the TargetSource + * interface, as used by the Spring AOP framework. There is usually no need to + * create objects of this class in application code. + * + *

    This class is serializable. However, the actual serializability of a + * SingletonTargetSource will depend on whether the target is serializable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.framework.AdvisedSupport#setTarget(Object) + */ +public class SingletonTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability. */ + private static final long serialVersionUID = 9031246629662423738L; + + + /** Target cached and invoked using reflection. */ + private final Object target; + + + /** + * Create a new SingletonTargetSource for the given target. + * @param target the target object + */ + public SingletonTargetSource(Object target) { + Assert.notNull(target, "Target object must not be null"); + this.target = target; + } + + + @Override + public Class getTargetClass() { + return this.target.getClass(); + } + + @Override + public Object getTarget() { + return this.target; + } + + @Override + public void releaseTarget(Object target) { + // nothing to do + } + + @Override + public boolean isStatic() { + return true; + } + + + /** + * Two invoker interceptors are equal if they have the same target or if the + * targets or the targets are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SingletonTargetSource)) { + return false; + } + SingletonTargetSource otherTargetSource = (SingletonTargetSource) other; + return this.target.equals(otherTargetSource.target); + } + + /** + * SingletonTargetSource uses the hash code of the target object. + */ + @Override + public int hashCode() { + return this.target.hashCode(); + } + + @Override + public String toString() { + return "SingletonTargetSource for target object [" + ObjectUtils.identityToString(this.target) + "]"; + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..5c4b03c78495a04bb929280a70548eb43015732b --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2016 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 + * + * https://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 org.springframework.aop.target; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.NamedThreadLocal; + +/** + * Alternative to an object pool. This {@link org.springframework.aop.TargetSource} + * uses a threading model in which every thread has its own copy of the target. + * There's no contention for targets. Target object creation is kept to a minimum + * on the running server. + * + *

    Application code is written as to a normal pool; callers can't assume they + * will be dealing with the same instance in invocations in different threads. + * However, state can be relied on during the operations of a single thread: + * for example, if one caller makes repeated calls on the AOP proxy. + * + *

    Cleanup of thread-bound objects is performed on BeanFactory destruction, + * calling their {@code DisposableBean.destroy()} method if available. + * Be aware that many thread-bound objects can be around until the application + * actually shuts down. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @see ThreadLocalTargetSourceStats + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ +@SuppressWarnings("serial") +public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource + implements ThreadLocalTargetSourceStats, DisposableBean { + + /** + * ThreadLocal holding the target associated with the current + * thread. Unlike most ThreadLocals, which are static, this variable + * is meant to be per thread per instance of the ThreadLocalTargetSource class. + */ + private final ThreadLocal targetInThread = + new NamedThreadLocal<>("Thread-local instance of bean '" + getTargetBeanName() + "'"); + + /** + * Set of managed targets, enabling us to keep track of the targets we've created. + */ + private final Set targetSet = new HashSet<>(); + + private int invocationCount; + + private int hitCount; + + + /** + * Implementation of abstract getTarget() method. + * We look for a target held in a ThreadLocal. If we don't find one, + * we create one and bind it to the thread. No synchronization is required. + */ + @Override + public Object getTarget() throws BeansException { + ++this.invocationCount; + Object target = this.targetInThread.get(); + if (target == null) { + if (logger.isDebugEnabled()) { + logger.debug("No target for prototype '" + getTargetBeanName() + "' bound to thread: " + + "creating one and binding it to thread '" + Thread.currentThread().getName() + "'"); + } + // Associate target with ThreadLocal. + target = newPrototypeInstance(); + this.targetInThread.set(target); + synchronized (this.targetSet) { + this.targetSet.add(target); + } + } + else { + ++this.hitCount; + } + return target; + } + + /** + * Dispose of targets if necessary; clear ThreadLocal. + * @see #destroyPrototypeInstance + */ + @Override + public void destroy() { + logger.debug("Destroying ThreadLocalTargetSource bindings"); + synchronized (this.targetSet) { + for (Object target : this.targetSet) { + destroyPrototypeInstance(target); + } + this.targetSet.clear(); + } + // Clear ThreadLocal, just in case. + this.targetInThread.remove(); + } + + + @Override + public int getInvocationCount() { + return this.invocationCount; + } + + @Override + public int getHitCount() { + return this.hitCount; + } + + @Override + public int getObjectCount() { + synchronized (this.targetSet) { + return this.targetSet.size(); + } + } + + + /** + * Return an introduction advisor mixin that allows the AOP proxy to be + * cast to ThreadLocalInvokerStats. + */ + public IntroductionAdvisor getStatsMixin() { + DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor(this); + return new DefaultIntroductionAdvisor(dii, ThreadLocalTargetSourceStats.class); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java new file mode 100644 index 0000000000000000000000000000000000000000..99405f6292806eef9d69a610eeb48a8bac102de7 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target; + +/** + * Statistics for a ThreadLocal TargetSource. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface ThreadLocalTargetSourceStats { + + /** + * Return the number of client invocations. + */ + int getInvocationCount(); + + /** + * Return the number of hits that were satisfied by a thread-bound object. + */ + int getHitCount(); + + /** + * Return the number of thread-bound objects created. + */ + int getObjectCount(); + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..32c6ce1c928fd19429e4f04870654cd29acdaf21 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java @@ -0,0 +1,161 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target.dynamic; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.lang.Nullable; + +/** + * Abstract {@link org.springframework.aop.TargetSource} implementation that + * wraps a refreshable target object. Subclasses can determine whether a + * refresh is required, and need to provide fresh target objects. + * + *

    Implements the {@link Refreshable} interface in order to allow for + * explicit control over the refresh status. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see #requiresRefresh() + * @see #freshTarget() + */ +public abstract class AbstractRefreshableTargetSource implements TargetSource, Refreshable { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + @Nullable + protected Object targetObject; + + private long refreshCheckDelay = -1; + + private long lastRefreshCheck = -1; + + private long lastRefreshTime = -1; + + private long refreshCount = 0; + + + /** + * Set the delay between refresh checks, in milliseconds. + * Default is -1, indicating no refresh checks at all. + *

    Note that an actual refresh will only happen when + * {@link #requiresRefresh()} returns {@code true}. + */ + public void setRefreshCheckDelay(long refreshCheckDelay) { + this.refreshCheckDelay = refreshCheckDelay; + } + + + @Override + public synchronized Class getTargetClass() { + if (this.targetObject == null) { + refresh(); + } + return this.targetObject.getClass(); + } + + /** + * Not static. + */ + @Override + public boolean isStatic() { + return false; + } + + @Override + @Nullable + public final synchronized Object getTarget() { + if ((refreshCheckDelayElapsed() && requiresRefresh()) || this.targetObject == null) { + refresh(); + } + return this.targetObject; + } + + /** + * No need to release target. + */ + @Override + public void releaseTarget(Object object) { + } + + + @Override + public final synchronized void refresh() { + logger.debug("Attempting to refresh target"); + + this.targetObject = freshTarget(); + this.refreshCount++; + this.lastRefreshTime = System.currentTimeMillis(); + + logger.debug("Target refreshed successfully"); + } + + @Override + public synchronized long getRefreshCount() { + return this.refreshCount; + } + + @Override + public synchronized long getLastRefreshTime() { + return this.lastRefreshTime; + } + + + private boolean refreshCheckDelayElapsed() { + if (this.refreshCheckDelay < 0) { + return false; + } + + long currentTimeMillis = System.currentTimeMillis(); + + if (this.lastRefreshCheck < 0 || currentTimeMillis - this.lastRefreshCheck > this.refreshCheckDelay) { + // Going to perform a refresh check - update the timestamp. + this.lastRefreshCheck = currentTimeMillis; + logger.debug("Refresh check delay elapsed - checking whether refresh is required"); + return true; + } + + return false; + } + + + /** + * Determine whether a refresh is required. + * Invoked for each refresh check, after the refresh check delay has elapsed. + *

    The default implementation always returns {@code true}, triggering + * a refresh every time the delay has elapsed. To be overridden by subclasses + * with an appropriate check of the underlying target resource. + * @return whether a refresh is required + */ + protected boolean requiresRefresh() { + return true; + } + + /** + * Obtain a fresh target object. + *

    Only invoked if a refresh check has found that a refresh is required + * (that is, {@link #requiresRefresh()} has returned {@code true}). + * @return the fresh target object + */ + protected abstract Object freshTarget(); + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java new file mode 100644 index 0000000000000000000000000000000000000000..66f84a1c9864243997fa4edac5362155da3efbf4 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target.dynamic; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.Assert; + +/** + * Refreshable TargetSource that fetches fresh target beans from a BeanFactory. + * + *

    Can be subclassed to override {@code requiresRefresh()} to suppress + * unnecessary refreshes. By default, a refresh will be performed every time + * the "refreshCheckDelay" has elapsed. + * + * @author Rob Harrop + * @author Rod Johnson + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory + * @see #requiresRefresh() + * @see #setRefreshCheckDelay + */ +public class BeanFactoryRefreshableTargetSource extends AbstractRefreshableTargetSource { + + private final BeanFactory beanFactory; + + private final String beanName; + + + /** + * Create a new BeanFactoryRefreshableTargetSource for the given + * bean factory and bean name. + *

    Note that the passed-in BeanFactory should have an appropriate + * bean definition set up for the given bean name. + * @param beanFactory the BeanFactory to fetch beans from + * @param beanName the name of the target bean + */ + public BeanFactoryRefreshableTargetSource(BeanFactory beanFactory, String beanName) { + Assert.notNull(beanFactory, "BeanFactory is required"); + Assert.notNull(beanName, "Bean name is required"); + this.beanFactory = beanFactory; + this.beanName = beanName; + } + + + /** + * Retrieve a fresh target object. + */ + @Override + protected final Object freshTarget() { + return this.obtainFreshBean(this.beanFactory, this.beanName); + } + + /** + * A template method that subclasses may override to provide a + * fresh target object for the given bean factory and bean name. + *

    This default implementation fetches a new target bean + * instance from the bean factory. + * @see org.springframework.beans.factory.BeanFactory#getBean + */ + protected Object obtainFreshBean(BeanFactory beanFactory, String beanName) { + return beanFactory.getBean(beanName); + } + +} diff --git a/spring-aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java new file mode 100644 index 0000000000000000000000000000000000000000..b03bb5544a6d3288ff7b4571bc30ec8d3b27935b --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target.dynamic; + +/** + * Interface to be implemented by dynamic target objects, + * which support reloading and optionally polling for updates. + * + * @author Rod Johnson + * @author Rob Harrop + * @since 2.0 + */ +public interface Refreshable { + + /** + * Refresh the underlying target object. + */ + void refresh(); + + /** + * Return the number of actual refreshes since startup. + */ + long getRefreshCount(); + + /** + * Return the last time an actual refresh happened (as timestamp). + */ + long getLastRefreshTime(); + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests.java b/spring-aop/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests.java new file mode 100644 index 0000000000000000000000000000000000000000..8c7a604131f26320ba6ba52e7b089cc46f596e44 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-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 + * + * https://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 org.springframework.aop.target; + +import org.junit.Test; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.Resource; +import org.springframework.tests.sample.beans.ITestBean; + +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + +/** + * @author Stephane Nicoll + */ +public class CommonsPool2TargetSourceProxyTests { + + private static final Resource CONTEXT = + qualifiedResource(CommonsPool2TargetSourceProxyTests.class, "context.xml"); + + @Test + public void testProxy() throws Exception { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); + reader.loadBeanDefinitions(CONTEXT); + beanFactory.preInstantiateSingletons(); + ITestBean bean = (ITestBean)beanFactory.getBean("testBean"); + assertTrue(AopUtils.isAopProxy(bean)); + } +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/HotSwappableTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/HotSwappableTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..917d9f602c91f654c4b3137fcc34f070a40558a7 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/HotSwappableTargetSourceTests.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2019 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 + * + * https://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 org.springframework.aop.target; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.tests.aop.interceptor.SerializableNopInterceptor; +import org.springframework.tests.sample.beans.Person; +import org.springframework.tests.sample.beans.SerializablePerson; +import org.springframework.tests.sample.beans.SideEffectBean; +import org.springframework.util.SerializationTestUtils; + +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + +/** + * @author Rod Johnson + * @author Chris Beams + */ +public class HotSwappableTargetSourceTests { + + /** Initial count value set in bean factory XML */ + private static final int INITIAL_COUNT = 10; + + private DefaultListableBeanFactory beanFactory; + + + @Before + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.beanFactory).loadBeanDefinitions( + qualifiedResource(HotSwappableTargetSourceTests.class, "context.xml")); + } + + /** + * We must simulate container shutdown, which should clear threads. + */ + @After + public void close() { + // Will call pool.close() + this.beanFactory.destroySingletons(); + } + + + /** + * Check it works like a normal invoker + */ + @Test + public void testBasicFunctionality() { + SideEffectBean proxied = (SideEffectBean) beanFactory.getBean("swappable"); + assertEquals(INITIAL_COUNT, proxied.getCount()); + proxied.doWork(); + assertEquals(INITIAL_COUNT + 1, proxied.getCount()); + + proxied = (SideEffectBean) beanFactory.getBean("swappable"); + proxied.doWork(); + assertEquals(INITIAL_COUNT + 2, proxied.getCount()); + } + + @Test + public void testValidSwaps() { + SideEffectBean target1 = (SideEffectBean) beanFactory.getBean("target1"); + SideEffectBean target2 = (SideEffectBean) beanFactory.getBean("target2"); + + SideEffectBean proxied = (SideEffectBean) beanFactory.getBean("swappable"); + assertEquals(target1.getCount(), proxied.getCount()); + proxied.doWork(); + assertEquals(INITIAL_COUNT + 1, proxied.getCount()); + + HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); + Object old = swapper.swap(target2); + assertEquals("Correct old target was returned", target1, old); + + // TODO should be able to make this assertion: need to fix target handling + // in AdvisedSupport + //assertEquals(target2, ((Advised) proxied).getTarget()); + + assertEquals(20, proxied.getCount()); + proxied.doWork(); + assertEquals(21, target2.getCount()); + + // Swap it back + swapper.swap(target1); + assertEquals(target1.getCount(), proxied.getCount()); + } + + @Test + public void testRejectsSwapToNull() { + HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); + IllegalArgumentException aopex = null; + try { + swapper.swap(null); + fail("Shouldn't be able to swap to invalid value"); + } + catch (IllegalArgumentException ex) { + // Ok + aopex = ex; + } + + // It shouldn't be corrupted, it should still work + testBasicFunctionality(); + assertTrue(aopex.getMessage().contains("null")); + } + + @Test + public void testSerialization() throws Exception { + SerializablePerson sp1 = new SerializablePerson(); + sp1.setName("Tony"); + SerializablePerson sp2 = new SerializablePerson(); + sp1.setName("Gordon"); + + HotSwappableTargetSource hts = new HotSwappableTargetSource(sp1); + ProxyFactory pf = new ProxyFactory(); + pf.addInterface(Person.class); + pf.setTargetSource(hts); + pf.addAdvisor(new DefaultPointcutAdvisor(new SerializableNopInterceptor())); + Person p = (Person) pf.getProxy(); + + assertEquals(sp1.getName(), p.getName()); + hts.swap(sp2); + assertEquals(sp2.getName(), p.getName()); + + p = (Person) SerializationTestUtils.serializeAndDeserialize(p); + // We need to get a reference to the client-side targetsource + hts = (HotSwappableTargetSource) ((Advised) p).getTargetSource(); + assertEquals(sp2.getName(), p.getName()); + hts.swap(sp1); + assertEquals(sp1.getName(), p.getName()); + + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/LazyCreationTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/LazyCreationTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..0e90e4856ad7228b7353c12f023187a57e042edc --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/LazyCreationTargetSourceTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target; + +import org.junit.Test; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; + +import static org.junit.Assert.*; + +/** + * @author Rob Harrop + * @author Juergen Hoeller + * @author Chris Beams + */ +public class LazyCreationTargetSourceTests { + + @Test + public void testCreateLazy() { + TargetSource targetSource = new AbstractLazyCreationTargetSource() { + @Override + protected Object createObject() { + return new InitCountingBean(); + } + @Override + public Class getTargetClass() { + return InitCountingBean.class; + } + }; + + InitCountingBean proxy = (InitCountingBean) ProxyFactory.getProxy(targetSource); + assertEquals("Init count should be 0", 0, InitCountingBean.initCount); + assertEquals("Target class incorrect", InitCountingBean.class, targetSource.getTargetClass()); + assertEquals("Init count should still be 0 after getTargetClass()", 0, InitCountingBean.initCount); + + proxy.doSomething(); + assertEquals("Init count should now be 1", 1, InitCountingBean.initCount); + + proxy.doSomething(); + assertEquals("Init count should still be 1", 1, InitCountingBean.initCount); + } + + + private static class InitCountingBean { + + public static int initCount; + + public InitCountingBean() { + if (InitCountingBean.class.equals(getClass())) { + // only increment when creating the actual target - not the proxy + initCount++; + } + } + + public void doSomething() { + //no-op + } + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/LazyInitTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/LazyInitTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..5201735c568b43fb20468fdb7df1ed06dd0a9e52 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/LazyInitTargetSourceTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2013 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 + * + * https://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 org.springframework.aop.target; + +import java.util.Set; + +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.Resource; +import org.springframework.tests.sample.beans.ITestBean; + +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + +/** + * @author Juergen Hoeller + * @author Rob Harrop + * @author Chris Beams + * @since 07.01.2005 + */ +public class LazyInitTargetSourceTests { + + private static final Class CLASS = LazyInitTargetSourceTests.class; + + private static final Resource SINGLETON_CONTEXT = qualifiedResource(CLASS, "singleton.xml"); + private static final Resource CUSTOM_TARGET_CONTEXT = qualifiedResource(CLASS, "customTarget.xml"); + private static final Resource FACTORY_BEAN_CONTEXT = qualifiedResource(CLASS, "factoryBean.xml"); + + @Test + public void testLazyInitSingletonTargetSource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(bf).loadBeanDefinitions(SINGLETON_CONTEXT); + bf.preInstantiateSingletons(); + + ITestBean tb = (ITestBean) bf.getBean("proxy"); + assertFalse(bf.containsSingleton("target")); + assertEquals(10, tb.getAge()); + assertTrue(bf.containsSingleton("target")); + } + + @Test + public void testCustomLazyInitSingletonTargetSource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CUSTOM_TARGET_CONTEXT); + bf.preInstantiateSingletons(); + + ITestBean tb = (ITestBean) bf.getBean("proxy"); + assertFalse(bf.containsSingleton("target")); + assertEquals("Rob Harrop", tb.getName()); + assertTrue(bf.containsSingleton("target")); + } + + @Test + public void testLazyInitFactoryBeanTargetSource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(bf).loadBeanDefinitions(FACTORY_BEAN_CONTEXT); + bf.preInstantiateSingletons(); + + Set set1 = (Set) bf.getBean("proxy1"); + assertFalse(bf.containsSingleton("target1")); + assertTrue(set1.contains("10")); + assertTrue(bf.containsSingleton("target1")); + + Set set2 = (Set) bf.getBean("proxy2"); + assertFalse(bf.containsSingleton("target2")); + assertTrue(set2.contains("20")); + assertTrue(bf.containsSingleton("target2")); + } + + + @SuppressWarnings("serial") + public static class CustomLazyInitTargetSource extends LazyInitTargetSource { + + @Override + protected void postProcessTargetObject(Object targetObject) { + ((ITestBean) targetObject).setName("Rob Harrop"); + } + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..2d63814b83491f3253af96091ea1840b133f4c42 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeBasedTargetSourceTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2013 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 + * + * https://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 org.springframework.aop.target; + +import org.junit.Test; + +import org.springframework.aop.TargetSource; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.tests.sample.beans.SerializablePerson; +import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.SerializationTestUtils; + +import static org.junit.Assert.*; + +/** + * Unit tests relating to the abstract {@link AbstractPrototypeBasedTargetSource} + * and not subclasses. + * + * @author Rod Johnson + * @author Chris Beams + */ +public class PrototypeBasedTargetSourceTests { + + @Test + public void testSerializability() throws Exception { + MutablePropertyValues tsPvs = new MutablePropertyValues(); + tsPvs.add("targetBeanName", "person"); + RootBeanDefinition tsBd = new RootBeanDefinition(TestTargetSource.class); + tsBd.setPropertyValues(tsPvs); + + MutablePropertyValues pvs = new MutablePropertyValues(); + RootBeanDefinition bd = new RootBeanDefinition(SerializablePerson.class); + bd.setPropertyValues(pvs); + bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("ts", tsBd); + bf.registerBeanDefinition("person", bd); + + TestTargetSource cpts = (TestTargetSource) bf.getBean("ts"); + TargetSource serialized = (TargetSource) SerializationTestUtils.serializeAndDeserialize(cpts); + assertTrue("Changed to SingletonTargetSource on deserialization", + serialized instanceof SingletonTargetSource); + SingletonTargetSource sts = (SingletonTargetSource) serialized; + assertNotNull(sts.getTarget()); + } + + + private static class TestTargetSource extends AbstractPrototypeBasedTargetSource { + + private static final long serialVersionUID = 1L; + + /** + * Nonserializable test field to check that subclass + * state can't prevent serialization from working + */ + private TestBean thisFieldIsNotSerializable = new TestBean(); + + @Override + public Object getTarget() throws Exception { + return newPrototypeInstance(); + } + + @Override + public void releaseTarget(Object target) throws Exception { + // Do nothing + } + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/PrototypeTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..4098ba3b7aeab990f92aea0288b395f0eb6f7173 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/PrototypeTargetSourceTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2019 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 + * + * https://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 org.springframework.aop.target; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.tests.sample.beans.SideEffectBean; + +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + +/** + * @author Rod Johnson + * @author Chris Beams + */ +public class PrototypeTargetSourceTests { + + /** Initial count value set in bean factory XML */ + private static final int INITIAL_COUNT = 10; + + private DefaultListableBeanFactory beanFactory; + + + @Before + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.beanFactory).loadBeanDefinitions( + qualifiedResource(PrototypeTargetSourceTests.class, "context.xml")); + } + + + /** + * Test that multiple invocations of the prototype bean will result + * in no change to visible state, as a new instance is used. + * With the singleton, there will be change. + */ + @Test + public void testPrototypeAndSingletonBehaveDifferently() { + SideEffectBean singleton = (SideEffectBean) beanFactory.getBean("singleton"); + assertEquals(INITIAL_COUNT, singleton.getCount()); + singleton.doWork(); + assertEquals(INITIAL_COUNT + 1, singleton.getCount()); + + SideEffectBean prototype = (SideEffectBean) beanFactory.getBean("prototype"); + assertEquals(INITIAL_COUNT, prototype.getCount()); + prototype.doWork(); + assertEquals(INITIAL_COUNT, prototype.getCount()); + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..0315c566e651bf9709797d0d7a626d9fe81d5959 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/ThreadLocalTargetSourceTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2019 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 + * + * https://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 org.springframework.aop.target; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.tests.sample.beans.ITestBean; +import org.springframework.tests.sample.beans.SideEffectBean; + +import static org.junit.Assert.*; +import static org.springframework.tests.TestResourceUtils.*; + +/** + * @author Rod Johnson + * @author Chris Beams + */ +public class ThreadLocalTargetSourceTests { + + /** Initial count value set in bean factory XML */ + private static final int INITIAL_COUNT = 10; + + private DefaultListableBeanFactory beanFactory; + + + @Before + public void setup() { + this.beanFactory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.beanFactory).loadBeanDefinitions( + qualifiedResource(ThreadLocalTargetSourceTests.class, "context.xml")); + } + + /** + * We must simulate container shutdown, which should clear threads. + */ + protected void close() { + this.beanFactory.destroySingletons(); + } + + + /** + * Check we can use two different ThreadLocalTargetSources + * managing objects of different types without them interfering + * with one another. + */ + @Test + public void testUseDifferentManagedInstancesInSameThread() { + SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment"); + assertEquals(INITIAL_COUNT, apartment.getCount()); + apartment.doWork(); + assertEquals(INITIAL_COUNT + 1, apartment.getCount()); + + ITestBean test = (ITestBean) beanFactory.getBean("threadLocal2"); + assertEquals("Rod", test.getName()); + assertEquals("Kerry", test.getSpouse().getName()); + } + + @Test + public void testReuseInSameThread() { + SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment"); + assertEquals(INITIAL_COUNT, apartment.getCount()); + apartment.doWork(); + assertEquals(INITIAL_COUNT + 1, apartment.getCount()); + + apartment = (SideEffectBean) beanFactory.getBean("apartment"); + assertEquals(INITIAL_COUNT + 1, apartment.getCount()); + } + + /** + * Relies on introduction. + */ + @Test + public void testCanGetStatsViaMixin() { + ThreadLocalTargetSourceStats stats = (ThreadLocalTargetSourceStats) beanFactory.getBean("apartment"); + // +1 because creating target for stats call counts + assertEquals(1, stats.getInvocationCount()); + SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment"); + apartment.doWork(); + // +1 again + assertEquals(3, stats.getInvocationCount()); + // + 1 for states call! + assertEquals(3, stats.getHitCount()); + apartment.doWork(); + assertEquals(6, stats.getInvocationCount()); + assertEquals(6, stats.getHitCount()); + // Only one thread so only one object can have been bound + assertEquals(1, stats.getObjectCount()); + } + + @Test + public void testNewThreadHasOwnInstance() throws InterruptedException { + SideEffectBean apartment = (SideEffectBean) beanFactory.getBean("apartment"); + assertEquals(INITIAL_COUNT, apartment.getCount()); + apartment.doWork(); + apartment.doWork(); + apartment.doWork(); + assertEquals(INITIAL_COUNT + 3, apartment.getCount()); + + class Runner implements Runnable { + public SideEffectBean mine; + @Override + public void run() { + this.mine = (SideEffectBean) beanFactory.getBean("apartment"); + assertEquals(INITIAL_COUNT, mine.getCount()); + mine.doWork(); + assertEquals(INITIAL_COUNT + 1, mine.getCount()); + } + } + Runner r = new Runner(); + Thread t = new Thread(r); + t.start(); + t.join(); + + assertNotNull(r); + + // Check it didn't affect the other thread's copy + assertEquals(INITIAL_COUNT + 3, apartment.getCount()); + + // When we use other thread's copy in this thread + // it should behave like ours + assertEquals(INITIAL_COUNT + 3, r.mine.getCount()); + + // Bound to two threads + assertEquals(2, ((ThreadLocalTargetSourceStats) apartment).getObjectCount()); + } + + /** + * Test for SPR-1442. Destroyed target should re-associated with thread and not throw NPE. + */ + @Test + public void testReuseDestroyedTarget() { + ThreadLocalTargetSource source = (ThreadLocalTargetSource)this.beanFactory.getBean("threadLocalTs"); + + // try first time + source.getTarget(); + source.destroy(); + + // try second time + try { + source.getTarget(); + } + catch (NullPointerException ex) { + fail("Should not throw NPE"); + } + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/target/dynamic/RefreshableTargetSourceTests.java b/spring-aop/src/test/java/org/springframework/aop/target/dynamic/RefreshableTargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..60aa8394d644c500dc085a84d75f094d74e20bbb --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/target/dynamic/RefreshableTargetSourceTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2012 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 + * + * https://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 org.springframework.aop.target.dynamic; + +import org.junit.Test; + +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; + +import static org.junit.Assert.*; + +/** + * @author Rob Harrop + * @author Chris Beams + */ +public class RefreshableTargetSourceTests { + + /** + * Test what happens when checking for refresh but not refreshing object. + */ + @Test + public void testRefreshCheckWithNonRefresh() throws Exception { + CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(); + ts.setRefreshCheckDelay(0); + + Object a = ts.getTarget(); + Thread.sleep(1); + Object b = ts.getTarget(); + + assertEquals("Should be one call to freshTarget to get initial target", 1, ts.getCallCount()); + assertSame("Returned objects should be the same - no refresh should occur", a, b); + } + + /** + * Test what happens when checking for refresh and refresh occurs. + */ + @Test + public void testRefreshCheckWithRefresh() throws Exception { + CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(true); + ts.setRefreshCheckDelay(0); + + Object a = ts.getTarget(); + Thread.sleep(100); + Object b = ts.getTarget(); + + assertEquals("Should have called freshTarget twice", 2, ts.getCallCount()); + assertNotSame("Should be different objects", a, b); + } + + /** + * Test what happens when no refresh occurs. + */ + @Test + public void testWithNoRefreshCheck() throws Exception { + CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(true); + ts.setRefreshCheckDelay(-1); + + Object a = ts.getTarget(); + Object b = ts.getTarget(); + + assertEquals("Refresh target should only be called once", 1, ts.getCallCount()); + assertSame("Objects should be the same - refresh check delay not elapsed", a, b); + } + + @Test + public void testRefreshOverTime() throws Exception { + Assume.group(TestGroup.PERFORMANCE); + + CountingRefreshableTargetSource ts = new CountingRefreshableTargetSource(true); + ts.setRefreshCheckDelay(100); + + Object a = ts.getTarget(); + Object b = ts.getTarget(); + assertEquals("Objects should be same", a, b); + + Thread.sleep(50); + + Object c = ts.getTarget(); + assertEquals("A and C should be same", a, c); + + Thread.sleep(60); + + Object d = ts.getTarget(); + assertNotNull("D should not be null", d); + assertFalse("A and D should not be equal", a.equals(d)); + + Object e = ts.getTarget(); + assertEquals("D and E should be equal", d, e); + + Thread.sleep(110); + + Object f = ts.getTarget(); + assertFalse("E and F should be different", e.equals(f)); + } + + + private static class CountingRefreshableTargetSource extends AbstractRefreshableTargetSource { + + private int callCount; + + private boolean requiresRefresh; + + public CountingRefreshableTargetSource() { + } + + public CountingRefreshableTargetSource(boolean requiresRefresh) { + this.requiresRefresh = requiresRefresh; + } + + @Override + protected Object freshTarget() { + this.callCount++; + return new Object(); + } + + public int getCallCount() { + return this.callCount; + } + + @Override + protected boolean requiresRefresh() { + return this.requiresRefresh; + } + } + +} diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..c3c614d4a634d62b867e3319ba83f6422c39d0e5 --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceProxyTests-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/HotSwappableTargetSourceTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/target/HotSwappableTargetSourceTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..dcc43922efd5f11ed35b3c082e01dd6b39000b62 --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/HotSwappableTargetSourceTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + 10 + + + + 20 + + + + + + + + + + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-customTarget.xml b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-customTarget.xml new file mode 100644 index 0000000000000000000000000000000000000000..cec1c980ba5a99770d314a7262cfe15caba62a64 --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-customTarget.xml @@ -0,0 +1,21 @@ + + + + + + + 10 + + + + + + + + + + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-factoryBean.xml b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-factoryBean.xml new file mode 100644 index 0000000000000000000000000000000000000000..1853c4cd567a00ff05c483779c8da436a0984cad --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-factoryBean.xml @@ -0,0 +1,47 @@ + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + 20 + + + + + + + + + + + + + + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-singleton.xml b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-singleton.xml new file mode 100644 index 0000000000000000000000000000000000000000..1e5c69f5499b4207cc8691cad446bbbfc5b5230c --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/LazyInitTargetSourceTests-singleton.xml @@ -0,0 +1,21 @@ + + + + + + + 10 + + + + + + + + + + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/PrototypeTargetSourceTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/target/PrototypeTargetSourceTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ee787b20fb47db3ef6a64d9137894e9dd1bd7ca --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/PrototypeTargetSourceTests-context.xml @@ -0,0 +1,33 @@ + + + + + + + + 10 + + + + 10 + + + + prototypeTest + + + + + + debugInterceptor,test + + + + + + debugInterceptor + + + diff --git a/spring-aop/src/test/resources/org/springframework/aop/target/ThreadLocalTargetSourceTests-context.xml b/spring-aop/src/test/resources/org/springframework/aop/target/ThreadLocalTargetSourceTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..6400db2f215f010c8d13c7dfc0434f2e2cbec65e --- /dev/null +++ b/spring-aop/src/test/resources/org/springframework/aop/target/ThreadLocalTargetSourceTests-context.xml @@ -0,0 +1,54 @@ + + + + + + + 10 + + + + prototypeTest + + + + + + + + getStatsMixin + + + + + debugInterceptor,statsAdvisor + + + true + + + + + + Rod + + + + + Kerry + + + + test + + + + + + + diff --git a/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java b/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..1bcb1161287db2c0c1fdcc9c8e8cfec53412a0e6 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/aop/target/CommonsPool2TargetSourceTests.java @@ -0,0 +1,241 @@ +/* + * Copyright 2002-2018 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 + * + * https://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 org.springframework.aop.target; + +import java.util.NoSuchElementException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.tests.sample.beans.Person; +import org.springframework.tests.sample.beans.SerializablePerson; +import org.springframework.tests.sample.beans.SideEffectBean; +import org.springframework.util.SerializationTestUtils; + +import static org.junit.Assert.*; + +/** + * Tests for pooling invoker interceptor. + * TODO: need to make these tests stronger: it's hard to + * make too many assumptions about a pool. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Chris Beams + * @author Stephane Nicoll + */ +public class CommonsPool2TargetSourceTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + /** + * Initial count value set in bean factory XML + */ + private static final int INITIAL_COUNT = 10; + + private DefaultListableBeanFactory beanFactory; + + @Before + public void setUp() throws Exception { + this.beanFactory = new DefaultListableBeanFactory(); + new XmlBeanDefinitionReader(this.beanFactory).loadBeanDefinitions( + new ClassPathResource(getClass().getSimpleName() + "-context.xml", getClass())); + } + + /** + * We must simulate container shutdown, which should clear threads. + */ + @After + public void tearDown() { + // Will call pool.close() + this.beanFactory.destroySingletons(); + } + + private void testFunctionality(String name) { + SideEffectBean pooled = (SideEffectBean) beanFactory.getBean(name); + assertEquals(INITIAL_COUNT, pooled.getCount()); + pooled.doWork(); + assertEquals(INITIAL_COUNT + 1, pooled.getCount()); + + pooled = (SideEffectBean) beanFactory.getBean(name); + // Just check that it works--we can't make assumptions + // about the count + pooled.doWork(); + //assertEquals(INITIAL_COUNT + 1, apartment.getCount()); + } + + @Test + public void testFunctionality() { + testFunctionality("pooled"); + } + + @Test + public void testFunctionalityWithNoInterceptors() { + testFunctionality("pooledNoInterceptors"); + } + + @Test + public void testConfigMixin() { + SideEffectBean pooled = (SideEffectBean) beanFactory.getBean("pooledWithMixin"); + assertEquals(INITIAL_COUNT, pooled.getCount()); + PoolingConfig conf = (PoolingConfig) beanFactory.getBean("pooledWithMixin"); + // TODO one invocation from setup + //assertEquals(1, conf.getInvocations()); + pooled.doWork(); + // assertEquals("No objects active", 0, conf.getActive()); + assertEquals("Correct target source", 25, conf.getMaxSize()); + // assertTrue("Some free", conf.getFree() > 0); + //assertEquals(2, conf.getInvocations()); + assertEquals(25, conf.getMaxSize()); + } + + @Test + public void testTargetSourceSerializableWithoutConfigMixin() throws Exception { + CommonsPool2TargetSource cpts = (CommonsPool2TargetSource) beanFactory.getBean("personPoolTargetSource"); + + SingletonTargetSource serialized = (SingletonTargetSource) SerializationTestUtils.serializeAndDeserialize(cpts); + assertTrue(serialized.getTarget() instanceof Person); + } + + + @Test + public void testProxySerializableWithoutConfigMixin() throws Exception { + Person pooled = (Person) beanFactory.getBean("pooledPerson"); + + //System.out.println(((Advised) pooled).toProxyConfigString()); + assertTrue(((Advised) pooled).getTargetSource() instanceof CommonsPool2TargetSource); + + //((Advised) pooled).setTargetSource(new SingletonTargetSource(new SerializablePerson())); + Person serialized = (Person) SerializationTestUtils.serializeAndDeserialize(pooled); + assertTrue(((Advised) serialized).getTargetSource() instanceof SingletonTargetSource); + serialized.setAge(25); + assertEquals(25, serialized.getAge()); + } + + @Test + public void testHitMaxSize() throws Exception { + int maxSize = 10; + + CommonsPool2TargetSource targetSource = new CommonsPool2TargetSource(); + targetSource.setMaxSize(maxSize); + targetSource.setMaxWait(1); + prepareTargetSource(targetSource); + + Object[] pooledInstances = new Object[maxSize]; + + for (int x = 0; x < maxSize; x++) { + Object instance = targetSource.getTarget(); + assertNotNull(instance); + pooledInstances[x] = instance; + } + + // should be at maximum now + try { + targetSource.getTarget(); + fail("Should throw NoSuchElementException"); + } + catch (NoSuchElementException ex) { + // desired + } + + // lets now release an object and try to acquire a new one + targetSource.releaseTarget(pooledInstances[9]); + pooledInstances[9] = targetSource.getTarget(); + + // release all objects + for (int i = 0; i < pooledInstances.length; i++) { + targetSource.releaseTarget(pooledInstances[i]); + } + } + + @Test + public void testHitMaxSizeLoadedFromContext() throws Exception { + Advised person = (Advised) beanFactory.getBean("maxSizePooledPerson"); + CommonsPool2TargetSource targetSource = (CommonsPool2TargetSource) person.getTargetSource(); + + int maxSize = targetSource.getMaxSize(); + Object[] pooledInstances = new Object[maxSize]; + + for (int x = 0; x < maxSize; x++) { + Object instance = targetSource.getTarget(); + assertNotNull(instance); + pooledInstances[x] = instance; + } + + // should be at maximum now + try { + targetSource.getTarget(); + fail("Should throw NoSuchElementException"); + } + catch (NoSuchElementException ex) { + // desired + } + + // lets now release an object and try to acquire a new one + targetSource.releaseTarget(pooledInstances[9]); + pooledInstances[9] = targetSource.getTarget(); + + // release all objects + for (int i = 0; i < pooledInstances.length; i++) { + System.out.println(i); + targetSource.releaseTarget(pooledInstances[i]); + } + } + + @Test + public void testSetWhenExhaustedAction() { + CommonsPool2TargetSource targetSource = new CommonsPool2TargetSource(); + targetSource.setBlockWhenExhausted(true); + assertEquals(true, targetSource.isBlockWhenExhausted()); + } + + @Test + public void referenceIdentityByDefault() throws Exception { + CommonsPool2TargetSource targetSource = new CommonsPool2TargetSource(); + targetSource.setMaxWait(1); + prepareTargetSource(targetSource); + + Object first = targetSource.getTarget(); + Object second = targetSource.getTarget(); + assertTrue(first instanceof SerializablePerson); + assertTrue(second instanceof SerializablePerson); + assertEquals(first, second); + + targetSource.releaseTarget(first); + targetSource.releaseTarget(second); + } + + private void prepareTargetSource(CommonsPool2TargetSource targetSource) { + String beanName = "target"; + + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.registerPrototype(beanName, SerializablePerson.class); + + targetSource.setTargetBeanName(beanName); + targetSource.setBeanFactory(applicationContext); + } + +} diff --git a/spring-context/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceTests-context.xml b/spring-context/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceTests-context.xml new file mode 100644 index 0000000000000000000000000000000000000000..989b1b11a9f27af2e3ba45a54deb70d0068f1e0a --- /dev/null +++ b/spring-context/src/test/resources/org/springframework/aop/target/CommonsPool2TargetSourceTests-context.xml @@ -0,0 +1,69 @@ + + + + + + + 10 + + + + prototypeTest + 25 + + + + + getPoolingConfigMixin + + + + + + + + nop + + + + + + + + + poolConfigAdvisor + + true + + + + + + + + + prototypePerson + 10 + + + + + serializableNop + + + + + + + + + + + + serializableNop + + +