/* * Copyright 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.batch.core.scope; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionVisitor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.Ordered; import org.springframework.util.Assert; import org.springframework.util.StringValueResolver; /** * ScopeSupport. * * @author Michael Minella * @since 3.0 */ public abstract class BatchScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered { private boolean autoProxy = true; private boolean proxyTargetClass = false; private String name; private int order = Ordered.LOWEST_PRECEDENCE; /** * @param order the order value to set priority of callback execution for * the {@link BeanFactoryPostProcessor} part of this scope bean. */ public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return order; } public String getName() { return this.name; } /** * Public setter for the name property. This can then be used as a bean * definition attribute, e.g. scope="job". * * @param name the name to set for this scope. */ public void setName(String name) { this.name = name; } /** * Flag to indicate that proxies should use dynamic subclassing. This allows * classes with no interface to be proxied. Defaults to false. * * @param proxyTargetClass set to true to have proxies created using dynamic * subclasses */ public void setProxyTargetClass(boolean proxyTargetClass) { this.proxyTargetClass = proxyTargetClass; } /** * Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the * bean definition (e.g. in an @Configuration class). * * @param autoProxy the flag value to set (default true) */ public void setAutoProxy(boolean autoProxy) { this.autoProxy = autoProxy; } public abstract String getTargetNamePrefix(); /** * Register this scope with the enclosing BeanFactory. * * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory) * * @param beanFactory the BeanFactory to register with * @throws BeansException if there is a problem. */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope(name, this); if(!autoProxy) { return; } Assert.state(beanFactory instanceof BeanDefinitionRegistry, "BeanFactory was not a BeanDefinitionRegistry, so JobScope cannot be used."); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (String beanName : beanFactory.getBeanDefinitionNames()) { if (!beanName.startsWith(getTargetNamePrefix())) { BeanDefinition definition = beanFactory.getBeanDefinition(beanName); // Replace this or any of its inner beans with scoped proxy if it // has this scope boolean scoped = name.equals(definition.getScope()); Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped); scopifier.visitBeanDefinition(definition); if (scoped && !definition.isAbstract()) { createScopedProxy(beanName, definition, registry, proxyTargetClass); } } } } /** * Wrap a target bean definition in a proxy that defers initialization until * after the {@link StepContext} is available. Amounts to adding * <aop-auto-proxy/> to a step scoped bean. * * @param beanName the bean name to replace * @param definition the bean definition to replace * @param registry the enclosing {@link BeanDefinitionRegistry} * @param proxyTargetClass true if we need to force use of dynamic * subclasses * @return a {@link BeanDefinitionHolder} for the new representation of the * target. Caller should register it if needed to be visible at top level in * bean factory. */ protected static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { BeanDefinitionHolder proxyHolder; proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, proxyTargetClass); registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition()); return proxyHolder; } /** * Helper class to scan a bean definition hierarchy and force the use of * auto-proxy for step scoped beans. * * @author Dave Syer * */ protected static class Scopifier extends BeanDefinitionVisitor { private final boolean proxyTargetClass; private final BeanDefinitionRegistry registry; private final String scope; private final boolean scoped; public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) { super(new StringValueResolver() { @Override public String resolveStringValue(String value) { return value; } }); this.registry = registry; this.proxyTargetClass = proxyTargetClass; this.scope = scope; this.scoped = scoped; } @Override protected Object resolveValue(Object value) { BeanDefinition definition = null; String beanName = null; if (value instanceof BeanDefinition) { definition = (BeanDefinition) value; beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry); } else if (value instanceof BeanDefinitionHolder) { BeanDefinitionHolder holder = (BeanDefinitionHolder) value; definition = holder.getBeanDefinition(); beanName = holder.getBeanName(); } if (definition != null) { boolean nestedScoped = scope.equals(definition.getScope()); boolean scopeChangeRequiresProxy = !scoped && nestedScoped; if (scopeChangeRequiresProxy) { // Exit here so that nested inner bean definitions are not // analysed return createScopedProxy(beanName, definition, registry, proxyTargetClass); } } // Nested inner bean definitions are recursively analysed here value = super.resolveValue(value); return value; } } }