Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions grails-doc/src/en/guide/introduction/whatsNew.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ This brings the Spring Boot 4 modular artifact layout, Spring Framework 7 API re

The Grails 8 upgrade guide calls out the major application-impacting changes and links to the Spring Boot 4.0 migration guide, Spring Boot 4.1 release notes and Spring Framework 7.0 release notes.

Grails 8 keeps the `resources.groovy` BeanBuilder DSL backward compatible on Spring Framework 7.
The standard BeanBuilder DSL path registers programmatic Spring `BeanDefinition` instances and does not initialize Spring XML infrastructure unless an application explicitly imports XML bean definitions or uses Spring XML namespace DSL support through `xmlns`.
The `resources.xml` file and BeanBuilder namespace support remain available for compatibility, but `resources.groovy` is the recommended path for custom bean definitions.

==== GSP Tag Library Improvements

Grails {grailsMajorVersion} continues the move toward method-based TagLib handlers while preserving compatibility with existing closure-based tags.
Expand Down
3 changes: 3 additions & 0 deletions grails-doc/src/en/guide/spring/springdslAdditional.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ NOTE: If you define a bean in `resources.groovy` with the same name as one previ

Beans can also be configured using a `grails-app/conf/spring/resources.xml`. In earlier versions of Grails this file was automatically generated for you by the `run-app` script, but the DSL in `resources.groovy` is the preferred approach now so it isn't automatically generated now. But it is still supported - you just need to create it yourself.

In Grails 8, `resources.xml` remains supported for backward compatibility on Spring Framework 7.
For new bean definitions, prefer `resources.groovy`; BeanBuilder registers standard Spring bean definitions programmatically and only initializes Spring XML infrastructure when XML imports or Spring XML namespaces are used.

This file is typical Spring XML file and the Spring documentation has an {springreference}core/beans/introduction.html[excellent reference] on how to configure Spring beans.

The `myBean` bean that we configured using the DSL would be configured with this syntax in the XML file:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,10 @@ In this example we create an abstract bean of type `KnightOfTheRoundTable` and u

Since Spring 2.0, users of Spring have had easier access to key features via XML namespaces. You can use a Spring namespace in BeanBuilder by declaring it with this syntax:

Spring namespace support in BeanBuilder is a compatibility feature backed by Spring's XML namespace handlers.
BeanBuilder only initializes that XML namespace infrastructure when `xmlns` is used.
For ordinary bean definitions in `resources.groovy`, BeanBuilder registers programmatic Spring bean definitions without using Spring XML configuration.

[source,groovy]
----
xmlns context:"https://www.springframework.org/schema/context"
Expand Down
8 changes: 8 additions & 0 deletions grails-doc/src/en/guide/upgrading/upgrading80x.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ For full details, consult:
3. https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.1-Release-Notes[Spring Boot 4.1 Release Notes]
4. https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes[Spring Framework 7.0 Release Notes]

===== BeanBuilder and Spring XML configuration

No application changes are required for existing `grails-app/conf/spring/resources.groovy` BeanBuilder DSL files.
Grails 8 keeps that DSL backed by programmatic Spring bean definitions and avoids Spring XML infrastructure unless an application explicitly uses `importBeans` for XML resources or Spring XML namespace DSL support through `xmlns`.

Existing `grails-app/conf/spring/resources.xml` files remain supported for compatibility.
For new custom bean definitions, prefer `resources.groovy` so applications stay on Grails' programmatic bean registration path.

==== 3. Spring Boot Autoconfigure Modularization

