Port GroovyDynamicElementReader to Java
Prior to this commit, GroovyDynamicElementReader was implemented in Groovy, which required that developers have Groovy language support installed and configured in their IDEs. This commit ports GroovyDynamicElementReader from Groovy to Java, making the compilation much simpler and allowing developers to open the project in Eclipse or VSCode (or other IDEs without Groovy support) without compiler errors. This commit also converts related tests from Groovy to Java. Closes gh-27945
This commit is contained in:
parent
912bb16e44
commit
f7c3706361
|
|
@ -1,6 +1,5 @@
|
|||
description = "Spring Beans"
|
||||
|
||||
apply plugin: "groovy"
|
||||
apply plugin: "kotlin"
|
||||
|
||||
dependencies {
|
||||
|
|
@ -14,25 +13,4 @@ dependencies {
|
|||
testImplementation("jakarta.annotation:jakarta.annotation-api")
|
||||
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
|
||||
testFixturesImplementation("org.assertj:assertj-core")
|
||||
}
|
||||
|
||||
// This module does joint compilation for Java and Groovy code with the compileGroovy task.
|
||||
sourceSets {
|
||||
main.groovy.srcDirs += "src/main/java"
|
||||
main.java.srcDirs = []
|
||||
}
|
||||
|
||||
compileGroovy {
|
||||
options.compilerArgs += "-Werror"
|
||||
}
|
||||
|
||||
// This module also builds Kotlin code and the compileKotlin task naturally depends on
|
||||
// compileJava. We need to redefine dependencies to break task cycles.
|
||||
tasks.named('compileGroovy') {
|
||||
// Groovy only needs the declared dependencies (and not the result of Java compilation)
|
||||
classpath = sourceSets.main.compileClasspath
|
||||
}
|
||||
tasks.named('compileKotlin') {
|
||||
// Kotlin also depends on the result of Groovy compilation
|
||||
classpath += files(sourceSets.main.groovy.classesDirectory)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.beans.factory.groovy
|
||||
|
||||
import groovy.xml.StreamingMarkupBuilder
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate
|
||||
import org.w3c.dom.Element
|
||||
|
||||
/**
|
||||
* Used by GroovyBeanDefinitionReader to read a Spring XML namespace expression
|
||||
* in the Groovy DSL.
|
||||
*
|
||||
* @author Jeff Brown
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.0
|
||||
*/
|
||||
@groovy.transform.PackageScope
|
||||
class GroovyDynamicElementReader extends GroovyObjectSupport {
|
||||
|
||||
private final String rootNamespace
|
||||
|
||||
private final Map<String, String> xmlNamespaces
|
||||
|
||||
private final BeanDefinitionParserDelegate delegate
|
||||
|
||||
private final GroovyBeanDefinitionWrapper beanDefinition
|
||||
|
||||
protected final Boolean decorating;
|
||||
|
||||
private boolean callAfterInvocation = true
|
||||
|
||||
|
||||
public GroovyDynamicElementReader(String namespace, Map<String, String> namespaceMap,
|
||||
BeanDefinitionParserDelegate delegate, GroovyBeanDefinitionWrapper beanDefinition, boolean decorating) {
|
||||
super();
|
||||
this.rootNamespace = namespace
|
||||
this.xmlNamespaces = namespaceMap
|
||||
this.delegate = delegate
|
||||
this.beanDefinition = beanDefinition;
|
||||
this.decorating = decorating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object invokeMethod(String name, Object args) {
|
||||
if (name.equals("doCall")) {
|
||||
def callable = args[0]
|
||||
callable.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
callable.delegate = this
|
||||
def result = callable.call()
|
||||
|
||||
if (this.callAfterInvocation) {
|
||||
afterInvocation()
|
||||
this.callAfterInvocation = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
else {
|
||||
StreamingMarkupBuilder builder = new StreamingMarkupBuilder();
|
||||
def myNamespace = this.rootNamespace
|
||||
def myNamespaces = this.xmlNamespaces
|
||||
|
||||
def callable = {
|
||||
for (namespace in myNamespaces) {
|
||||
mkp.declareNamespace([(namespace.key):namespace.value])
|
||||
}
|
||||
if (args && (args[-1] instanceof Closure)) {
|
||||
args[-1].resolveStrategy = Closure.DELEGATE_FIRST
|
||||
args[-1].delegate = builder
|
||||
}
|
||||
delegate."$myNamespace"."$name"(*args)
|
||||
}
|
||||
|
||||
callable.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
callable.delegate = builder
|
||||
def writable = builder.bind(callable)
|
||||
def sw = new StringWriter()
|
||||
writable.writeTo(sw)
|
||||
|
||||
Element element = this.delegate.readerContext.readDocumentFromString(sw.toString()).documentElement
|
||||
this.delegate.initDefaults(element)
|
||||
if (this.decorating) {
|
||||
BeanDefinitionHolder holder = this.beanDefinition.beanDefinitionHolder;
|
||||
holder = this.delegate.decorateIfRequired(element, holder, null)
|
||||
this.beanDefinition.setBeanDefinitionHolder(holder)
|
||||
}
|
||||
else {
|
||||
def beanDefinition = this.delegate.parseCustomElement(element)
|
||||
if (beanDefinition) {
|
||||
this.beanDefinition.setBeanDefinition(beanDefinition)
|
||||
}
|
||||
}
|
||||
if (this.callAfterInvocation) {
|
||||
afterInvocation()
|
||||
this.callAfterInvocation = false
|
||||
}
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that subclass or anonymous classes can overwrite to implement custom behavior
|
||||
* after invocation completes.
|
||||
*/
|
||||
protected void afterInvocation() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -541,9 +541,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
|||
* @param ann the Autowired annotation
|
||||
* @return whether the annotation indicates that a dependency is required
|
||||
*/
|
||||
@SuppressWarnings("cast")
|
||||
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
|
||||
// Cast to (AnnotationAttributes) is required. Otherwise, the :spring-beans:compileGroovy
|
||||
// task fails in the Gradle build.
|
||||
return determineRequiredStatus((AnnotationAttributes)
|
||||
ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.beans.factory.groovy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groovy.lang.GroovyObject;
|
||||
import groovy.lang.GroovyObjectSupport;
|
||||
import groovy.lang.Writable;
|
||||
import groovy.xml.StreamingMarkupBuilder;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
|
||||
|
||||
/**
|
||||
* Used by GroovyBeanDefinitionReader to read a Spring XML namespace expression
|
||||
* in the Groovy DSL.
|
||||
*
|
||||
* @author Jeff Brown
|
||||
* @author Juergen Hoeller
|
||||
* @author Dave Syer
|
||||
* @since 4.0
|
||||
*/
|
||||
class GroovyDynamicElementReader extends GroovyObjectSupport {
|
||||
|
||||
private final String rootNamespace;
|
||||
|
||||
private final Map<String, String> xmlNamespaces;
|
||||
|
||||
private final BeanDefinitionParserDelegate delegate;
|
||||
|
||||
private final GroovyBeanDefinitionWrapper beanDefinition;
|
||||
|
||||
protected final boolean decorating;
|
||||
|
||||
private boolean callAfterInvocation = true;
|
||||
|
||||
|
||||
public GroovyDynamicElementReader(String namespace, Map<String, String> namespaceMap,
|
||||
BeanDefinitionParserDelegate delegate, GroovyBeanDefinitionWrapper beanDefinition, boolean decorating) {
|
||||
super();
|
||||
this.rootNamespace = namespace;
|
||||
this.xmlNamespaces = namespaceMap;
|
||||
this.delegate = delegate;
|
||||
this.beanDefinition = beanDefinition;
|
||||
this.decorating = decorating;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object invokeMethod(String name, Object obj) {
|
||||
Object[] args = (Object[]) obj;
|
||||
if (name.equals("doCall")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Closure<Object> callable = (Closure<Object>) args[0];
|
||||
callable.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||
callable.setDelegate(this);
|
||||
Object result = callable.call();
|
||||
|
||||
if (this.callAfterInvocation) {
|
||||
afterInvocation();
|
||||
this.callAfterInvocation = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
StreamingMarkupBuilder builder = new StreamingMarkupBuilder();
|
||||
String myNamespace = this.rootNamespace;
|
||||
Map<String, String> myNamespaces = this.xmlNamespaces;
|
||||
|
||||
Closure<Object> callable = new Closure<>(this) {
|
||||
@Override
|
||||
public Object call(Object... arguments) {
|
||||
((GroovyObject) getProperty("mkp")).invokeMethod("declareNamespace", new Object[] {myNamespaces});
|
||||
int len = args.length;
|
||||
if (len > 0 && args[len-1] instanceof Closure<?> callable) {
|
||||
callable.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||
callable.setDelegate(builder);
|
||||
}
|
||||
return ((GroovyObject) ((GroovyObject) getDelegate()).getProperty(myNamespace)).invokeMethod(name, args);
|
||||
}
|
||||
};
|
||||
|
||||
callable.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||
callable.setDelegate(builder);
|
||||
Writable writable = (Writable) builder.bind(callable);
|
||||
StringWriter sw = new StringWriter();
|
||||
try {
|
||||
writable.writeTo(sw);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
Element element = this.delegate.getReaderContext().readDocumentFromString(sw.toString()).getDocumentElement();
|
||||
this.delegate.initDefaults(element);
|
||||
if (this.decorating) {
|
||||
BeanDefinitionHolder holder = this.beanDefinition.getBeanDefinitionHolder();
|
||||
holder = this.delegate.decorateIfRequired(element, holder, null);
|
||||
this.beanDefinition.setBeanDefinitionHolder(holder);
|
||||
}
|
||||
else {
|
||||
BeanDefinition beanDefinition = this.delegate.parseCustomElement(element);
|
||||
if (beanDefinition != null) {
|
||||
this.beanDefinition.setBeanDefinition((AbstractBeanDefinition) beanDefinition);
|
||||
}
|
||||
}
|
||||
if (this.callAfterInvocation) {
|
||||
afterInvocation();
|
||||
this.callAfterInvocation = false;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that subclasses or anonymous classes can override to implement custom behavior
|
||||
* after invocation completes.
|
||||
*/
|
||||
protected void afterInvocation() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1935,6 +1935,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private class BeanPostProcessorCacheAwareList extends CopyOnWriteArrayList<BeanPostProcessor> {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
description = "Spring Context"
|
||||
|
||||
apply plugin: "groovy"
|
||||
apply plugin: "kotlin"
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -14,14 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.groovy
|
||||
package org.springframework.context.groovy;
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException
|
||||
import org.springframework.context.support.GenericGroovyApplicationContext
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.support.GenericGroovyApplicationContext;
|
||||
|
||||
import static groovy.test.GroovyAssert.*
|
||||
import static groovy.test.GroovyAssert.assertEquals;
|
||||
import static groovy.test.GroovyAssert.assertNotNull;
|
||||
import static groovy.test.GroovyAssert.assertThrows;
|
||||
|
||||
/**
|
||||
* @author Jeff Brown
|
||||
|
|
@ -31,24 +33,26 @@ class GroovyApplicationContextDynamicBeanPropertyTests {
|
|||
|
||||
@Test
|
||||
void testAccessDynamicBeanProperties() {
|
||||
def ctx = new GenericGroovyApplicationContext();
|
||||
ctx.reader.loadBeanDefinitions("org/springframework/context/groovy/applicationContext.groovy");
|
||||
ctx.refresh()
|
||||
var ctx = new GenericGroovyApplicationContext();
|
||||
ctx.getReader().loadBeanDefinitions("org/springframework/context/groovy/applicationContext.groovy");
|
||||
ctx.refresh();
|
||||
|
||||
def framework = ctx.framework
|
||||
assertNotNull 'could not find framework bean', framework
|
||||
assertEquals 'Grails', framework
|
||||
var framework = ctx.getProperty("framework");
|
||||
assertNotNull("could not find framework bean", framework);
|
||||
assertEquals("Grails", framework);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAccessingNonExistentBeanViaDynamicProperty() {
|
||||
def ctx = new GenericGroovyApplicationContext();
|
||||
ctx.reader.loadBeanDefinitions("org/springframework/context/groovy/applicationContext.groovy");
|
||||
ctx.refresh()
|
||||
var ctx = new GenericGroovyApplicationContext();
|
||||
ctx.getReader().loadBeanDefinitions("org/springframework/context/groovy/applicationContext.groovy");
|
||||
ctx.refresh();
|
||||
|
||||
def err = shouldFail NoSuchBeanDefinitionException, { ctx.someNonExistentBean }
|
||||
var err = assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getProperty("someNonExistentBean"));
|
||||
|
||||
assertEquals "No bean named 'someNonExistentBean' available", err.message
|
||||
assertEquals("No bean named 'someNonExistentBean' available", err.getMessage());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue