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.Map; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.aop.TargetSource; 026import org.springframework.aop.framework.AopInfrastructureBean; 027import org.springframework.aop.framework.ProxyFactory; 028import org.springframework.aop.support.DelegatingIntroductionInterceptor; 029import org.springframework.asm.Type; 030import org.springframework.beans.BeanUtils; 031import org.springframework.beans.PropertyValue; 032import org.springframework.beans.PropertyValues; 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.ConfigurableBeanFactory; 043import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; 044import org.springframework.beans.factory.support.BeanDefinitionValidationException; 045import org.springframework.beans.factory.support.DefaultListableBeanFactory; 046import org.springframework.beans.factory.support.GenericBeanDefinition; 047import org.springframework.cglib.core.Signature; 048import org.springframework.cglib.proxy.InterfaceMaker; 049import org.springframework.context.ResourceLoaderAware; 050import org.springframework.core.Conventions; 051import org.springframework.core.Ordered; 052import org.springframework.core.io.DefaultResourceLoader; 053import org.springframework.core.io.ResourceLoader; 054import org.springframework.lang.Nullable; 055import org.springframework.scripting.ScriptFactory; 056import org.springframework.scripting.ScriptSource; 057import org.springframework.util.Assert; 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 /** 151 * The {@code refreshCheckDelay} attribute. 152 */ 153 public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions.getQualifiedAttributeName( 154 ScriptFactoryPostProcessor.class, "refreshCheckDelay"); 155 156 /** 157 * The {@code proxyTargetClass} attribute. 158 */ 159 public static final String PROXY_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName( 160 ScriptFactoryPostProcessor.class, "proxyTargetClass"); 161 162 /** 163 * The {@code language} attribute. 164 */ 165 public static final String LANGUAGE_ATTRIBUTE = Conventions.getQualifiedAttributeName( 166 ScriptFactoryPostProcessor.class, "language"); 167 168 private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory."; 169 170 private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject."; 171 172 173 /** Logger available to subclasses. */ 174 protected final Log logger = LogFactory.getLog(getClass()); 175 176 private long defaultRefreshCheckDelay = -1; 177 178 private boolean defaultProxyTargetClass = false; 179 180 @Nullable 181 private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 182 183 @Nullable 184 private ConfigurableBeanFactory beanFactory; 185 186 private ResourceLoader resourceLoader = new DefaultResourceLoader(); 187 188 final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory(); 189 190 /** Map from bean name String to ScriptSource object. */ 191 private final Map<String, ScriptSource> scriptSourceCache = new HashMap<>(); 192 193 194 /** 195 * Set the delay between refresh checks, in milliseconds. 196 * Default is -1, indicating no refresh checks at all. 197 * <p>Note that an actual refresh will only happen when 198 * the {@link org.springframework.scripting.ScriptSource} indicates 199 * that it has been modified. 200 * @see org.springframework.scripting.ScriptSource#isModified() 201 */ 202 public void setDefaultRefreshCheckDelay(long defaultRefreshCheckDelay) { 203 this.defaultRefreshCheckDelay = defaultRefreshCheckDelay; 204 } 205 206 /** 207 * Flag to signal that refreshable proxies should be created to proxy the target class not its interfaces. 208 * @param defaultProxyTargetClass the flag value to set 209 */ 210 public void setDefaultProxyTargetClass(boolean defaultProxyTargetClass) { 211 this.defaultProxyTargetClass = defaultProxyTargetClass; 212 } 213 214 @Override 215 public void setBeanClassLoader(ClassLoader classLoader) { 216 this.beanClassLoader = classLoader; 217 } 218 219 @Override 220 public void setBeanFactory(BeanFactory beanFactory) { 221 if (!(beanFactory instanceof ConfigurableBeanFactory)) { 222 throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with " + 223 "non-ConfigurableBeanFactory: " + beanFactory.getClass()); 224 } 225 this.beanFactory = (ConfigurableBeanFactory) beanFactory; 226 227 // Required so that references (up container hierarchies) are correctly resolved. 228 this.scriptBeanFactory.setParentBeanFactory(this.beanFactory); 229 230 // Required so that all BeanPostProcessors, Scopes, etc become available. 231 this.scriptBeanFactory.copyConfigurationFrom(this.beanFactory); 232 233 // Filter out BeanPostProcessors that are part of the AOP infrastructure, 234 // since those are only meant to apply to beans defined in the original factory. 235 this.scriptBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor -> 236 beanPostProcessor instanceof AopInfrastructureBean); 237 } 238 239 @Override 240 public void setResourceLoader(ResourceLoader resourceLoader) { 241 this.resourceLoader = resourceLoader; 242 } 243 244 @Override 245 public int getOrder() { 246 return Integer.MIN_VALUE; 247 } 248 249 250 @Override 251 @Nullable 252 public Class<?> predictBeanType(Class<?> beanClass, String beanName) { 253 // We only apply special treatment to ScriptFactory implementations here. 254 if (!ScriptFactory.class.isAssignableFrom(beanClass)) { 255 return null; 256 } 257 258 Assert.state(this.beanFactory != null, "No BeanFactory set"); 259 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); 260 261 try { 262 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName; 263 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName; 264 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); 265 266 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 267 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 268 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 269 270 Class<?> scriptedType = scriptFactory.getScriptedObjectType(scriptSource); 271 if (scriptedType != null) { 272 return scriptedType; 273 } 274 else if (!ObjectUtils.isEmpty(interfaces)) { 275 return (interfaces.length == 1 ? interfaces[0] : createCompositeInterface(interfaces)); 276 } 277 else { 278 if (bd.isSingleton()) { 279 return this.scriptBeanFactory.getBean(scriptedObjectBeanName).getClass(); 280 } 281 } 282 } 283 catch (Exception ex) { 284 if (ex instanceof BeanCreationException && 285 ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) { 286 if (logger.isTraceEnabled()) { 287 logger.trace("Could not determine scripted object type for bean '" + beanName + "': " + 288 ex.getMessage()); 289 } 290 } 291 else { 292 if (logger.isDebugEnabled()) { 293 logger.debug("Could not determine scripted object type for bean '" + beanName + "'", ex); 294 } 295 } 296 } 297 298 return null; 299 } 300 301 @Override 302 public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { 303 return pvs; 304 } 305 306 @Override 307 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { 308 // We only apply special treatment to ScriptFactory implementations here. 309 if (!ScriptFactory.class.isAssignableFrom(beanClass)) { 310 return null; 311 } 312 313 Assert.state(this.beanFactory != null, "No BeanFactory set"); 314 BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); 315 String scriptFactoryBeanName = SCRIPT_FACTORY_NAME_PREFIX + beanName; 316 String scriptedObjectBeanName = SCRIPTED_OBJECT_NAME_PREFIX + beanName; 317 prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName); 318 319 ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 320 ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 321 boolean isFactoryBean = false; 322 try { 323 Class<?> scriptedObjectType = scriptFactory.getScriptedObjectType(scriptSource); 324 // Returned type may be null if the factory is unable to determine the type. 325 if (scriptedObjectType != null) { 326 isFactoryBean = FactoryBean.class.isAssignableFrom(scriptedObjectType); 327 } 328 } 329 catch (Exception ex) { 330 throw new BeanCreationException(beanName, 331 "Could not determine scripted object type for " + scriptFactory, ex); 332 } 333 334 long refreshCheckDelay = resolveRefreshCheckDelay(bd); 335 if (refreshCheckDelay >= 0) { 336 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 337 RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory, 338 scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean); 339 boolean proxyTargetClass = resolveProxyTargetClass(bd); 340 String language = (String) bd.getAttribute(LANGUAGE_ATTRIBUTE); 341 if (proxyTargetClass && (language == null || !language.equals("groovy"))) { 342 throw new BeanDefinitionValidationException( 343 "Cannot use proxyTargetClass=true with script beans where language is not 'groovy': '" + 344 language + "'"); 345 } 346 ts.setRefreshCheckDelay(refreshCheckDelay); 347 return createRefreshableProxy(ts, interfaces, proxyTargetClass); 348 } 349 350 if (isFactoryBean) { 351 scriptedObjectBeanName = BeanFactory.FACTORY_BEAN_PREFIX + scriptedObjectBeanName; 352 } 353 return this.scriptBeanFactory.getBean(scriptedObjectBeanName); 354 } 355 356 /** 357 * Prepare the script beans in the internal BeanFactory that this 358 * post-processor uses. Each original bean definition will be split 359 * into a ScriptFactory definition and a scripted object definition. 360 * @param bd the original bean definition in the main BeanFactory 361 * @param scriptFactoryBeanName the name of the internal ScriptFactory bean 362 * @param scriptedObjectBeanName the name of the internal scripted object bean 363 */ 364 protected void prepareScriptBeans(BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) { 365 // Avoid recreation of the script bean definition in case of a prototype. 366 synchronized (this.scriptBeanFactory) { 367 if (!this.scriptBeanFactory.containsBeanDefinition(scriptedObjectBeanName)) { 368 369 this.scriptBeanFactory.registerBeanDefinition( 370 scriptFactoryBeanName, createScriptFactoryBeanDefinition(bd)); 371 ScriptFactory scriptFactory = 372 this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class); 373 ScriptSource scriptSource = 374 getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator()); 375 Class<?>[] interfaces = scriptFactory.getScriptInterfaces(); 376 377 Class<?>[] scriptedInterfaces = interfaces; 378 if (scriptFactory.requiresConfigInterface() && !bd.getPropertyValues().isEmpty()) { 379 Class<?> configInterface = createConfigInterface(bd, interfaces); 380 scriptedInterfaces = ObjectUtils.addObjectToArray(interfaces, configInterface); 381 } 382 383 BeanDefinition objectBd = createScriptedObjectBeanDefinition( 384 bd, scriptFactoryBeanName, scriptSource, scriptedInterfaces); 385 long refreshCheckDelay = resolveRefreshCheckDelay(bd); 386 if (refreshCheckDelay >= 0) { 387 objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE); 388 } 389 390 this.scriptBeanFactory.registerBeanDefinition(scriptedObjectBeanName, objectBd); 391 } 392 } 393 } 394 395 /** 396 * Get the refresh check delay for the given {@link ScriptFactory} {@link BeanDefinition}. 397 * If the {@link BeanDefinition} has a 398 * {@link org.springframework.core.AttributeAccessor metadata attribute} 399 * under the key {@link #REFRESH_CHECK_DELAY_ATTRIBUTE} which is a valid {@link Number} 400 * type, then this value is used. Otherwise, the {@link #defaultRefreshCheckDelay} 401 * value is used. 402 * @param beanDefinition the BeanDefinition to check 403 * @return the refresh check delay 404 */ 405 protected long resolveRefreshCheckDelay(BeanDefinition beanDefinition) { 406 long refreshCheckDelay = this.defaultRefreshCheckDelay; 407 Object attributeValue = beanDefinition.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE); 408 if (attributeValue instanceof Number) { 409 refreshCheckDelay = ((Number) attributeValue).longValue(); 410 } 411 else if (attributeValue instanceof String) { 412 refreshCheckDelay = Long.parseLong((String) attributeValue); 413 } 414 else if (attributeValue != null) { 415 throw new BeanDefinitionStoreException("Invalid refresh check delay attribute [" + 416 REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value '" + attributeValue + 417 "': needs to be of type Number or String"); 418 } 419 return refreshCheckDelay; 420 } 421 422 protected boolean resolveProxyTargetClass(BeanDefinition beanDefinition) { 423 boolean proxyTargetClass = this.defaultProxyTargetClass; 424 Object attributeValue = beanDefinition.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE); 425 if (attributeValue instanceof Boolean) { 426 proxyTargetClass = (Boolean) attributeValue; 427 } 428 else if (attributeValue instanceof String) { 429 proxyTargetClass = Boolean.parseBoolean((String) attributeValue); 430 } 431 else if (attributeValue != null) { 432 throw new BeanDefinitionStoreException("Invalid proxy target class attribute [" + 433 PROXY_TARGET_CLASS_ATTRIBUTE + "] with value '" + attributeValue + 434 "': needs to be of type Boolean or String"); 435 } 436 return proxyTargetClass; 437 } 438 439 /** 440 * Create a ScriptFactory bean definition based on the given script definition, 441 * extracting only the definition data that is relevant for the ScriptFactory 442 * (that is, only bean class and constructor arguments). 443 * @param bd the full script bean definition 444 * @return the extracted ScriptFactory bean definition 445 * @see org.springframework.scripting.ScriptFactory 446 */ 447 protected BeanDefinition createScriptFactoryBeanDefinition(BeanDefinition bd) { 448 GenericBeanDefinition scriptBd = new GenericBeanDefinition(); 449 scriptBd.setBeanClassName(bd.getBeanClassName()); 450 scriptBd.getConstructorArgumentValues().addArgumentValues(bd.getConstructorArgumentValues()); 451 return scriptBd; 452 } 453 454 /** 455 * Obtain a ScriptSource for the given bean, lazily creating it 456 * if not cached already. 457 * @param beanName the name of the scripted bean 458 * @param scriptSourceLocator the script source locator associated with the bean 459 * @return the corresponding ScriptSource instance 460 * @see #convertToScriptSource 461 */ 462 protected ScriptSource getScriptSource(String beanName, String scriptSourceLocator) { 463 synchronized (this.scriptSourceCache) { 464 ScriptSource scriptSource = this.scriptSourceCache.get(beanName); 465 if (scriptSource == null) { 466 scriptSource = convertToScriptSource(beanName, scriptSourceLocator, this.resourceLoader); 467 this.scriptSourceCache.put(beanName, scriptSource); 468 } 469 return scriptSource; 470 } 471 } 472 473 /** 474 * Convert the given script source locator to a ScriptSource instance. 475 * <p>By default, supported locators are Spring resource locations 476 * (such as "file:C:/myScript.bsh" or "classpath:myPackage/myScript.bsh") 477 * and inline scripts ("inline:myScriptText..."). 478 * @param beanName the name of the scripted bean 479 * @param scriptSourceLocator the script source locator 480 * @param resourceLoader the ResourceLoader to use (if necessary) 481 * @return the ScriptSource instance 482 */ 483 protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator, 484 ResourceLoader resourceLoader) { 485 486 if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) { 487 return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName); 488 } 489 else { 490 return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator)); 491 } 492 } 493 494 /** 495 * Create a config interface for the given bean definition, defining setter 496 * methods for the defined property values as well as an init method and 497 * a destroy method (if defined). 498 * <p>This implementation creates the interface via CGLIB's InterfaceMaker, 499 * determining the property types from the given interfaces (as far as possible). 500 * @param bd the bean definition (property values etc) to create a 501 * config interface for 502 * @param interfaces the interfaces to check against (might define 503 * getters corresponding to the setters we're supposed to generate) 504 * @return the config interface 505 * @see org.springframework.cglib.proxy.InterfaceMaker 506 * @see org.springframework.beans.BeanUtils#findPropertyType 507 */ 508 protected Class<?> createConfigInterface(BeanDefinition bd, @Nullable Class<?>[] interfaces) { 509 InterfaceMaker maker = new InterfaceMaker(); 510 PropertyValue[] pvs = bd.getPropertyValues().getPropertyValues(); 511 for (PropertyValue pv : pvs) { 512 String propertyName = pv.getName(); 513 Class<?> propertyType = BeanUtils.findPropertyType(propertyName, interfaces); 514 String setterName = "set" + StringUtils.capitalize(propertyName); 515 Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)}); 516 maker.add(signature, new Type[0]); 517 } 518 if (bd.getInitMethodName() != null) { 519 Signature signature = new Signature(bd.getInitMethodName(), Type.VOID_TYPE, new Type[0]); 520 maker.add(signature, new Type[0]); 521 } 522 if (StringUtils.hasText(bd.getDestroyMethodName())) { 523 Signature signature = new Signature(bd.getDestroyMethodName(), Type.VOID_TYPE, new Type[0]); 524 maker.add(signature, new Type[0]); 525 } 526 return maker.create(); 527 } 528 529 /** 530 * Create a composite interface Class for the given interfaces, 531 * implementing the given interfaces in one single Class. 532 * <p>The default implementation builds a JDK proxy class 533 * for the given interfaces. 534 * @param interfaces the interfaces to merge 535 * @return the merged interface as Class 536 * @see java.lang.reflect.Proxy#getProxyClass 537 */ 538 protected Class<?> createCompositeInterface(Class<?>[] interfaces) { 539 return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader); 540 } 541 542 /** 543 * Create a bean definition for the scripted object, based on the given script 544 * definition, extracting the definition data that is relevant for the scripted 545 * object (that is, everything but bean class and constructor arguments). 546 * @param bd the full script bean definition 547 * @param scriptFactoryBeanName the name of the internal ScriptFactory bean 548 * @param scriptSource the ScriptSource for the scripted bean 549 * @param interfaces the interfaces that the scripted bean is supposed to implement 550 * @return the extracted ScriptFactory bean definition 551 * @see org.springframework.scripting.ScriptFactory#getScriptedObject 552 */ 553 protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName, 554 ScriptSource scriptSource, @Nullable Class<?>[] interfaces) { 555 556 GenericBeanDefinition objectBd = new GenericBeanDefinition(bd); 557 objectBd.setFactoryBeanName(scriptFactoryBeanName); 558 objectBd.setFactoryMethodName("getScriptedObject"); 559 objectBd.getConstructorArgumentValues().clear(); 560 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource); 561 objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces); 562 return objectBd; 563 } 564 565 /** 566 * Create a refreshable proxy for the given AOP TargetSource. 567 * @param ts the refreshable TargetSource 568 * @param interfaces the proxy interfaces (may be {@code null} to 569 * indicate proxying of all interfaces implemented by the target class) 570 * @return the generated proxy 571 * @see RefreshableScriptTargetSource 572 */ 573 protected Object createRefreshableProxy(TargetSource ts, @Nullable Class<?>[] interfaces, boolean proxyTargetClass) { 574 ProxyFactory proxyFactory = new ProxyFactory(); 575 proxyFactory.setTargetSource(ts); 576 ClassLoader classLoader = this.beanClassLoader; 577 578 if (interfaces != null) { 579 proxyFactory.setInterfaces(interfaces); 580 } 581 else { 582 Class<?> targetClass = ts.getTargetClass(); 583 if (targetClass != null) { 584 proxyFactory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.beanClassLoader)); 585 } 586 } 587 588 if (proxyTargetClass) { 589 classLoader = null; // force use of Class.getClassLoader() 590 proxyFactory.setProxyTargetClass(true); 591 } 592 593 DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(ts); 594 introduction.suppressInterface(TargetSource.class); 595 proxyFactory.addAdvice(introduction); 596 597 return proxyFactory.getProxy(classLoader); 598 } 599 600 /** 601 * Destroy the inner bean factory (used for scripts) on shutdown. 602 */ 603 @Override 604 public void destroy() { 605 this.scriptBeanFactory.destroySingletons(); 606 } 607 608}