001/* 002 * Copyright 2002-2018 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 */ 016 017package org.springframework.scripting.support; 018 019import java.util.HashMap; 020import java.util.Iterator; 021import java.util.Map; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.aop.TargetSource; 027import org.springframework.aop.framework.AopInfrastructureBean; 028import org.springframework.aop.framework.ProxyFactory; 029import org.springframework.aop.support.DelegatingIntroductionInterceptor; 030import org.springframework.asm.Type; 031import org.springframework.beans.BeanUtils; 032import org.springframework.beans.PropertyValue; 033import org.springframework.beans.factory.BeanClassLoaderAware; 034import org.springframework.beans.factory.BeanCreationException; 035import org.springframework.beans.factory.BeanCurrentlyInCreationException; 036import org.springframework.beans.factory.BeanDefinitionStoreException; 037import org.springframework.beans.factory.BeanFactory; 038import org.springframework.beans.factory.BeanFactoryAware; 039import org.springframework.beans.factory.DisposableBean; 040import org.springframework.beans.factory.FactoryBean; 041import org.springframework.beans.factory.config.BeanDefinition; 042import org.springframework.beans.factory.config.BeanPostProcessor; 043import org.springframework.beans.factory.config.ConfigurableBeanFactory; 044import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; 045import org.springframework.beans.factory.support.AbstractBeanDefinition; 046import org.springframework.beans.factory.support.BeanDefinitionValidationException; 047import org.springframework.beans.factory.support.DefaultListableBeanFactory; 048import org.springframework.beans.factory.support.GenericBeanDefinition; 049import org.springframework.cglib.core.Signature; 050import org.springframework.cglib.proxy.InterfaceMaker; 051import org.springframework.context.ResourceLoaderAware; 052import org.springframework.core.Conventions; 053import org.springframework.core.Ordered; 054import org.springframework.core.io.DefaultResourceLoader; 055import org.springframework.core.io.ResourceLoader; 056import org.springframework.scripting.ScriptFactory; 057import org.springframework.scripting.ScriptSource; 058import org.springframework.util.ClassUtils; 059import org.springframework.util.ObjectUtils; 060import org.springframework.util.StringUtils; 061 062/** 063 * {@link org.springframework.beans.factory.config.BeanPostProcessor} that 064 * handles {@link org.springframework.scripting.ScriptFactory} definitions, 065 * replacing each factory with the actual scripted Java object generated by it. 066 * 067 * <p>This is similar to the 068 * {@link org.springframework.beans.factory.FactoryBean} mechanism, but is 069 * specifically tailored for scripts and not built into Spring's core 070 * container itself but rather implemented as an extension. 071 * 072 * <p><b>NOTE:</b> The most important characteristic of this post-processor 073 * is that constructor arguments are applied to the 074 * {@link org.springframework.scripting.ScriptFactory} instance 075 * while bean property values are applied to the generated scripted object. 076 * Typically, constructor arguments include a script source locator and 077 * potentially script interfaces, while bean property values include 078 * references and config values to inject into the scripted object itself. 079 * 080 * <p>The following {@link ScriptFactoryPostProcessor} will automatically 081 * be applied to the two 082 * {@link org.springframework.scripting.ScriptFactory} definitions below. 083 * At runtime, the actual scripted objects will be exposed for 084 * "bshMessenger" and "groovyMessenger", rather than the 085 * {@link org.springframework.scripting.ScriptFactory} instances. Both of 086 * those are supposed to be castable to the example's {@code Messenger} 087 * interfaces here. 088 * 089 * <pre class="code"><bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/> 090 * 091 * <bean id="bshMessenger" class="org.springframework.scripting.bsh.BshScriptFactory"> 092 * <constructor-arg value="classpath:mypackage/Messenger.bsh"/> 093 * <constructor-arg value="mypackage.Messenger"/> 094 * <property name="message" value="Hello World!"/> 095 * </bean> 096 * 097 * <bean id="groovyMessenger" class="org.springframework.scripting.groovy.GroovyScriptFactory"> 098 * <constructor-arg value="classpath:mypackage/Messenger.groovy"/> 099 * <property name="message" value="Hello World!"/> 100 * </bean></pre> 101 * 102 * <p><b>NOTE:</b> Please note that the above excerpt from a Spring 103 * XML bean definition file uses just the <bean/>-style syntax 104 * (in an effort to illustrate using the {@link ScriptFactoryPostProcessor} itself). 105 * In reality, you would never create a <bean/> definition for a 106 * {@link ScriptFactoryPostProcessor} explicitly; rather you would import the 107 * tags from the {@code 'lang'} namespace and simply create scripted 108 * beans using the tags in that namespace... as part of doing so, a 109 * {@link ScriptFactoryPostProcessor} will implicitly be created for you. 110 * 111 * <p>The Spring reference documentation contains numerous examples of using 112 * tags in the {@code 'lang'} namespace; by way of an example, find below 113 * a Groovy-backed bean defined using the {@code 'lang:groovy'} tag. 114 * 115 * <pre class="code"> 116 * <?xml version="1.0" encoding="UTF-8"?> 117 * <beans xmlns="http://www.springframework.org/schema/beans" 118 * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 119 * xmlns:lang="http://www.springframework.org/schema/lang"> 120 * 121 * <!-- this is the bean definition for the Groovy-backed Messenger implementation --> 122 * <lang:groovy id="messenger" script-source="classpath:Messenger.groovy"> 123 * <lang:property name="message" value="I Can Do The Frug" /> 124 * </lang:groovy> 125 * 126 * <!-- an otherwise normal bean that will be injected by the Groovy-backed Messenger --> 127 * <bean id="bookingService" class="x.y.DefaultBookingService"> 128 * <property name="messenger" ref="messenger" /> 129 * </bean> 130 * 131 * </beans></pre> 132 * 133 * @author Juergen Hoeller 134 * @author Rob Harrop 135 * @author Rick Evans 136 * @author Mark Fisher 137 * @since 2.0 138 */ 139public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter 140 implements BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered { 141 142 /** 143 * The {@link org.springframework.core.io.Resource}-style prefix that denotes 144 * an inline script. 145 * <p>An inline script is a script that is defined right there in the (typically XML) 146 * configuration, as opposed to being defined in an external file. 147 */ 148 public static final String INLINE_SCRIPT_PREFIX = "inline:"; 149 150 public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions.getQualifiedAttributeName( 151 ScriptFactoryPostProcessor.class, "refreshCheckDelay"); 152 153 public static final String PROXY_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName( 154 ScriptFactoryPostProcessor.class, "proxyTargetClass"); 155 156 public static final String LANGUAGE_ATTRIBUTE = Conventions.getQualifiedAttributeName( 157 ScriptFactoryPostProcessor.class, "language"); 158 159 private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory."; 160 161 private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject."; 162 163 164 /** Logger available to subclasses */ 165 protected final Log logger = LogFactory.getLog(getClass()); 166 167 private long defaultRefreshCheckDelay = -1; 168 169 private boolean defaultProxyTargetClass = false; 170 171 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 172 173 private ConfigurableBeanFactory beanFactory; 174 175 private ResourceLoader resourceLoader = new DefaultResourceLoader(); 176 177 final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory(); 178 179 /** Map from bean name String to ScriptSource object */ 180 private final Map<String, ScriptSource> scriptSourceCache = new HashMap<String, ScriptSource>(); 181 182 /** 183 * Set the delay between refresh checks, in milliseconds. 184 * Default is -1, indicating no refresh checks at all. 185 * <p>Note that an actual refresh will only happen when 186 * the {@link org.springframework.scripting.ScriptSource} indicates 187 * that it has been modified. 188 * @see org.springframework.scripting.ScriptSource#isModified() 189 */ 190 public void setDefaultRefreshCheckDelay(long defaultRefreshCheckDelay) { 191 this.defaultRefreshCheckDelay = defaultRefreshCheckDelay; 192 } 193 194 /** 195 * Flag to signal that refreshable proxies should be created to proxy the target class not its interfaces. 196 * @param defaultProxyTargetClass the flag value to set 197 */ 198 public void setDefaultProxyTargetClass(boolean defaultProxyTargetClass) { 199 this.defaultProxyTargetClass = defaultProxyTargetClass; 200 } 201 202 @Override 203 public void setBeanClassLoader(ClassLoader classLoader) { 204 this.beanClassLoader = classLoader; 205 } 206 207 @Override 208 public void setBeanFactory(BeanFactory beanFactory) { 209 if (!(beanFactory instanceof ConfigurableBeanFactory)) { 210 throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with " + 211 "non-ConfigurableBeanFactory: " + beanFactory.getClass()); 212 } 213 this.beanFactory = (ConfigurableBeanFactory) beanFactory; 214 215 // Required so that references (up container hierarchies) are correctly resolved. 216 this.scriptBeanFactory.setParentBeanFactory(this.beanFactory); 217 218 // Required so that all BeanPostProcessors, Scopes, etc become available. 219 this.scriptBeanFactory.copyConfigurationFrom(this.beanFactory); 220 221 // Filter out BeanPostProcessors that are part of the AOP infrastructure, 222 // since those are only meant to apply to beans defined in the original factory. 223 for (Iterator<BeanPostProcessor> it = this.scriptBeanFactory.getBeanPostProcessors().iterator(); it.hasNext();) { 224 if (it.next() instanceof AopInfrastructureBean) { 225 it.remove(); // effectively deprecated: use List.removeIf on Java 8+ 226 } 227 } 228 } 229 230 @Override 231 public void setResourceLoader(ResourceLoader resourceLoader) { 232 this.resourceLoader = resourceLoader; 233 } 234 235 @Override 236 public int getOrder() { 237 return Integer.MIN_VALUE; 238 } 239 240 241 @Override 242 public Class<?> predictBeanType(Class<?> beanClass, String beanName) { 243 // We only apply special treatment to ScriptFactory implementations here. 244 if (!ScriptFactory.class.isAssignableFrom(beanClass)) { 245 return null; 246 } 247 248 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); 249 250 try { 251 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName; 252 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName; 253 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); 254 255 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 256 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 257 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 258 259 Class<?> scriptedType = scriptFactory.getScriptedObjectType(scriptSource); 260 if (scriptedType != null) { 261 return scriptedType; 262 } 263 else if (!ObjectUtils.isEmpty(interfaces)) { 264 return (interfaces.length == 1 ? interfaces[0] : createCompositeInterface(interfaces)); 265 } 266 else { 267 if (bd.isSingleton()) { 268 Object bean = this.scriptBeanFactory.getBean(scriptedObjectBeanName); 269 if (bean != null) { 270 return bean.getClass(); 271 } 272 } 273 } 274 } 275 catch (Exception ex) { 276 if (ex instanceof BeanCreationException && 277 ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) { 278 if (logger.isTraceEnabled()) { 279 logger.trace("Could not determine scripted object type for bean '" + beanName + "': " + 280 ex.getMessage()); 281 } 282 } 283 else { 284 if (logger.isDebugEnabled()) { 285 logger.debug("Could not determine scripted object type for bean '" + beanName + "'", ex); 286 } 287 } 288 } 289 290 return null; 291 } 292 293 @Override 294 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { 295 // We only apply special treatment to ScriptFactory implementations here. 296 if (!ScriptFactory.class.isAssignableFrom(beanClass)) { 297 return null; 298 } 299 300 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); 301 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName; 302 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName; 303 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); 304 305 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 306 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 307 boolean isFactoryBean = false; 308 try { 309 Class<?> scriptedObjectType = scriptFactory.getScriptedObjectType(scriptSource); 310 // Returned type may be null if the factory is unable to determine the type. 311 if (scriptedObjectType != null) { 312 isFactoryBean = FactoryBean.class.isAssignableFrom(scriptedObjectType); 313 } 314 } 315 catch (Exception ex) { 316 throw new BeanCreationException(beanName, 317 "Could not determine scripted object type for " + scriptFactory, ex); 318 } 319 320 long refreshCheckDelay = resolveRefreshCheckDelay(bd); 321 if (refreshCheckDelay >= 0) { 322 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 323 RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory, 324 scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean); 325 boolean proxyTargetClass = resolveProxyTargetClass(bd); 326 String language = (String) bd.getAttribute(LANGUAGE_ATTRIBUTE); 327 if (proxyTargetClass && (language == null || !language.equals("groovy"))) { 328 throw new BeanDefinitionValidationException( 329 "Cannot use proxyTargetClass=true with script beans where language is not 'groovy': '" + 330 language + "'"); 331 } 332 ts.setRefreshCheckDelay(refreshCheckDelay); 333 return createRefreshableProxy(ts, interfaces, proxyTargetClass); 334 } 335 336 if (isFactoryBean) { 337 scriptedObjectBeanName = BeanFactory.FACTORY_BEAN_PREFIX + scriptedObjectBeanName; 338 } 339 return this.scriptBeanFactory.getBean(scriptedObjectBeanName); 340 } 341 342 /** 343 * Prepare the script beans in the internal BeanFactory that this 344 * post-processor uses. Each original bean definition will be split 345 * into a ScriptFactory definition and a scripted object definition. 346 * @param bd the original bean definition in the main BeanFactory 347 * @param scriptFactoryBeanName the name of the internal ScriptFactory bean 348 * @param scriptedObjectBeanName the name of the internal scripted object bean 349 */ 350 protected void prepareScriptBeans(BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) { 351 // Avoid recreation of the script bean definition in case of a prototype. 352 synchronized (this.scriptBeanFactory) { 353 if (!this.scriptBeanFactory.containsBeanDefinition(scriptedObjectBeanName)) { 354 355 this.scriptBeanFactory.registerBeanDefinition( 356 scriptFactoryBeanName, createScriptFactoryBeanDefinition(bd)); 357 ScriptFactory scriptFactory = 358 this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 359 ScriptSource scriptSource = 360 getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 361 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 362 363 Class<?>[] scriptedInterfaces = interfaces; 364 if (scriptFactory.requiresConfigInterface() && !bd.getPropertyValues().isEmpty()) { 365 Class<?> configInterface = createConfigInterface(bd, interfaces); 366 scriptedInterfaces = ObjectUtils.addObjectToArray(interfaces, configInterface); 367 } 368 369 BeanDefinition objectBd = createScriptedObjectBeanDefinition( 370 bd, scriptFactoryBeanName, scriptSource, scriptedInterfaces); 371 long refreshCheckDelay = resolveRefreshCheckDelay(bd); 372 if (refreshCheckDelay >= 0) { 373 objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE); 374 } 375 376 this.scriptBeanFactory.registerBeanDefinition(scriptedObjectBeanName, objectBd); 377 } 378 } 379 } 380 381 /** 382 * Get the refresh check delay for the given {@link ScriptFactory} {@link BeanDefinition}. 383 * If the {@link BeanDefinition} has a 384 * {@link org.springframework.core.AttributeAccessor metadata attribute} 385 * under the key {@link #REFRESH_CHECK_DELAY_ATTRIBUTE} which is a valid {@link Number} 386 * type, then this value is used. Otherwise, the {@link #defaultRefreshCheckDelay} 387 * value is used. 388 * @param beanDefinition the BeanDefinition to check 389 * @return the refresh check delay 390 */ 391 protected long resolveRefreshCheckDelay(BeanDefinition beanDefinition) { 392 long refreshCheckDelay = this.defaultRefreshCheckDelay; 393 Object attributeValue = beanDefinition.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); 394 if (attributeValue instanceof Number) { 395 refreshCheckDelay = ((Number) attributeValue).longValue(); 396 } 397 else if (attributeValue instanceof String) { 398 refreshCheckDelay = Long.parseLong((String) attributeValue); 399 } 400 else if (attributeValue != null) { 401 throw new BeanDefinitionStoreException("Invalid refresh check delay attribute [" + 402 REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value '" + attributeValue + 403 "': needs to be of type Number or String"); 404 } 405 return refreshCheckDelay; 406 } 407 408 protected boolean resolveProxyTargetClass(BeanDefinition beanDefinition) { 409 boolean proxyTargetClass = this.defaultProxyTargetClass; 410 Object attributeValue = beanDefinition.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); 411 if (attributeValue instanceof Boolean) { 412 proxyTargetClass = (Boolean) attributeValue; 413 } 414 else if (attributeValue instanceof String) { 415 proxyTargetClass = Boolean.valueOf((String) attributeValue); 416 } 417 else if (attributeValue != null) { 418 throw new BeanDefinitionStoreException("Invalid proxy target class attribute [" + 419 PROXY_TARGET_CLASS_ATTRIBUTE + "] with value '" + attributeValue + 420 "': needs to be of type Boolean or String"); 421 } 422 return proxyTargetClass; 423 } 424 425 /** 426 * Create a ScriptFactory bean definition based on the given script definition, 427 * extracting only the definition data that is relevant for the ScriptFactory 428 * (that is, only bean class and constructor arguments). 429 * @param bd the full script bean definition 430 * @return the extracted ScriptFactory bean definition 431 * @see org.springframework.scripting.ScriptFactory 432 */ 433 protected BeanDefinition createScriptFactoryBeanDefinition(BeanDefinition bd) { 434 GenericBeanDefinition scriptBd = new GenericBeanDefinition(); 435 scriptBd.setBeanClassName(bd.getBeanClassName()); 436 scriptBd.getConstructorArgumentValues().addArgumentValues(bd.getConstructorArgumentValues()); 437 return scriptBd; 438 } 439 440 /** 441 * Obtain a ScriptSource for the given bean, lazily creating it 442 * if not cached already. 443 * @param beanName the name of the scripted bean 444 * @param scriptSourceLocator the script source locator associated with the bean 445 * @return the corresponding ScriptSource instance 446 * @see #convertToScriptSource 447 */ 448 protected ScriptSource getScriptSource(String beanName, String scriptSourceLocator) { 449 synchronized (this.scriptSourceCache) { 450 ScriptSource scriptSource = this.scriptSourceCache.get(beanName); 451 if (scriptSource == null) { 452 scriptSource = convertToScriptSource(beanName, scriptSourceLocator, this.resourceLoader); 453 this.scriptSourceCache.put(beanName, scriptSource); 454 } 455 return scriptSource; 456 } 457 } 458 459 /** 460 * Convert the given script source locator to a ScriptSource instance. 461 * <p>By default, supported locators are Spring resource locations 462 * (such as "file:C:/myScript.bsh" or "classpath:myPackage/myScript.bsh") 463 * and inline scripts ("inline:myScriptText..."). 464 * @param beanName the name of the scripted bean 465 * @param scriptSourceLocator the script source locator 466 * @param resourceLoader the ResourceLoader to use (if necessary) 467 * @return the ScriptSource instance 468 */ 469 protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator, 470 ResourceLoader resourceLoader) { 471 472 if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) { 473 return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName); 474 } 475 else { 476 return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator)); 477 } 478 } 479 480 /** 481 * Create a config interface for the given bean definition, defining setter 482 * methods for the defined property values as well as an init method and 483 * a destroy method (if defined). 484 * <p>This implementation creates the interface via CGLIB's InterfaceMaker, 485 * determining the property types from the given interfaces (as far as possible). 486 * @param bd the bean definition (property values etc) to create a 487 * config interface for 488 * @param interfaces the interfaces to check against (might define 489 * getters corresponding to the setters we're supposed to generate) 490 * @return the config interface 491 * @see org.springframework.cglib.proxy.InterfaceMaker 492 * @see org.springframework.beans.BeanUtils#findPropertyType 493 */ 494 protected Class<?> createConfigInterface(BeanDefinition bd, Class<?>[] interfaces) { 495 InterfaceMaker maker = new InterfaceMaker(); 496 PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues(); 497 for (PropertyValue pv : pvs) { 498 String propertyName = pv.getName(); 499 Class<?> propertyType = BeanUtils.findPropertyType(propertyName, interfaces); 500 String setterName = "set" + StringUtils.capitalize(propertyName); 501 Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)}); 502 maker.add(signature, new Type[0]); 503 } 504 if (bd instanceof AbstractBeanDefinition) { 505 AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; 506 if (abd.getInitMethodName() != null) { 507 Signature signature = new Signature(abd.getInitMethodName(), Type.VOID_TYPE, new Type[0]); 508 maker.add(signature, new Type[0]); 509 } 510 if (StringUtils.hasText(abd.getDestroyMethodName())) { 511 Signature signature = new Signature(abd.getDestroyMethodName(), Type.VOID_TYPE, new Type[0]); 512 maker.add(signature, new Type[0]); 513 } 514 } 515 return maker.create(); 516 } 517 518 /** 519 * Create a composite interface Class for the given interfaces, 520 * implementing the given interfaces in one single Class. 521 * <p>The default implementation builds a JDK proxy class 522 * for the given interfaces. 523 * @param interfaces the interfaces to merge 524 * @return the merged interface as Class 525 * @see java.lang.reflect.Proxy#getProxyClass 526 */ 527 protected Class<?> createCompositeInterface(Class<?>[] interfaces) { 528 return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader); 529 } 530 531 /** 532 * Create a bean definition for the scripted object, based on the given script 533 * definition, extracting the definition data that is relevant for the scripted 534 * object (that is, everything but bean class and constructor arguments). 535 * @param bd the full script bean definition 536 * @param scriptFactoryBeanName the name of the internal ScriptFactory bean 537 * @param scriptSource the ScriptSource for the scripted bean 538 * @param interfaces the interfaces that the scripted bean is supposed to implement 539 * @return the extracted ScriptFactory bean definition 540 * @see org.springframework.scripting.ScriptFactory#getScriptedObject 541 */ 542 protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName, 543 ScriptSource scriptSource, Class<?>[] interfaces) { 544 545 GenericBeanDefinition objectBd = new GenericBeanDefinition(bd); 546 objectBd.setFactoryBeanName(scriptFactoryBeanName); 547 objectBd.setFactoryMethodName("getScriptedObject"); 548 objectBd.getConstructorArgumentValues().clear(); 549 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource); 550 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces); 551 return objectBd; 552 } 553 554 /** 555 * Create a refreshable proxy for the given AOP TargetSource. 556 * @param ts the refreshable TargetSource 557 * @param interfaces the proxy interfaces (may be {@code null} to 558 * indicate proxying of all interfaces implemented by the target class) 559 * @return the generated proxy 560 * @see RefreshableScriptTargetSource 561 */ 562 protected Object createRefreshableProxy(TargetSource ts, Class<?>[] interfaces, boolean proxyTargetClass) { 563 ProxyFactory proxyFactory = new ProxyFactory(); 564 proxyFactory.setTargetSource(ts); 565 ClassLoader classLoader = this.beanClassLoader; 566 567 if (interfaces == null) { 568 interfaces = ClassUtils.getAllInterfacesForClass(ts.getTargetClass(), this.beanClassLoader); 569 } 570 proxyFactory.setInterfaces(interfaces); 571 if (proxyTargetClass) { 572 classLoader = null; // force use of Class.getClassLoader() 573 proxyFactory.setProxyTargetClass(true); 574 } 575 576 DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(ts); 577 introduction.suppressInterface(TargetSource.class); 578 proxyFactory.addAdvice(introduction); 579 580 return proxyFactory.getProxy(classLoader); 581 } 582 583 /** 584 * Destroy the inner bean factory (used for scripts) on shutdown. 585 */ 586 @Override 587 public void destroy() { 588 this.scriptBeanFactory.destroySingletons(); 589 } 590 591}