SPR-6268: Add proxy-target-class to <lang:groovy/>

This commit is contained in:
David Syer 2011-06-17 12:14:01 +00:00
parent 5bfeb34b89
commit 64fd0b081d
11 changed files with 305 additions and 120 deletions

View File

@ -71,8 +71,9 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref";
private static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class";
private static final String CUSTOMIZER_REF_ATTRIBUTE = "customizer-ref";
/**
* The {@link org.springframework.scripting.ScriptFactory} class that this
@ -80,7 +81,6 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
*/
private final String scriptFactoryClassName;
/**
* Create a new instance of this parser, creating bean definitions for the
* supplied {@link org.springframework.scripting.ScriptFactory} class.
@ -90,7 +90,6 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
this.scriptFactoryClassName = scriptFactoryClassName;
}
/**
* Parses the dynamic object element and returns the resulting bean definition.
* Registers a {@link ScriptFactoryPostProcessor} if needed.
@ -110,6 +109,7 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClassName(this.scriptFactoryClassName);
bd.setSource(parserContext.extractSource(element));
bd.setAttribute(ScriptFactoryPostProcessor.LANGUAGE_ATTRIBUTE, element.getLocalName());
// Determine bean scope.
String scope = element.getAttribute(SCOPE_ATTRIBUTE);
@ -123,8 +123,7 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
// Only "byType" and "byName" supported, but maybe other default inherited...
if (autowireMode == GenericBeanDefinition.AUTOWIRE_AUTODETECT) {
autowireMode = GenericBeanDefinition.AUTOWIRE_BY_TYPE;
}
else if (autowireMode == GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR) {
} else if (autowireMode == GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR) {
autowireMode = GenericBeanDefinition.AUTOWIRE_NO;
}
bd.setAutowireMode(autowireMode);
@ -134,31 +133,34 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
bd.setDependencyCheck(parserContext.getDelegate().getDependencyCheck(dependencyCheck));
// Retrieve the defaults for bean definitions within this parser context
BeanDefinitionDefaults beanDefinitionDefaults =
parserContext.getDelegate().getBeanDefinitionDefaults();
BeanDefinitionDefaults beanDefinitionDefaults = parserContext.getDelegate().getBeanDefinitionDefaults();
// Determine init method and destroy method.
String initMethod = element.getAttribute(INIT_METHOD_ATTRIBUTE);
if (StringUtils.hasLength(initMethod)) {
bd.setInitMethodName(initMethod);
}
else if (beanDefinitionDefaults.getInitMethodName() != null) {
} else if (beanDefinitionDefaults.getInitMethodName() != null) {
bd.setInitMethodName(beanDefinitionDefaults.getInitMethodName());
}
String destroyMethod = element.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (StringUtils.hasLength(destroyMethod)) {
bd.setDestroyMethodName(destroyMethod);
}
else if (beanDefinitionDefaults.getDestroyMethodName() != null) {
} else if (beanDefinitionDefaults.getDestroyMethodName() != null) {
bd.setDestroyMethodName(beanDefinitionDefaults.getDestroyMethodName());
}
// Attach any refresh metadata.
String refreshCheckDelay = element.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
if (StringUtils.hasText(refreshCheckDelay)) {
bd.setAttribute(
ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, new Long(refreshCheckDelay));
bd.setAttribute(ScriptFactoryPostProcessor.REFRESH_CHECK_DELAY_ATTRIBUTE, new Long(refreshCheckDelay));
}
// Attach any proxy target class metadata.
String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
if (StringUtils.hasText(proxyTargetClass)) {
Boolean flag = new Boolean(proxyTargetClass);
bd.setAttribute(ScriptFactoryPostProcessor.PROXY_TARGET_CLASS_ATTRIBUTE, flag);
}
// Add constructor arguments.
@ -174,8 +176,7 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
String customizerBeanName = element.getAttribute(CUSTOMIZER_REF_ATTRIBUTE);
if (!StringUtils.hasText(customizerBeanName)) {
parserContext.getReaderContext().error("Attribute 'customizer-ref' has empty value", element);
}
else {
} else {
cav.addIndexedArgumentValue(constructorArgNum++, new RuntimeBeanReference(customizerBeanName));
}
}
@ -197,15 +198,12 @@ class ScriptBeanDefinitionParser extends AbstractBeanDefinitionParser {
if (hasScriptSource && !elements.isEmpty()) {
readerContext.error("Only one of 'script-source' and 'inline-script' should be specified.", element);
return null;
}
else if (hasScriptSource) {
} else if (hasScriptSource) {
return element.getAttribute(SCRIPT_SOURCE_ATTRIBUTE);
}
else if (!elements.isEmpty()) {
} else if (!elements.isEmpty()) {
Element inlineElement = (Element) elements.get(0);
return "inline:" + DomUtils.getTextValue(inlineElement);
}
else {
} else {
readerContext.error("Must specify either 'script-source' or 'inline-script'.", element);
return null;
}

View File

@ -19,6 +19,7 @@ package org.springframework.scripting.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
@ -31,6 +32,8 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser {
private static final String REFRESH_CHECK_DELAY_ATTRIBUTE = "refresh-check-delay";
private static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class";
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinition bd =
@ -39,6 +42,10 @@ public class ScriptingDefaultsParser implements BeanDefinitionParser {
if (StringUtils.hasText(refreshCheckDelay)) {
bd.getPropertyValues().add("defaultRefreshCheckDelay", new Long(refreshCheckDelay));
}
String proxyTargetClass = element.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
if (StringUtils.hasText(proxyTargetClass)) {
bd.getPropertyValues().add("defaultProxyTargetClass", new TypedStringValue(proxyTargetClass, Boolean.class));
}
return null;
}

View File

@ -45,6 +45,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ResourceLoaderAware;
@ -135,8 +136,8 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @since 2.0
*/
public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered {
public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements
BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware, DisposableBean, Ordered {
/**
* The {@link org.springframework.core.io.Resource}-style prefix that denotes
@ -146,19 +147,26 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
*/
public static final String INLINE_SCRIPT_PREFIX = "inline:";
public static final String REFRESH_CHECK_DELAY_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ScriptFactoryPostProcessor.class, "refreshCheckDelay");
public static final String REFRESH_CHECK_DELAY_ATTRIBUTE = Conventions.getQualifiedAttributeName(
ScriptFactoryPostProcessor.class, "refreshCheckDelay");
public static final String PROXY_TARGET_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(
ScriptFactoryPostProcessor.class, "proxyTargetClass");
public static final String LANGUAGE_ATTRIBUTE = Conventions.getQualifiedAttributeName(
ScriptFactoryPostProcessor.class, "language");
private static final String SCRIPT_FACTORY_NAME_PREFIX = "scriptFactory.";
private static final String SCRIPTED_OBJECT_NAME_PREFIX = "scriptedObject.";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private long defaultRefreshCheckDelay = -1;
private boolean defaultProxyTargetClass = false;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private ConfigurableBeanFactory beanFactory;
@ -170,7 +178,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
/** Map from bean name String to ScriptSource object */
private final Map<String, ScriptSource> scriptSourceCache = new HashMap<String, ScriptSource>();
/**
* Set the delay between refresh checks, in milliseconds.
* Default is -1, indicating no refresh checks at all.
@ -183,14 +190,22 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
this.defaultRefreshCheckDelay = defaultRefreshCheckDelay;
}
/**
* Flag to signal that refreshable proxies should be created to proxy the target class not its interfaces.
* @param defaultProxyTargetClass the flag value to set
*/
public void setDefaultProxyTargetClass(boolean defaultProxyTargetClass) {
this.defaultProxyTargetClass = defaultProxyTargetClass;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with a BeanFactory " +
"which does not implement ConfigurableBeanFactory: " + beanFactory.getClass());
throw new IllegalStateException("ScriptFactoryPostProcessor doesn't work with a BeanFactory "
+ "which does not implement ConfigurableBeanFactory: " + beanFactory.getClass());
}
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
@ -217,7 +232,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
return Integer.MIN_VALUE;
}
@Override
public Class predictBeanType(Class beanClass, String beanName) {
// We only apply special treatment to ScriptFactory implementations here.
@ -233,18 +247,15 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
ScriptSource scriptSource =
getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
Class[] interfaces = scriptFactory.getScriptInterfaces();
Class scriptedType = scriptFactory.getScriptedObjectType(scriptSource);
if (scriptedType != null) {
return scriptedType;
}
else if (!ObjectUtils.isEmpty(interfaces)) {
} else if (!ObjectUtils.isEmpty(interfaces)) {
return (interfaces.length == 1 ? interfaces[0] : createCompositeInterface(interfaces));
}
else {
} else {
if (bd.isSingleton()) {
Object bean = this.scriptBeanFactory.getBean(scriptedObjectBeanName);
if (bean != null) {
@ -252,15 +263,14 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
}
}
}
}
catch (Exception ex) {
if (ex instanceof BeanCreationException &&
((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) {
} catch (Exception ex) {
if (ex instanceof BeanCreationException
&& ((BeanCreationException) ex).getMostSpecificCause() instanceof BeanCurrentlyInCreationException) {
if (logger.isTraceEnabled()) {
logger.trace("Could not determine scripted object type for bean '" + beanName + "': " + ex.getMessage());
logger.trace("Could not determine scripted object type for bean '" + beanName + "': "
+ ex.getMessage());
}
}
else {
} else {
if (logger.isDebugEnabled()) {
logger.debug("Could not determine scripted object type for bean '" + beanName + "'", ex);
}
@ -283,8 +293,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
ScriptSource scriptSource =
getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
boolean isFactoryBean = false;
try {
Class scriptedObjectType = scriptFactory.getScriptedObjectType(scriptSource);
@ -292,19 +301,25 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
if (scriptedObjectType != null) {
isFactoryBean = FactoryBean.class.isAssignableFrom(scriptedObjectType);
}
}
catch (Exception ex) {
throw new BeanCreationException(
beanName, "Could not determine scripted object type for " + scriptFactory, ex);
} catch (Exception ex) {
throw new BeanCreationException(beanName, "Could not determine scripted object type for " + scriptFactory,
ex);
}
long refreshCheckDelay = resolveRefreshCheckDelay(bd);
if (refreshCheckDelay >= 0) {
Class[] interfaces = scriptFactory.getScriptInterfaces();
RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(
this.scriptBeanFactory, scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean);
RefreshableScriptTargetSource ts = new RefreshableScriptTargetSource(this.scriptBeanFactory,
scriptedObjectBeanName, scriptFactory, scriptSource, isFactoryBean);
boolean proxyTargetClass = resolveProxyTargetClass(bd);
String language = (String) bd.getAttribute(LANGUAGE_ATTRIBUTE);
if (proxyTargetClass && (language==null || !language.equals("groovy"))) {
throw new BeanDefinitionValidationException(
"Cannot use proxyTargetClass=true with script beans where language is not groovy (found "
+ language + ")");
}
ts.setRefreshCheckDelay(refreshCheckDelay);
return createRefreshableProxy(ts, interfaces);
return createRefreshableProxy(ts, interfaces, proxyTargetClass);
}
if (isFactoryBean) {
@ -313,7 +328,6 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
return this.scriptBeanFactory.getBean(scriptedObjectBeanName);
}
/**
* Prepare the script beans in the internal BeanFactory that this
* post-processor uses. Each original bean definition will be split
@ -322,18 +336,18 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* @param scriptFactoryBeanName the name of the internal ScriptFactory bean
* @param scriptedObjectBeanName the name of the internal scripted object bean
*/
protected void prepareScriptBeans(
BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) {
protected void prepareScriptBeans(BeanDefinition bd, String scriptFactoryBeanName, String scriptedObjectBeanName) {
// Avoid recreation of the script bean definition in case of a prototype.
synchronized (this.scriptBeanFactory) {
if (!this.scriptBeanFactory.containsBeanDefinition(scriptedObjectBeanName)) {
this.scriptBeanFactory.registerBeanDefinition(
scriptFactoryBeanName, createScriptFactoryBeanDefinition(bd));
ScriptFactory scriptFactory = this.scriptBeanFactory.getBean(scriptFactoryBeanName, ScriptFactory.class);
ScriptSource scriptSource =
getScriptSource(scriptFactoryBeanName, scriptFactory.getScriptSourceLocator());
this.scriptBeanFactory.registerBeanDefinition(scriptFactoryBeanName,
createScriptFactoryBeanDefinition(bd));
ScriptFactory scriptFactory = this.scriptBeanFactory
.getBean(scriptFactoryBeanName, ScriptFactory.class);
ScriptSource scriptSource = getScriptSource(scriptFactoryBeanName,
scriptFactory.getScriptSourceLocator());
Class<?>[] interfaces = scriptFactory.getScriptInterfaces();
Class<?>[] scriptedInterfaces = interfaces;
@ -342,8 +356,8 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
scriptedInterfaces = ObjectUtils.addObjectToArray(interfaces, configInterface);
}
BeanDefinition objectBd = createScriptedObjectBeanDefinition(
bd, scriptFactoryBeanName, scriptSource, scriptedInterfaces);
BeanDefinition objectBd = createScriptedObjectBeanDefinition(bd, scriptFactoryBeanName, scriptSource,
scriptedInterfaces);
long refreshCheckDelay = resolveRefreshCheckDelay(bd);
if (refreshCheckDelay >= 0) {
objectBd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
@ -369,18 +383,31 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
Object attributeValue = beanDefinition.getAttribute(REFRESH_CHECK_DELAY_ATTRIBUTE);
if (attributeValue instanceof Number) {
refreshCheckDelay = ((Number) attributeValue).longValue();
}
else if (attributeValue instanceof String) {
} else if (attributeValue instanceof String) {
refreshCheckDelay = Long.parseLong((String) attributeValue);
}
else if (attributeValue != null) {
throw new BeanDefinitionStoreException(
"Invalid refresh check delay attribute [" + REFRESH_CHECK_DELAY_ATTRIBUTE +
"] with value [" + attributeValue + "]: needs to be of type Number or String");
} else if (attributeValue != null) {
throw new BeanDefinitionStoreException("Invalid refresh check delay attribute ["
+ REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value [" + attributeValue
+ "]: needs to be of type Number or String");
}
return refreshCheckDelay;
}
protected boolean resolveProxyTargetClass(BeanDefinition beanDefinition) {
boolean proxyTargetClass = this.defaultProxyTargetClass;
Object attributeValue = beanDefinition.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE);
if (attributeValue instanceof Boolean) {
proxyTargetClass = ((Boolean) attributeValue).booleanValue();
} else if (attributeValue instanceof String) {
proxyTargetClass = new Boolean((String) attributeValue);
} else if (attributeValue != null) {
throw new BeanDefinitionStoreException("Invalid refresh check delay attribute ["
+ REFRESH_CHECK_DELAY_ATTRIBUTE + "] with value [" + attributeValue
+ "]: needs to be of type Number or String");
}
return proxyTargetClass;
}
/**
* Create a ScriptFactory bean definition based on the given script definition,
* extracting only the definition data that is relevant for the ScriptFactory
@ -425,13 +452,12 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* @param resourceLoader the ResourceLoader to use (if necessary)
* @return the ScriptSource instance
*/
protected ScriptSource convertToScriptSource(
String beanName, String scriptSourceLocator, ResourceLoader resourceLoader) {
protected ScriptSource convertToScriptSource(String beanName, String scriptSourceLocator,
ResourceLoader resourceLoader) {
if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) {
return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName);
}
else {
} else {
return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator));
}
}
@ -457,7 +483,7 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
String propertyName = pv.getName();
Class propertyType = BeanUtils.findPropertyType(propertyName, interfaces);
String setterName = "set" + StringUtils.capitalize(propertyName);
Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] {Type.getType(propertyType)});
Signature signature = new Signature(setterName, Type.VOID_TYPE, new Type[] { Type.getType(propertyType) });
maker.add(signature, new Type[0]);
}
if (bd instanceof AbstractBeanDefinition) {
@ -498,8 +524,8 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* @return the extracted ScriptFactory bean definition
* @see org.springframework.scripting.ScriptFactory#getScriptedObject
*/
protected BeanDefinition createScriptedObjectBeanDefinition(
BeanDefinition bd, String scriptFactoryBeanName, ScriptSource scriptSource, Class[] interfaces) {
protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName,
ScriptSource scriptSource, Class[] interfaces) {
GenericBeanDefinition objectBd = new GenericBeanDefinition(bd);
objectBd.setFactoryBeanName(scriptFactoryBeanName);
@ -518,23 +544,27 @@ public class ScriptFactoryPostProcessor extends InstantiationAwareBeanPostProces
* @return the generated proxy
* @see RefreshableScriptTargetSource
*/
protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces) {
protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces, boolean proxyTargetClass) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(ts);
ClassLoader classLoader = this.beanClassLoader;
if (interfaces == null) {
interfaces = ClassUtils.getAllInterfacesForClass(ts.getTargetClass(), this.beanClassLoader);
}
proxyFactory.setInterfaces(interfaces);
if (proxyTargetClass) {
classLoader = null; // Force use of Class.getClassLoader()
proxyFactory.setProxyTargetClass(proxyTargetClass);
}
DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(ts);
introduction.suppressInterface(TargetSource.class);
proxyFactory.addAdvice(introduction);
return proxyFactory.getProxy(this.beanClassLoader);
return proxyFactory.getProxy(classLoader);
}
/**
* Destroy the inner bean factory (used for scripts) on shutdown.
*/

View File

@ -30,28 +30,49 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="groovy" type="customizableScriptType">
<xsd:element name="groovy">
<xsd:annotation>
<xsd:documentation><![CDATA[
A Spring bean backed by a Groovy class definition.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="customizableScriptType">
<xsd:attributeGroup ref="defaultableAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="jruby" type="dynamicScriptType">
<xsd:element name="jruby">
<xsd:annotation>
<xsd:documentation><![CDATA[
A Spring bean backed by a JRuby class definition.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="dynamicScriptType">
<xsd:attributeGroup ref="vanillaScriptAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="bsh" type="dynamicScriptType">
<xsd:element name="bsh">
<xsd:annotation>
<xsd:documentation><![CDATA[
A Spring bean backed by a BeanShell script.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="dynamicScriptType">
<xsd:attributeGroup ref="vanillaScriptAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<!-- Script Types -->
@ -77,7 +98,6 @@
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attributeGroup ref="defaultableAttributes"/>
<xsd:attribute name="script-source" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.io.Resource"><![CDATA[
@ -185,7 +205,7 @@
</xsd:complexContent>
</xsd:complexType>
<xsd:attributeGroup name="defaultableAttributes">
<xsd:attributeGroup name="vanillaScriptAttributes">
<xsd:attribute name="refresh-check-delay" type="xsd:long">
<xsd:annotation>
<xsd:documentation><![CDATA[
@ -196,4 +216,18 @@
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="defaultableAttributes">
<xsd:attribute name="proxy-target-class" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag to tell the bean factory that if this bean is proxied it should be done using the target class type,
not its interfaces. A refreshable script is normally proxied, so often this is useful in conjunction with
refresh-check-delay. Defaults to false requiring no additional library dependencies, but hiding behaviour in the
bean that is not defined in an interface.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="vanillaScriptAttributes"></xsd:attributeGroup>
</xsd:attributeGroup>
</xsd:schema>

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Field;
import junit.framework.TestCase;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.dynamic.AbstractRefreshableTargetSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@ -32,7 +33,10 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ScriptingDefaultsTests extends TestCase {
private static final String CONFIG =
"org/springframework/scripting/config/scriptingDefaultsTests.xml";
"org/springframework/scripting/config/scriptingDefaultsTests.xml";
private static final String PROXY_CONFIG =
"org/springframework/scripting/config/scriptingDefaultsProxyTargetClassTests.xml";
public void testDefaultRefreshCheckDelay() throws Exception {
@ -73,4 +77,10 @@ public class ScriptingDefaultsTests extends TestCase {
assertEquals(otherBean, testBean.getOtherBean());
}
public void testDefaultProxyTargetClass() {
ApplicationContext context = new ClassPathXmlApplicationContext(PROXY_CONFIG);
Object testBean = context.getBean("testBean");
assertTrue(AopUtils.isCglibProxy(testBean));
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2002-2011 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.scripting.groovy;
import org.springframework.scripting.ConfigurableMessenger;
/**
* @author Dave Syer
*
*/
public class ConcreteMessenger implements ConfigurableMessenger {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -16,7 +16,13 @@
package org.springframework.scripting.groovy;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.GroovyObject;
@ -178,8 +184,7 @@ public class GroovyScriptFactoryTests {
try {
new ClassPathXmlApplicationContext("org/springframework/scripting/groovy/groovyBrokenContext.xml");
fail("Should throw exception for broken script file");
}
catch (NestedRuntimeException ex) {
} catch (NestedRuntimeException ex) {
assertTrue("Wrong root cause: " + ex, ex.contains(ScriptCompilationException.class));
}
}
@ -194,12 +199,12 @@ public class GroovyScriptFactoryTests {
script.suggestedClassName();
mock.setReturnValue("someName");
mock.replay();
GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + badScript);
GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX
+ badScript);
try {
factory.getScriptedObject(script, new Class[]{});
factory.getScriptedObject(script, new Class[] {});
fail("Must have thrown a ScriptCompilationException (no public no-arg ctor in scripted class).");
}
catch (ScriptCompilationException expected) {
} catch (ScriptCompilationException expected) {
assertTrue(expected.contains(InstantiationException.class));
}
mock.verify();
@ -215,12 +220,12 @@ public class GroovyScriptFactoryTests {
script.suggestedClassName();
mock.setReturnValue("someName");
mock.replay();
GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX + badScript);
GroovyScriptFactory factory = new GroovyScriptFactory(ScriptFactoryPostProcessor.INLINE_SCRIPT_PREFIX
+ badScript);
try {
factory.getScriptedObject(script, new Class[]{});
factory.getScriptedObject(script, new Class[] {});
fail("Must have thrown a ScriptCompilationException (no oublic no-arg ctor in scripted class).");
}
catch (ScriptCompilationException expected) {
} catch (ScriptCompilationException expected) {
assertTrue(expected.contains(IllegalAccessException.class));
}
mock.verify();
@ -254,8 +259,7 @@ public class GroovyScriptFactoryTests {
try {
new GroovyScriptFactory(null);
fail("Must have thrown exception by this point.");
}
catch (IllegalArgumentException expected) {
} catch (IllegalArgumentException expected) {
}
}
@ -264,8 +268,7 @@ public class GroovyScriptFactoryTests {
try {
new GroovyScriptFactory("");
fail("Must have thrown exception by this point.");
}
catch (IllegalArgumentException expected) {
} catch (IllegalArgumentException expected) {
}
}
@ -274,8 +277,7 @@ public class GroovyScriptFactoryTests {
try {
new GroovyScriptFactory("\n ");
fail("Must have thrown exception by this point.");
}
catch (IllegalArgumentException expected) {
} catch (IllegalArgumentException expected) {
}
}
@ -284,8 +286,7 @@ public class GroovyScriptFactoryTests {
try {
new ClassPathXmlApplicationContext("lwspBadGroovyContext.xml", getClass());
fail("Must have thrown a BeanCreationException ('inline:' prefix was preceded by whitespace");
}
catch (BeanCreationException expected) {
} catch (BeanCreationException expected) {
assertTrue(expected.contains(FileNotFoundException.class));
}
}
@ -312,12 +313,12 @@ public class GroovyScriptFactoryTests {
try {
factory.getScriptedObject(null, null);
fail("Must have thrown a NullPointerException as per contract ('null' ScriptSource supplied");
}
catch (NullPointerException expected) {
} catch (NullPointerException expected) {
}
}
@Ignore // see http://build.springframework.org/browse/SPR-TRUNKQUICK-908
@Ignore
// see http://build.springframework.org/browse/SPR-TRUNKQUICK-908
@Test
public void testResourceScriptFromTag() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass());
@ -375,6 +376,32 @@ public class GroovyScriptFactoryTests {
assertTrue(ctx.getBeansOfType(Messenger.class).values().contains(messenger));
}
@Test
// Test for SPR-6268
public void testRefreshableFromTagProxyTargetClass() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd-proxy-target-class.xml",
getClass());
assertTrue(Arrays.asList(ctx.getBeanNamesForType(Messenger.class)).contains("refreshableMessenger"));
Messenger messenger = (Messenger) ctx.getBean("refreshableMessenger");
assertTrue(AopUtils.isAopProxy(messenger));
assertTrue(messenger instanceof Refreshable);
assertEquals("Hello World!", messenger.getMessage());
assertTrue(ctx.getBeansOfType(ConcreteMessenger.class).values().contains(messenger));
}
@Test
// Test for SPR-6268
public void testProxyTargetClassNotAllowedIfNotGroovy() throws Exception {
try {
new ClassPathXmlApplicationContext("jruby-with-xsd-proxy-target-class.xml", getClass());
} catch (BeanCreationException e) {
assertTrue(e.getMessage().contains("Cannot use proxyTargetClass=true"));
}
}
@Test
public void testAnonymousScriptDetected() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("groovy-with-xsd.xml", getClass());
@ -404,8 +431,7 @@ public class GroovyScriptFactoryTests {
try {
ctx.getBean("bean3");
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
} catch (BeanCreationException ex) {
// expected
assertTrue(ex.contains(UnsatisfiedDependencyException.class));
}
@ -424,8 +450,7 @@ public class GroovyScriptFactoryTests {
private void testMetaClass(final String xmlFile) {
// expect the exception we threw in the custom metaclass to show it got invoked
try {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(xmlFile);
ApplicationContext ctx = new ClassPathXmlApplicationContext(xmlFile);
Calculator calc = (Calculator) ctx.getBean("delegatingCalculator");
calc.add(1, 2);
fail("expected IllegalStateException");
@ -454,7 +479,6 @@ public class GroovyScriptFactoryTests {
assertEquals("test", result);
}
public static class TestCustomizer implements GroovyObjectCustomizer {
public void customize(GroovyObject goo) {
@ -462,8 +486,7 @@ public class GroovyScriptFactoryTests {
public Object invokeMethod(Object arg0, String mName, Object[] arg2) {
if (mName.indexOf("Missing") != -1) {
throw new IllegalStateException("Gotcha");
}
else {
} else {
return super.invokeMethod(arg0, mName, arg2);
}
}

View File

@ -2,7 +2,5 @@ package org.springframework.scripting.groovy;
import org.springframework.scripting.ConfigurableMessenger
class GroovyMessenger implements ConfigurableMessenger {
def String message;
class GroovyMessenger extends ConcreteMessenger {
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.1.xsd">
<lang:groovy id="refreshableMessenger" refresh-check-delay="5000" proxy-target-class="true"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Hello World!" />
</lang:groovy>
</beans>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.1.xsd">
<lang:defaults proxy-target-class="true"/>
<lang:jruby id="refreshableMessenger" refresh-check-delay="1000"
script-source="classpath:org/springframework/scripting/jruby/Messenger.rb"
script-interfaces="org.springframework.scripting.Messenger">
<lang:property name="message" value="Hello World!"/>
</lang:jruby>
</beans>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang-3.1.xsd"
default-autowire="byName"
default-init-method="startup"
default-destroy-method="shutdown">
<lang:defaults refresh-check-delay="5000" proxy-target-class="true"/>
<lang:groovy id="testBean" name="/url" script-source="classpath:org/springframework/scripting/config/TestBean.groovy"/>
</beans>