001/* 002 * Copyright 2013 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.core.scope; 017 018import org.springframework.aop.scope.ScopedProxyUtils; 019import org.springframework.batch.core.scope.context.StepContext; 020import org.springframework.beans.BeansException; 021import org.springframework.beans.factory.config.BeanDefinition; 022import org.springframework.beans.factory.config.BeanDefinitionHolder; 023import org.springframework.beans.factory.config.BeanDefinitionVisitor; 024import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 025import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 026import org.springframework.beans.factory.config.Scope; 027import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 028import org.springframework.beans.factory.support.BeanDefinitionRegistry; 029import org.springframework.core.Ordered; 030import org.springframework.util.Assert; 031import org.springframework.util.StringValueResolver; 032 033/** 034 * ScopeSupport. 035 * 036 * @author Michael Minella 037 * @since 3.0 038 */ 039public abstract class BatchScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered { 040 041 private boolean autoProxy = true; 042 043 private boolean proxyTargetClass = false; 044 045 private String name; 046 047 private int order = Ordered.LOWEST_PRECEDENCE; 048 049 /** 050 * @param order the order value to set priority of callback execution for 051 * the {@link BeanFactoryPostProcessor} part of this scope bean. 052 */ 053 public void setOrder(int order) { 054 this.order = order; 055 } 056 057 @Override 058 public int getOrder() { 059 return order; 060 } 061 062 public String getName() { 063 return this.name; 064 } 065 066 /** 067 * Public setter for the name property. This can then be used as a bean 068 * definition attribute, e.g. scope="job". 069 * 070 * @param name the name to set for this scope. 071 */ 072 public void setName(String name) { 073 this.name = name; 074 } 075 076 /** 077 * Flag to indicate that proxies should use dynamic subclassing. This allows 078 * classes with no interface to be proxied. Defaults to false. 079 * 080 * @param proxyTargetClass set to true to have proxies created using dynamic 081 * subclasses 082 */ 083 public void setProxyTargetClass(boolean proxyTargetClass) { 084 this.proxyTargetClass = proxyTargetClass; 085 } 086 087 /** 088 * Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the 089 * bean definition (e.g. in an @Configuration class). 090 * 091 * @param autoProxy the flag value to set (default true) 092 */ 093 public void setAutoProxy(boolean autoProxy) { 094 this.autoProxy = autoProxy; 095 } 096 097 public abstract String getTargetNamePrefix(); 098 099 /** 100 * Register this scope with the enclosing BeanFactory. 101 * 102 * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory) 103 * 104 * @param beanFactory the BeanFactory to register with 105 * @throws BeansException if there is a problem. 106 */ 107 @Override 108 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 109 110 beanFactory.registerScope(name, this); 111 112 if(!autoProxy) { 113 return; 114 } 115 116 Assert.state(beanFactory instanceof BeanDefinitionRegistry, 117 "BeanFactory was not a BeanDefinitionRegistry, so JobScope cannot be used."); 118 BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; 119 120 for (String beanName : beanFactory.getBeanDefinitionNames()) { 121 if (!beanName.startsWith(getTargetNamePrefix())) { 122 BeanDefinition definition = beanFactory.getBeanDefinition(beanName); 123 // Replace this or any of its inner beans with scoped proxy if it 124 // has this scope 125 boolean scoped = name.equals(definition.getScope()); 126 Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped); 127 scopifier.visitBeanDefinition(definition); 128 129 if (scoped && !definition.isAbstract()) { 130 createScopedProxy(beanName, definition, registry, proxyTargetClass); 131 } 132 } 133 } 134 135 } 136 137 /** 138 * Wrap a target bean definition in a proxy that defers initialization until 139 * after the {@link StepContext} is available. Amounts to adding 140 * <aop-auto-proxy/> to a step scoped bean. 141 * 142 * @param beanName the bean name to replace 143 * @param definition the bean definition to replace 144 * @param registry the enclosing {@link BeanDefinitionRegistry} 145 * @param proxyTargetClass true if we need to force use of dynamic 146 * subclasses 147 * @return a {@link BeanDefinitionHolder} for the new representation of the 148 * target. Caller should register it if needed to be visible at top level in 149 * bean factory. 150 */ 151 protected static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition, 152 BeanDefinitionRegistry registry, boolean proxyTargetClass) { 153 154 BeanDefinitionHolder proxyHolder; 155 156 proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, 157 proxyTargetClass); 158 159 registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition()); 160 161 return proxyHolder; 162 163 } 164 165 /** 166 * Helper class to scan a bean definition hierarchy and force the use of 167 * auto-proxy for step scoped beans. 168 * 169 * @author Dave Syer 170 * 171 */ 172 protected static class Scopifier extends BeanDefinitionVisitor { 173 174 private final boolean proxyTargetClass; 175 176 private final BeanDefinitionRegistry registry; 177 178 private final String scope; 179 180 private final boolean scoped; 181 182 public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) { 183 super(new StringValueResolver() { 184 @Override 185 public String resolveStringValue(String value) { 186 return value; 187 } 188 }); 189 this.registry = registry; 190 this.proxyTargetClass = proxyTargetClass; 191 this.scope = scope; 192 this.scoped = scoped; 193 } 194 195 @Override 196 protected Object resolveValue(Object value) { 197 198 BeanDefinition definition = null; 199 String beanName = null; 200 if (value instanceof BeanDefinition) { 201 definition = (BeanDefinition) value; 202 beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry); 203 } 204 else if (value instanceof BeanDefinitionHolder) { 205 BeanDefinitionHolder holder = (BeanDefinitionHolder) value; 206 definition = holder.getBeanDefinition(); 207 beanName = holder.getBeanName(); 208 } 209 210 if (definition != null) { 211 boolean nestedScoped = scope.equals(definition.getScope()); 212 boolean scopeChangeRequiresProxy = !scoped && nestedScoped; 213 if (scopeChangeRequiresProxy) { 214 // Exit here so that nested inner bean definitions are not 215 // analysed 216 return createScopedProxy(beanName, definition, registry, proxyTargetClass); 217 } 218 } 219 220 // Nested inner bean definitions are recursively analysed here 221 value = super.resolveValue(value); 222 return value; 223 224 } 225 } 226}