diff --git a/.gitignore b/.gitignore index 3f904904f76f79d7da09470749ff021417a301cc..08a19adb84e415eb32887eb0d95d20e07003d59e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ buildSrc/build /spring-*/build /spring-core/kotlin-coroutines/build /framework-bom/build +/framework-docs/build /integration-tests/build /src/asciidoc/build target/ @@ -41,3 +42,5 @@ out test-output atlassian-ide-plugin.xml .gradletasknamecache + +cached-antora-playbook.yml diff --git a/build.gradle b/build.gradle index 79d6cf76de6f8c21ad2bcdec1f3dabb3ba514d27..c46c4d285709e5fac2f1e624ee20e08d30f853d2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,15 @@ +buildscript { + repositories { + maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } + maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'} + maven { url 'https://maven.aliyun.com/repository/public'} + maven { url 'https://maven.aliyun.com/repository/central'} + maven { url "https://maven.aliyun.com/repository/spring-plugin" } + maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} + gradlePluginPortal() + } +} + plugins { id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false id 'io.spring.ge.conventions' version '0.0.7' diff --git a/ci/images/ci-image/Dockerfile b/ci/images/ci-image/Dockerfile index 157d7f28b848d0cecb003e03e15937f5b27c83a8..e208a98191849059a09bb88f90ad3b96fd0f8d24 100644 --- a/ci/images/ci-image/Dockerfile +++ b/ci/images/ci-image/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:focal-20220302 +FROM ubuntu:jammy-20230624 ADD setup.sh /setup.sh ADD get-jdk-url.sh /get-jdk-url.sh diff --git a/ci/images/get-jdk-url.sh b/ci/images/get-jdk-url.sh index fca99d595e5cd7b11377fdc11587bdee52614424..aa55e129d50fc308fe78613a6a1724121b33e311 100755 --- a/ci/images/get-jdk-url.sh +++ b/ci/images/get-jdk-url.sh @@ -3,10 +3,10 @@ set -e case "$1" in java8) - echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz" ;; java11) - echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz" + echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19%2B7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz" ;; *) echo $"Unknown java version" diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 1f57b3d999b94f61c088d54ee86c3dd515646d88..9211871608990b57f2f087485a89200b47017ec5 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -26,7 +26,6 @@ anchors: docker-resource-source: &docker-resource-source username: ((docker-hub-username)) password: ((docker-hub-password)) - tag: ((milestone)) slack-fail-params: &slack-fail-params text: > :concourse-failed: @@ -44,24 +43,34 @@ anchors: GITHUB_TOKEN: ((github-ci-release-token)) resource_types: +- name: registry-image + type: registry-image + source: + <<: *docker-resource-source + repository: concourse/registry-image-resource + tag: 1.7.1 - name: artifactory-resource type: registry-image source: + <<: *docker-resource-source repository: springio/artifactory-resource tag: 0.0.17 - name: github-release type: registry-image source: + <<: *docker-resource-source repository: concourse/github-release-resource tag: 1.5.5 - name: github-status-resource type: registry-image source: + <<: *docker-resource-source repository: dpb587/github-status-resource tag: master - name: slack-notification type: registry-image source: + <<: *docker-resource-source repository: cfcommunity/slack-notification-resource tag: latest resources: @@ -90,12 +99,14 @@ resources: source: <<: *docker-resource-source repository: ((docker-hub-organization))/spring-framework-ci + tag: ((milestone)) - name: ci-image-jdk11 type: docker-image icon: docker source: <<: *docker-resource-source repository: ((docker-hub-organization))/spring-framework-ci-jdk11 + tag: ((milestone)) - name: artifactory-repo type: artifactory-resource icon: package-variant @@ -266,7 +277,6 @@ jobs: download_artifacts: false save_build_info: true - task: promote - image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: M @@ -311,7 +321,6 @@ jobs: download_artifacts: false save_build_info: true - task: promote - image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: RC @@ -356,7 +365,6 @@ jobs: download_artifacts: true save_build_info: true - task: promote - image: ci-image file: git-repo/ci/tasks/promote-version.yml params: RELEASE_TYPE: RELEASE diff --git a/ci/scripts/promote-version.sh b/ci/scripts/promote-version.sh index 44c5ff626f91b787ef92667ed2302ab13d728849..2b932f5f20f909256f2c315c83ddcfd59c0678d1 100755 --- a/ci/scripts/promote-version.sh +++ b/ci/scripts/promote-version.sh @@ -6,11 +6,11 @@ CONFIG_DIR=git-repo/ci/config version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' ) export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json -java -jar /opt/concourse-release-scripts.jar \ +java -jar /concourse-release-scripts.jar \ --spring.config.location=${CONFIG_DIR}/release-scripts.yml \ publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; } -java -jar /opt/concourse-release-scripts.jar \ +java -jar /concourse-release-scripts.jar \ --spring.config.location=${CONFIG_DIR}/release-scripts.yml \ promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; } diff --git a/ci/tasks/generate-changelog.yml b/ci/tasks/generate-changelog.yml index b3f40278c8e6ec7df1940bed8c4883c75ac0005a..f4004743db92d408eaf55653266decb285862e1b 100755 --- a/ci/tasks/generate-changelog.yml +++ b/ci/tasks/generate-changelog.yml @@ -4,7 +4,9 @@ image_resource: type: registry-image source: repository: springio/github-changelog-generator - tag: '0.0.7' + tag: '0.0.8' + username: ((docker-hub-username)) + password: ((docker-hub-password)) inputs: - name: git-repo - name: artifactory-repo diff --git a/ci/tasks/promote-version.yml b/ci/tasks/promote-version.yml index abdd8fed5c5cd64898fc32fb5951ea673c32dbf9..2a59aea38d0b737d8d3fb19123e1dfe8ec775580 100644 --- a/ci/tasks/promote-version.yml +++ b/ci/tasks/promote-version.yml @@ -1,5 +1,12 @@ --- platform: linux +image_resource: + type: registry-image + source: + repository: springio/concourse-release-scripts + tag: '0.3.4' + username: ((docker-hub-username)) + password: ((docker-hub-password)) inputs: - name: git-repo - name: artifactory-repo diff --git a/gradle.properties b/gradle.properties index 79b9aa18e0b914038192e4c4391060cf16c436cf..4c4ffba218704ec4c969051a26e2dcec615f9c4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=5.2.22.RELEASE +version=5.2.26.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx1536M org.gradle.caching=true org.gradle.parallel=true diff --git a/gradle/docs.gradle b/gradle/docs.gradle index 27005827312de4c207e32ccf236a188a1b4ef830..2086366517941472e6dd058e5ed892993e92239d 100644 --- a/gradle/docs.gradle +++ b/gradle/docs.gradle @@ -3,7 +3,7 @@ configurations { } dependencies { - asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.0.RELEASE") + asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.1") } repositories { @@ -107,9 +107,8 @@ dokka { } task downloadResources(type: Download) { - def version = "0.2.1.RELEASE" - src "https://repo.spring.io/release/io/spring/docresources/" + - "spring-doc-resources/$version/spring-doc-resources-${version}.zip" + src "https://repo.spring.io/artifactory/snapshot/io/spring/docresources/" + + "spring-doc-resources/0.2.6-SNAPSHOT/spring-doc-resources-0.2.6-20210308.231804-2.zip" dest project.file("$buildDir/docs/spring-doc-resources.zip") onlyIfModified true useETag "all" diff --git a/settings.gradle b/settings.gradle index 711d66b2794dc70fdf3687665abdf3fa658cb3d5..ccd66de9d5a1a8d2a8720faffe8b7d76aa713c03 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ include "spring-context" include "spring-context-indexer" include "spring-context-support" include "spring-core" +include "spring-demo" include "kotlin-coroutines" project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines') include "spring-expression" diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 96a7cdc1c637cf5e87b7d2b8aa2cb5b31e89d9f3..639a447253a686fd9a438d1f6e67463f9d4374f8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -39,6 +39,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Supplier; +import org.apache.commons.logging.Log; +import org.springframework.beans.*; +import org.springframework.beans.factory.*; +import org.springframework.beans.factory.config.*; +import org.springframework.core.*; +import org.springframework.lang.Nullable; +import org.springframework.util.*; +import org.springframework.util.ReflectionUtils.MethodCallback; + /** * 抽象的bean工厂超类,它实现默认的bean创建,并具有{@link RootBeanDefinition}类指定的全部功能。 * 除了AbstractBeanFactory的{@link #createBean}方法之外, @@ -446,7 +455,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. + // 解析 Bean 的 Class 对象 Class resolvedClass = resolveBeanClass(mbd, beanName); + + // 如果满足以下三个条件,则需要创建一个新的 RootBeanDefinition: + // 1. resolvedClass 不为空(已成功解析到 Class) + // 2. !mbd.hasBeanClass()(原 BeanDefinition 中没有 Class 对象) + // 3. mbd.getBeanClassName() 不为空(有类名) if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); @@ -454,6 +469,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac // Prepare method overrides. try { + // lookup method overrides for this bean mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { @@ -1071,14 +1087,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @Nullable protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; + // 如果 beforeInstantiationResolved 没有被设置为 false if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { // Make sure bean class is actually resolved at this point. + // 如果不是合成的 Bean 且存在 InstantiationAwareBeanPostProcessor if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + // 确定目标类型 Class targetType = determineTargetType(beanName, mbd); if (targetType != null) { + // InstantiationAwareBeanPostProcessor 的前置处理 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); + // 如果生成了代理对象,并且不为空,则进行后置处理 if (bean != null) { - bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + // 后置处理 + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index d4b0aef01522777e4e6af5a07a748adf44ac2402..46a5215bf4e282db5a7f1f03f363fc6109b77702 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -1130,6 +1130,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess */ public void prepareMethodOverrides() throws BeanDefinitionValidationException { // Check that lookup methods exist and determine their overloaded status. + // 如果没有方法重写,直接返回 if (hasMethodOverrides()) { getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride); } @@ -1143,6 +1144,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess * @throws BeanDefinitionValidationException in case of validation failure */ protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException { + // 获取对应方法的个数 int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName()); if (count == 0) { throw new BeanDefinitionValidationException( diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java index 5ce09896b1f729b8cbde9846428d7f40f3489269..5159182a9da7e61ae11b05b662c9e1425e3144e7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -16,13 +16,8 @@ package org.springframework.beans.factory.support; -import java.io.IOException; -import java.util.Collections; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; @@ -34,6 +29,10 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + /** * Abstract base class for bean definition readers which implement * the {@link BeanDefinitionReader} interface. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index cf202ac0e839e6e1c5cc2ce9b45a6312b52a9f67..9723d67dce855c1c4e1f3b56f536a6fa3b8d52e2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -16,38 +16,9 @@ package org.springframework.beans.factory.support; -import java.beans.ConstructorProperties; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.commons.logging.Log; - -import org.springframework.beans.BeanMetadataElement; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.BeansException; -import org.springframework.beans.TypeConverter; -import org.springframework.beans.TypeMismatchException; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.InjectionPoint; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.UnsatisfiedDependencyException; +import org.springframework.beans.*; +import org.springframework.beans.factory.*; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; @@ -57,12 +28,13 @@ import org.springframework.core.MethodParameter; import org.springframework.core.NamedThreadLocal; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MethodInvoker; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; +import org.springframework.util.*; + +import java.beans.ConstructorProperties; +import java.lang.reflect.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; /** * Delegate for resolving constructors and factory methods. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java index d15f9376874435876e4026eeaa434c852d547245..5e9547d0d0255409505017c2bdbeb6cdeb37d711 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParserDelegate.java @@ -16,56 +16,23 @@ package org.springframework.beans.factory.xml; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import org.springframework.beans.BeanMetadataAttribute; import org.springframework.beans.BeanMetadataAttributeAccessor; import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanNameReference; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.beans.factory.parsing.BeanEntry; -import org.springframework.beans.factory.parsing.ConstructorArgumentEntry; -import org.springframework.beans.factory.parsing.ParseState; -import org.springframework.beans.factory.parsing.PropertyEntry; -import org.springframework.beans.factory.parsing.QualifierEntry; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.AutowireCandidateQualifier; -import org.springframework.beans.factory.support.BeanDefinitionDefaults; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.LookupOverride; -import org.springframework.beans.factory.support.ManagedArray; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.beans.factory.support.ManagedProperties; -import org.springframework.beans.factory.support.ManagedSet; -import org.springframework.beans.factory.support.MethodOverrides; -import org.springframework.beans.factory.support.ReplaceOverride; +import org.springframework.beans.factory.config.*; +import org.springframework.beans.factory.parsing.*; +import org.springframework.beans.factory.support.*; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.PatternMatchUtils; -import org.springframework.util.StringUtils; +import org.springframework.util.*; import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.*; /** * xml 解析代理类 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java index 8893284c0470641f92da2cf5ae5c7489a1c20605..c9b9c8e5d86ba8d252486ed286cedc9659f4be70 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/BeansDtdResolver.java @@ -16,17 +16,16 @@ package org.springframework.beans.factory.xml; -import java.io.FileNotFoundException; -import java.io.IOException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; - import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +import java.io.FileNotFoundException; +import java.io.IOException; /** * dtd 定义默认获取定义处理类,从classpath中获取spring-beans.dtd的定义信息 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java index 9ad87422aaaa900b10da1569b7931c27802a45fa..098f06ccbbde8dbd170a11ec8a6d1389fda3e61b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultDocumentLoader.java @@ -16,19 +16,18 @@ package org.springframework.beans.factory.xml; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.lang.Nullable; +import org.springframework.util.xml.XmlValidationModeDetector; import org.w3c.dom.Document; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; -import org.springframework.lang.Nullable; -import org.springframework.util.xml.XmlValidationModeDetector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; /** * Spring's default {@link DocumentLoader} implementation. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java index 113ea6f060db812653f2af1c7d599fff889a40db..cba711d08d4f931fa9f2912851c9020ec69a5419 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultNamespaceHandlerResolver.java @@ -16,14 +16,8 @@ package org.springframework.beans.factory.xml; -import java.io.IOException; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanUtils; import org.springframework.beans.FatalBeanException; import org.springframework.core.io.support.PropertiesLoaderUtils; @@ -32,6 +26,11 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + /** * Default implementation of the {@link NamespaceHandlerResolver} interface. * Resolves namespace URIs to implementation classes based on the mappings diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java index dd20b2ad45a95ed5be5caf2f6e8c3518cdd72b7c..ff4c4df3a6e24fd1f29f2cd7e7bfa30f4a9e8447 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DelegatingEntityResolver.java @@ -16,14 +16,13 @@ package org.springframework.beans.factory.xml; -import java.io.IOException; - +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; +import java.io.IOException; /** * {@link EntityResolver}代理类,内部包含xsd和dtd两种EntityResolver的解析 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java index eae6b5f985baf33f51fb33228a6cbe5564206677..f9340c2b4f809bc5a76605067a970bf01f59c3e2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/NamespaceHandlerSupport.java @@ -16,16 +16,15 @@ package org.springframework.beans.factory.xml; -import java.util.HashMap; -import java.util.Map; - +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.lang.Nullable; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.lang.Nullable; +import java.util.HashMap; +import java.util.Map; /** * Support class for implementing custom {@link NamespaceHandler NamespaceHandlers}. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java index 14c715478efce9d6c5651cca006cc07f6435abd9..7e0851d293843dc600fa4ab2fa167f8016ac12b8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/PluggableSchemaResolver.java @@ -16,23 +16,22 @@ package org.springframework.beans.factory.xml; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; - import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; /** * xsd 定义默认获取定义处理类,从META-INF/spring.schemas中获取定义 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java index a106cb2c65f477f9740b7d1a4a7dae4ba1671d31..46642dca180008c015860f1f8fb66101fa6cd02c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/ResourceEntityResolver.java @@ -16,19 +16,18 @@ package org.springframework.beans.factory.xml; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLDecoder; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.lang.Nullable; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLDecoder; /** * {@code EntityResolver}的实现类,尝试通过使用{@link org.springframework.core.io.ResourceLoader} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java index fe9b5d503f41f69f2e61329135852eed69e389bc..5eb6f31509f01256615e60b64ecbfa8f328449e6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java @@ -58,13 +58,13 @@ import java.util.Set; * @author Juergen Hoeller * @author Rob Harrop * @author Chris Beams - * @since 26.11.2003 * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader * @see DefaultBeanDefinitionDocumentReader * @see BeanDefinitionRegistry * @see org.springframework.beans.factory.support.DefaultListableBeanFactory * @see org.springframework.context.support.GenericApplicationContext + * @since 26.11.2003 */ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { @@ -89,7 +89,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD; - /** Constants instance for this class. */ + /** + * Constants instance for this class. + */ private static final Constants constants = new Constants(XmlBeanDefinitionReader.class); private int validationMode = VALIDATION_AUTO; @@ -118,7 +120,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector(); private final ThreadLocal> resourcesCurrentlyBeingLoaded = - new NamedThreadLocal>("XML bean definition resources currently being loaded"){ + new NamedThreadLocal>("XML bean definition resources currently being loaded") { @Override protected Set initialValue() { return new HashSet<>(4); @@ -128,8 +130,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Create new XmlBeanDefinitionReader for the given bean factory. + * * @param registry the BeanFactory to load bean definitions into, - * in the form of a BeanDefinitionRegistry + * in the form of a BeanDefinitionRegistry */ public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { // 指定bean注册信息 @@ -141,6 +144,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * Set whether to use XML validation. Default is {@code true}. *

This method switches namespace awareness on if validation is turned off, * in order to still process schema namespaces properly in such a scenario. + * * @see #setValidationMode * @see #setNamespaceAware */ @@ -151,6 +155,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Set the validation mode to use by name. Defaults to {@link #VALIDATION_AUTO}. + * * @see #setValidationMode */ public void setValidationModeName(String validationModeName) { @@ -251,8 +256,6 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { } /** - * 解析获取 - * EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法 * EntityResolver,SAX解析时候需要DTD或XSD的定义,一般是从网络上面查找 * 但是在网络不通的情况下,就需要程序提供一个查找DTD或XSD定义,就是程序来实现寻找DTD声明的过程 * 比如 @@ -278,9 +281,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); - } - else { - // 使用代理的解析器 + } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } @@ -293,6 +294,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { *

If not set, a default SimpleSaxErrorHandler is used that simply * logs warnings using the logger instance of the view class, * and rethrows errors to discontinue the XML transformation. + * * @see SimpleSaxErrorHandler */ public void setErrorHandler(ErrorHandler errorHandler) { @@ -303,6 +305,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * Specify the {@link BeanDefinitionDocumentReader} implementation to use, * responsible for the actual reading of the XML bean definition document. *

The default is {@link DefaultBeanDefinitionDocumentReader}. + * * @param documentReaderClass the desired BeanDefinitionDocumentReader implementation class */ public void setDocumentReaderClass(Class documentReaderClass) { @@ -311,9 +314,11 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** + * XmlBeanDefinitionReader 加载资源的入口方法 * 从特定的xml文件中加载bean definitions * 这里会做一个转换,将资源文件转换为EncodedResource,带有编码的资源信息 * Load bean definitions from the specified XML file. + * * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors @@ -329,8 +334,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * * 通过encodedResource进行资源解析,encodedResource对象持有resource对象和encoding编码格式 * Load bean definitions from the specified XML file. + * * @param encodedResource the resource descriptor for the XML file, - * allowing to specify an encoding to use for parsing the file + * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @@ -339,6 +345,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } + // 获取当前已经资源文件 // 获取已加载资源信息 Set currentResources = this.resourcesCurrentlyBeingLoaded.get(); // 添加待加载资源信息,如果存在循环依赖,则报错 @@ -346,6 +353,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } + // 判断是否已经加载过当前这个资源 + try (InputStream inputStream = encodedResource.getResource().getInputStream()) { + // 转换输入流到org.xml.sax.InputSource // 读取资源 从encodedResource中获取resource,在获取InputStream try (InputStream inputStream = encodedResource.getResource().getInputStream()) { // InputSource不是来源于spring,而是来源 org.xml.sax @@ -355,12 +365,10 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { } // 加载、读取、注册bean return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); - } - catch (IOException ex) { + } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); - } - finally { + } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); @@ -370,6 +378,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Load bean definitions from the specified XML file. + * * @param inputSource the SAX InputSource to read from * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors @@ -380,9 +389,10 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Load bean definitions from the specified XML file. - * @param inputSource the SAX InputSource to read from + * + * @param inputSource the SAX InputSource to read from * @param resourceDescription a description of the resource - * (can be {@code null} or empty) + * (can be {@code null} or empty) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @@ -397,8 +407,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * 实际加载方法,从XML文件中解析bean,封装成BeanDefinition对象的具体实现 * 实际上是从这里开始从指定的XML文件加载bean定义 * Actually load bean definitions from the specified XML file. + * * @param inputSource the SAX InputSource to read from - * @param resource the resource descriptor for the XML file + * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument @@ -408,6 +419,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { throws BeanDefinitionStoreException { try { + // 获取资源的文档信息,通过documentLoader获取到document对象 + Document doc = doLoadDocument(inputSource, resource); + // 解析和注册 // 封装xml Document对象 Document doc = doLoadDocument(inputSource, resource); // 读取并注册bean @@ -416,27 +430,21 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; - } - catch (BeanDefinitionStoreException ex) { + } catch (BeanDefinitionStoreException ex) { throw ex; - } - catch (SAXParseException ex) { + } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); - } - catch (SAXException ex) { + } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); - } - catch (ParserConfigurationException ex) { + } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); - } - catch (IOException ex) { + } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); - } - catch (Throwable ex) { + } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } @@ -444,8 +452,9 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { /** * Actually load the specified document using the configured DocumentLoader. + * * @param inputSource the SAX InputSource to read from - * @param resource the resource descriptor for the XML file + * @param resource the resource descriptor for the XML file * @return the DOM Document * @throws Exception when thrown from the DocumentLoader * @see #setDocumentLoader @@ -463,6 +472,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * mode gets {@link #detectValidationMode detected} from the given resource. *

Override this method if you would like full control over the validation * mode, even when something other than {@link #VALIDATION_AUTO} was set. + * * @see #detectValidationMode */ protected int getValidationModeForResource(Resource resource) { @@ -482,6 +492,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { } /** + * 定义xml使用那种模式的校验 * 获取xml模式采用何种模式,dtd或者xsd * Detect which kind of validation to perform on the XML file identified * by the supplied {@link Resource}. If the file has a {@code DOCTYPE} @@ -494,28 +505,27 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { if (resource.isOpen()) { throw new BeanDefinitionStoreException( "Passed-in Resource [" + resource + "] contains an open stream: " + - "cannot determine validation mode automatically. Either pass in a Resource " + - "that is able to create fresh streams, or explicitly specify the validationMode " + - "on your XmlBeanDefinitionReader instance."); + "cannot determine validation mode automatically. Either pass in a Resource " + + "that is able to create fresh streams, or explicitly specify the validationMode " + + "on your XmlBeanDefinitionReader instance."); } InputStream inputStream; try { // 获取资源的输入流 inputStream = resource.getInputStream(); - } - catch (IOException ex) { + } catch (IOException ex) { throw new BeanDefinitionStoreException( "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + - "Did you attempt to load directly from a SAX InputSource without specifying the " + - "validationMode on your XmlBeanDefinitionReader instance?", ex); + "Did you attempt to load directly from a SAX InputSource without specifying the " + + "validationMode on your XmlBeanDefinitionReader instance?", ex); } try { + // 模式校验 // 解析xml采用的模式 return this.validationModeDetector.detectValidationMode(inputStream); - } - catch (IOException ex) { + } catch (IOException ex) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex); } @@ -526,7 +536,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * Called by {@code loadBeanDefinitions}. *

Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. - * @param doc the DOM document + * + * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors @@ -548,6 +559,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * Create the {@link BeanDefinitionDocumentReader} to use for actually * reading bean definitions from an XML document. *

The default implementation instantiates the specified "documentReaderClass". + * * @see #setDocumentReaderClass */ protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { @@ -566,6 +578,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * 获取namespace对应的解析器 *

* Lazily create a default NamespaceHandlerResolver, if not set before. + * * @see #createDefaultNamespaceHandlerResolver() */ public NamespaceHandlerResolver getNamespaceHandlerResolver() { @@ -579,6 +592,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { * 创建一个默认的namespaceHandler * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified. *

The default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. + * * @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader) */ protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 696655ad0d7f8396cc676734be6a73f8feca5047..5c4943eba17cffd0c156e6c71e5072f5cf071e1a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -16,8 +16,6 @@ package org.springframework.context.annotation; -import java.util.function.Supplier; - import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -26,7 +24,12 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.function.Supplier; + /** + * 标准ApplicationContext的视线,接受使用{@link Configuration @Configuration}中定义的Component classes, + * 同样也可以支持@Component和@Resource注解的bean + * * Standalone application context, accepting component classes as input — * in particular {@link Configuration @Configuration}-annotated classes, but also plain * {@link org.springframework.stereotype.Component @Component} types and JSR-330 compliant diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 65cbb9bdb9f2eff224b7864c80cf38c31f7e1a97..6888e57b6c76c9ada2ae44df8248d3447fd98c4d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -16,17 +16,10 @@ package org.springframework.context.annotation; -import java.util.LinkedHashSet; -import java.util.Set; - import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 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.support.BeanDefinitionDefaults; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.beans.factory.support.*; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; @@ -35,7 +28,12 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; +import java.util.LinkedHashSet; +import java.util.Set; + /** + * 从classpath获取bean定义信息,并且通过BeanFactory或者ApplicationContext提供的接口注册bean定义信息 + *

* A bean definition scanner that detects bean candidates on the classpath, * registering corresponding bean definitions with a given registry ({@code BeanFactory} * or {@code ApplicationContext}). diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 92d502a13f0b5ea2c659ac40fd8677b70509baed..3ddae4f7369a332a680f34f3c73df7fe45ec007b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -16,18 +16,8 @@ package org.springframework.context.annotation; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Lookup; @@ -53,15 +43,18 @@ import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Controller; -import org.springframework.stereotype.Indexed; -import org.springframework.stereotype.Repository; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.*; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.*; + /** + * 组件提供者,从一个basePackage中读取 + *

* A component provider that provides candidate components from a base package. Can * use {@link CandidateComponentsIndex the index} if it is available of scans the * classpath otherwise. Candidate components are identified by applying exclude and @@ -193,6 +186,8 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } /** + * 注册默认过滤器,用于 {@link Component @Component} + *

* Register the default filter for {@link Component @Component}. *

This will implicitly register all annotations that have the * {@link Component @Component} meta-annotation including the @@ -304,6 +299,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC /** + * 扫描classpath下面的备选组件,其中basePackage 是扫描路径包名信息 * Scan the class path for candidate components. * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions @@ -413,6 +409,10 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC return candidates; } + /** + * 从${@code basePackage}中获取备选的组件信息 + * classpath*:basePackage\/**\/*.class + */ private Set scanCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet<>(); try { @@ -426,9 +426,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC logger.trace("Scanning " + resource); } try { + // 解析获取的bean 信息 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); + // 判断是否备选的bean if (isCandidateComponent(metadataReader)) { + // 创建ScannedGenericBeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); + // 设置来源 sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 83be8cdd2b9790ca5b722282a834858544de62fc..6ee9e0d6a8df688fcf4d7274d92e760ba6e2cf08 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -16,21 +16,8 @@ package org.springframework.context.support; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.beans.CachedIntrospectionResults; import org.springframework.beans.factory.BeanFactory; @@ -40,29 +27,8 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.support.ResourceEditorRegistrar; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.EmbeddedValueResolverAware; -import org.springframework.context.EnvironmentAware; -import org.springframework.context.HierarchicalMessageSource; -import org.springframework.context.LifecycleProcessor; -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.MessageSourceResolvable; -import org.springframework.context.NoSuchMessageException; -import org.springframework.context.PayloadApplicationEvent; -import org.springframework.context.ResourceLoaderAware; -import org.springframework.context.event.ApplicationEventMulticaster; -import org.springframework.context.event.ContextClosedEvent; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.ContextStartedEvent; -import org.springframework.context.event.ContextStoppedEvent; -import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.context.*; +import org.springframework.context.event.*; import org.springframework.context.expression.StandardBeanExpressionResolver; import org.springframework.context.weaving.LoadTimeWeaverAware; import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor; @@ -83,6 +49,11 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Abstract implementation of the {@link org.springframework.context.ApplicationContext} * interface. Doesn't mandate the type of storage used for configuration; simply @@ -513,44 +484,68 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return this.applicationListeners; } + /** + * 容器初始化的过程:BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。 + * BeanDefinition 的载入和 bean 的依赖注入是两个独立的过程,依赖注入一般发生在 应用第一次通过 + * getBean() 方法从容器获取 bean 时。 + * + * 另外需要注意的是,IoC 容器有一个预实例化的配置(即,将 AbstractBeanDefinition 中的 lazyInit 属性 + * 设为 true),使用户可以对容器的初始化过程做一个微小的调控,lazyInit 设为 false 的 bean + * 将在容器初始化时进行依赖注入,而不会等到 getBean() 方法调用时才进行 + * + */ @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. + // 调用容器准备刷新,获取容器的当前时间,同时给容器设置同步标识 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. + // 告诉子类启动 refreshBeanFactory() 方法,BeanDefinition 资源文件的载入从子类的 refreshBeanFactory() 方法启动开始 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. + // 为 BeanFactory 配置容器特性,例如类加载器、事件处理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. + // 为容器的某些子类指定特殊的 BeanPost 事件处理器 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. + // 调用所有注册的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. + // 为 BeanFactory 注册 BeanPost 事件处理器. + // BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. + // 初始化信息源,和国际化相关. initMessageSource(); // Initialize event multicaster for this context. + // 初始化事件广播器,用于监听容器触发的事件 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. + // 调用子类的某些特殊 Bean 初始化方法 + // 初始化其他特殊的 Bean,留给子类实现 onRefresh(); // Check for listener beans and register them. + // 为事件传播器注册事件监听器. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. + // 实例化所有剩余的(非懒加载)单例 Bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. + // 完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程 finishRefresh(); } @@ -561,9 +556,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } // Destroy already created singletons to avoid dangling resources. + // 销毁已经创建的单例 Bean,以避免资源泄露 destroyBeans(); // Reset 'active' flag. + // 取消 refresh 操作,重置容器的同步标识 cancelRefresh(ex); // Propagate exception to caller. @@ -629,13 +626,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } /** + * 告诉子类去刷新内部的 beanFactory * Tell the subclass to refresh the internal bean factory. * @return the fresh BeanFactory instance * @see #refreshBeanFactory() * @see #getBeanFactory() */ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { + // 自己定义了抽象的 refreshBeanFactory() 方法,具体实现交给了自己的子类 refreshBeanFactory(); + // getBeanFactory() 也是一个抽象方法,交由子类实现 + // 看到这里是不是很容易想起 “模板方法模式”,父类在模板方法中定义好流程,定义好抽象方法 + // 具体实现交由子类完成 return getBeanFactory(); } @@ -689,6 +691,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } /** + * 在标准初始化后修改应用程序上下文的内部 Bean 工厂。 + * 所有 Bean 定义都将被加载,但尚未实例化任何 Bean。 * Modify the application context's internal bean factory after its standard * initialization. All bean definitions will have been loaded, but no beans * will have been instantiated yet. This allows for registering special @@ -704,6 +708,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader *

Must be called before singleton instantiation. */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { + // 调用 BeanFactoryPostProcessor 的方法 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java index 9c87844e9d71c2d4a8f600b0a3d41b13c6dda38d..acb6fee93c8985676a584ff8504661606673107b 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java @@ -16,8 +16,6 @@ package org.springframework.context.support; -import java.io.IOException; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -25,6 +23,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.lang.Nullable; +import java.io.IOException; + /** * Base class for {@link org.springframework.context.ApplicationContext} * implementations which are supposed to support multiple calls to {@link #refresh()}, @@ -113,21 +113,27 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl /** + * 在这里完成了容器的初始化,并赋值给自己私有的 beanFactory 属性,为下一步调用做准备 * This implementation performs an actual refresh of this context's underlying * bean factory, shutting down the previous bean factory (if any) and * initializing a fresh bean factory for the next phase of the context's lifecycle. */ @Override protected final void refreshBeanFactory() throws BeansException { + // 如果已经存在 beanFactory,则销毁它 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { + // 创建一个新的 beanFactory, DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); + // 定制化 beanFactory,如设置启动参数,开启注解的自动装配等 customizeBeanFactory(beanFactory); + // 加载 beanDefinitions, 在当前类中只定义了抽象的 loadBeanDefinitions() 方法,具体实现 调用子类容器 loadBeanDefinitions(beanFactory); + // 给自己的属性赋值 this.beanFactory = beanFactory; } catch (IOException ex) { diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java index 85cb2250b59e4f959e99905bf23b6b8f6f64aa9d..68dd816c099b303bfd796b1d323672890b339f07 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractXmlApplicationContext.java @@ -16,8 +16,6 @@ package org.springframework.context.support; -import java.io.IOException; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.ResourceEntityResolver; @@ -26,6 +24,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.lang.Nullable; +import java.io.IOException; + /** * Convenient base class for {@link org.springframework.context.ApplicationContext} * implementations, drawing configuration from XML documents containing bean definitions @@ -72,6 +72,7 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC /** + * 实现了基类 AbstractRefreshableApplicationContext 的抽象方法 * Loads the bean definitions via an XmlBeanDefinitionReader. * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader * @see #initBeanDefinitionReader @@ -80,17 +81,26 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. + // 创建一个Bean xml定义的读取器 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); + // 初始化 BeanDefinitionReader // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); + // 设置资源加载器,因为基类实现了继承了DefaultResourceLoader beanDefinitionReader.setResourceLoader(this); + // 设置实体解析器 + // 设置 SAX 解析器,SAX(simple API for XML)是另一种 XML 解析方法。相比于 DOM,SAX 速度更快,占用内存更小。 + // 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描近内存,再进行解析的 DOM,SAX 可以在解析文档的 + // 任意时刻停止解析,但操作也比 DOM 复杂。 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. + // 初始化 BeanDefinitionReader, 该方法同时启用了 Xml 的校验机制 initBeanDefinitionReader(beanDefinitionReader); + // 加载Bean定义信息 loadBeanDefinitions(beanDefinitionReader); } @@ -107,6 +117,7 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC } /** + * 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition * Load the bean definitions with the given XmlBeanDefinitionReader. *

The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory} * method; hence this method is just supposed to load and/or register bean definitions. @@ -119,12 +130,20 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC * @see #getResourcePatternResolver */ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { + // 获取存放了 BeanDefinition 的所有 Resource,FileSystemXmlApplicationContext 类未对 + // getConfigResources() 进行重写,所以调用父类的,return null。 + // 而 ClassPathXmlApplicationContext 对该方法进行了重写,返回设置的值 Resource[] configResources = getConfigResources(); if (configResources != null) { + // XmlBeanDefinitionReader 调用其父类 AbstractBeanDefinitionReader 的方法加载 BeanDefinition reader.loadBeanDefinitions(configResources); } + // 获取配置文件路径 + // 调用父类 AbstractRefreshableConfigApplicationContext 的实现, + // 优先返回 FileSystemXmlApplicationContext 构造方法中调用 setConfigLocations() 方法设置的资源 String[] configLocations = getConfigLocations(); if (configLocations != null) { + // XmlBeanDefinitionReader 调用其父类 AbstractBeanDefinitionReader 的方法从配置位置加载 BeanDefinition reader.loadBeanDefinitions(configLocations); } } diff --git a/spring-context/src/main/java/org/springframework/context/support/FileSystemXmlApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/FileSystemXmlApplicationContext.java index e6abe2b0f121c929507c176616da98eeae8a3531..1f521f33fad5e0cfd9540c1ee833124c68cce886 100644 --- a/spring-context/src/main/java/org/springframework/context/support/FileSystemXmlApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/FileSystemXmlApplicationContext.java @@ -76,6 +76,8 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte } /** + * 单个路径下面包含beanDefinition的xml文件 + * 下面四个方法都是走第五个方法进入处理 * Create a new FileSystemXmlApplicationContext, loading the definitions * from the given XML file and automatically refreshing the context. * @param configLocation file path @@ -86,6 +88,7 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte } /** + * 可以定义多个 BeanDefinition 所在的文件路径 * Create a new FileSystemXmlApplicationContext, loading the definitions * from the given XML files and automatically refreshing the context. * @param configLocations array of file paths @@ -96,6 +99,7 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte } /** + * 在定义多个 BeanDefinition 所在的文件路径 的同时,还能指定自己的双亲 IoC 容器 * Create a new FileSystemXmlApplicationContext with the given parent, * loading the definitions from the given XML files and automatically * refreshing the context. @@ -122,6 +126,7 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte } /** + * 如果应用直接使用 FileSystemXmlApplicationContext 进行实例化,则都会进到这个构造方法中来 * Create a new FileSystemXmlApplicationContext with the given parent, * loading the definitions from the given XML files. * @param configLocations array of file paths @@ -135,9 +140,11 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte public FileSystemXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { - + // 动态地确定用哪个加载器去加载我们的配置文件 super(parent); + // 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableConfigApplicationContext setConfigLocations(configLocations); + // 容器初始化 if (refresh) { refresh(); } @@ -145,6 +152,9 @@ public class FileSystemXmlApplicationContext extends AbstractXmlApplicationConte /** + * + * 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作 + * 本方法是在其父类 DefaultResourceLoader 的 getResource 方法中被调用的, * Resolve resource paths as file system paths. *

Note: Even if a given path starts with a slash, it will get * interpreted as relative to the current VM working directory. diff --git a/spring-core/src/main/java/org/springframework/core/AliasRegistry.java b/spring-core/src/main/java/org/springframework/core/AliasRegistry.java index 689b3aeea9be05df939a7f20c4b01420fa2bfd49..28cfc8ed5465b90358ab7c2d1deca33ec1c56272 100644 --- a/spring-core/src/main/java/org/springframework/core/AliasRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/AliasRegistry.java @@ -17,6 +17,7 @@ package org.springframework.core; /** + * 别名注册中心,也是别名管理接口 * Common interface for managing aliases. Serves as a super-interface for * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. * diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java index 936c1d3e50fceebdcf141516a34398cf058d89f0..9e4bfecbf5d2d39a50b9b1dc2ae586cb817cffb9 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java @@ -16,6 +16,12 @@ package org.springframework.core.io; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; @@ -24,12 +30,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ResourceUtils; -import org.springframework.util.StringUtils; - /** * Default implementation of the {@link ResourceLoader} interface. * Used by {@link ResourceEditor}, and serves as base class for @@ -151,19 +151,26 @@ public class DefaultResourceLoader implements ResourceLoader { } } + // 如果是文件路径,则走文件路径进行加载 if (location.startsWith("/")) { return getResourceByPath(location); } + // // 如果 location 是类路径的方式,返回 ClassPathResource 类型的文件资源对象 else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... + // 如果是 URL 方式,返回 UrlResource 类型的文件资源对象, + // 否则将抛出的异常进入 catch 代码块,返回另一种资源对象 URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { + // 如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用容器本身的 + // getResourceByPath() 方法获取 Resource。根据实例化的子类对象,调用其子类对象中 + // 重写的此方法,如 FileSystemXmlApplicationContext 子类中对此方法的重写 // No URL -> resolve as resource path. return getResourceByPath(location); } @@ -171,6 +178,7 @@ public class DefaultResourceLoader implements ResourceLoader { } /** + * 返回一个ClassPathContextResource对象,该对象表示给定路径的资源。 * Return a Resource handle for the resource at the given path. *

The default implementation supports class path locations. This should * be appropriate for standalone implementations but can be overridden, diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java index 305619e1fb0cba167023a2fd80bc91f5d8b67d1a..da42e06c968ba7707ce07b6a7d985803cdcc235c 100644 --- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java +++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java @@ -16,15 +16,11 @@ package org.springframework.util.xml; -import java.io.BufferedReader; -import java.io.CharConversionException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; +import java.io.*; + /** * Detects whether an XML stream is using DTD- or XSD-based validation. * @@ -81,6 +77,7 @@ public class XmlValidationModeDetector { /** + * 从 {@link InputStream}读取XML 定义信息来判断xml使用的模式 * Detect the validation mode for the XML document in the supplied {@link InputStream}. * Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse @@ -90,19 +87,23 @@ public class XmlValidationModeDetector { */ public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. + // 读取内容 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); + // 如果读取的行是空行或者注释则略过 if (this.inComment || !StringUtils.hasText(content)) { continue; } + // 判断是否存在 DOCTYPE,如果存在则是DTD if (hasDoctype(content)) { isDtdValidated = true; break; } + // 判断当前内容是否为<,就是标签开始的地方,验证模式一定在标签开始的地方定义 if (hasOpeningTag(content)) { // End of meaningful data... break; diff --git a/spring-demo/spring-demo.gradle b/spring-demo/spring-demo.gradle new file mode 100644 index 0000000000000000000000000000000000000000..dea91d9f8ed0925ca316a321c785244e65f3dec5 --- /dev/null +++ b/spring-demo/spring-demo.gradle @@ -0,0 +1,36 @@ +description = "Spring Demo" + +apply plugin: "groovy" +apply plugin: "kotlin" + +dependencies { + compile(project(":spring-context")) + optional("javax.inject:javax.inject") + optional("org.yaml:snakeyaml") + optional("org.codehaus.groovy:groovy-xml") + optional("org.jetbrains.kotlin:kotlin-reflect") + optional("org.jetbrains.kotlin:kotlin-stdlib") + testCompile(testFixtures(project(":spring-core"))) + testCompile("javax.annotation:javax.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 { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + 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. +def deps = compileGroovy.taskDependencies.immutableValues + compileGroovy.taskDependencies.mutableValues +compileGroovy.dependsOn = deps - "compileJava" +compileKotlin.dependsOn(compileGroovy) +compileKotlin.classpath += files(compileGroovy.destinationDir) diff --git a/spring-demo/src/main/java/org/chensj/test/custom/handler/UserBeanNamespaceHandler.java b/spring-demo/src/main/java/org/chensj/test/custom/handler/UserBeanNamespaceHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..8424593101bb23c6e94482aed97cd367876a35be --- /dev/null +++ b/spring-demo/src/main/java/org/chensj/test/custom/handler/UserBeanNamespaceHandler.java @@ -0,0 +1,19 @@ +package org.chensj.test.custom.handler; + +import org.chensj.test.custom.parser.UserBeanDefinitionParser; +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * user-tag + * + * @author chensj + * @description user-tag + * @date 2024-02-29 21:12 + * @package org.chensj.test.custom.handler + */ +public class UserBeanNamespaceHandler extends NamespaceHandlerSupport { + @Override + public void init() { + registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); + } +} diff --git a/spring-demo/src/main/java/org/chensj/test/custom/parser/UserBeanDefinitionParser.java b/spring-demo/src/main/java/org/chensj/test/custom/parser/UserBeanDefinitionParser.java new file mode 100644 index 0000000000000000000000000000000000000000..8654dfea1226c4ea50e71c768b28ece1865e5b87 --- /dev/null +++ b/spring-demo/src/main/java/org/chensj/test/custom/parser/UserBeanDefinitionParser.java @@ -0,0 +1,47 @@ +package org.chensj.test.custom.parser; + +import org.chensj.test.custom.tag.User; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * user 解析 + * + * @author chensj + * @description user 解析 + * @date 2024-02-29 21:08 + * @package org.chensj.test.custom.parser + */ +public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return User.class; + } + + @Override + protected String getBeanClassName(Element element) { + return User.class.getName(); + } + + @Override + protected void doParse(Element element, BeanDefinitionBuilder builder) { + String id = element.getAttribute("id"); + String username = element.getAttribute("username"); + String password = element.getAttribute("password"); + String email = element.getAttribute("email"); + // 将取出的属性放置到BeanDefinitionBuilder中,待完成所有bean解析后统一注册到BeanFactory中 + if(StringUtils.hasText(username)){ + builder.addPropertyValue("username",username); + } + if(StringUtils.hasText(password)){ + builder.addPropertyValue("password",password); + } + if(StringUtils.hasText(email)){ + builder.addPropertyValue("email",email); + } + } +} diff --git a/spring-demo/src/main/java/org/chensj/test/custom/tag/User.java b/spring-demo/src/main/java/org/chensj/test/custom/tag/User.java new file mode 100644 index 0000000000000000000000000000000000000000..b4818539301271a8609d14044f9a42958dbb9b29 --- /dev/null +++ b/spring-demo/src/main/java/org/chensj/test/custom/tag/User.java @@ -0,0 +1,50 @@ +package org.chensj.test.custom.tag; + +/** + * User + * + * @author chensj + * @description User + * @date 2024-02-29 21:01 + * @package org.chensj.test.custom.tag + */ +public class User { + + private String id; + private String username; + private String password; + + private String email; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/spring-demo/src/main/resources/META-INF/spring.handlers b/spring-demo/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000000000000000000000000000000000000..0de71084b0f81418bf32c2700870e37faf7991ca --- /dev/null +++ b/spring-demo/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +https\://www.chensj.org/schema/spring-user=org.chensj.test.custom.handler.UserBeanNamespaceHandler \ No newline at end of file diff --git a/spring-demo/src/main/resources/META-INF/spring.schemas b/spring-demo/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000000000000000000000000000000000000..cb025ba17bbbc69c209a36139f72852c33d5ac85 --- /dev/null +++ b/spring-demo/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +https\://www.chensj.org/schema/user/spring-user.xsd=META-INF/tag-user.xsd \ No newline at end of file diff --git a/spring-demo/src/main/resources/META-INF/tag-user.xsd b/spring-demo/src/main/resources/META-INF/tag-user.xsd new file mode 100644 index 0000000000000000000000000000000000000000..20bf3c9c2b0a53e18d54c039368f938597955a67 --- /dev/null +++ b/spring-demo/src/main/resources/META-INF/tag-user.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-demo/src/test/java/UserTagTest.java b/spring-demo/src/test/java/UserTagTest.java new file mode 100644 index 0000000000000000000000000000000000000000..78287d02493114f0628083be332f2cf047b49bb9 --- /dev/null +++ b/spring-demo/src/test/java/UserTagTest.java @@ -0,0 +1,22 @@ +import org.chensj.test.custom.tag.User; +import org.springframework.beans.factory.xml.XmlBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * user + * + * @author chensj + * @description user + * @date 2024-02-29 21:25 + * @package PACKAGE_NAME + */ +public class UserTagTest { + + public static void main(String[] args) { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-tag.xml"); + + User user = (User) applicationContext.getBean("user"); + System.out.println(user.getEmail()); + } +} diff --git a/spring-demo/src/test/resources/spring-tag.xml b/spring-demo/src/test/resources/spring-tag.xml new file mode 100644 index 0000000000000000000000000000000000000000..8830068eea9f0cf7f60b95529e8ffeee5afd496e --- /dev/null +++ b/spring-demo/src/test/resources/spring-tag.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java index 0b4d2eab971b08309e321dc733848f8c8eb519b1..0c393a86dbe6c3018168886f9c183da58e44eead 100644 --- a/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -17,6 +17,7 @@ package org.springframework.expression; import java.util.List; +import java.util.function.Supplier; import org.springframework.lang.Nullable; @@ -24,12 +25,21 @@ import org.springframework.lang.Nullable; * Expressions are executed in an evaluation context. It is in this context that * references are resolved when encountered during expression evaluation. * - *

There is a default implementation of this EvaluationContext interface: - * {@link org.springframework.expression.spel.support.StandardEvaluationContext} - * which can be extended, rather than having to implement everything manually. + *

There are two default implementations of this interface. + *

    + *
  • {@link org.springframework.expression.spel.support.SimpleEvaluationContext + * SimpleEvaluationContext}: a simpler builder-style {@code EvaluationContext} + * variant for data-binding purposes, which allows for opting into several SpEL + * features as needed.
  • + *
  • {@link org.springframework.expression.spel.support.StandardEvaluationContext + * StandardEvaluationContext}: a powerful and highly configurable {@code EvaluationContext} + * implementation, which can be extended, rather than having to implement everything + * manually.
  • + *
* * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public interface EvaluationContext { @@ -85,7 +95,30 @@ public interface EvaluationContext { OperatorOverloader getOperatorOverloader(); /** - * Set a named variable within this evaluation context to a specified value. + * Assign the value created by the specified {@link Supplier} to a named variable + * within this evaluation context. + *

In contrast to {@link #setVariable(String, Object)}, this method should only + * be invoked to support the assignment operator ({@code =}) within an expression. + *

By default, this method delegates to {@code setVariable(String, Object)}, + * providing the value created by the {@code valueSupplier}. Concrete implementations + * may override this default method to provide different semantics. + * @param name the name of the variable to assign + * @param valueSupplier the supplier of the value to be assigned to the variable + * @return a {@link TypedValue} wrapping the assigned value + * @since 5.2.24 + */ + default TypedValue assignVariable(String name, Supplier valueSupplier) { + TypedValue typedValue = valueSupplier.get(); + setVariable(name, typedValue.getValue()); + return typedValue; + } + + /** + * Set a named variable in this evaluation context to a specified value. + *

In contrast to {@link #assignVariable(String, Supplier)}, this method + * should only be invoked programmatically when interacting directly with the + * {@code EvaluationContext} — for example, to provide initial + * configuration for the context. * @param name the name of the variable to set * @param value the value to be placed in the variable */ @@ -93,7 +126,7 @@ public interface EvaluationContext { /** * Look up a named variable within this evaluation context. - * @param name variable to lookup + * @param name the name of the variable to look up * @return the value of the variable, or {@code null} if not found */ @Nullable diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 252af447af317995ccb5ee23a14a9caf84bbc901..8dadae596732f95740506ba96b7f837aed30d046 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.function.Supplier; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.EvaluationContext; @@ -38,18 +39,19 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** - * An ExpressionState is for maintaining per-expression-evaluation state, any changes to - * it are not seen by other expressions but it gives a place to hold local variables and + * ExpressionState is for maintaining per-expression-evaluation state: any changes to + * it are not seen by other expressions, but it gives a place to hold local variables and * for component expressions in a compound expression to communicate state. This is in * contrast to the EvaluationContext, which is shared amongst expression evaluations, and * any changes to it will be seen by other expressions or any code that chooses to ask * questions of the context. * - *

It also acts as a place for to define common utility routines that the various AST + *

It also acts as a place to define common utility routines that the various AST * nodes might need. * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class ExpressionState { @@ -138,6 +140,29 @@ public class ExpressionState { return this.scopeRootObjects.element(); } + /** + * Assign the value created by the specified {@link Supplier} to a named variable + * within the evaluation context. + *

In contrast to {@link #setVariable(String, Object)}, this method should + * only be invoked to support assignment within an expression. + * @param name the name of the variable to assign + * @param valueSupplier the supplier of the value to be assigned to the variable + * @return a {@link TypedValue} wrapping the assigned value + * @since 5.2.24 + * @see EvaluationContext#assignVariable(String, Supplier) + */ + public TypedValue assignVariable(String name, Supplier valueSupplier) { + return this.relatedContext.assignVariable(name, valueSupplier); + } + + /** + * Set a named variable in the evaluation context to a specified value. + *

In contrast to {@link #assignVariable(String, Supplier)}, this method + * should only be invoked programmatically. + * @param name the name of the variable to set + * @param value the value to be placed in the variable + * @see EvaluationContext#setVariable(String, Object) + */ public void setVariable(String name, @Nullable Object value) { this.relatedContext.setVariable(name, value); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index a3f588a1de4d1558bc5b04fc0526b4780f1e10e4..74246bc45c2d77dc052bde5ae17bfaab1359e9e9 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -260,7 +260,27 @@ public enum SpelMessage { /** @since 5.2.20 */ MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075, - "Array declares too many elements, exceeding the threshold of ''{0}''"); + "Array declares too many elements, exceeding the threshold of ''{0}''"), + + /** @since 5.2.23 */ + MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076, + "Repeated text is too long, exceeding the threshold of ''{0}'' characters"), + + /** @since 5.2.23 */ + MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077, + "Regular expression is too long, exceeding the threshold of ''{0}'' characters"), + + /** @since 5.2.24 */ + MAX_CONCATENATED_STRING_LENGTH_EXCEEDED(Kind.ERROR, 1078, + "Concatenated string is too long, exceeding the threshold of ''{0}'' characters"), + + /** @since 5.2.24 */ + MAX_EXPRESSION_LENGTH_EXCEEDED(Kind.ERROR, 1079, + "SpEL expression is too long, exceeding the threshold of ''{0}'' characters"), + + /** @since 5.2.24 */ + VARIABLE_ASSIGNMENT_NOT_SUPPORTED(Kind.ERROR, 1080, + "Assignment to variable ''{0}'' is not supported"); private final Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index fb1260570d489b37a2bbf7fe0655c572299af67c..9e4ccfd68ccb841da6bec28a33dab475aca5e90f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2023 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. @@ -25,11 +25,19 @@ import org.springframework.lang.Nullable; * @author Juergen Hoeller * @author Phillip Webb * @author Andy Clement + * @author Sam Brannen * @since 3.0 * @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration) */ public class SpelParserConfiguration { + /** + * Default maximum length permitted for a SpEL expression. + * @since 5.2.24 + */ + private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000; + + private static final SpelCompilerMode defaultCompilerMode; static { @@ -50,6 +58,8 @@ public class SpelParserConfiguration { private final int maximumAutoGrowSize; + private final int maximumExpressionLength; + /** * Create a new {@code SpelParserConfiguration} instance with default settings. @@ -98,11 +108,30 @@ public class SpelParserConfiguration { public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { + this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections, + maximumAutoGrowSize, DEFAULT_MAX_EXPRESSION_LENGTH); + } + + /** + * Create a new {@code SpelParserConfiguration} instance. + * @param compilerMode the compiler mode that parsers using this configuration object should use + * @param compilerClassLoader the ClassLoader to use as the basis for expression compilation + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @param maximumAutoGrowSize the maximum size that a collection can auto grow + * @param maximumExpressionLength the maximum length of a SpEL expression; + * must be a positive number + * @since 5.2.25 + */ + public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, + boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) { + this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode); this.compilerClassLoader = compilerClassLoader; this.autoGrowNullReferences = autoGrowNullReferences; this.autoGrowCollections = autoGrowCollections; this.maximumAutoGrowSize = maximumAutoGrowSize; + this.maximumExpressionLength = maximumExpressionLength; } @@ -142,4 +171,12 @@ public class SpelParserConfiguration { return this.maximumAutoGrowSize; } + /** + * Return the maximum number of characters that a SpEL expression can contain. + * @since 5.2.25 + */ + public int getMaximumExpressionLength() { + return this.maximumExpressionLength; + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java index a009a07db5126173b5f2be1946ae21a6a0955243..55e5d2e4ff086f9741c87f9d0dc1d509726a9299 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -27,6 +27,7 @@ import org.springframework.expression.spel.ExpressionState; *

Example: 'someNumberProperty=42' * * @author Andy Clement + * @author Sam Brannen * @since 3.0 */ public class Assign extends SpelNodeImpl { @@ -38,9 +39,7 @@ public class Assign extends SpelNodeImpl { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - TypedValue newValue = this.children[1].getValueInternal(state); - getChild(0).setValue(state, newValue.getValue()); - return newValue; + return this.children[0].setValueInternal(state, () -> this.children[1].getValueInternal(state)); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java index 0e47facfa7bf39f27bf97336549d33336f8e8ca9..616a503a4ec1243ef35ea880a5d6724e71527619 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -17,6 +17,7 @@ package org.springframework.expression.spel.ast; import java.util.StringJoiner; +import java.util.function.Supplier; import org.springframework.asm.MethodVisitor; import org.springframework.expression.EvaluationException; @@ -24,13 +25,13 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; -import org.springframework.lang.Nullable; /** * Represents a DOT separated expression sequence, such as * {@code 'property1.property2.methodOne()'}. * * @author Andy Clement + * @author Sam Brannen * @since 3.0 */ public class CompoundExpression extends SpelNodeImpl { @@ -95,8 +96,12 @@ public class CompoundExpression extends SpelNodeImpl { } @Override - public void setValue(ExpressionState state, @Nullable Object value) throws EvaluationException { - getValueRef(state).setValue(value); + public TypedValue setValueInternal(ExpressionState state, Supplier valueSupplier) + throws EvaluationException { + + TypedValue typedValue = valueSupplier.get(); + getValueRef(state).setValue(typedValue.getValue()); + return typedValue; } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 4ef4a0ed4ece185014886d68a6b4ba69b5b8e371..5ae8f962139d615f34e2261cdad8344ef7a68399 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.StringJoiner; +import java.util.function.Supplier; import org.springframework.asm.MethodVisitor; import org.springframework.core.convert.TypeDescriptor; @@ -45,11 +46,12 @@ import org.springframework.util.ReflectionUtils; /** * An Indexer can index into some proceeding structure to access a particular piece of it. - * Supported structures are: strings / collections (lists/sets) / arrays. + *

Supported structures are: strings / collections (lists/sets) / arrays. * * @author Andy Clement * @author Phillip Webb * @author Stephane Nicoll + * @author Sam Brannen * @since 3.0 */ // TODO support multidimensional arrays @@ -102,8 +104,12 @@ public class Indexer extends SpelNodeImpl { } @Override - public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException { - getValueRef(state).setValue(newValue); + public TypedValue setValueInternal(ExpressionState state, Supplier valueSupplier) + throws EvaluationException { + + TypedValue typedValue = valueSupplier.get(); + getValueRef(state).setValue(typedValue.getValue()); + return typedValue; } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java index fbf28317b32b27e27a7fd4aac44d29bb8dc9f832..0cff5cb0c8c0541d2104139aa8dec94d53ad139c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -25,6 +25,8 @@ import org.springframework.expression.Operation; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; @@ -52,6 +54,13 @@ import org.springframework.util.NumberUtils; */ public class OpMultiply extends Operator { + /** + * Maximum number of characters permitted in repeated text. + * @since 5.2.23 + */ + private static final int MAX_REPEATED_TEXT_SIZE = 256; + + public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) { super("*", startPos, endPos, operands); } @@ -109,10 +118,13 @@ public class OpMultiply extends Operator { } if (leftOperand instanceof String && rightOperand instanceof Integer) { - int repeats = (Integer) rightOperand; - StringBuilder result = new StringBuilder(); - for (int i = 0; i < repeats; i++) { - result.append(leftOperand); + String text = (String) leftOperand; + int count = (Integer) rightOperand; + int requestedSize = text.length() * count; + checkRepeatedTextSize(requestedSize); + StringBuilder result = new StringBuilder(requestedSize); + for (int i = 0; i < count; i++) { + result.append(text); } return new TypedValue(result.toString()); } @@ -120,6 +132,13 @@ public class OpMultiply extends Operator { return state.operate(Operation.MULTIPLY, leftOperand, rightOperand); } + private void checkRepeatedTextSize(int requestedSize) { + if (requestedSize > MAX_REPEATED_TEXT_SIZE) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE); + } + } + @Override public boolean isCompilable() { if (!getLeftOperand().isCompilable()) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java index d128f56b2debe3cb8d8434b27c85809b3ce53535..365aa8399b29e22d9a725eff2da7782749cc6fdf 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -27,6 +27,8 @@ import org.springframework.expression.TypeConverter; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; @@ -46,10 +48,18 @@ import org.springframework.util.NumberUtils; * @author Juergen Hoeller * @author Ivo Smid * @author Giovanni Dall'Oglio Risso + * @author Sam Brannen * @since 3.0 */ public class OpPlus extends Operator { + /** + * Maximum number of characters permitted in a concatenated string. + * @since 5.2.24 + */ + private static final int MAX_CONCATENATED_STRING_LENGTH = 100_000; + + public OpPlus(int startPos, int endPos, SpelNodeImpl... operands) { super("+", startPos, endPos, operands); Assert.notEmpty(operands, "Operands must not be empty"); @@ -123,22 +133,45 @@ public class OpPlus extends Operator { if (leftOperand instanceof String && rightOperand instanceof String) { this.exitTypeDescriptor = "Ljava/lang/String"; - return new TypedValue((String) leftOperand + rightOperand); + String leftString = (String) leftOperand; + String rightString = (String) rightOperand; + checkStringLength(leftString); + checkStringLength(rightString); + return concatenate(leftString, rightString); } if (leftOperand instanceof String) { - return new TypedValue( - leftOperand + (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state))); + String leftString = (String) leftOperand; + checkStringLength(leftString); + String rightString = (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state)); + checkStringLength(rightString); + return concatenate(leftString, rightString); } if (rightOperand instanceof String) { - return new TypedValue( - (leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state)) + rightOperand); + String rightString = (String) rightOperand; + checkStringLength(rightString); + String leftString = (leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state)); + checkStringLength(leftString); + return concatenate(leftString, rightString); } return state.operate(Operation.ADD, leftOperand, rightOperand); } + private void checkStringLength(String string) { + if (string.length() > MAX_CONCATENATED_STRING_LENGTH) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, MAX_CONCATENATED_STRING_LENGTH); + } + } + + private TypedValue concatenate(String leftString, String rightString) { + String result = leftString + rightString; + checkStringLength(result); + return new TypedValue(result); + } + @Override public String toStringAST() { if (this.children.length < 2) { // unary plus diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java index 22795a92879620e1d820cbf0b37d0cc5f57122e2..de277c68eaf60a48824a76343121f62b1d075fd4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -36,19 +36,41 @@ import org.springframework.expression.spel.support.BooleanTypedValue; * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class OperatorMatches extends Operator { private static final int PATTERN_ACCESS_THRESHOLD = 1000000; - private final ConcurrentMap patternCache = new ConcurrentHashMap<>(); + /** + * Maximum number of characters permitted in a regular expression. + * @since 5.2.23 + */ + private static final int MAX_REGEX_LENGTH = 1000; + private final ConcurrentMap patternCache; + + /** + * Create a new {@link OperatorMatches} instance. + * @deprecated as of Spring Framework 5.2.23 in favor of invoking + * {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)} + * with a shared pattern cache instead + */ + @Deprecated public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) { - super("matches", startPos, endPos, operands); + this(new ConcurrentHashMap<>(), startPos, endPos, operands); } + /** + * Create a new {@link OperatorMatches} instance with a shared pattern cache. + * @since 5.2.23 + */ + public OperatorMatches(ConcurrentMap patternCache, int startPos, int endPos, SpelNodeImpl... operands) { + super("matches", startPos, endPos, operands); + this.patternCache = patternCache; + } /** * Check the first operand matches the regex specified as the second operand. @@ -62,26 +84,28 @@ public class OperatorMatches extends Operator { public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { SpelNodeImpl leftOp = getLeftOperand(); SpelNodeImpl rightOp = getRightOperand(); - String left = leftOp.getValue(state, String.class); - Object right = getRightOperand().getValue(state); - if (left == null) { + String input = leftOp.getValue(state, String.class); + if (input == null) { throw new SpelEvaluationException(leftOp.getStartPosition(), SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null); } + + Object right = rightOp.getValue(state); if (!(right instanceof String)) { throw new SpelEvaluationException(rightOp.getStartPosition(), SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right); } + String regex = (String) right; try { - String rightString = (String) right; - Pattern pattern = this.patternCache.get(rightString); + Pattern pattern = this.patternCache.get(regex); if (pattern == null) { - pattern = Pattern.compile(rightString); - this.patternCache.putIfAbsent(rightString, pattern); + checkRegexLength(regex); + pattern = Pattern.compile(regex); + this.patternCache.putIfAbsent(regex, pattern); } - Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount())); + Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount())); return BooleanTypedValue.forValue(matcher.matches()); } catch (PatternSyntaxException ex) { @@ -94,6 +118,13 @@ public class OperatorMatches extends Operator { } } + private void checkRegexLength(String regex) { + if (regex.length() > MAX_REGEX_LENGTH) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH); + } + } + private static class AccessCount { @@ -111,7 +142,7 @@ public class OperatorMatches extends Operator { private final CharSequence value; - private AccessCount access; + private final AccessCount access; public MatcherInput(CharSequence value, AccessCount access) { this.value = value; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 476fb4c9b7a2bd2611fddc2ad66f6f138c708419..87feb586565ad5a92894a22a5ac979f5b2caaffe 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.springframework.asm.Label; import org.springframework.asm.MethodVisitor; @@ -46,6 +47,7 @@ import org.springframework.util.ReflectionUtils; * @author Andy Clement * @author Juergen Hoeller * @author Clark Duplichien + * @author Sam Brannen * @since 3.0 */ public class PropertyOrFieldReference extends SpelNodeImpl { @@ -147,8 +149,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } @Override - public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException { - writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue); + public TypedValue setValueInternal(ExpressionState state, Supplier valueSupplier) + throws EvaluationException { + + TypedValue typedValue = valueSupplier.get(); + writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, typedValue.getValue()); + return typedValue; } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 3976fbf3425d2e7e1d2768e2bb969de53b987328..daff267578395aa168f5d465b6f683490a0512e2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.util.function.Supplier; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; @@ -40,6 +41,7 @@ import org.springframework.util.ObjectUtils; * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public abstract class SpelNodeImpl implements SpelNode, Opcodes { @@ -64,7 +66,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes { *

The descriptor is like the bytecode form but is slightly easier to work with. * It does not include the trailing semicolon (for non array reference types). * Some examples: Ljava/lang/String, I, [I - */ + */ @Nullable protected volatile String exitTypeDescriptor; @@ -83,8 +85,8 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes { /** - * Return {@code true} if the next child is one of the specified classes. - */ + * Return {@code true} if the next child is one of the specified classes. + */ protected boolean nextChildIs(Class... classes) { if (this.parent != null) { SpelNodeImpl[] peers = this.parent.children; @@ -125,6 +127,28 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes { @Override public void setValue(ExpressionState expressionState, @Nullable Object newValue) throws EvaluationException { + setValueInternal(expressionState, () -> new TypedValue(newValue)); + } + + /** + * Evaluate the expression to a node and then set the new value created by the + * specified {@link Supplier} on that node. + *

For example, if the expression evaluates to a property reference, then the + * property will be set to the new value. + *

Favor this method over {@link #setValue(ExpressionState, Object)} when + * the value should be lazily computed. + *

By default, this method throws a {@link SpelEvaluationException}, + * effectively disabling this feature. Subclasses may override this method to + * provide an actual implementation. + * @param expressionState the current expression state (includes the context) + * @param valueSupplier a supplier of the new value + * @throws EvaluationException if any problem occurs evaluating the expression or + * setting the new value + * @since 5.2.24 + */ + public TypedValue setValueInternal(ExpressionState expressionState, Supplier valueSupplier) + throws EvaluationException { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java index 769e4efedbea1c845e6f557681c078fc0b0455bb..97dae78e902ef2b35665ca0f97fb2fd918b2179e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -17,9 +17,11 @@ package org.springframework.expression.spel.ast; import java.lang.reflect.Modifier; +import java.util.function.Supplier; import org.springframework.asm.MethodVisitor; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; @@ -27,10 +29,11 @@ import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.lang.Nullable; /** - * Represents a variable reference, eg. #someVar. Note this is different to a *local* - * variable like $someVar + * Represents a variable reference — for example, {@code #someVar}. Note + * that this is different than a local variable like {@code $someVar}. * * @author Andy Clement + * @author Sam Brannen * @since 3.0 */ public class VariableReference extends SpelNodeImpl { @@ -53,14 +56,14 @@ public class VariableReference extends SpelNodeImpl { @Override public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException { if (this.name.equals(THIS)) { - return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(),this); + return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(), this); } if (this.name.equals(ROOT)) { - return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(),this); + return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(), this); } TypedValue result = state.lookupVariable(this.name); // a null value will mean either the value was null or the variable was not found - return new VariableRef(this.name,result,state.getEvaluationContext()); + return new VariableRef(this.name, result, state.getEvaluationContext()); } @Override @@ -90,8 +93,10 @@ public class VariableReference extends SpelNodeImpl { } @Override - public void setValue(ExpressionState state, @Nullable Object value) throws SpelEvaluationException { - state.setVariable(this.name, value); + public TypedValue setValueInternal(ExpressionState state, Supplier valueSupplier) + throws EvaluationException { + + return state.assignVariable(this.name, valueSupplier); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index 5b3f69f781a442c5cd42ad74545f0003eea8ed35..062a82f12b6939ac9526b443ae3911f804ec7b78 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -21,12 +21,15 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; import org.springframework.expression.ParseException; import org.springframework.expression.ParserContext; import org.springframework.expression.common.TemplateAwareExpressionParser; import org.springframework.expression.spel.InternalParseException; +import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.SpelParseException; import org.springframework.expression.spel.SpelParserConfiguration; @@ -83,18 +86,21 @@ import org.springframework.util.StringUtils; * @author Andy Clement * @author Juergen Hoeller * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+"); - private final SpelParserConfiguration configuration; // For rules that build nodes, they are stacked here for return private final Deque constructedNodes = new ArrayDeque<>(); + // Shared cache for compiled regex patterns + private final ConcurrentMap patternCache = new ConcurrentHashMap<>(); + // The expression being parsed private String expressionString = ""; @@ -121,6 +127,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { + checkExpressionLength(expressionString); + try { this.expressionString = expressionString; Tokenizer tokenizer = new Tokenizer(expressionString); @@ -142,6 +150,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } } + private void checkExpressionLength(String string) { + if (string != null) { + int maxLength = this.configuration.getMaximumExpressionLength(); + if (string.length() > maxLength) { + throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, maxLength); + } + } + } + // expression // : logicalOrExpression // ( (ASSIGN^ logicalOrExpression) @@ -248,7 +265,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } if (tk == TokenKind.MATCHES) { - return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr); + return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr); } Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected"); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java index d8826e344720d966e349116b2c5dee4f1246a656..1168c9c91a262ba9636a594eb5217f539a26f312 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; @@ -78,6 +79,7 @@ import org.springframework.lang.Nullable; * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Sam Brannen * @since 4.3.15 * @see #forPropertyAccessors * @see #forReadOnlyDataBinding() @@ -200,6 +202,17 @@ public final class SimpleEvaluationContext implements EvaluationContext { return this.operatorOverloader; } + /** + * {@code SimpleEvaluationContext} does not support variable assignment within + * expressions. + * @throws SpelEvaluationException with {@link SpelMessage#VARIABLE_ASSIGNMENT_NOT_SUPPORTED} + * @since 5.2.24 + */ + @Override + public TypedValue assignVariable(String name, Supplier valueSupplier) { + throw new SpelEvaluationException(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED, "#" + name); + } + @Override public void setVariable(String name, @Nullable Object value) { this.variables.put(name, value); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java index 7a682dbd4e58bbe54d26b70f9680765fb1b64efd..eade07374563f78c6fa102a411d2667f85025b59 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -164,6 +164,24 @@ public abstract class AbstractExpressionTests { */ protected void evaluateAndCheckError(String expression, Class expectedReturnType, SpelMessage expectedMessage, Object... otherProperties) { + + evaluateAndCheckError(this.parser, expression, expectedReturnType, expectedMessage, otherProperties); + } + + /** + * Evaluate the specified expression and ensure the expected message comes out. + * The message may have inserts and they will be checked if otherProperties is specified. + * The first entry in otherProperties should always be the position. + * @param parser the expression parser to use + * @param expression the expression to evaluate + * @param expectedReturnType ask the expression return value to be of this type if possible + * ({@code null} indicates don't ask for conversion) + * @param expectedMessage the expected message + * @param otherProperties the expected inserts within the message + */ + protected void evaluateAndCheckError(ExpressionParser parser, String expression, Class expectedReturnType, SpelMessage expectedMessage, + Object... otherProperties) { + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> { Expression expr = parser.parseExpression(expression); assertThat(expr).as("expression").isNotNull(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 98b9704ea336b4ec875a8f81e4281e0ece28a13e..1b0bc6bf14f62a341b55adfbbb8961e9541471a6 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; @@ -38,6 +40,7 @@ import org.springframework.expression.MethodResolver; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.expression.spel.testresources.TestPerson; @@ -59,6 +62,40 @@ import static org.assertj.core.api.Assertions.within; */ public class EvaluationTests extends AbstractExpressionTests { + @Test + void expressionLength() { + String expression = String.format("'X' + '%s'", repeat(" ", 9_992)); + assertThat(expression).hasSize(10_000); + Expression expr = parser.parseExpression(expression); + String result = expr.getValue(context, String.class); + assertThat(result).hasSize(9_993); + assertThat(result.trim()).isEqualTo("X"); + + expression = String.format("'X' + '%s'", repeat(" ", 9_993)); + assertThat(expression).hasSize(10_001); + evaluateAndCheckError(expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED); + } + + @Test + void maxExpressionLengthIsConfigurable() { + int maximumExpressionLength = 20_000; + + String expression = String.format("'%s'", repeat("Y", 19_998)); + assertThat(expression).hasSize(maximumExpressionLength); + + SpelParserConfiguration configuration = + new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength); + ExpressionParser parser = new SpelExpressionParser(configuration); + + Expression expr = parser.parseExpression(expression); + String result = expr.getValue(String.class); + assertThat(result).hasSize(19_998); + + expression = String.format("'%s'", repeat("Y", 25_000)); + assertThat(expression).hasSize(25_002); + evaluateAndCheckError(parser, expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED); + } + @Test public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); @@ -168,39 +205,47 @@ public class EvaluationTests extends AbstractExpressionTests { } @Test - public void testRelOperatorsMatches01() { - evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class); + void matchesTrue() { + evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class); } @Test - public void testRelOperatorsMatches02() { - evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class); + void matchesFalse() { + evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class); } @Test - public void testRelOperatorsMatches03() { - evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null); + void matchesWithInputConversion() { + evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int --> string } @Test - public void testRelOperatorsMatches04() { - evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null); + void matchesWithNullInput() { + evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null); } @Test - public void testRelOperatorsMatches05() { - evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string + void matchesWithNullPattern() { + evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null); } @Test // SPR-16731 - public void testMatchesWithPatternAccessThreshold() { + void matchesWithPatternAccessThreshold() { String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$"; - String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'"; - Expression expr = parser.parseExpression(expression); - assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy( - expr::getValue) - .withCauseInstanceOf(IllegalStateException.class) - .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.FLAWED_PATTERN)); + String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches '" + pattern + "'"; + evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN); + } + + @Test + void matchesWithPatternLengthThreshold() { + String pattern = String.format("^(%s|X)", repeat("12345", 199)); + assertThat(pattern).hasSize(1000); + Expression expr = parser.parseExpression("'X' matches '" + pattern + "'"); + assertThat(expr.getValue(context, Boolean.class)).isTrue(); + + pattern += "?"; + assertThat(pattern).hasSize(1001); + evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED); } // mixing operators @@ -345,9 +390,26 @@ public class EvaluationTests extends AbstractExpressionTests { } // assignment + @Test - public void testAssignmentToVariables01() { - evaluate("#var1='value1'", "value1", String.class); + void assignmentToVariableWithStandardEvaluationContext() { + evaluate("#var1 = 'value1'", "value1", String.class); + } + + @ParameterizedTest + @CsvSource(delimiterString = "->", value = { + "'#var1 = \"value1\"' -> #var1", + "'true ? #myVar = 4 : 0' -> #myVar" + }) + void assignmentToVariableWithSimpleEvaluationContext(String expression, String varName) { + EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); + Expression expr = parser.parseExpression(expression); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> expr.getValue(context)) + .satisfies(ex -> { + assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED); + assertThat(ex.getInserts()).as("inserts").containsExactly(varName); + }); } @Test @@ -1357,6 +1419,15 @@ public class EvaluationTests extends AbstractExpressionTests { } + private static String repeat(String str, int count) { + String result = ""; + for (int i = 0; i < count; i++) { + result += str; + } + return result; + } + + @SuppressWarnings("rawtypes") static class TestClass { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index 92fef3bec695a1a14f8a0f4ee9f6b63da6c6d8f4..83904eb1a68dec0a878cb5c14e40fa92ded1bf04 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -21,10 +21,13 @@ import java.math.BigInteger; import org.junit.jupiter.api.Test; +import org.springframework.expression.Expression; import org.springframework.expression.spel.ast.Operator; import org.springframework.expression.spel.standard.SpelExpression; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.expression.spel.SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED; +import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED; /** * Tests the evaluation of expressions using relational operators. @@ -32,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andy Clement * @author Juergen Hoeller * @author Giovanni Dall'Oglio Risso + * @author Sam Brannen */ public class OperatorTests extends AbstractExpressionTests { @@ -324,11 +328,6 @@ public class OperatorTests extends AbstractExpressionTests { evaluate("3.5", 3.5d, Double.class); } - @Test - public void testMultiplyStringInt() { - evaluate("'a' * 5", "aaaaa", String.class); - } - @Test public void testMultiplyDoubleDoubleGivesDouble() { evaluate("3.0d * 5.0d", 15.0d, Double.class); @@ -393,11 +392,7 @@ public class OperatorTests extends AbstractExpressionTests { evaluate("3.0f + 5.0f", 8.0f, Float.class); evaluate("3.0d + 5.0d", 8.0d, Double.class); evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class); - - evaluate("'ab' + 2", "ab2", String.class); - evaluate("2 + 'a'", "2a", String.class); - evaluate("'ab' + null", "abnull", String.class); - evaluate("null + 'ab'", "nullab", String.class); + evaluate("5 + new Integer('37')", 42, Integer.class); // AST: SpelExpression expr = (SpelExpression)parser.parseExpression("+3"); @@ -406,11 +401,11 @@ public class OperatorTests extends AbstractExpressionTests { assertThat(expr.toStringAST()).isEqualTo("(2 + 3)"); // use as a unary operator - evaluate("+5d",5d,Double.class); - evaluate("+5L",5L,Long.class); - evaluate("+5",5,Integer.class); - evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"),BigDecimal.class); - evaluateAndCheckError("+'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); + evaluate("+5d", 5d, Double.class); + evaluate("+5L", 5L, Long.class); + evaluate("+5", 5, Integer.class); + evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"), BigDecimal.class); + evaluateAndCheckError("+'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); // string concatenation evaluate("'abc'+'def'","abcdef",String.class); @@ -576,6 +571,72 @@ public class OperatorTests extends AbstractExpressionTests { evaluate("'abc' != 'def'", true, Boolean.class); } + @Test + void stringRepeat() { + evaluate("'abc' * 0", "", String.class); + evaluate("'abc' * 1", "abc", String.class); + evaluate("'abc' * 2", "abcabc", String.class); + + Expression expr = parser.parseExpression("'a' * 256"); + assertThat(expr.getValue(context, String.class)).hasSize(256); + + // 4 is the position of the '*' (repeat operator) + evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4); + } + + @Test + void stringConcatenation() { + evaluate("'' + ''", "", String.class); + evaluate("'' + null", "null", String.class); + evaluate("null + ''", "null", String.class); + evaluate("'ab' + null", "abnull", String.class); + evaluate("null + 'ab'", "nullab", String.class); + evaluate("'ab' + 2", "ab2", String.class); + evaluate("2 + 'ab'", "2ab", String.class); + evaluate("'abc' + 'def'", "abcdef", String.class); + + // Text is big but not too big + final int maxSize = 100_000; + context.setVariable("text1", createString(maxSize)); + Expression expr = parser.parseExpression("#text1 + ''"); + assertThat(expr.getValue(context, String.class)).hasSize(maxSize); + + expr = parser.parseExpression("'' + #text1"); + assertThat(expr.getValue(context, String.class)).hasSize(maxSize); + + context.setVariable("text1", createString(maxSize / 2)); + expr = parser.parseExpression("#text1 + #text1"); + assertThat(expr.getValue(context, String.class)).hasSize(maxSize); + + // Text is too big + context.setVariable("text1", createString(maxSize + 1)); + evaluateAndCheckError("#text1 + ''", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("#text1 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("'' + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 3); + evaluateAndCheckError("true + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 5); + + context.setVariable("text1", createString(maxSize / 2)); + context.setVariable("text2", createString((maxSize / 2) + 1)); + evaluateAndCheckError("#text1 + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("#text1 + #text2 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("#text1 + true + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14); + evaluateAndCheckError("true + #text1 + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14); + + evaluateAndCheckError("#text2 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("#text2 + #text1 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + evaluateAndCheckError("#text2 + true + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14); + evaluateAndCheckError("true + #text2 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14); + + context.setVariable("text1", createString((maxSize / 3) + 1)); + evaluateAndCheckError("#text1 + #text1 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 16); + evaluateAndCheckError("(#text1 + #text1) + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 18); + evaluateAndCheckError("#text1 + (#text1 + #text1)", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7); + } + + private static String createString(int size) { + return new String(new char[size]); + } + @Test public void testLongs() { evaluate("3L == 4L", false, Boolean.class); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java index f2a37e55ede53ea156583e987c52146edeaeb600..787489d4761a80224ed0aa53cdb8b31eda6c048b 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -21,7 +21,9 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; import org.springframework.expression.ExpressionException; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelParseException; @@ -35,9 +37,19 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen */ public class SpelParserTests { + @Test // gh-30464 + public void nullExpression() { + ExpressionParser parser = new SpelExpressionParser(); + String expression = null; + Expression expr = parser.parseExpression(expression); + Object result = expr.getValue(); + assertThat(result).isNull(); + } + @Test public void theMostBasic() { SpelExpressionParser parser = new SpelExpressionParser(); diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPatternParser.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPatternParser.java index f7122a53184352077ceb38dad0a36f81c43672fe..c7be4075ac124797caabb7adc172eef92a9efc18 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPatternParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPatternParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -20,6 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.server.PathContainer; +import org.springframework.util.StringUtils; /** * Parser for URI path patterns producing {@link PathPattern} instances that can @@ -103,6 +104,17 @@ public class PathPatternParser { } + /** + * Prepare the given pattern for use in matching to full URL paths. + *

By default, prepend a leading slash if needed for non-empty patterns. + * @param pattern the pattern to initialize + * @return the updated pattern + * @since 5.2.25 + */ + public String initFullPathPattern(String pattern) { + return (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern); + } + /** * Process the path pattern content, a character at a time, breaking it into * path elements around separator boundaries and verifying the structure at each diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java index 006c8fea4623382602ee06dd8f33e93572316fcb..b587686a675ec0a51c3f11887092de9a0b607156 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -109,10 +109,9 @@ public abstract class RequestPredicates { */ public static RequestPredicate path(String pattern) { Assert.notNull(pattern, "'pattern' must not be null"); - if (!pattern.isEmpty() && !pattern.startsWith("/")) { - pattern = "/" + pattern; - } - return pathPredicates(PathPatternParser.defaultInstance).apply(pattern); + PathPatternParser parser = PathPatternParser.defaultInstance; + pattern = parser.initFullPathPattern(pattern); + return pathPredicates(parser).apply(pattern); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index 10296b8d4b83bad8d269356409d6c818ee919c91..9c7a6bf8cbde5b40f5f522e02e8d5e6daf364298 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -28,9 +28,9 @@ import org.springframework.beans.BeansException; import org.springframework.http.server.PathContainer; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; /** * Abstract base class for URL-mapped @@ -185,8 +185,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { Object resolvedHandler = handler; // Parse path pattern - urlPath = prependLeadingSlash(urlPath); - PathPattern pattern = getPathPatternParser().parse(urlPath); + PathPatternParser parser = getPathPatternParser(); + urlPath = parser.initFullPathPattern(urlPath); + PathPattern pattern = parser.parse(urlPath); if (this.handlerMap.containsKey(pattern)) { Object existingHandler = this.handlerMap.get(pattern); if (existingHandler != null && existingHandler != resolvedHandler) { @@ -215,14 +216,4 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { return (handler instanceof String ? "'" + handler + "'" : handler.toString()); } - - private static String prependLeadingSlash(String pattern) { - if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) { - return "/" + pattern; - } - else { - return pattern; - } - } - } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java index b78d8532008509ded2c8358fdadfb94444b7e0d1..211b5da503e4aeca0fa71bb478778c73d183d2ae 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceUrlProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -35,7 +35,6 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; @@ -86,8 +85,9 @@ public class ResourceUrlProvider implements ApplicationListener handlerMap) { this.handlerMap.clear(); handlerMap.forEach((rawPattern, resourceWebHandler) -> { - rawPattern = prependLeadingSlash(rawPattern); - PathPattern pattern = PathPatternParser.defaultInstance.parse(rawPattern); + PathPatternParser parser = PathPatternParser.defaultInstance; + rawPattern = parser.initFullPathPattern(rawPattern); + PathPattern pattern = parser.parse(rawPattern); this.handlerMap.put(pattern, resourceWebHandler); }); } @@ -173,14 +173,4 @@ public class ResourceUrlProvider implements ApplicationListener result = new ArrayList<>(patterns.length); - for (String path : patterns) { - if (StringUtils.hasText(path) && !path.startsWith("/")) { - path = "/" + path; - } - result.add(parser.parse(path)); + for (String pattern : patterns) { + pattern = parser.initFullPathPattern(pattern); + result.add(parser.parse(pattern)); } return result; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java index 342074a81647975b3d60f05a3013c07de2618c54..3f73e59bd0832751abac260ef2029ec1f60d14ef 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RequestPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -107,10 +107,9 @@ public abstract class RequestPredicates { */ public static RequestPredicate path(String pattern) { Assert.notNull(pattern, "'pattern' must not be null"); - if (!pattern.isEmpty() && !pattern.startsWith("/")) { - pattern = "/" + pattern; - } - return pathPredicates(PathPatternParser.defaultInstance).apply(pattern); + PathPatternParser parser = PathPatternParser.defaultInstance; + pattern = parser.initFullPathPattern(pattern); + return pathPredicates(parser).apply(pattern); } /** @@ -333,14 +332,14 @@ public abstract class RequestPredicates { void method(Set methods); /** - * Receive notification of an path predicate. + * Receive notification of a path predicate. * @param pattern the path pattern that makes up the predicate * @see RequestPredicates#path(String) */ void path(String pattern); /** - * Receive notification of an path extension predicate. + * Receive notification of a path extension predicate. * @param extension the path extension that makes up the predicate * @see RequestPredicates#pathExtension(String) */ @@ -426,11 +425,11 @@ public abstract class RequestPredicates { void unknown(RequestPredicate predicate); } + private static class HttpMethodPredicate implements RequestPredicate { private final Set httpMethods; - public HttpMethodPredicate(HttpMethod httpMethod) { Assert.notNull(httpMethod, "HttpMethod must not be null"); this.httpMethods = EnumSet.of(httpMethod); @@ -641,12 +640,14 @@ public abstract class RequestPredicates { } } + private static class PathExtensionPredicate implements RequestPredicate { private final Predicate extensionPredicate; @Nullable private final String extension; + public PathExtensionPredicate(Predicate extensionPredicate) { Assert.notNull(extensionPredicate, "Predicate must not be null"); this.extensionPredicate = extensionPredicate; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java index 03ae39abdfbe1da0e9192a021bd9c19389549fe2..9421de0d80c97fec322126ed2e1f340556eb1460 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMappingIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java index 4062a053405fc12e99a59ddf5be62545fe0b5bfb..8d23ab7b8de4709f9f34205c59ae0d6b82b67c97 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/condition/PatternsRequestCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 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. @@ -34,6 +34,7 @@ import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.util.UrlPathHelper; +import org.springframework.web.util.pattern.PathPatternParser; /** * A logical disjunction (' || ') request condition that matches a request @@ -142,9 +143,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition result = new LinkedHashSet<>(patterns.length); for (String pattern : patterns) { - if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) { - pattern = "/" + pattern; - } + pattern = PathPatternParser.defaultInstance.initFullPathPattern(pattern); result.add(pattern); } return result; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index a437295c923831dc02f3fea5567c1ebcef7207de..efffafe8658f37bf82d6d50db951db57c88853aa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -65,6 +65,7 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.pattern.PathPatternParser; /** * Creates instances of {@link org.springframework.web.util.UriComponentsBuilder} @@ -545,9 +546,7 @@ public class MvcUriComponentsBuilder { String typePath = getClassMapping(controllerType); String methodPath = getMethodMapping(method); String path = pathMatcher.combine(typePath, methodPath); - if (StringUtils.hasLength(path) && !path.startsWith("/")) { - path = "/" + path; - } + path = PathPatternParser.defaultInstance.initFullPathPattern(path); builder.path(path); return applyContributors(builder, method, args);