Spring Boot 4 split the monolithic `spring-boot-autoconfigure` module into domain-specific modules.
Expand Down
3 changes: 2 additions & 1 deletion grails-spring/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ dependencies {

apply {
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
}
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
}
46 changes: 18 additions & 28 deletions grails-spring/src/main/groovy/grails/spring/BeanBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,14 @@
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.EmptyReaderEventListener;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.NullSourceExtractor;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ByteArrayResource;
Expand Down Expand Up @@ -121,13 +112,11 @@ public class BeanBuilder extends GroovyObjectSupport {
private ApplicationContext parentCtx;
private Map<String, Object> binding = Collections.emptyMap();
private ClassLoader classLoader = null;
private NamespaceHandlerResolver namespaceHandlerResolver;
private Map<String, NamespaceHandler> namespaceHandlers = new HashMap<>();
private XmlBeanDefinitionReader xmlBeanDefinitionReader;
private Map<String, String> namespaces = new HashMap<>();
private Resource beanBuildResource = new ByteArrayResource(new byte[0]);
private XmlReaderContext readerContext;
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private BeanBuilderXmlSupport xmlSupport;

public BeanBuilder() {
this(null, null);
Expand Down Expand Up @@ -158,24 +147,22 @@ public void setResourcePatternResolver(ResourcePatternResolver resourcePatternRe
}

protected void initializeSpringConfig() {
xmlBeanDefinitionReader = new XmlBeanDefinitionReader((GenericApplicationContext) springConfig.getUnrefreshedApplicationContext());
initializeBeanBuilderForClassLoader(classLoader);
xmlSupport = null;
}

public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader == null ? getClass().getClassLoader() : classLoader;
initializeBeanBuilderForClassLoader(classLoader);
if (xmlSupport != null) {
xmlSupport.setClassLoader(this.classLoader);
}
}

protected void initializeBeanBuilderForClassLoader(ClassLoader classLoader) {
xmlBeanDefinitionReader.setBeanClassLoader(classLoader);
namespaceHandlerResolver = new DefaultNamespaceHandlerResolver(this.classLoader);
readerContext = new XmlReaderContext(beanBuildResource, new FailFastProblemReporter(), new EmptyReaderEventListener(),
new NullSourceExtractor(), xmlBeanDefinitionReader, namespaceHandlerResolver);
getXmlSupport().setClassLoader(classLoader);
}

public void setNamespaceHandlerResolver(NamespaceHandlerResolver namespaceHandlerResolver) {
this.namespaceHandlerResolver = namespaceHandlerResolver;
getXmlSupport().setNamespaceHandlerResolver(namespaceHandlerResolver);
}

protected RuntimeSpringConfiguration createRuntimeSpringConfiguration(ApplicationContext parent, ClassLoader cl) {
Expand Down Expand Up @@ -207,9 +194,7 @@ public void importBeans(Resource resource) {
loadBeans(resource);
}
else if (resource.getFilename().endsWith(".xml")) {
SimpleBeanDefinitionRegistry beanRegistry = new SimpleBeanDefinitionRegistry();
XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(beanRegistry);
beanReader.loadBeanDefinitions(resource);
BeanDefinitionRegistry beanRegistry = getXmlSupport().loadBeanDefinitions(resource);
String[] beanNames = beanRegistry.getBeanDefinitionNames();
for (String beanName : beanNames) {
springConfig.addBeanDefinition(beanName, beanRegistry.getBeanDefinition(beanName));
Expand All @@ -223,7 +208,6 @@ else if (resource.getFilename().endsWith(".xml")) {
* @param definition The definition
*/
public void xmlns(Map<String, String> definition) {
Assert.notNull(namespaceHandlerResolver, "You cannot define a Spring namespace without a [namespaceHandlerResolver] set");
if (definition.isEmpty()) {
return;
}
Expand All @@ -234,11 +218,11 @@ public void xmlns(Map<String, String> definition) {

Assert.notNull(uri, "Namespace definition cannot supply a null URI");

final NamespaceHandler namespaceHandler = namespaceHandlerResolver.resolve(uri);
final NamespaceHandler namespaceHandler = getXmlSupport().resolveNamespaceHandler(uri);
if (namespaceHandler == null) {
throw new BeanDefinitionParsingException(
new Problem("No namespace handler found for URI: " + uri,
new Location(readerContext.getResource())));
new Location(beanBuildResource)));
}
namespaceHandlers.put(namespace, namespaceHandler);
namespaces.put(namespace, uri);
Expand Down Expand Up @@ -952,8 +936,7 @@ public Object getProperty(String name) {

protected DynamicElementReader createDynamicElementReader(String namespace, final boolean decorator) {
NamespaceHandler handler = namespaceHandlers.get(namespace);
ParserContext parserContext = new ParserContext(readerContext, new BeanDefinitionParserDelegate(readerContext));
final DynamicElementReader dynamicElementReader = new DynamicElementReader(namespace, namespaces, handler, parserContext) {
final DynamicElementReader dynamicElementReader = new DynamicElementReader(namespace, namespaces, handler, getXmlSupport().createParserContext(beanBuildResource)) {
@Override
protected void afterInvocation() {
if (!decorator) {
Expand All @@ -973,6 +956,13 @@ else if (!decorator) {
return dynamicElementReader;
}

private BeanBuilderXmlSupport getXmlSupport() {
if (xmlSupport == null) {
xmlSupport = new BeanBuilderXmlSupport(springConfig, classLoader);
}
return xmlSupport;
}

/**
* Sets the binding (the variables available in the scope of the BeanBuilder).
* @param b The Binding instance
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 grails.spring;

import org.springframework.beans.factory.parsing.EmptyReaderEventListener;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.NullSourceExtractor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;

import org.grails.spring.RuntimeSpringConfiguration;

final class BeanBuilderXmlSupport {

private final RuntimeSpringConfiguration springConfig;
private ClassLoader classLoader;
private NamespaceHandlerResolver namespaceHandlerResolver;
private XmlBeanDefinitionReader xmlBeanDefinitionReader;
private XmlReaderContext readerContext;
private Resource readerContextResource;

BeanBuilderXmlSupport(RuntimeSpringConfiguration springConfig, ClassLoader classLoader) {
this.springConfig = springConfig;
setClassLoader(classLoader);
}

void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
namespaceHandlerResolver = new DefaultNamespaceHandlerResolver(classLoader);
xmlBeanDefinitionReader = null;
readerContext = null;
readerContextResource = null;
}

void setNamespaceHandlerResolver(NamespaceHandlerResolver namespaceHandlerResolver) {
this.namespaceHandlerResolver = namespaceHandlerResolver;
readerContext = null;
readerContextResource = null;
}

NamespaceHandler resolveNamespaceHandler(String uri) {
return namespaceHandlerResolver.resolve(uri);
}

ParserContext createParserContext(Resource resource) {
XmlReaderContext currentReaderContext = getReaderContext(resource);
return new ParserContext(currentReaderContext, new BeanDefinitionParserDelegate(currentReaderContext));
}

XmlReaderContext getReaderContext(Resource resource) {
if (readerContext == null || !resource.equals(readerContextResource)) {
readerContext = new XmlReaderContext(resource, new FailFastProblemReporter(), new EmptyReaderEventListener(),
new NullSourceExtractor(), getXmlBeanDefinitionReader(), namespaceHandlerResolver);
readerContextResource = resource;
}
return readerContext;
}

BeanDefinitionRegistry loadBeanDefinitions(Resource resource) {
SimpleBeanDefinitionRegistry beanRegistry = new SimpleBeanDefinitionRegistry();
XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(beanRegistry);
beanReader.setBeanClassLoader(classLoader);
beanReader.loadBeanDefinitions(resource);
return beanRegistry;
}

private XmlBeanDefinitionReader getXmlBeanDefinitionReader() {
if (xmlBeanDefinitionReader == null) {
xmlBeanDefinitionReader = new XmlBeanDefinitionReader((GenericApplicationContext) springConfig.getUnrefreshedApplicationContext());
xmlBeanDefinitionReader.setBeanClassLoader(classLoader);
}
return xmlBeanDefinitionReader;
}
}
Loading
Loading