mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
81 Commits
main
...
v5.2.13.RELEASE
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d85e7f81e | |||
| 940f57d023 | |||
| 29799be474 | |||
| 67979c93c3 | |||
| 4ae3ab14ec | |||
| 81f0d76f3d | |||
| 9f1e822f3e | |||
| b4baa86bfa | |||
| 81be4c22d0 | |||
| a17c2ccb4e | |||
| 64df93168d | |||
| 27c5480c82 | |||
| 594ec8f8a9 | |||
| c09b2513e0 | |||
| 6c22f7ef5e | |||
| 236623f630 | |||
| e18fba1626 | |||
| 070c596a66 | |||
| 7a9bf1578e | |||
| 8a150ee3a4 | |||
| 6051f4ecbd | |||
| 698e74f7cd | |||
| 51079a40ae | |||
| 94ac2e4de7 | |||
| 271a9097ac | |||
| 3c030edbf2 | |||
| 059cff52b7 | |||
| 619a3edae5 | |||
| eb3811e590 | |||
| f011e58f90 | |||
| 7325a863bb | |||
| fe26b7d3fd | |||
| cfdceae70f | |||
| 17647801aa | |||
| 33476a2eae | |||
| a8091b916b | |||
| 396fdf125f | |||
| 085825fbc0 | |||
| b0c6e5e322 | |||
| a7efa9659a | |||
| f7605eaf58 | |||
| ce67b89861 | |||
| 736af46fc0 | |||
| a495bd6679 | |||
| 7c33c70742 | |||
| 81c1b60f19 | |||
| 3703be5aaf | |||
| b929edb221 | |||
| 50803ce142 | |||
| 346445ee7e | |||
| e238c8a87c | |||
| 9e99fd5df2 | |||
| 990c74b1e2 | |||
| 37bda566eb | |||
| 322babc04a | |||
| 4e720e8104 | |||
| fdab75a1d8 | |||
| 240cfb1224 | |||
| db2786264b | |||
| 763fa98bdf | |||
| 25cbc263f0 | |||
| 9949a91048 | |||
| d8b7a593c5 | |||
| d3d8f1a487 | |||
| a637f6a27c | |||
| e9416b369e | |||
| cde95e1446 | |||
| da4e37dc1d | |||
| 010d0947c7 | |||
| 5b06c23a1b | |||
| 7881329cf7 | |||
| 58aa0659cc | |||
| dbbedc6c86 | |||
| 09c1e986b9 | |||
| 10bff054a9 | |||
| e713e0d6d5 | |||
| b4f8fc8177 | |||
| 8c3cdc6118 | |||
| 01827fd8d2 | |||
| af1d721aa3 | |||
| 0acb1e5513 |
+14
-14
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
|
||||
id 'io.spring.gradle-enterprise-conventions' version '0.0.2'
|
||||
id 'io.spring.ge.conventions' version '0.0.7'
|
||||
id 'io.spring.nohttp' version '0.0.5.RELEASE'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
|
||||
id 'org.jetbrains.dokka' version '0.10.1' apply false
|
||||
@@ -8,9 +8,9 @@ plugins {
|
||||
id 'org.asciidoctor.jvm.pdf' version '2.4.0'
|
||||
id 'de.undercouch.download' version '4.1.1'
|
||||
id "io.freefair.aspectj" version '4.1.6' apply false
|
||||
id "com.github.ben-manes.versions" version '0.28.0'
|
||||
id 'com.gradle.build-scan' version '3.2'
|
||||
id "com.jfrog.artifactory" version '4.12.0' apply false
|
||||
id "com.github.ben-manes.versions" version '0.24.0'
|
||||
}
|
||||
|
||||
apply from: "$rootDir/gradle/build-scan-user-data.gradle"
|
||||
@@ -29,7 +29,7 @@ configure(allprojects) { project ->
|
||||
imports {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5"
|
||||
mavenBom "io.netty:netty-bom:4.1.51.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR13"
|
||||
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR17"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.0.3"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
|
||||
@@ -61,7 +61,7 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "io.reactivex:rxjava:1.3.8"
|
||||
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
|
||||
dependency "io.reactivex.rxjava2:rxjava:2.2.19"
|
||||
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
|
||||
|
||||
dependency "com.caucho:hessian:4.0.63"
|
||||
@@ -76,7 +76,7 @@ configure(allprojects) { project ->
|
||||
exclude group: "xpp3", name: "xpp3_min"
|
||||
exclude group: "xmlpull", name: "xmlpull"
|
||||
}
|
||||
dependency "org.apache.johnzon:johnzon-jsonb:1.2.8"
|
||||
dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
|
||||
dependency("org.codehaus.jettison:jettison:1.3.8") {
|
||||
exclude group: "stax", name: "stax-api"
|
||||
}
|
||||
@@ -88,8 +88,8 @@ configure(allprojects) { project ->
|
||||
dependency "org.yaml:snakeyaml:1.27"
|
||||
|
||||
dependency "com.h2database:h2:1.4.200"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
|
||||
dependency "com.github.librepdf:openpdf:1.3.22"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
|
||||
dependency "com.github.librepdf:openpdf:1.3.25"
|
||||
dependency "com.rometools:rome:1.12.2"
|
||||
dependency "commons-io:commons-io:2.5"
|
||||
dependency "io.vavr:vavr:0.10.3"
|
||||
@@ -116,8 +116,8 @@ configure(allprojects) { project ->
|
||||
dependency "net.sf.ehcache:ehcache:2.10.6"
|
||||
dependency "org.ehcache:jcache:1.0.1"
|
||||
dependency "org.ehcache:ehcache:3.4.0"
|
||||
dependency "org.hibernate:hibernate-core:5.4.22.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
|
||||
dependency "org.hibernate:hibernate-core:5.4.28.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.1.7.Final"
|
||||
dependency "org.webjars:webjars-locator-core:0.46"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
@@ -147,7 +147,7 @@ configure(allprojects) { project ->
|
||||
entry 'okhttp'
|
||||
entry 'mockwebserver'
|
||||
}
|
||||
dependency("org.apache.httpcomponents:httpclient:4.5.12") {
|
||||
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.apache.httpcomponents:httpasyncclient:4.1.4") {
|
||||
@@ -180,7 +180,7 @@ configure(allprojects) { project ->
|
||||
dependency "org.testng:testng:6.14.3"
|
||||
dependency "org.hamcrest:hamcrest:2.1"
|
||||
dependency "org.awaitility:awaitility:3.1.6"
|
||||
dependency "org.assertj:assertj-core:3.17.2"
|
||||
dependency "org.assertj:assertj-core:3.18.1"
|
||||
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
|
||||
entry 'xmlunit-assertj'
|
||||
entry('xmlunit-matchers') {
|
||||
@@ -193,7 +193,7 @@ configure(allprojects) { project ->
|
||||
}
|
||||
entry 'mockito-junit-jupiter'
|
||||
}
|
||||
dependency "io.mockk:mockk:1.10.0"
|
||||
dependency "io.mockk:mockk:1.10.2"
|
||||
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.43.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
@@ -225,7 +225,7 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "com.ibm.websphere:uow:6.0.2.17"
|
||||
dependency "com.jamonapi:jamon:2.82"
|
||||
dependency "joda-time:joda-time:2.10.6"
|
||||
dependency "joda-time:joda-time:2.10.10"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
|
||||
dependency "org.javamoney:moneta:1.3"
|
||||
|
||||
@@ -326,7 +326,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "8.36.2"
|
||||
toolVersion = "8.38"
|
||||
configDir = rootProject.file("src/checkstyle")
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.2.11.BUILD-SNAPSHOT
|
||||
version=5.2.13.RELEASE
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
+1
-2
@@ -29,12 +29,11 @@ eclipse.classpath.file.whenMerged { classpath ->
|
||||
classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
|
||||
}
|
||||
|
||||
|
||||
// Use separate main/test outputs (prevents WTP from packaging test classes)
|
||||
eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse")
|
||||
eclipse.classpath.file.beforeMerged { classpath ->
|
||||
classpath.entries.findAll{ it instanceof SourceFolder }.each {
|
||||
if(it.output.startsWith("bin/")) {
|
||||
if (it.output.startsWith("bin/")) {
|
||||
it.output = null
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ include "spring-aop"
|
||||
include "spring-aspects"
|
||||
include "spring-beans"
|
||||
include "spring-context"
|
||||
include "spring-context-support"
|
||||
include "spring-context-indexer"
|
||||
include "spring-context-support"
|
||||
include "spring-core"
|
||||
include "kotlin-coroutines"
|
||||
project(':kotlin-coroutines').projectDir = file('spring-core/kotlin-coroutines')
|
||||
@@ -26,8 +26,8 @@ include "spring-oxm"
|
||||
include "spring-test"
|
||||
include "spring-tx"
|
||||
include "spring-web"
|
||||
include "spring-webmvc"
|
||||
include "spring-webflux"
|
||||
include "spring-webmvc"
|
||||
include "spring-websocket"
|
||||
include "framework-bom"
|
||||
include "integration-tests"
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -219,10 +219,12 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames() {
|
||||
if (this.parameterNames == null) {
|
||||
this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
|
||||
String[] parameterNames = this.parameterNames;
|
||||
if (parameterNames == null) {
|
||||
parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
|
||||
this.parameterNames = parameterNames;
|
||||
}
|
||||
return this.parameterNames;
|
||||
return parameterNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -59,7 +59,8 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearTransactionManagerCache(); // An aspect is basically a singleton
|
||||
// An aspect is basically a singleton -> cleanup on destruction
|
||||
clearTransactionManagerCache();
|
||||
}
|
||||
|
||||
@SuppressAjWarnings("adviceDidNotMatch")
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -422,9 +422,12 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw createNotWritablePropertyException(tokens.canonicalName);
|
||||
if (this.suppressNotWritablePropertyException) {
|
||||
// Optimization for common ignoreUnknown=true scenario since the
|
||||
// exception would be caught and swallowed higher up anyway...
|
||||
return;
|
||||
}
|
||||
throw createNotWritablePropertyException(tokens.canonicalName);
|
||||
}
|
||||
|
||||
Object oldValue = null;
|
||||
@@ -806,7 +809,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
* @param propertyPath property path, which may be nested
|
||||
* @return a property accessor for the target bean
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // avoid nested generic
|
||||
protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
|
||||
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
|
||||
// Handle nested properties recursively.
|
||||
|
||||
+33
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -40,6 +40,8 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
|
||||
|
||||
private boolean autoGrowNestedPaths = false;
|
||||
|
||||
boolean suppressNotWritablePropertyException = false;
|
||||
|
||||
|
||||
@Override
|
||||
public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
|
||||
@@ -89,30 +91,41 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
|
||||
List<PropertyAccessException> propertyAccessExceptions = null;
|
||||
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
|
||||
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
|
||||
for (PropertyValue pv : propertyValues) {
|
||||
try {
|
||||
// This method may throw any BeansException, which won't be caught
|
||||
|
||||
if (ignoreUnknown) {
|
||||
this.suppressNotWritablePropertyException = true;
|
||||
}
|
||||
try {
|
||||
for (PropertyValue pv : propertyValues) {
|
||||
// setPropertyValue may throw any BeansException, which won't be caught
|
||||
// here, if there is a critical failure such as no matching field.
|
||||
// We can attempt to deal only with less serious exceptions.
|
||||
setPropertyValue(pv);
|
||||
}
|
||||
catch (NotWritablePropertyException ex) {
|
||||
if (!ignoreUnknown) {
|
||||
throw ex;
|
||||
try {
|
||||
setPropertyValue(pv);
|
||||
}
|
||||
// Otherwise, just ignore it and continue...
|
||||
}
|
||||
catch (NullValueInNestedPathException ex) {
|
||||
if (!ignoreInvalid) {
|
||||
throw ex;
|
||||
catch (NotWritablePropertyException ex) {
|
||||
if (!ignoreUnknown) {
|
||||
throw ex;
|
||||
}
|
||||
// Otherwise, just ignore it and continue...
|
||||
}
|
||||
// Otherwise, just ignore it and continue...
|
||||
}
|
||||
catch (PropertyAccessException ex) {
|
||||
if (propertyAccessExceptions == null) {
|
||||
propertyAccessExceptions = new ArrayList<>();
|
||||
catch (NullValueInNestedPathException ex) {
|
||||
if (!ignoreInvalid) {
|
||||
throw ex;
|
||||
}
|
||||
// Otherwise, just ignore it and continue...
|
||||
}
|
||||
propertyAccessExceptions.add(ex);
|
||||
catch (PropertyAccessException ex) {
|
||||
if (propertyAccessExceptions == null) {
|
||||
propertyAccessExceptions = new ArrayList<>();
|
||||
}
|
||||
propertyAccessExceptions.add(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (ignoreUnknown) {
|
||||
this.suppressNotWritablePropertyException = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -506,6 +506,7 @@ public abstract class BeanUtils {
|
||||
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassLoader cl = targetType.getClassLoader();
|
||||
if (cl == null) {
|
||||
try {
|
||||
@@ -522,28 +523,34 @@ public abstract class BeanUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String targetTypeName = targetType.getName();
|
||||
String editorName = targetTypeName + "Editor";
|
||||
try {
|
||||
Class<?> editorClass = cl.loadClass(editorName);
|
||||
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Editor class [" + editorName +
|
||||
"] does not implement [java.beans.PropertyEditor] interface");
|
||||
if (editorClass != null) {
|
||||
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Editor class [" + editorName +
|
||||
"] does not implement [java.beans.PropertyEditor] interface");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
return (PropertyEditor) instantiateClass(editorClass);
|
||||
}
|
||||
return (PropertyEditor) instantiateClass(editorClass);
|
||||
// Misbehaving ClassLoader returned null instead of ClassNotFoundException
|
||||
// - fall back to unknown editor type registration below
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No property editor [" + editorName + "] found for type " +
|
||||
targetTypeName + " according to 'Editor' suffix convention");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
// Ignore - fall back to unknown editor type registration below
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No property editor [" + editorName + "] found for type " +
|
||||
targetTypeName + " according to 'Editor' suffix convention");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -92,8 +92,7 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
|
||||
@Override
|
||||
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
|
||||
PropertyMatches matches = PropertyMatches.forField(propertyName, getRootClass());
|
||||
throw new NotWritablePropertyException(
|
||||
getRootClass(), getNestedPath() + propertyName,
|
||||
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
|
||||
matches.buildErrorMessage(), matches.getPossibleMatches());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException {
|
||||
private String propertyName;
|
||||
|
||||
@Nullable
|
||||
private transient Object value;
|
||||
private final transient Object value;
|
||||
|
||||
@Nullable
|
||||
private Class<?> requiredType;
|
||||
private final Class<?> requiredType;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+108
-85
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -613,7 +613,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
|
||||
private final boolean required;
|
||||
|
||||
private volatile boolean cached = false;
|
||||
private volatile boolean cached;
|
||||
|
||||
@Nullable
|
||||
private volatile Object cachedFieldValue;
|
||||
@@ -628,46 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
Field field = (Field) this.member;
|
||||
Object value;
|
||||
if (this.cached) {
|
||||
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
|
||||
try {
|
||||
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Unexpected removal of target bean for cached argument -> re-resolve
|
||||
value = resolveFieldValue(field, bean, beanName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
|
||||
desc.setContainingClass(bean.getClass());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
try {
|
||||
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (value != null || this.required) {
|
||||
this.cachedFieldValue = desc;
|
||||
registerDependentBeans(beanName, autowiredBeanNames);
|
||||
if (autowiredBeanNames.size() == 1) {
|
||||
String autowiredBeanName = autowiredBeanNames.iterator().next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
|
||||
this.cachedFieldValue = new ShortcutDependencyDescriptor(
|
||||
desc, autowiredBeanName, field.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.cachedFieldValue = null;
|
||||
}
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
value = resolveFieldValue(field, bean, beanName);
|
||||
}
|
||||
if (value != null) {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
field.set(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
|
||||
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
|
||||
desc.setContainingClass(bean.getClass());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
Object value;
|
||||
try {
|
||||
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
Object cachedFieldValue = null;
|
||||
if (value != null || this.required) {
|
||||
cachedFieldValue = desc;
|
||||
registerDependentBeans(beanName, autowiredBeanNames);
|
||||
if (autowiredBeanNames.size() == 1) {
|
||||
String autowiredBeanName = autowiredBeanNames.iterator().next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
|
||||
cachedFieldValue = new ShortcutDependencyDescriptor(
|
||||
desc, autowiredBeanName, field.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedFieldValue = cachedFieldValue;
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -678,7 +690,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
|
||||
private final boolean required;
|
||||
|
||||
private volatile boolean cached = false;
|
||||
private volatile boolean cached;
|
||||
|
||||
@Nullable
|
||||
private volatile Object[] cachedMethodArguments;
|
||||
@@ -696,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
Method method = (Method) this.member;
|
||||
Object[] arguments;
|
||||
if (this.cached) {
|
||||
// Shortcut for avoiding synchronization...
|
||||
arguments = resolveCachedArguments(beanName);
|
||||
try {
|
||||
arguments = resolveCachedArguments(beanName);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Unexpected removal of target bean for cached argument -> re-resolve
|
||||
arguments = resolveMethodArguments(method, bean, beanName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int argumentCount = method.getParameterCount();
|
||||
arguments = new Object[argumentCount];
|
||||
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
|
||||
currDesc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = currDesc;
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
|
||||
if (arg == null && !this.required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments[i] = arg;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (arguments != null) {
|
||||
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
|
||||
registerDependentBeans(beanName, autowiredBeans);
|
||||
if (autowiredBeans.size() == argumentCount) {
|
||||
Iterator<String> it = autowiredBeans.iterator();
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
String autowiredBeanName = it.next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
|
||||
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
|
||||
descriptors[i], autowiredBeanName, paramTypes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedMethodArguments = cachedMethodArguments;
|
||||
}
|
||||
else {
|
||||
this.cachedMethodArguments = null;
|
||||
}
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
arguments = resolveMethodArguments(method, bean, beanName);
|
||||
}
|
||||
if (arguments != null) {
|
||||
try {
|
||||
@@ -772,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
|
||||
int argumentCount = method.getParameterCount();
|
||||
Object[] arguments = new Object[argumentCount];
|
||||
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
|
||||
currDesc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = currDesc;
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
|
||||
if (arg == null && !this.required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments[i] = arg;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (arguments != null) {
|
||||
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
|
||||
registerDependentBeans(beanName, autowiredBeans);
|
||||
if (autowiredBeans.size() == argumentCount) {
|
||||
Iterator<String> it = autowiredBeans.iterator();
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
String autowiredBeanName = it.next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
|
||||
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
|
||||
descriptors[i], autowiredBeanName, paramTypes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedMethodArguments = cachedMethodArguments;
|
||||
}
|
||||
else {
|
||||
this.cachedMethodArguments = null;
|
||||
}
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-14
@@ -26,9 +26,6 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
@@ -69,8 +66,6 @@ public class InjectionMetadata {
|
||||
};
|
||||
|
||||
|
||||
private static final Log logger = LogFactory.getLog(InjectionMetadata.class);
|
||||
|
||||
private final Class<?> targetClass;
|
||||
|
||||
private final Collection<InjectedElement> injectedElements;
|
||||
@@ -110,9 +105,6 @@ public class InjectionMetadata {
|
||||
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
|
||||
beanDefinition.registerExternallyManagedConfigMember(member);
|
||||
checkedElements.add(element);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checkedElements = checkedElements;
|
||||
@@ -124,9 +116,6 @@ public class InjectionMetadata {
|
||||
(checkedElements != null ? checkedElements : this.injectedElements);
|
||||
if (!elementsToIterate.isEmpty()) {
|
||||
for (InjectedElement element : elementsToIterate) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
|
||||
}
|
||||
element.inject(target, beanName, pvs);
|
||||
}
|
||||
}
|
||||
@@ -152,12 +141,12 @@ public class InjectionMetadata {
|
||||
* Return an {@code InjectionMetadata} instance, possibly for empty elements.
|
||||
* @param elements the elements to inject (possibly empty)
|
||||
* @param clazz the target class
|
||||
* @return a new {@link #InjectionMetadata(Class, Collection)} instance,
|
||||
* or {@link #EMPTY} in case of no elements
|
||||
* @return a new {@link #InjectionMetadata(Class, Collection)} instance
|
||||
* @since 5.2
|
||||
*/
|
||||
public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
|
||||
return (elements.isEmpty() ? InjectionMetadata.EMPTY : new InjectionMetadata(clazz, elements));
|
||||
return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) :
|
||||
new InjectionMetadata(clazz, elements));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -1013,6 +1013,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
throw ex;
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
// Don't swallow a linkage error since it contains a full stacktrace on
|
||||
// first occurrence... and just a plain NoClassDefFoundError afterwards.
|
||||
if (ex.contains(LinkageError.class)) {
|
||||
throw ex;
|
||||
}
|
||||
// Instantiation failure, maybe too early...
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
|
||||
|
||||
+23
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -1194,8 +1194,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
this.primary == that.primary &&
|
||||
this.nonPublicAccessAllowed == that.nonPublicAccessAllowed &&
|
||||
this.lenientConstructorResolution == that.lenientConstructorResolution &&
|
||||
ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) &&
|
||||
ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) &&
|
||||
equalsConstructorArgumentValues(that) &&
|
||||
equalsPropertyValues(that) &&
|
||||
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
|
||||
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
|
||||
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
|
||||
@@ -1208,12 +1208,30 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
super.equals(other));
|
||||
}
|
||||
|
||||
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
|
||||
if (!hasConstructorArgumentValues()) {
|
||||
return !other.hasConstructorArgumentValues();
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues);
|
||||
}
|
||||
|
||||
private boolean equalsPropertyValues(AbstractBeanDefinition other) {
|
||||
if (!hasPropertyValues()) {
|
||||
return !other.hasPropertyValues();
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName());
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
|
||||
if (hasConstructorArgumentValues()) {
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
|
||||
}
|
||||
if (hasPropertyValues()) {
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
|
||||
}
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName);
|
||||
hashCode = 29 * hashCode + super.hashCode();
|
||||
|
||||
+7
-5
@@ -66,6 +66,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Delegate for resolving constructors and factory methods.
|
||||
*
|
||||
* <p>Performs constructor resolution through argument matching.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
@@ -84,7 +85,7 @@ class ConstructorResolver {
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
/**
|
||||
* Marker for autowired arguments in a cached argument array, to be later replaced
|
||||
* Marker for autowired arguments in a cached argument array, to be replaced
|
||||
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
|
||||
*/
|
||||
private static final Object autowiredArgumentMarker = new Object();
|
||||
@@ -148,7 +149,7 @@ class ConstructorResolver {
|
||||
}
|
||||
}
|
||||
if (argsToResolve != null) {
|
||||
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
|
||||
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,6 +410,7 @@ class ConstructorResolver {
|
||||
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
|
||||
throw new ImplicitlyAppearedSingletonException();
|
||||
}
|
||||
this.beanFactory.registerDependentBean(factoryBeanName, beanName);
|
||||
factoryClass = factoryBean.getClass();
|
||||
isStatic = false;
|
||||
}
|
||||
@@ -443,7 +445,7 @@ class ConstructorResolver {
|
||||
}
|
||||
}
|
||||
if (argsToResolve != null) {
|
||||
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);
|
||||
argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +817,7 @@ class ConstructorResolver {
|
||||
* Resolve the prepared arguments stored in the given bean definition.
|
||||
*/
|
||||
private Object[] resolvePreparedArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
|
||||
Executable executable, Object[] argsToResolve, boolean fallback) {
|
||||
Executable executable, Object[] argsToResolve) {
|
||||
|
||||
TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
|
||||
TypeConverter converter = (customConverter != null ? customConverter : bw);
|
||||
@@ -828,7 +830,7 @@ class ConstructorResolver {
|
||||
Object argValue = argsToResolve[argIndex];
|
||||
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
|
||||
if (argValue == autowiredArgumentMarker) {
|
||||
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, fallback);
|
||||
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
|
||||
}
|
||||
else if (argValue instanceof BeanMetadataElement) {
|
||||
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
|
||||
|
||||
+77
-76
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -87,7 +87,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
private transient Method destroyMethod;
|
||||
|
||||
@Nullable
|
||||
private List<DestructionAwareBeanPostProcessor> beanPostProcessors;
|
||||
private final List<DestructionAwareBeanPostProcessor> beanPostProcessors;
|
||||
|
||||
|
||||
/**
|
||||
@@ -120,14 +120,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
}
|
||||
else {
|
||||
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
|
||||
if (paramTypes.length > 1) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has more than one parameter - not supported as destroy method");
|
||||
}
|
||||
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has a non-boolean parameter - not supported as destroy method");
|
||||
if (destroyMethod.getParameterCount() > 0) {
|
||||
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
|
||||
if (paramTypes.length > 1) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has more than one parameter - not supported as destroy method");
|
||||
}
|
||||
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has a non-boolean parameter - not supported as destroy method");
|
||||
}
|
||||
}
|
||||
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod);
|
||||
}
|
||||
@@ -169,67 +171,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the current value of the given beanDefinition's "destroyMethodName" property is
|
||||
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
|
||||
* Candidate methods are currently limited to public, no-arg methods named "close" or
|
||||
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
|
||||
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
|
||||
* to the name of the inferred method. This constant serves as the default for the
|
||||
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
|
||||
* used in XML within the {@code <bean destroy-method="">} or {@code
|
||||
* <beans default-destroy-method="">} attributes.
|
||||
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
|
||||
* interfaces, reflectively calling the "close" method on implementing beans as well.
|
||||
*/
|
||||
@Nullable
|
||||
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
|
||||
String destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
|
||||
// Only perform destroy method inference or Closeable detection
|
||||
// in case of the bean not explicitly implementing DisposableBean
|
||||
if (!(bean instanceof DisposableBean)) {
|
||||
try {
|
||||
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
try {
|
||||
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex2) {
|
||||
// no candidate destroy method found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all DestructionAwareBeanPostProcessors in the List.
|
||||
* @param processors the List to search
|
||||
* @return the filtered List of DestructionAwareBeanPostProcessors
|
||||
*/
|
||||
@Nullable
|
||||
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
|
||||
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
|
||||
if (!CollectionUtils.isEmpty(processors)) {
|
||||
filteredPostProcessors = new ArrayList<>(processors.size());
|
||||
for (BeanPostProcessor processor : processors) {
|
||||
if (processor instanceof DestructionAwareBeanPostProcessor) {
|
||||
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
|
||||
if (dabpp.requiresDestruction(bean)) {
|
||||
filteredPostProcessors.add(dabpp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredPostProcessors;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
destroy();
|
||||
@@ -384,12 +325,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
|
||||
return true;
|
||||
}
|
||||
String destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
|
||||
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
|
||||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
|
||||
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the current value of the given beanDefinition's "destroyMethodName" property is
|
||||
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
|
||||
* Candidate methods are currently limited to public, no-arg methods named "close" or
|
||||
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
|
||||
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
|
||||
* to the name of the inferred method. This constant serves as the default for the
|
||||
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
|
||||
* used in XML within the {@code <bean destroy-method="">} or {@code
|
||||
* <beans default-destroy-method="">} attributes.
|
||||
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
|
||||
* interfaces, reflectively calling the "close" method on implementing beans as well.
|
||||
*/
|
||||
@Nullable
|
||||
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
|
||||
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
|
||||
if (destroyMethodName == null) {
|
||||
destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
|
||||
// Only perform destroy method inference or Closeable detection
|
||||
// in case of the bean not explicitly implementing DisposableBean
|
||||
destroyMethodName = null;
|
||||
if (!(bean instanceof DisposableBean)) {
|
||||
try {
|
||||
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
try {
|
||||
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex2) {
|
||||
// no candidate destroy method found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
|
||||
}
|
||||
return StringUtils.hasLength(destroyMethodName);
|
||||
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,4 +390,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all DestructionAwareBeanPostProcessors in the List.
|
||||
* @param processors the List to search
|
||||
* @return the filtered List of DestructionAwareBeanPostProcessors
|
||||
*/
|
||||
@Nullable
|
||||
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
|
||||
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
|
||||
if (!CollectionUtils.isEmpty(processors)) {
|
||||
filteredPostProcessors = new ArrayList<>(processors.size());
|
||||
for (BeanPostProcessor processor : processors) {
|
||||
if (processor instanceof DestructionAwareBeanPostProcessor) {
|
||||
DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
|
||||
if (dabpp.requiresDestruction(bean)) {
|
||||
filteredPostProcessors.add(dabpp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredPostProcessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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,7 +65,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
|
||||
boolean allowCaching = true;
|
||||
|
||||
boolean isFactoryMethodUnique = false;
|
||||
boolean isFactoryMethodUnique;
|
||||
|
||||
@Nullable
|
||||
volatile ResolvableType targetType;
|
||||
@@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
@Nullable
|
||||
volatile Method factoryMethodToIntrospect;
|
||||
|
||||
/** Package-visible field for caching a resolved destroy method name (also for inferred). */
|
||||
@Nullable
|
||||
volatile String resolvedDestroyMethodName;
|
||||
|
||||
/** Common lock for the four constructor fields below. */
|
||||
final Object constructorArgumentLock = new Object();
|
||||
|
||||
|
||||
+16
-11
@@ -63,9 +63,8 @@ public class BeanFactoryUtilsTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
public void setup() {
|
||||
// Interesting hierarchical factory to test counts.
|
||||
// Slow to read so we cache it.
|
||||
|
||||
DefaultListableBeanFactory grandParent = new DefaultListableBeanFactory();
|
||||
new XmlBeanDefinitionReader(grandParent).loadBeanDefinitions(ROOT_CONTEXT);
|
||||
@@ -93,7 +92,7 @@ public class BeanFactoryUtilsTests {
|
||||
* Check that override doesn't count as two separate beans.
|
||||
*/
|
||||
@Test
|
||||
public void testHierarchicalCountBeansWithOverride() throws Exception {
|
||||
public void testHierarchicalCountBeansWithOverride() {
|
||||
// Leaf count
|
||||
assertThat(this.listableBeanFactory.getBeanDefinitionCount() == 1).isTrue();
|
||||
// Count minus duplicate
|
||||
@@ -101,14 +100,14 @@ public class BeanFactoryUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchicalNamesWithNoMatch() throws Exception {
|
||||
public void testHierarchicalNamesWithNoMatch() {
|
||||
List<String> names = Arrays.asList(
|
||||
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, NoOp.class));
|
||||
assertThat(names.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchicalNamesWithMatchOnlyInRoot() throws Exception {
|
||||
public void testHierarchicalNamesWithMatchOnlyInRoot() {
|
||||
List<String> names = Arrays.asList(
|
||||
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, IndexedTestBean.class));
|
||||
assertThat(names.size()).isEqualTo(1);
|
||||
@@ -118,7 +117,7 @@ public class BeanFactoryUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBeanNamesForTypeWithOverride() throws Exception {
|
||||
public void testGetBeanNamesForTypeWithOverride() {
|
||||
List<String> names = Arrays.asList(
|
||||
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.listableBeanFactory, ITestBean.class));
|
||||
// includes 2 TestBeans from FactoryBeans (DummyFactory definitions)
|
||||
@@ -236,7 +235,7 @@ public class BeanFactoryUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchicalResolutionWithOverride() throws Exception {
|
||||
public void testHierarchicalResolutionWithOverride() {
|
||||
Object test3 = this.listableBeanFactory.getBean("test3");
|
||||
Object test = this.listableBeanFactory.getBean("test");
|
||||
|
||||
@@ -276,14 +275,14 @@ public class BeanFactoryUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchicalNamesForAnnotationWithNoMatch() throws Exception {
|
||||
public void testHierarchicalNamesForAnnotationWithNoMatch() {
|
||||
List<String> names = Arrays.asList(
|
||||
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, Override.class));
|
||||
assertThat(names.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() throws Exception {
|
||||
public void testHierarchicalNamesForAnnotationWithMatchOnlyInRoot() {
|
||||
List<String> names = Arrays.asList(
|
||||
BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.listableBeanFactory, TestAnnotation.class));
|
||||
assertThat(names.size()).isEqualTo(1);
|
||||
@@ -293,7 +292,7 @@ public class BeanFactoryUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBeanNamesForAnnotationWithOverride() throws Exception {
|
||||
public void testGetBeanNamesForAnnotationWithOverride() {
|
||||
AnnotatedBean annotatedBean = new AnnotatedBean();
|
||||
this.listableBeanFactory.registerSingleton("anotherAnnotatedBean", annotatedBean);
|
||||
List<String> names = Arrays.asList(
|
||||
@@ -433,6 +432,7 @@ public class BeanFactoryUtilsTests {
|
||||
String basePackage() default "";
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ControllerAdvice
|
||||
@interface RestControllerAdvice {
|
||||
@@ -444,18 +444,23 @@ public class BeanFactoryUtilsTests {
|
||||
String basePackage() default "";
|
||||
}
|
||||
|
||||
|
||||
@ControllerAdvice("com.example")
|
||||
static class ControllerAdviceClass {
|
||||
}
|
||||
|
||||
|
||||
@RestControllerAdvice("com.example")
|
||||
static class RestControllerAdviceClass {
|
||||
}
|
||||
|
||||
|
||||
static class TestBeanSmartFactoryBean implements SmartFactoryBean<TestBean> {
|
||||
|
||||
private final TestBean testBean = new TestBean("enigma", 42);
|
||||
|
||||
private final boolean singleton;
|
||||
|
||||
private final boolean prototype;
|
||||
|
||||
TestBeanSmartFactoryBean(boolean singleton, boolean prototype) {
|
||||
@@ -478,7 +483,7 @@ public class BeanFactoryUtilsTests {
|
||||
return TestBean.class;
|
||||
}
|
||||
|
||||
public TestBean getObject() throws Exception {
|
||||
public TestBean getObject() {
|
||||
// We don't really care if the actual instance is a singleton or prototype
|
||||
// for the tests that use this factory.
|
||||
return this.testBean;
|
||||
|
||||
+16
-22
@@ -796,12 +796,13 @@ class DefaultListableBeanFactoryTests {
|
||||
factory.registerBeanDefinition("child", childDefinition);
|
||||
factory.registerAlias("parent", "alias");
|
||||
|
||||
TestBean child = (TestBean) factory.getBean("child");
|
||||
TestBean child = factory.getBean("child", TestBean.class);
|
||||
assertThat(child.getName()).isEqualTo(EXPECTED_NAME);
|
||||
assertThat(child.getAge()).isEqualTo(EXPECTED_AGE);
|
||||
Object mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
|
||||
BeanDefinition mergedBeanDefinition1 = factory.getMergedBeanDefinition("child");
|
||||
BeanDefinition mergedBeanDefinition2 = factory.getMergedBeanDefinition("child");
|
||||
|
||||
assertThat(mergedBeanDefinition2).as("Use cached merged bean definition").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(mergedBeanDefinition1).as("Use cached merged bean definition").isSameAs(mergedBeanDefinition2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1849,8 +1850,7 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(factoryBean).as("The FactoryBean should have been registered.").isNotNull();
|
||||
FactoryBeanDependentBean bean = (FactoryBeanDependentBean) lbf.autowire(FactoryBeanDependentBean.class,
|
||||
AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
|
||||
Object mergedBeanDefinition2 = bean.getFactoryBean();
|
||||
assertThat(mergedBeanDefinition2).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(bean.getFactoryBean()).as("The FactoryBeanDependentBean should have been autowired 'by type' with the LazyInitFactory.").isEqualTo(factoryBean);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2556,8 +2556,7 @@ class DefaultListableBeanFactoryTests {
|
||||
BeanWithDestroyMethod.closeCount = 0;
|
||||
lbf.preInstantiateSingletons();
|
||||
lbf.destroySingletons();
|
||||
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
|
||||
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2571,8 +2570,7 @@ class DefaultListableBeanFactoryTests {
|
||||
BeanWithDestroyMethod.closeCount = 0;
|
||||
lbf.preInstantiateSingletons();
|
||||
lbf.destroySingletons();
|
||||
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
|
||||
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2587,8 +2585,7 @@ class DefaultListableBeanFactoryTests {
|
||||
BeanWithDestroyMethod.closeCount = 0;
|
||||
lbf.preInstantiateSingletons();
|
||||
lbf.destroySingletons();
|
||||
Object mergedBeanDefinition2 = BeanWithDestroyMethod.closeCount;
|
||||
assertThat(mergedBeanDefinition2).as("Destroy methods invoked").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(BeanWithDestroyMethod.closeCount).as("Destroy methods invoked").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2710,14 +2707,15 @@ class DefaultListableBeanFactoryTests {
|
||||
factory.registerBeanDefinition("child", child);
|
||||
|
||||
AbstractBeanDefinition def = (AbstractBeanDefinition) factory.getBeanDefinition("child");
|
||||
Object mergedBeanDefinition2 = def.getScope();
|
||||
assertThat(mergedBeanDefinition2).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(def.getScope()).as("Child 'scope' not overriding parent scope (it must).").isEqualTo(theChildScope);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scopeInheritanceForChildBeanDefinitions() {
|
||||
String theParentScope = "bonanza!";
|
||||
|
||||
RootBeanDefinition parent = new RootBeanDefinition();
|
||||
parent.setScope("bonanza!");
|
||||
parent.setScope(theParentScope);
|
||||
|
||||
AbstractBeanDefinition child = new ChildBeanDefinition("parent");
|
||||
child.setBeanClass(TestBean.class);
|
||||
@@ -2727,8 +2725,7 @@ class DefaultListableBeanFactoryTests {
|
||||
factory.registerBeanDefinition("child", child);
|
||||
|
||||
BeanDefinition def = factory.getMergedBeanDefinition("child");
|
||||
Object mergedBeanDefinition2 = def.getScope();
|
||||
assertThat(mergedBeanDefinition2).as("Child 'scope' not inherited").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(def.getScope()).as("Child 'scope' not inherited").isEqualTo(theParentScope);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2764,15 +2761,12 @@ class DefaultListableBeanFactoryTests {
|
||||
});
|
||||
lbf.preInstantiateSingletons();
|
||||
TestBean tb = (TestBean) lbf.getBean("test");
|
||||
Object mergedBeanDefinition2 = tb.getName();
|
||||
assertThat(mergedBeanDefinition2).as("Name was set on field by IAPP").isEqualTo(mergedBeanDefinition2);
|
||||
assertThat(tb.getName()).as("Name was set on field by IAPP").isEqualTo(nameSetOnField);
|
||||
if (!skipPropertyPopulation) {
|
||||
Object mergedBeanDefinition21 = tb.getAge();
|
||||
assertThat(mergedBeanDefinition21).as("Property value still set").isEqualTo(mergedBeanDefinition21);
|
||||
assertThat(tb.getAge()).as("Property value still set").isEqualTo(ageSetByPropertyValue);
|
||||
}
|
||||
else {
|
||||
Object mergedBeanDefinition21 = tb.getAge();
|
||||
assertThat(mergedBeanDefinition21).as("Property value was NOT set and still has default value").isEqualTo(mergedBeanDefinition21);
|
||||
assertThat(tb.getAge()).as("Property value was NOT set and still has default value").isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+193
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalResourceInjectionWithSingletonRemoval() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
|
||||
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", rbd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
IndexedTestBean itb = new IndexedTestBean();
|
||||
bf.registerSingleton("indexedTestBean", itb);
|
||||
NestedTestBean ntb1 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean1", ntb1);
|
||||
NestedTestBean ntb2 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean2", ntb2);
|
||||
|
||||
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.destroySingleton("testBean");
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
assertThat(bean.getTestBean4()).isNull();
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
|
||||
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", rbd);
|
||||
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
|
||||
IndexedTestBean itb = new IndexedTestBean();
|
||||
bf.registerSingleton("indexedTestBean", itb);
|
||||
NestedTestBean ntb1 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean1", ntb1);
|
||||
NestedTestBean ntb2 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean2", ntb2);
|
||||
|
||||
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.removeBeanDefinition("testBean");
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
assertThat(bean.getTestBean4()).isNull();
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCollectionResourceInjection() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class);
|
||||
@@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithSingletonRemoval() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
NestedTestBean ntb = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean", ntb);
|
||||
|
||||
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.destroySingleton("nestedTestBean");
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isNull();
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.registerSingleton("nestedTestBean", ntb);
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
|
||||
|
||||
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.removeBeanDefinition("nestedTestBean");
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isNull();
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithNullFromFactoryBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -133,6 +133,16 @@ public class BeanDefinitionTests {
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
|
||||
bd.getPropertyValues();
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
|
||||
bd.getConstructorArgumentValues();
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||
*
|
||||
* <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation
|
||||
* does not cause the advised method to be skipped. Rather, it always causes the
|
||||
* method to be invoked and its result to be stored in the associated cache. Note
|
||||
* method to be invoked and its result to be stored in the associated cache if the
|
||||
* {@link #condition()} and {@link #unless()} expressions match accordingly. Note
|
||||
* that Java8's {@code Optional} return types are automatically handled and its
|
||||
* content is stored in the cache if present.
|
||||
*
|
||||
@@ -118,10 +119,15 @@ public @interface CachePut {
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used for making the cache
|
||||
* put operation conditional.
|
||||
* <p>This expression is evaluated after the method has been called due to the
|
||||
* nature of the put operation and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning the method result is always cached.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
* <ul>
|
||||
* <li>{@code #result} for a reference to the result of the method invocation. For
|
||||
* supported wrappers such as {@code Optional}, {@code #result} refers to the actual
|
||||
* object, not the wrapper</li>
|
||||
* <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
|
||||
* references to the {@link java.lang.reflect.Method method}, target object, and
|
||||
* affected cache(s) respectively.</li>
|
||||
@@ -136,8 +142,6 @@ public @interface CachePut {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
|
||||
* <p>Unlike {@link #condition}, this expression is evaluated after the method
|
||||
* has been called and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning that caching is never vetoed.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
|
||||
+9
@@ -43,6 +43,15 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
|
||||
|
||||
/**
|
||||
* A convenient constant for a default {@code FullyQualifiedAnnotationBeanNameGenerator}
|
||||
* instance, as used for configuration-level import purposes.
|
||||
* @since 5.2.11
|
||||
*/
|
||||
public static final FullyQualifiedAnnotationBeanNameGenerator INSTANCE =
|
||||
new FullyQualifiedAnnotationBeanNameGenerator();
|
||||
|
||||
|
||||
@Override
|
||||
protected String buildDefaultBeanName(BeanDefinition definition) {
|
||||
String beanClassName = definition.getBeanClassName();
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -106,7 +106,6 @@ public @interface DateTimeFormat {
|
||||
/**
|
||||
* The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX},
|
||||
* e.g. "2000-10-31T01:30:00.000-05:00".
|
||||
* <p>This is the default if no annotation value is specified.
|
||||
*/
|
||||
DATE_TIME,
|
||||
|
||||
|
||||
+30
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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,8 +17,10 @@
|
||||
package org.springframework.validation.beanvalidation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -291,12 +293,17 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
|
||||
}
|
||||
|
||||
List<InputStream> mappingStreams = null;
|
||||
if (this.mappingLocations != null) {
|
||||
mappingStreams = new ArrayList<>(this.mappingLocations.length);
|
||||
for (Resource location : this.mappingLocations) {
|
||||
try {
|
||||
configuration.addMapping(location.getInputStream());
|
||||
InputStream stream = location.getInputStream();
|
||||
mappingStreams.add(stream);
|
||||
configuration.addMapping(stream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
closeMappingStreams(mappingStreams);
|
||||
throw new IllegalStateException("Cannot read mapping resource: " + location);
|
||||
}
|
||||
}
|
||||
@@ -307,8 +314,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
// Allow for custom post-processing before we actually build the ValidatorFactory.
|
||||
postProcessConfiguration(configuration);
|
||||
|
||||
this.validatorFactory = configuration.buildValidatorFactory();
|
||||
setTargetValidator(this.validatorFactory.getValidator());
|
||||
try {
|
||||
this.validatorFactory = configuration.buildValidatorFactory();
|
||||
setTargetValidator(this.validatorFactory.getValidator());
|
||||
}
|
||||
finally {
|
||||
closeMappingStreams(mappingStreams);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
|
||||
@@ -329,6 +341,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
});
|
||||
}
|
||||
|
||||
private void closeMappingStreams(@Nullable List<InputStream> mappingStreams){
|
||||
if (!CollectionUtils.isEmpty(mappingStreams)) {
|
||||
for (InputStream stream : mappingStreams) {
|
||||
try {
|
||||
stream.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given Bean Validation configuration,
|
||||
* adding to or overriding any of its settings.
|
||||
@@ -397,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
return super.unwrap(type);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore - we'll try ValidatorFactory unwrapping next
|
||||
// Ignore - we'll try ValidatorFactory unwrapping next
|
||||
}
|
||||
}
|
||||
if (this.validatorFactory != null) {
|
||||
@@ -405,7 +429,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
return this.validatorFactory.unwrap(type);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore if just being asked for ValidatorFactory
|
||||
// Ignore if just being asked for ValidatorFactory
|
||||
if (ValidatorFactory.class == type) {
|
||||
return (T) this.validatorFactory;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -179,7 +179,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||
}
|
||||
}
|
||||
else {
|
||||
// got no BindingResult - can only do standard rejectValue call
|
||||
// Got no BindingResult - can only do standard rejectValue call
|
||||
// with automatic extraction of the current field value
|
||||
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
|
||||
}
|
||||
@@ -386,7 +386,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore if just being asked for plain Validator
|
||||
// Ignore if just being asked for plain JSR-303 Validator
|
||||
if (javax.validation.Validator.class == type) {
|
||||
return (T) this.targetValidator;
|
||||
}
|
||||
|
||||
+8
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,24 +36,26 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class AtAspectJAfterThrowingTests {
|
||||
|
||||
@Test
|
||||
public void testAccessThrowable() throws Exception {
|
||||
public void testAccessThrowable() {
|
||||
ClassPathXmlApplicationContext ctx =
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
|
||||
|
||||
ITestBean bean = (ITestBean) ctx.getBean("testBean");
|
||||
ExceptionHandlingAspect aspect = (ExceptionHandlingAspect) ctx.getBean("aspect");
|
||||
|
||||
assertThat(AopUtils.isAopProxy(bean)).isTrue();
|
||||
IOException exceptionThrown = null;
|
||||
try {
|
||||
bean.unreliableFileOperation();
|
||||
}
|
||||
catch (IOException e) {
|
||||
//
|
||||
catch (IOException ex) {
|
||||
exceptionThrown = ex;
|
||||
}
|
||||
|
||||
assertThat(aspect.handled).isEqualTo(1);
|
||||
assertThat(aspect.lastException).isNotNull();
|
||||
assertThat(aspect.lastException).isSameAs(exceptionThrown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class AtAspectJAnnotationBindingTests {
|
||||
|
||||
private AnnotatedTestBean testBean;
|
||||
|
||||
private ClassPathXmlApplicationContext ctx;
|
||||
|
||||
|
||||
@@ -70,8 +71,7 @@ public class AtAspectJAnnotationBindingTests {
|
||||
class AtAspectJAnnotationBindingTestAspect {
|
||||
|
||||
@Around("execution(* *(..)) && @annotation(testAnn)")
|
||||
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn)
|
||||
throws Throwable {
|
||||
public Object doWithAnnotation(ProceedingJoinPoint pjp, TestAnnotation testAnn) throws Throwable {
|
||||
String annValue = testAnn.value();
|
||||
Object result = pjp.proceed();
|
||||
return (result instanceof String ? annValue + " " + result : result);
|
||||
|
||||
+69
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -16,14 +16,23 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @author Liu Dongmiao
|
||||
*/
|
||||
public class AggressiveFactoryBeanInstantiationTests {
|
||||
|
||||
@@ -49,17 +58,66 @@ public class AggressiveFactoryBeanInstantiationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkLinkageError() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
|
||||
context.register(BeanMethodConfigurationWithExceptionInInitializer.class);
|
||||
context.refresh();
|
||||
fail("Should have thrown BeanCreationException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintWriter pw = new PrintWriter(baos);
|
||||
ex.printStackTrace(pw);
|
||||
pw.flush();
|
||||
String stackTrace = baos.toString();
|
||||
assertThat(stackTrace.contains(".<clinit>")).isTrue();
|
||||
assertThat(stackTrace.contains("java.lang.NoClassDefFoundError")).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class BeanMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
public String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AutowiredBean autowiredBean() {
|
||||
return new AutowiredBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn("autowiredBean")
|
||||
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
|
||||
return new SimpleFactoryBean(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class BeanMethodConfigurationWithExceptionInInitializer extends BeanMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
@DependsOn("autowiredBean")
|
||||
@Override
|
||||
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
|
||||
new ExceptionInInitializer();
|
||||
return new SimpleFactoryBean(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class AutowiredBean {
|
||||
|
||||
@Autowired
|
||||
String foo;
|
||||
}
|
||||
|
||||
|
||||
static class SimpleFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
public SimpleFactoryBean(ApplicationContext applicationContext) {
|
||||
@@ -76,4 +134,14 @@ public class AggressiveFactoryBeanInstantiationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ExceptionInInitializer {
|
||||
|
||||
private static final int ERROR = callInClinit();
|
||||
|
||||
private static int callInClinit() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,7 +20,6 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -105,7 +104,9 @@ public class ConfigurationClassPostProcessorTests {
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isSameAs(foo);
|
||||
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,7 +118,9 @@ public class ConfigurationClassPostProcessorTests {
|
||||
Foo foo = beanFactory.getBean("foo", Foo.class);
|
||||
Bar bar = beanFactory.getBean("bar", Bar.class);
|
||||
assertThat(bar.foo).isSameAs(foo);
|
||||
assertThat(Arrays.asList(beanFactory.getDependentBeans("foo")).contains("bar")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("foo"), "bar")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "foo")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(beanFactory.getDependentBeans("config"), "bar")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -40,7 +40,7 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Test
|
||||
public void control() {
|
||||
GenericXmlApplicationContext ctx =
|
||||
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
|
||||
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
|
||||
ctx.getBean("loadTimeWeaver", LoadTimeWeaver.class);
|
||||
}
|
||||
|
||||
@@ -73,9 +73,11 @@ public class EnableLoadTimeWeavingTests {
|
||||
verify(loadTimeWeaver).addTransformer(isA(ClassFileTransformer.class));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
|
||||
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
@@ -85,6 +87,7 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
|
||||
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
@@ -94,9 +97,11 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
|
||||
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
@@ -307,6 +307,23 @@ public class ConfigurationClassProcessingTests {
|
||||
assertThat(tb.getLawyer()).isEqualTo(ctx.getBean(NestedTestBean.class));
|
||||
}
|
||||
|
||||
@Test // gh-26019
|
||||
public void autowiringWithDynamicPrototypeBeanClass() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
|
||||
ConfigWithDynamicPrototype.class, PrototypeDependency.class);
|
||||
|
||||
PrototypeInterface p1 = ctx.getBean(PrototypeInterface.class, 1);
|
||||
assertThat(p1).isInstanceOf(PrototypeOne.class);
|
||||
assertThat(((PrototypeOne) p1).prototypeDependency).isNotNull();
|
||||
|
||||
PrototypeInterface p2 = ctx.getBean(PrototypeInterface.class, 2);
|
||||
assertThat(p2).isInstanceOf(PrototypeTwo.class);
|
||||
|
||||
PrototypeInterface p3 = ctx.getBean(PrototypeInterface.class, 1);
|
||||
assertThat(p3).isInstanceOf(PrototypeOne.class);
|
||||
assertThat(((PrototypeOne) p3).prototypeDependency).isNotNull();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
|
||||
@@ -632,4 +649,42 @@ public class ConfigurationClassProcessingTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class PrototypeDependency {
|
||||
}
|
||||
|
||||
interface PrototypeInterface {
|
||||
}
|
||||
|
||||
static class PrototypeOne extends AbstractPrototype {
|
||||
|
||||
@Autowired
|
||||
PrototypeDependency prototypeDependency;
|
||||
|
||||
}
|
||||
|
||||
static class PrototypeTwo extends AbstractPrototype {
|
||||
|
||||
// no autowired dependency here, in contrast to above
|
||||
}
|
||||
|
||||
static class AbstractPrototype implements PrototypeInterface {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithDynamicPrototype {
|
||||
|
||||
@Bean
|
||||
@Scope(value = "prototype")
|
||||
public PrototypeInterface getDemoBean( int i) {
|
||||
switch ( i) {
|
||||
case 1: return new PrototypeOne();
|
||||
case 2:
|
||||
default:
|
||||
return new PrototypeTwo();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -66,10 +66,10 @@ internal fun isSuspendingFunction(method: Method) = method.kotlinFunction!!.isSu
|
||||
* @since 5.2
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun invokeSuspendingFunction(method: Method, bean: Any, vararg args: Any?): Publisher<*> {
|
||||
internal fun invokeSuspendingFunction(method: Method, target: Any, vararg args: Any?): Publisher<*> {
|
||||
val function = method.kotlinFunction!!
|
||||
val mono = mono(Dispatchers.Unconfined) {
|
||||
function.callSuspend(bean, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
|
||||
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
|
||||
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
|
||||
return if (function.returnType.classifier == Flow::class) {
|
||||
mono.flatMapMany { (it as Flow<Any>).asFlux() }
|
||||
|
||||
@@ -76,7 +76,7 @@ public abstract class AnnotationVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.av = annotationVisitor;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class ClassVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.cv = classVisitor;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public abstract class FieldVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.fv = fieldVisitor;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public abstract class MethodVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.mv = methodVisitor;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public abstract class ModuleVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.mv = moduleVisitor;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class RecordComponentVisitor {
|
||||
&& api != Opcodes.ASM8_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 8 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.delegate = recordComponentVisitor;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Spring's repackaging of
|
||||
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.0</a>
|
||||
* <a href="https://gitlab.ow2.org/asm/asm">ASM 7.x</a>
|
||||
* (with Spring-specific patches; for internal use only).
|
||||
*
|
||||
* <p>This repackaging technique avoids any potential conflicts with
|
||||
|
||||
+30
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -18,8 +18,8 @@ package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
@@ -31,10 +31,11 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Collector implementations that provide various reduction operations for
|
||||
* {@link Collector} implementations that provide various reduction operations for
|
||||
* {@link MergedAnnotation} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2
|
||||
*/
|
||||
public abstract class MergedAnnotationCollectors {
|
||||
@@ -52,13 +53,16 @@ public abstract class MergedAnnotationCollectors {
|
||||
* Create a new {@link Collector} that accumulates merged annotations to a
|
||||
* {@link LinkedHashSet} containing {@linkplain MergedAnnotation#synthesize()
|
||||
* synthesized} versions.
|
||||
* <p>The collector returned by this method is effectively equivalent to
|
||||
* {@code Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))}
|
||||
* but avoids the creation of a composite collector.
|
||||
* @param <A> the annotation type
|
||||
* @return a {@link Collector} which collects and synthesizes the
|
||||
* annotations into a {@link Set}
|
||||
*/
|
||||
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
|
||||
return Collector.of(ArrayList<A>::new, (list, annotation) -> list.add(annotation.synthesize()),
|
||||
MergedAnnotationCollectors::addAll, LinkedHashSet::new);
|
||||
return Collector.of(LinkedHashSet::new, (set, annotation) -> set.add(annotation.synthesize()),
|
||||
MergedAnnotationCollectors::combiner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,14 +94,14 @@ public abstract class MergedAnnotationCollectors {
|
||||
IntFunction<R[]> generator) {
|
||||
|
||||
return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
|
||||
MergedAnnotationCollectors::addAll, list -> list.toArray(generator.apply(list.size())));
|
||||
MergedAnnotationCollectors::combiner, list -> list.toArray(generator.apply(list.size())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Collector} that accumulates merged annotations to an
|
||||
* Create a new {@link Collector} that accumulates merged annotations to a
|
||||
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
|
||||
* added} from each merged annotation
|
||||
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* @param <A> the annotation type
|
||||
* @param adaptations the adaptations that should be applied to the annotation values
|
||||
* @return a {@link Collector} which collects and synthesizes the
|
||||
@@ -111,13 +115,13 @@ public abstract class MergedAnnotationCollectors {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Collector} that accumulates merged annotations to an
|
||||
* Create a new {@link Collector} that accumulates merged annotations to a
|
||||
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
|
||||
* added} from each merged annotation
|
||||
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* @param <A> the annotation type
|
||||
* @param adaptations the adaptations that should be applied to the annotation values
|
||||
* @param finisher the finisher function for the new {@link MultiValueMap}
|
||||
* @param adaptations the adaptations that should be applied to the annotation values
|
||||
* @return a {@link Collector} which collects and synthesizes the
|
||||
* annotations into a {@link LinkedMultiValueMap}
|
||||
* @see #toMultiValueMap(MergedAnnotation.Adapt...)
|
||||
@@ -130,7 +134,7 @@ public abstract class MergedAnnotationCollectors {
|
||||
IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
|
||||
return Collector.of(LinkedMultiValueMap::new,
|
||||
(map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
|
||||
MergedAnnotationCollectors::merge, finisher, characteristics);
|
||||
MergedAnnotationCollectors::combiner, finisher, characteristics);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,13 +142,22 @@ public abstract class MergedAnnotationCollectors {
|
||||
return instance == candidate;
|
||||
}
|
||||
|
||||
private static <E, L extends List<E>> L addAll(L list, L additions) {
|
||||
list.addAll(additions);
|
||||
return list;
|
||||
/**
|
||||
* {@link Collector#combiner() Combiner} for collections.
|
||||
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
|
||||
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
|
||||
*/
|
||||
private static <E, C extends Collection<E>> C combiner(C collection, C additions) {
|
||||
collection.addAll(additions);
|
||||
return collection;
|
||||
}
|
||||
|
||||
private static <K, V> MultiValueMap<K, V> merge(MultiValueMap<K, V> map,
|
||||
MultiValueMap<K, V> additions) {
|
||||
/**
|
||||
* {@link Collector#combiner() Combiner} for multi-value maps.
|
||||
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
|
||||
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
|
||||
*/
|
||||
private static <K, V> MultiValueMap<K, V> combiner(MultiValueMap<K, V> map, MultiValueMap<K, V> additions) {
|
||||
map.addAll(additions);
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -157,16 +157,20 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
int startIndex = buffer.readPosition();
|
||||
int length = (endIndex - startIndex + 1);
|
||||
DataBuffer slice = buffer.retainedSlice(startIndex, length);
|
||||
if (this.stripDelimiter) {
|
||||
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result = (result != null ? result : new ArrayList<>());
|
||||
if (chunks.isEmpty()) {
|
||||
if (this.stripDelimiter) {
|
||||
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result.add(slice);
|
||||
}
|
||||
else {
|
||||
chunks.add(slice);
|
||||
result.add(buffer.factory().join(chunks));
|
||||
DataBuffer joined = buffer.factory().join(chunks);
|
||||
if (this.stripDelimiter) {
|
||||
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result.add(joined);
|
||||
chunks.clear();
|
||||
}
|
||||
buffer.readPosition(endIndex + 1);
|
||||
|
||||
+8
-8
@@ -19,14 +19,15 @@ package org.springframework.core.convert.support;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -499,9 +500,9 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
*/
|
||||
private static class Converters {
|
||||
|
||||
private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
|
||||
private final Set<GenericConverter> globalConverters = new CopyOnWriteArraySet<>();
|
||||
|
||||
private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);
|
||||
private final Map<ConvertiblePair, ConvertersForPair> converters = new ConcurrentHashMap<>(256);
|
||||
|
||||
public void add(GenericConverter converter) {
|
||||
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
|
||||
@@ -512,8 +513,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
}
|
||||
else {
|
||||
for (ConvertiblePair convertiblePair : convertibleTypes) {
|
||||
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
|
||||
convertersForPair.add(converter);
|
||||
getMatchableConverters(convertiblePair).add(converter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,7 +651,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
*/
|
||||
private static class ConvertersForPair {
|
||||
|
||||
private final LinkedList<GenericConverter> converters = new LinkedList<>();
|
||||
private final Deque<GenericConverter> converters = new ConcurrentLinkedDeque<>();
|
||||
|
||||
public void add(GenericConverter converter) {
|
||||
this.converters.addFirst(converter);
|
||||
|
||||
+3
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -54,11 +54,8 @@ public class LimitedDataBufferList extends ArrayList<DataBuffer> {
|
||||
|
||||
@Override
|
||||
public boolean add(DataBuffer buffer) {
|
||||
boolean result = super.add(buffer);
|
||||
if (result) {
|
||||
updateCount(buffer.readableByteCount());
|
||||
}
|
||||
return result;
|
||||
updateCount(buffer.readableByteCount());
|
||||
return super.add(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -432,6 +432,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
|
||||
filePath = StringUtils.capitalize(filePath);
|
||||
}
|
||||
// # can appear in directories/filenames, java.net.URL should not treat it as a fragment
|
||||
filePath = StringUtils.replace(filePath, "#", "%23");
|
||||
// Build URL that points to the root of the jar file
|
||||
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
|
||||
ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
|
||||
// Potentially overlapping with URLClassLoader.getURLs() result above!
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.BitSet;
|
||||
@@ -104,7 +106,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
private final Map<String, String> parameters;
|
||||
|
||||
@Nullable
|
||||
private Charset resolvedCharset;
|
||||
private transient Charset resolvedCharset;
|
||||
|
||||
@Nullable
|
||||
private volatile String toStringValue;
|
||||
@@ -184,9 +186,9 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
|
||||
if (!CollectionUtils.isEmpty(parameters)) {
|
||||
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
|
||||
parameters.forEach((attribute, value) -> {
|
||||
checkParameters(attribute, value);
|
||||
map.put(attribute, value);
|
||||
parameters.forEach((parameter, value) -> {
|
||||
checkParameters(parameter, value);
|
||||
map.put(parameter, value);
|
||||
});
|
||||
this.parameters = Collections.unmodifiableMap(map);
|
||||
}
|
||||
@@ -210,11 +212,11 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkParameters(String attribute, String value) {
|
||||
Assert.hasLength(attribute, "'attribute' must not be empty");
|
||||
protected void checkParameters(String parameter, String value) {
|
||||
Assert.hasLength(parameter, "'parameter' must not be empty");
|
||||
Assert.hasLength(value, "'value' must not be empty");
|
||||
checkToken(attribute);
|
||||
if (PARAM_CHARSET.equals(attribute)) {
|
||||
checkToken(parameter);
|
||||
if (PARAM_CHARSET.equals(parameter)) {
|
||||
if (this.resolvedCharset == null) {
|
||||
this.resolvedCharset = Charset.forName(unquote(value));
|
||||
}
|
||||
@@ -569,6 +571,17 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization, just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
String charsetName = getParameter(PARAM_CHARSET);
|
||||
if (charsetName != null) {
|
||||
this.resolvedCharset = Charset.forName(unquote(charsetName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given String value into a {@code MimeType} object,
|
||||
|
||||
@@ -125,10 +125,10 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
|
||||
);
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("")
|
||||
.expectNext("").as("1st")
|
||||
.expectNext("abc")
|
||||
.expectNext("defghi")
|
||||
.expectNext("")
|
||||
.expectNext("").as("2nd")
|
||||
.expectNext("jklmno")
|
||||
.expectNext("pqr")
|
||||
.expectNext("stuvwxyz")
|
||||
@@ -136,6 +136,21 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeNewlinesAcrossBuffers() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
stringBuffer("\r"),
|
||||
stringBuffer("\n"),
|
||||
stringBuffer("xyz")
|
||||
);
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("")
|
||||
.expectNext("xyz")
|
||||
.expectComplete()
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxInMemoryLimit() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
|
||||
+18
@@ -35,6 +35,8 @@ import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
@@ -834,6 +836,22 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
.verifyError(DataBufferLimitException.class);
|
||||
}
|
||||
|
||||
@Test // gh-26060
|
||||
void joinWithLimitDoesNotOverRelease() {
|
||||
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
|
||||
byte[] bytes = "foo-bar-baz".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
NettyDataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
|
||||
buffer.getNativeBuffer().retain(); // should be at 2 now
|
||||
buffer.write(bytes);
|
||||
|
||||
Mono<DataBuffer> result = DataBufferUtils.join(Flux.just(buffer), 8);
|
||||
|
||||
StepVerifier.create(result).verifyError(DataBufferLimitException.class);
|
||||
assertThat(buffer.getNativeBuffer().refCnt()).isEqualTo(1);
|
||||
buffer.release();
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void joinErrors(String displayName, DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
+7
-3
@@ -17,9 +17,11 @@ package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LimitedDataBufferList}.
|
||||
* @author Rossen Stoyanchev
|
||||
@@ -32,8 +34,10 @@ public class LimitedDataBufferListTests {
|
||||
|
||||
@Test
|
||||
void limitEnforced() {
|
||||
Assertions.assertThatThrownBy(() -> new LimitedDataBufferList(5).add(toDataBuffer("123456")))
|
||||
.isInstanceOf(DataBufferLimitException.class);
|
||||
LimitedDataBufferList list = new LimitedDataBufferList(5);
|
||||
|
||||
assertThatThrownBy(() -> list.add(toDataBuffer("123456"))).isInstanceOf(DataBufferLimitException.class);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -267,13 +268,13 @@ class MimeTypeTests {
|
||||
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
|
||||
}
|
||||
|
||||
@Test // SPR-16630
|
||||
@Test // SPR-16630
|
||||
void parseMimeTypeWithSpacesAroundEquals() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("multipart/x-mixed-replace;boundary = --myboundary");
|
||||
assertThat(mimeType.getParameter("boundary")).isEqualTo("--myboundary");
|
||||
}
|
||||
|
||||
@Test // SPR-16630
|
||||
@Test // SPR-16630
|
||||
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
|
||||
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
|
||||
@@ -303,14 +304,14 @@ class MimeTypeTests {
|
||||
assertThat(mimeTypes.size()).as("Invalid amount of mime types").isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // gh-23241
|
||||
@Test // gh-23241
|
||||
void parseMimeTypesWithTrailingComma() {
|
||||
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes("text/plain, text/html,");
|
||||
assertThat(mimeTypes).as("No mime types returned").isNotNull();
|
||||
assertThat(mimeTypes.size()).as("Incorrect number of mime types").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test // SPR-17459
|
||||
@Test // SPR-17459
|
||||
void parseMimeTypesWithQuotedParameters() {
|
||||
testWithQuotedParameters("foo/bar;param=\",\"");
|
||||
testWithQuotedParameters("foo/bar;param=\"s,a,\"");
|
||||
@@ -323,8 +324,9 @@ class MimeTypeTests {
|
||||
private void testWithQuotedParameters(String... mimeTypes) {
|
||||
String s = String.join(",", mimeTypes);
|
||||
List<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
|
||||
|
||||
assertThat(actual.size()).isEqualTo(mimeTypes.length);
|
||||
for (int i=0; i < mimeTypes.length; i++) {
|
||||
for (int i = 0; i < mimeTypes.length; i++) {
|
||||
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
|
||||
}
|
||||
}
|
||||
@@ -351,6 +353,7 @@ class MimeTypeTests {
|
||||
|
||||
List<MimeType> result = new ArrayList<>(expected);
|
||||
Random rnd = new Random();
|
||||
|
||||
// shuffle & sort 10 times
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Collections.shuffle(result, rnd);
|
||||
@@ -380,11 +383,7 @@ class MimeTypeTests {
|
||||
assertThat(m2.compareTo(m1) != 0).as("Invalid comparison result").isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* SPR-13157
|
||||
* @since 4.2
|
||||
*/
|
||||
@Test
|
||||
@Test // SPR-13157
|
||||
void equalsIsCaseInsensitiveForCharsets() {
|
||||
MimeType m1 = new MimeType("text", "plain", singletonMap("charset", "UTF-8"));
|
||||
MimeType m2 = new MimeType("text", "plain", singletonMap("charset", "utf-8"));
|
||||
@@ -394,4 +393,12 @@ class MimeTypeTests {
|
||||
assertThat(m2.compareTo(m1)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // gh-26127
|
||||
void serialize() throws Exception {
|
||||
MimeType original = new MimeType("text", "plain", StandardCharsets.UTF_8);
|
||||
MimeType deserialized = (MimeType) SerializationTestUtils.serializeAndDeserialize(original);
|
||||
assertThat(deserialized).isEqualTo(original);
|
||||
assertThat(original).isEqualTo(deserialized);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -54,10 +54,9 @@ public abstract class AstUtils {
|
||||
}
|
||||
else {
|
||||
if (targetType != null) {
|
||||
int pos = 0;
|
||||
for (Class<?> clazz : targets) {
|
||||
if (clazz == targetType) { // put exact matches on the front to be tried first?
|
||||
specificAccessors.add(pos++, resolver);
|
||||
specificAccessors.add(resolver);
|
||||
}
|
||||
else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the
|
||||
// specificAccessor list
|
||||
|
||||
+23
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -63,18 +63,20 @@ import org.springframework.util.StringUtils;
|
||||
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.1
|
||||
*/
|
||||
public final class SpelCompiler implements Opcodes {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
|
||||
|
||||
private static final int CLASSES_DEFINED_LIMIT = 100;
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
|
||||
|
||||
// A compiler is created for each classloader, it manages a child class loader of that
|
||||
// classloader and the child is used to load the compiled expressions.
|
||||
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
|
||||
// The child ClassLoader used to load the compiled expression classes
|
||||
private ChildClassLoader ccl;
|
||||
|
||||
@@ -90,7 +92,7 @@ public final class SpelCompiler implements Opcodes {
|
||||
/**
|
||||
* Attempt compilation of the supplied expression. A check is made to see
|
||||
* if it is compilable before compilation proceeds. The check involves
|
||||
* visiting all the nodes in the expression Ast and ensuring enough state
|
||||
* visiting all the nodes in the expression AST and ensuring enough state
|
||||
* is known about them that bytecode can be generated for them.
|
||||
* @param expression the expression to compile
|
||||
* @return an instance of the class implementing the compiled expression,
|
||||
@@ -125,7 +127,7 @@ public final class SpelCompiler implements Opcodes {
|
||||
|
||||
/**
|
||||
* Generate the class that encapsulates the compiled expression and define it.
|
||||
* The generated class will be a subtype of CompiledExpression.
|
||||
* The generated class will be a subtype of CompiledExpression.
|
||||
* @param expressionToCompile the expression to be compiled
|
||||
* @return the expression call, or {@code null} if the decision was to opt out of
|
||||
* compilation during code generation
|
||||
@@ -150,7 +152,7 @@ public final class SpelCompiler implements Opcodes {
|
||||
// Create getValue() method
|
||||
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
|
||||
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
|
||||
new String[ ]{"org/springframework/expression/EvaluationException"});
|
||||
new String[] {"org/springframework/expression/EvaluationException"});
|
||||
mv.visitCode();
|
||||
|
||||
CodeFlow cf = new CodeFlow(className, cw);
|
||||
@@ -187,7 +189,7 @@ public final class SpelCompiler implements Opcodes {
|
||||
|
||||
/**
|
||||
* Load a compiled expression class. Makes sure the classloaders aren't used too much
|
||||
* because they anchor compiled classes in memory and prevent GC. If you have expressions
|
||||
* because they anchor compiled classes in memory and prevent GC. If you have expressions
|
||||
* continually recompiling over time then by replacing the classloader periodically
|
||||
* at least some of the older variants can be garbage collected.
|
||||
* @param name the name of the class
|
||||
@@ -202,6 +204,7 @@ public final class SpelCompiler implements Opcodes {
|
||||
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method for compiler instances. The returned SpelCompiler will
|
||||
* attach a class loader as the child of the given class loader and this
|
||||
@@ -222,10 +225,12 @@ public final class SpelCompiler implements Opcodes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that an attempt is made to compile the specified expression. It may fail if
|
||||
* components of the expression are not suitable for compilation or the data types
|
||||
* involved are not suitable for compilation. Used for testing.
|
||||
* @return true if the expression was successfully compiled
|
||||
* Request that an attempt is made to compile the specified expression.
|
||||
* It may fail if components of the expression are not suitable for compilation
|
||||
* or the data types involved are not suitable for compilation. Used for testing.
|
||||
* @param expression the expression to compile
|
||||
* @return {@code true} if the expression was successfully compiled,
|
||||
* {@code false} otherwise
|
||||
*/
|
||||
public static boolean compile(Expression expression) {
|
||||
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
|
||||
@@ -256,18 +261,21 @@ public final class SpelCompiler implements Opcodes {
|
||||
super(NO_URLS, classLoader);
|
||||
}
|
||||
|
||||
int getClassesDefinedCount() {
|
||||
return this.classesDefinedCount;
|
||||
}
|
||||
|
||||
public Class<?> defineClass(String name, byte[] bytes) {
|
||||
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
|
||||
this.classesDefinedCount++;
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public int getClassesDefinedCount() {
|
||||
return this.classesDefinedCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader.
|
||||
*/
|
||||
private class ExpressionClassWriter extends ClassWriter {
|
||||
|
||||
public ExpressionClassWriter() {
|
||||
|
||||
+5
-5
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A powerful {@link PropertyAccessor} that uses reflection to access properties
|
||||
* for reading and possibly also for writing.
|
||||
* for reading and possibly also for writing on a target instance.
|
||||
*
|
||||
* <p>A property can be referenced through a public getter method (when being read)
|
||||
* or a public setter method (when being written), and also as a public field.
|
||||
@@ -98,8 +98,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new property accessor for reading and possibly writing.
|
||||
* @param allowWrite whether to also allow for write operations
|
||||
* Create a new property accessor for reading and possibly also writing.
|
||||
* @param allowWrite whether to allow write operations on a target instance
|
||||
* @since 4.3.15
|
||||
* @see #canWrite
|
||||
*/
|
||||
@@ -623,8 +623,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " +
|
||||
this.property + ", targetIsClass=" + this.targetIsClass + "]";
|
||||
return "PropertyCacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property +
|
||||
", targetIsClass=" + this.targetIsClass + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-1
@@ -76,7 +76,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
public class SpelReproTests extends AbstractExpressionTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void NPE_SPR5661() {
|
||||
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.expression.spel.testresources;
|
||||
|
||||
///CLOVER:OFF
|
||||
public class Person {
|
||||
|
||||
private String privateName;
|
||||
|
||||
Company company;
|
||||
|
||||
public Person(String name) {
|
||||
@@ -41,4 +42,5 @@ public class Person {
|
||||
public Company getCompany() {
|
||||
return company;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,19 +19,25 @@ package org.springframework.expression.spel.testresources;
|
||||
import java.util.List;
|
||||
|
||||
public class TestAddress{
|
||||
private String street;
|
||||
private List<String> crossStreets;
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
public List<String> getCrossStreets() {
|
||||
return crossStreets;
|
||||
}
|
||||
public void setCrossStreets(List<String> crossStreets) {
|
||||
this.crossStreets = crossStreets;
|
||||
}
|
||||
private String street;
|
||||
|
||||
private List<String> crossStreets;
|
||||
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
||||
public void setStreet(String street) {
|
||||
this.street = street;
|
||||
}
|
||||
|
||||
public List<String> getCrossStreets() {
|
||||
return crossStreets;
|
||||
}
|
||||
|
||||
public void setCrossStreets(List<String> crossStreets) {
|
||||
this.crossStreets = crossStreets;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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,19 +17,25 @@
|
||||
package org.springframework.expression.spel.testresources;
|
||||
|
||||
public class TestPerson {
|
||||
private String name;
|
||||
private TestAddress address;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public TestAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
public void setAddress(TestAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
private String name;
|
||||
|
||||
private TestAddress address;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public TestAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(TestAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -30,7 +30,6 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class that efficiently creates multiple {@link PreparedStatementCreator}
|
||||
@@ -200,9 +199,8 @@ public class PreparedStatementCreatorFactory {
|
||||
|
||||
public PreparedStatementCreatorImpl(String actualSql, List<?> parameters) {
|
||||
this.actualSql = actualSql;
|
||||
Assert.notNull(parameters, "Parameters List must not be null");
|
||||
this.parameters = parameters;
|
||||
if (this.parameters.size() != declaredParameters.size()) {
|
||||
if (parameters.size() != declaredParameters.size()) {
|
||||
// Account for named parameters being used multiple times
|
||||
Set<String> names = new HashSet<>();
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
|
||||
+11
-9
@@ -637,21 +637,23 @@ public class CallMetaDataContext {
|
||||
schemaNameToUse = this.metaDataProvider.schemaNameToUse(getSchemaName());
|
||||
}
|
||||
|
||||
String procedureNameToUse = this.metaDataProvider.procedureNameToUse(getProcedureName());
|
||||
if (isFunction() || isReturnValueRequired()) {
|
||||
callString = new StringBuilder().append("{? = call ").
|
||||
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
|
||||
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
|
||||
append(procedureNameToUse).append("(");
|
||||
callString = new StringBuilder("{? = call ");
|
||||
parameterCount = -1;
|
||||
}
|
||||
else {
|
||||
callString = new StringBuilder().append("{call ").
|
||||
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
|
||||
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
|
||||
append(procedureNameToUse).append("(");
|
||||
callString = new StringBuilder("{call ");
|
||||
}
|
||||
|
||||
if (StringUtils.hasLength(catalogNameToUse)) {
|
||||
callString.append(catalogNameToUse).append(".");
|
||||
}
|
||||
if (StringUtils.hasLength(schemaNameToUse)) {
|
||||
callString.append(schemaNameToUse).append(".");
|
||||
}
|
||||
callString.append(this.metaDataProvider.procedureNameToUse(getProcedureName()));
|
||||
callString.append("(");
|
||||
|
||||
for (SqlParameter parameter : this.callParameters) {
|
||||
if (!parameter.isResultsParameter()) {
|
||||
if (parameterCount > 0) {
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
@@ -414,8 +415,15 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Error while retrieving meta-data for procedure columns: " + ex);
|
||||
logger.warn("Error while retrieving meta-data for procedure columns. " +
|
||||
"Consider declaring explicit parameters -- for example, via SimpleJdbcCall#addDeclaredParameter().",
|
||||
ex);
|
||||
}
|
||||
// Although we could invoke `this.callParameterMetaData.clear()` so that
|
||||
// we don't retain a partial list of column names (like we do in
|
||||
// GenericTableMetaDataProvider.processTableColumns(...)), we choose
|
||||
// not to do that here, since invocation of the stored procedure will
|
||||
// likely fail anyway with an incorrect argument list.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
@@ -422,8 +423,12 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Error while retrieving meta-data for table columns: " + ex.getMessage());
|
||||
logger.warn("Error while retrieving meta-data for table columns. " +
|
||||
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().",
|
||||
ex);
|
||||
}
|
||||
// Clear the metadata so that we don't retain a partial list of column names
|
||||
this.tableParameterMetaData.clear();
|
||||
}
|
||||
finally {
|
||||
JdbcUtils.closeResultSet(tableColumns);
|
||||
@@ -432,7 +437,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Inner class representing table meta-data.
|
||||
* Class representing table meta-data.
|
||||
*/
|
||||
private static class TableMetaData {
|
||||
|
||||
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -43,6 +43,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class TableMetaDataContext {
|
||||
@@ -302,8 +303,12 @@ public class TableMetaDataContext {
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new InvalidDataAccessApiUsageException("Unable to locate columns for table '" +
|
||||
getTableName() + "' so an insert statement can't be generated");
|
||||
String message = "Unable to locate columns for table '" + getTableName()
|
||||
+ "' so an insert statement can't be generated.";
|
||||
if (isAccessTableColumnMetaData()) {
|
||||
message += " Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().";
|
||||
}
|
||||
throw new InvalidDataAccessApiUsageException(message);
|
||||
}
|
||||
}
|
||||
String params = String.join(", ", Collections.nCopies(columnCount, "?"));
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -51,7 +51,6 @@ public final class TableMetaDataProviderFactory {
|
||||
try {
|
||||
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
||||
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
||||
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
|
||||
TableMetaDataProvider provider;
|
||||
|
||||
if ("Oracle".equals(databaseProductName)) {
|
||||
@@ -70,15 +69,17 @@ public final class TableMetaDataProviderFactory {
|
||||
else {
|
||||
provider = new GenericTableMetaDataProvider(databaseMetaData);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Using " + provider.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
provider.initializeWithMetaData(databaseMetaData);
|
||||
if (accessTableColumnMetaData) {
|
||||
|
||||
if (context.isAccessTableColumnMetaData()) {
|
||||
provider.initializeWithTableColumnMetaData(databaseMetaData,
|
||||
context.getCatalogName(), context.getSchemaName(), context.getTableName());
|
||||
}
|
||||
|
||||
return provider;
|
||||
});
|
||||
}
|
||||
|
||||
+13
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class NamedParameterUtils {
|
||||
@@ -83,7 +84,7 @@ public abstract class NamedParameterUtils {
|
||||
Assert.notNull(sql, "SQL must not be null");
|
||||
|
||||
Set<String> namedParameters = new HashSet<>();
|
||||
String sqlToUse = sql;
|
||||
StringBuilder sqlToUse = new StringBuilder(sql);
|
||||
List<ParameterHolder> parameterList = new ArrayList<>();
|
||||
|
||||
char[] statement = sql.toCharArray();
|
||||
@@ -155,7 +156,7 @@ public abstract class NamedParameterUtils {
|
||||
int j = i + 1;
|
||||
if (j < statement.length && statement[j] == ':') {
|
||||
// escaped ":" should be skipped
|
||||
sqlToUse = sqlToUse.substring(0, i - escapes) + sqlToUse.substring(i - escapes + 1);
|
||||
sqlToUse.deleteCharAt(i - escapes);
|
||||
escapes++;
|
||||
i = i + 2;
|
||||
continue;
|
||||
@@ -174,7 +175,7 @@ public abstract class NamedParameterUtils {
|
||||
}
|
||||
i++;
|
||||
}
|
||||
ParsedSql parsedSql = new ParsedSql(sqlToUse);
|
||||
ParsedSql parsedSql = new ParsedSql(sqlToUse.toString());
|
||||
for (ParameterHolder ph : parameterList) {
|
||||
parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex());
|
||||
}
|
||||
@@ -345,9 +346,15 @@ public abstract class NamedParameterUtils {
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
String paramName = paramNames.get(i);
|
||||
try {
|
||||
Object value = paramSource.getValue(paramName);
|
||||
SqlParameter param = findParameter(declaredParams, paramName, i);
|
||||
paramArray[i] = (param != null ? new SqlParameterValue(param, value) : value);
|
||||
Object paramValue = paramSource.getValue(paramName);
|
||||
if (paramValue instanceof SqlParameterValue) {
|
||||
paramArray[i] = paramValue;
|
||||
}
|
||||
else {
|
||||
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
|
||||
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
|
||||
+3
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -44,7 +44,6 @@ public abstract class SqlParameterSourceUtils {
|
||||
* @see BeanPropertySqlParameterSource
|
||||
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[])
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static SqlParameterSource[] createBatch(Object... candidates) {
|
||||
return createBatch(Arrays.asList(candidates));
|
||||
}
|
||||
@@ -93,17 +92,13 @@ public abstract class SqlParameterSourceUtils {
|
||||
* @param source the source of parameter values and type information
|
||||
* @param parameterName the name of the parameter
|
||||
* @return the value object
|
||||
* @see SqlParameterValue
|
||||
*/
|
||||
@Nullable
|
||||
public static Object getTypedValue(SqlParameterSource source, String parameterName) {
|
||||
int sqlType = source.getSqlType(parameterName);
|
||||
if (sqlType != SqlParameterSource.TYPE_UNKNOWN) {
|
||||
if (source.getTypeName(parameterName) != null) {
|
||||
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
|
||||
}
|
||||
else {
|
||||
return new SqlParameterValue(sqlType, source.getValue(parameterName));
|
||||
}
|
||||
return new SqlParameterValue(sqlType, source.getTypeName(parameterName), source.getValue(parameterName));
|
||||
}
|
||||
else {
|
||||
return source.getValue(parameterName);
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcCall {
|
||||
* Has this operation been compiled? Compilation means at least checking
|
||||
* that a DataSource or JdbcTemplate has been provided.
|
||||
*/
|
||||
private volatile boolean compiled = false;
|
||||
private volatile boolean compiled;
|
||||
|
||||
/** The generated string used for call statement. */
|
||||
@Nullable
|
||||
@@ -433,7 +433,7 @@ public abstract class AbstractJdbcCall {
|
||||
/**
|
||||
* Match the provided in parameter values with registered parameters and
|
||||
* parameters defined via meta-data processing.
|
||||
* @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
|
||||
* @param parameterSource the parameter values provided as a {@link SqlParameterSource}
|
||||
* @return a Map with parameter names and values
|
||||
*/
|
||||
protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcInsert {
|
||||
/** Context used to retrieve and manage database meta-data. */
|
||||
private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
|
||||
|
||||
/** List of columns objects to be used in insert statement. */
|
||||
/** List of column names to be used in insert statement. */
|
||||
private final List<String> declaredColumns = new ArrayList<>();
|
||||
|
||||
/** The names of the columns holding the generated key. */
|
||||
|
||||
+9
-6
@@ -526,10 +526,11 @@ public class NamedParameterJdbcTemplateTests {
|
||||
|
||||
@Test
|
||||
public void testBatchUpdateWithSqlParameterSourcePlusTypeInfo() throws Exception {
|
||||
SqlParameterSource[] ids = new SqlParameterSource[2];
|
||||
ids[0] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
|
||||
ids[1] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
|
||||
final int[] rowsAffected = new int[] {1, 2};
|
||||
SqlParameterSource[] ids = new SqlParameterSource[3];
|
||||
ids[0] = new MapSqlParameterSource().addValue("id", null, Types.NULL);
|
||||
ids[1] = new MapSqlParameterSource().addValue("id", 100, Types.NUMERIC);
|
||||
ids[2] = new MapSqlParameterSource().addValue("id", 200, Types.NUMERIC);
|
||||
final int[] rowsAffected = new int[] {1, 2, 3};
|
||||
|
||||
given(preparedStatement.executeBatch()).willReturn(rowsAffected);
|
||||
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||
@@ -537,13 +538,15 @@ public class NamedParameterJdbcTemplateTests {
|
||||
|
||||
int[] actualRowsAffected = namedParameterTemplate.batchUpdate(
|
||||
"UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = :id", ids);
|
||||
assertThat(actualRowsAffected.length == 2).as("executed 2 updates").isTrue();
|
||||
assertThat(actualRowsAffected.length == 3).as("executed 3 updates").isTrue();
|
||||
assertThat(actualRowsAffected[0]).isEqualTo(rowsAffected[0]);
|
||||
assertThat(actualRowsAffected[1]).isEqualTo(rowsAffected[1]);
|
||||
assertThat(actualRowsAffected[2]).isEqualTo(rowsAffected[2]);
|
||||
verify(connection).prepareStatement("UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?");
|
||||
verify(preparedStatement).setNull(1, Types.NULL);
|
||||
verify(preparedStatement).setObject(1, 100, Types.NUMERIC);
|
||||
verify(preparedStatement).setObject(1, 200, Types.NUMERIC);
|
||||
verify(preparedStatement, times(2)).addBatch();
|
||||
verify(preparedStatement, times(3)).addBatch();
|
||||
verify(preparedStatement, atLeastOnce()).close();
|
||||
verify(connection, atLeastOnce()).close();
|
||||
}
|
||||
|
||||
+17
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.jdbc.core.SqlParameterValue;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rick Evans
|
||||
* @author Artur Geraschenko
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class NamedParameterUtilsTests {
|
||||
|
||||
@@ -96,6 +98,20 @@ public class NamedParameterUtilsTests {
|
||||
.buildSqlTypeArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams)[4]).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertSqlParameterValueToArray() {
|
||||
SqlParameterValue sqlParameterValue = new SqlParameterValue(2, "b");
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
paramMap.put("a", "a");
|
||||
paramMap.put("b", sqlParameterValue);
|
||||
paramMap.put("c", "c");
|
||||
assertThat(NamedParameterUtils.buildValueArray("xxx :a :b :c xx :a :b", paramMap)[4]).isSameAs(sqlParameterValue);
|
||||
MapSqlParameterSource namedParams = new MapSqlParameterSource();
|
||||
namedParams.addValue("a", "a", 1).addValue("b", sqlParameterValue).addValue("c", "c", 3);
|
||||
assertThat(NamedParameterUtils
|
||||
.buildValueArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams, null)[4]).isSameAs(sqlParameterValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertTypeMapToSqlParameterList() {
|
||||
MapSqlParameterSource namedParams = new MapSqlParameterSource();
|
||||
|
||||
+61
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -46,31 +46,28 @@ import static org.mockito.Mockito.verify;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Kiril Nugmanov
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class SimpleJdbcCallTests {
|
||||
class SimpleJdbcCallTests {
|
||||
|
||||
private Connection connection;
|
||||
private final Connection connection = mock(Connection.class);
|
||||
|
||||
private DatabaseMetaData databaseMetaData;
|
||||
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||
|
||||
private DataSource dataSource;
|
||||
private final DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
private CallableStatement callableStatement;
|
||||
private final CallableStatement callableStatement = mock(CallableStatement.class);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
connection = mock(Connection.class);
|
||||
databaseMetaData = mock(DatabaseMetaData.class);
|
||||
dataSource = mock(DataSource.class);
|
||||
callableStatement = mock(CallableStatement.class);
|
||||
void setUp() throws Exception {
|
||||
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoSuchStoredProcedure() throws Exception {
|
||||
void noSuchStoredProcedure() throws Exception {
|
||||
final String NO_SUCH_PROC = "x";
|
||||
SQLException sqlException = new SQLException("Syntax error or access violation exception", "42000");
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
@@ -81,8 +78,8 @@ public class SimpleJdbcCallTests {
|
||||
given(connection.prepareCall("{call " + NO_SUCH_PROC + "()}")).willReturn(callableStatement);
|
||||
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(NO_SUCH_PROC);
|
||||
try {
|
||||
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() ->
|
||||
sproc.execute())
|
||||
assertThatExceptionOfType(BadSqlGrammarException.class)
|
||||
.isThrownBy(() -> sproc.execute())
|
||||
.withCause(sqlException);
|
||||
}
|
||||
finally {
|
||||
@@ -92,7 +89,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnnamedParameterHandling() throws Exception {
|
||||
void unnamedParameterHandling() throws Exception {
|
||||
final String MY_PROC = "my_proc";
|
||||
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(MY_PROC);
|
||||
// Shouldn't succeed in adding unnamed parameter
|
||||
@@ -101,7 +98,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -117,7 +114,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -131,7 +128,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
Number newId = adder.executeObject(Number.class, new MapSqlParameterSource()
|
||||
@@ -143,7 +140,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
Number newId = adder.executeObject(Number.class, 1103, 3);
|
||||
@@ -153,7 +150,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -169,7 +166,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -183,7 +180,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
Number newId = adder.executeFunction(Number.class, new MapSqlParameterSource()
|
||||
@@ -192,22 +189,20 @@ public class SimpleJdbcCallTests {
|
||||
assertThat(newId.intValue()).isEqualTo(4);
|
||||
verifyAddInvoiceWithMetaData(true);
|
||||
verify(connection, atLeastOnce()).close();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
Number newId = adder.executeFunction(Number.class, 1103, 3);
|
||||
assertThat(newId.intValue()).isEqualTo(4);
|
||||
verifyAddInvoiceWithMetaData(true);
|
||||
verify(connection, atLeastOnce()).close();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectFunctionStatement() throws Exception {
|
||||
void correctFunctionStatement() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.compile();
|
||||
@@ -215,7 +210,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectFunctionStatementNamed() throws Exception {
|
||||
void correctFunctionStatementNamed() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withFunctionName("add_invoice");
|
||||
adder.compile();
|
||||
@@ -223,13 +218,51 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectProcedureStatementNamed() throws Exception {
|
||||
void correctProcedureStatementNamed() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
|
||||
adder.compile();
|
||||
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test demonstrates that a CALL statement will still be generated if
|
||||
* an exception occurs while retrieving metadata, potentially resulting in
|
||||
* missing metadata and consequently a failure while invoking the stored
|
||||
* procedure.
|
||||
*/
|
||||
@Test // gh-26486
|
||||
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet proceduresResultSet = mock(ResultSet.class);
|
||||
ResultSet procedureColumnsResultSet = mock(ResultSet.class);
|
||||
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
|
||||
given(databaseMetaData.getUserName()).willReturn("ME");
|
||||
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
|
||||
given(proceduresResultSet.next()).willReturn(true, false);
|
||||
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
|
||||
|
||||
given(procedureColumnsResultSet.next()).willReturn(true, true, true, false);
|
||||
given(procedureColumnsResultSet.getString("COLUMN_NAME")).willReturn("amount", "custid", "newid");
|
||||
given(procedureColumnsResultSet.getInt("DATA_TYPE"))
|
||||
// Return a valid data type for the first 2 columns.
|
||||
.willReturn(Types.INTEGER, Types.INTEGER)
|
||||
// 3rd time, simulate an error while retrieving metadata.
|
||||
.willThrow(new SQLException("error with DATA_TYPE for column 3"));
|
||||
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
|
||||
adder.compile();
|
||||
// If an exception were not thrown for column 3, we would expect:
|
||||
// {call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}
|
||||
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?)}");
|
||||
|
||||
verify(proceduresResultSet).close();
|
||||
verify(procedureColumnsResultSet).close();
|
||||
}
|
||||
|
||||
|
||||
private void verifyStatement(SimpleJdbcCall adder, String expected) {
|
||||
assertThat(adder.getCallString()).as("Incorrect call statement").isEqualTo(expected);
|
||||
|
||||
+73
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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,7 +19,9 @@ package org.springframework.jdbc.core.simple;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.HashMap;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -29,45 +31,44 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Mock object based tests for SimpleJdbcInsert.
|
||||
* Mock object based tests for {@link SimpleJdbcInsert}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class SimpleJdbcInsertTests {
|
||||
class SimpleJdbcInsertTests {
|
||||
|
||||
private Connection connection;
|
||||
private final Connection connection = mock(Connection.class);
|
||||
|
||||
private DatabaseMetaData databaseMetaData;
|
||||
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||
|
||||
private DataSource dataSource;
|
||||
private final DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
connection = mock(Connection.class);
|
||||
databaseMetaData = mock(DatabaseMetaData.class);
|
||||
dataSource = mock(DataSource.class);
|
||||
void setUp() throws Exception {
|
||||
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void verifyClosed() throws Exception {
|
||||
void verifyClosed() throws Exception {
|
||||
verify(connection).close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoSuchTable() throws Exception {
|
||||
void noSuchTable() throws Exception {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.next()).willReturn(false);
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
given(databaseMetaData.getDatabaseProductVersion()).willReturn("1.0");
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
@@ -76,9 +77,65 @@ public class SimpleJdbcInsertTests {
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("x");
|
||||
// Shouldn't succeed in inserting into table which doesn't exist
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
|
||||
insert.execute(new HashMap<>()));
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> insert.execute(Collections.emptyMap()))
|
||||
.withMessageStartingWith("Unable to locate columns for table 'x' so an insert statement can't be generated");
|
||||
|
||||
verify(resultSet).close();
|
||||
}
|
||||
|
||||
@Test // gh-26486
|
||||
void retrieveColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet tableResultSet = mock(ResultSet.class);
|
||||
given(tableResultSet.next()).willReturn(true, false);
|
||||
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
|
||||
|
||||
ResultSet columnResultSet = mock(ResultSet.class);
|
||||
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
|
||||
given(columnResultSet.next()).willReturn(true, true, false);
|
||||
given(columnResultSet.getString("COLUMN_NAME")).willReturn("col1", "col2");
|
||||
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
|
||||
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
|
||||
insert.compile();
|
||||
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO me (col1, col2) VALUES(?, ?)");
|
||||
|
||||
verify(columnResultSet).close();
|
||||
verify(tableResultSet).close();
|
||||
}
|
||||
|
||||
@Test // gh-26486
|
||||
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet tableResultSet = mock(ResultSet.class);
|
||||
given(tableResultSet.next()).willReturn(true, false);
|
||||
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
|
||||
|
||||
ResultSet columnResultSet = mock(ResultSet.class);
|
||||
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
|
||||
// true, true, false --> simulates processing of two columns
|
||||
given(columnResultSet.next()).willReturn(true, true, false);
|
||||
given(columnResultSet.getString("COLUMN_NAME"))
|
||||
// Return a column name the first time.
|
||||
.willReturn("col1")
|
||||
// Second time, simulate an error while retrieving metadata.
|
||||
.willThrow(new SQLException("error with col2"));
|
||||
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
|
||||
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(insert::compile)
|
||||
.withMessage("Unable to locate columns for table 'me' so an insert statement can't be generated. " +
|
||||
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().");
|
||||
|
||||
verify(columnResultSet).close();
|
||||
verify(tableResultSet).close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -233,7 +233,6 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
|
||||
|
||||
@Override
|
||||
protected StompBrokerRelayMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
|
||||
|
||||
StompBrokerRelayMessageHandler handler = new StompBrokerRelayMessageHandler(
|
||||
getClientInboundChannel(), getClientOutboundChannel(),
|
||||
brokerChannel, getDestinationPrefixes());
|
||||
|
||||
+14
-12
@@ -378,13 +378,14 @@ public class MessageHeaderAccessor {
|
||||
* {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values.
|
||||
*/
|
||||
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
|
||||
if (headersToCopy != null) {
|
||||
headersToCopy.forEach((key, value) -> {
|
||||
if (!isReadOnly(key)) {
|
||||
setHeader(key, value);
|
||||
}
|
||||
});
|
||||
if (headersToCopy == null || this.headers == headersToCopy) {
|
||||
return;
|
||||
}
|
||||
headersToCopy.forEach((key, value) -> {
|
||||
if (!isReadOnly(key)) {
|
||||
setHeader(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -392,13 +393,14 @@ public class MessageHeaderAccessor {
|
||||
* <p>This operation will <em>not</em> overwrite any existing values.
|
||||
*/
|
||||
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
|
||||
if (headersToCopy != null) {
|
||||
headersToCopy.forEach((key, value) -> {
|
||||
if (!isReadOnly(key)) {
|
||||
setHeaderIfAbsent(key, value);
|
||||
}
|
||||
});
|
||||
if (headersToCopy == null || this.headers == headersToCopy) {
|
||||
return;
|
||||
}
|
||||
headersToCopy.forEach((key, value) -> {
|
||||
if (!isReadOnly(key)) {
|
||||
setHeaderIfAbsent(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isReadOnly(String headerName) {
|
||||
|
||||
+52
-20
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.messaging.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -75,6 +76,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> map = (Map<String, List<String>>) getHeader(NATIVE_HEADERS);
|
||||
if (map != null) {
|
||||
// setHeader checks for equality but we need copy of native headers
|
||||
setHeader(NATIVE_HEADERS, null);
|
||||
setHeader(NATIVE_HEADERS, new LinkedMultiValueMap<>(map));
|
||||
}
|
||||
}
|
||||
@@ -103,6 +106,8 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
if (isMutable()) {
|
||||
Map<String, List<String>> map = getNativeHeaders();
|
||||
if (map != null) {
|
||||
// setHeader checks for equality but we need immutable wrapper
|
||||
setHeader(NATIVE_HEADERS, null);
|
||||
setHeader(NATIVE_HEADERS, Collections.unmodifiableMap(map));
|
||||
}
|
||||
super.setImmutable();
|
||||
@@ -110,31 +115,34 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, @Nullable Object value) {
|
||||
if (name.equalsIgnoreCase(NATIVE_HEADERS)) {
|
||||
// Force removal since setHeader checks for equality
|
||||
super.setHeader(NATIVE_HEADERS, null);
|
||||
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
|
||||
if (headersToCopy == null) {
|
||||
return;
|
||||
}
|
||||
super.setHeader(name, value);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
|
||||
if (map != null && map != getNativeHeaders()) {
|
||||
map.forEach(this::setNativeHeaderValues);
|
||||
}
|
||||
|
||||
// setHeader checks for equality, native headers should be equal by now
|
||||
super.copyHeaders(headersToCopy);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
|
||||
if (headersToCopy != null) {
|
||||
Map<String, List<String>> nativeHeaders = getNativeHeaders();
|
||||
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
|
||||
if (map != null) {
|
||||
if (nativeHeaders != null) {
|
||||
nativeHeaders.putAll(map);
|
||||
}
|
||||
else {
|
||||
nativeHeaders = new LinkedMultiValueMap<>(map);
|
||||
}
|
||||
}
|
||||
super.copyHeaders(headersToCopy);
|
||||
setHeader(NATIVE_HEADERS, nativeHeaders);
|
||||
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
|
||||
if (headersToCopy == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
|
||||
if (map != null && getNativeHeaders() == null) {
|
||||
map.forEach(this::setNativeHeaderValues);
|
||||
}
|
||||
|
||||
super.copyHeadersIfAbsent(headersToCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,6 +209,30 @@ public class NativeMessageHeaderAccessor extends MessageHeaderAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #addNativeHeader(String, String)} for all values.
|
||||
* @since 5.2.12
|
||||
*/
|
||||
public void setNativeHeaderValues(String name, @Nullable List<String> values) {
|
||||
Assert.state(isMutable(), "Already immutable");
|
||||
Map<String, List<String>> map = getNativeHeaders();
|
||||
if (values == null) {
|
||||
if (map != null && map.get(name) != null) {
|
||||
setModified(true);
|
||||
map.remove(name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (map == null) {
|
||||
map = new LinkedMultiValueMap<>(3);
|
||||
setHeader(NATIVE_HEADERS, map);
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(values, getHeader(name))) {
|
||||
setModified(true);
|
||||
map.put(name, new ArrayList<>(values));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified native header value to existing values.
|
||||
* <p>In order for this to work, the accessor must be {@link #isMutable()
|
||||
|
||||
+35
-8
@@ -226,19 +226,46 @@ public class NativeMessageHeaderAccessorTests {
|
||||
|
||||
@Test // gh-25821
|
||||
void copyImmutableToMutable() {
|
||||
NativeMessageHeaderAccessor source = new NativeMessageHeaderAccessor();
|
||||
source.addNativeHeader("foo", "bar");
|
||||
Message<String> message = MessageBuilder.createMessage("payload", source.getMessageHeaders());
|
||||
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
|
||||
sourceAccessor.addNativeHeader("foo", "bar");
|
||||
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
|
||||
|
||||
NativeMessageHeaderAccessor target = new NativeMessageHeaderAccessor();
|
||||
target.copyHeaders(message.getHeaders());
|
||||
target.setLeaveMutable(true);
|
||||
message = MessageBuilder.createMessage(message.getPayload(), target.getMessageHeaders());
|
||||
NativeMessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
|
||||
targetAccessor.copyHeaders(source.getHeaders());
|
||||
targetAccessor.setLeaveMutable(true);
|
||||
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
|
||||
|
||||
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(message);
|
||||
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
|
||||
assertThat(accessor.isMutable());
|
||||
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
|
||||
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
|
||||
}
|
||||
|
||||
@Test // gh-25821
|
||||
void copyIfAbsentImmutableToMutable() {
|
||||
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
|
||||
sourceAccessor.addNativeHeader("foo", "bar");
|
||||
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
|
||||
|
||||
MessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
|
||||
targetAccessor.copyHeadersIfAbsent(source.getHeaders());
|
||||
targetAccessor.setLeaveMutable(true);
|
||||
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
|
||||
|
||||
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
|
||||
assertThat(accessor.isMutable());
|
||||
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
|
||||
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
|
||||
}
|
||||
|
||||
@Test // gh-26155
|
||||
void copySelf() {
|
||||
NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor();
|
||||
accessor.addNativeHeader("foo", "bar");
|
||||
accessor.setHeader("otherHeader", "otherHeaderValue");
|
||||
accessor.setLeaveMutable(true);
|
||||
|
||||
// Does not fail with ConcurrentModificationException
|
||||
accessor.copyHeaders(accessor.getMessageHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -193,8 +193,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
* {@code Persistence.createEntityManagerFactory} (if any).
|
||||
* <p>Can be populated with a String "value" (parsed via PropertiesEditor) or a
|
||||
* "props" element in XML bean definitions.
|
||||
* @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
|
||||
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
|
||||
* @see javax.persistence.Persistence#createEntityManagerFactory(String, Map)
|
||||
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map)
|
||||
*/
|
||||
public void setJpaProperties(Properties jpaProperties) {
|
||||
CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
|
||||
@@ -204,8 +204,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
* Specify JPA properties as a Map, to be passed into
|
||||
* {@code Persistence.createEntityManagerFactory} (if any).
|
||||
* <p>Can be populated with a "map" or "props" element in XML bean definitions.
|
||||
* @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map)
|
||||
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map)
|
||||
* @see javax.persistence.Persistence#createEntityManagerFactory(String, Map)
|
||||
* @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map)
|
||||
*/
|
||||
public void setJpaPropertyMap(@Nullable Map<String, ?> jpaProperties) {
|
||||
if (jpaProperties != null) {
|
||||
@@ -400,10 +400,13 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
String message = ex.getMessage();
|
||||
String causeString = cause.toString();
|
||||
if (!message.endsWith(causeString)) {
|
||||
throw new PersistenceException(message + "; nested exception is " + causeString, cause);
|
||||
ex = new PersistenceException(message + "; nested exception is " + causeString, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("Failed to initialize JPA EntityManagerFactory: " + ex.getMessage());
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
@@ -42,10 +43,10 @@ public class MockMultipartFile implements MultipartFile {
|
||||
|
||||
private final String name;
|
||||
|
||||
private String originalFilename;
|
||||
private final String originalFilename;
|
||||
|
||||
@Nullable
|
||||
private String contentType;
|
||||
private final String contentType;
|
||||
|
||||
private final byte[] content;
|
||||
|
||||
@@ -79,7 +80,7 @@ public class MockMultipartFile implements MultipartFile {
|
||||
public MockMultipartFile(
|
||||
String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {
|
||||
|
||||
Assert.hasLength(name, "Name must not be null");
|
||||
Assert.hasLength(name, "Name must not be empty");
|
||||
this.name = name;
|
||||
this.originalFilename = (originalFilename != null ? originalFilename : "");
|
||||
this.contentType = contentType;
|
||||
@@ -108,6 +109,7 @@ public class MockMultipartFile implements MultipartFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String getOriginalFilename() {
|
||||
return this.originalFilename;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -79,7 +79,7 @@ public @interface ActiveProfiles {
|
||||
* <p>The default value is {@code true}, which means that a test
|
||||
* class will <em>inherit</em> bean definition profiles defined by a
|
||||
* test superclass. Specifically, the bean definition profiles for a test
|
||||
* class will be added to the list of bean definition profiles
|
||||
* class will be appended to the list of bean definition profiles
|
||||
* defined by a test superclass. Thus, subclasses have the option of
|
||||
* <em>extending</em> the list of bean definition profiles.
|
||||
* <p>If {@code inheritProfiles} is set to {@code false}, the bean
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2017 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,8 +19,8 @@ package org.springframework.test.context;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
@@ -533,8 +533,8 @@ public class MergedContextConfiguration implements Serializable {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
// Active profiles must be unique and sorted
|
||||
Set<String> profilesSet = new TreeSet<>(Arrays.asList(activeProfiles));
|
||||
// Active profiles must be unique
|
||||
Set<String> profilesSet = new LinkedHashSet<>(Arrays.asList(activeProfiles));
|
||||
return StringUtils.toStringArray(profilesSet);
|
||||
}
|
||||
|
||||
|
||||
+18
-7
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.test.context.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -67,7 +70,7 @@ abstract class ActiveProfilesUtils {
|
||||
static String[] resolveActiveProfiles(Class<?> testClass) {
|
||||
Assert.notNull(testClass, "Class must not be null");
|
||||
|
||||
Set<String> activeProfiles = new TreeSet<>();
|
||||
List<String[]> profileArrays = new ArrayList<>();
|
||||
|
||||
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
|
||||
AnnotationDescriptor<ActiveProfiles> descriptor =
|
||||
@@ -106,17 +109,25 @@ abstract class ActiveProfilesUtils {
|
||||
|
||||
String[] profiles = resolver.resolve(rootDeclaringClass);
|
||||
if (!ObjectUtils.isEmpty(profiles)) {
|
||||
for (String profile : profiles) {
|
||||
if (StringUtils.hasText(profile)) {
|
||||
activeProfiles.add(profile.trim());
|
||||
}
|
||||
}
|
||||
profileArrays.add(profiles);
|
||||
}
|
||||
|
||||
descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
|
||||
rootDeclaringClass.getSuperclass(), annotationType) : null);
|
||||
}
|
||||
|
||||
// Reverse the list so that we can traverse "down" the hierarchy.
|
||||
Collections.reverse(profileArrays);
|
||||
|
||||
Set<String> activeProfiles = new LinkedHashSet<>();
|
||||
for (String[] profiles : profileArrays) {
|
||||
for (String profile : profiles) {
|
||||
if (StringUtils.hasText(profile)) {
|
||||
activeProfiles.add(profile.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.toStringArray(activeProfiles);
|
||||
}
|
||||
|
||||
|
||||
+7
-21
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.test.context.support;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -26,7 +23,6 @@ import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ActiveProfilesResolver;
|
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor;
|
||||
|
||||
@@ -43,6 +39,8 @@ import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDe
|
||||
*/
|
||||
public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class);
|
||||
|
||||
|
||||
@@ -58,36 +56,24 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
|
||||
@Override
|
||||
public String[] resolve(Class<?> testClass) {
|
||||
Assert.notNull(testClass, "Class must not be null");
|
||||
|
||||
Set<String> activeProfiles = new TreeSet<>();
|
||||
|
||||
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
|
||||
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, annotationType);
|
||||
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, ActiveProfiles.class);
|
||||
|
||||
if (descriptor == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(
|
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
|
||||
annotationType.getName(), testClass.getName()));
|
||||
ActiveProfiles.class.getName(), testClass.getName()));
|
||||
}
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
else {
|
||||
Class<?> declaringClass = descriptor.getDeclaringClass();
|
||||
ActiveProfiles annotation = descriptor.synthesizeAnnotation();
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
|
||||
declaringClass.getName()));
|
||||
}
|
||||
|
||||
for (String profile : annotation.profiles()) {
|
||||
if (StringUtils.hasText(profile)) {
|
||||
activeProfiles.add(profile.trim());
|
||||
}
|
||||
descriptor.getDeclaringClass().getName()));
|
||||
}
|
||||
return annotation.profiles();
|
||||
}
|
||||
|
||||
return StringUtils.toStringArray(activeProfiles);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -143,7 +143,7 @@ class MergedContextConfigurationTests {
|
||||
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
|
||||
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
|
||||
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
|
||||
assertThat(mergedConfig2).hasSameHashCodeAs(mergedConfig1);
|
||||
assertThat(mergedConfig2.hashCode()).isNotEqualTo(mergedConfig1.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -339,7 +339,7 @@ class MergedContextConfigurationTests {
|
||||
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
|
||||
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
|
||||
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
|
||||
assertThat(mergedConfig2).isEqualTo(mergedConfig1);
|
||||
assertThat(mergedConfig2).isNotEqualTo(mergedConfig1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -90,8 +90,8 @@ class ContextCacheTests {
|
||||
int size = 0, hit = 0, miss = 0;
|
||||
loadCtxAndAssertStats(FooBarProfilesTestCase.class, ++size, hit, ++miss);
|
||||
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
|
||||
// Profiles {foo, bar} MUST hash to the same as {bar, foo}
|
||||
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
|
||||
// Profiles {foo, bar} should not hash to the same as {bar,foo}
|
||||
loadCtxAndAssertStats(BarFooProfilesTestCase.class, ++size, hit, ++miss);
|
||||
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
|
||||
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
|
||||
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
|
||||
|
||||
+7
-7
@@ -67,12 +67,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
|
||||
|
||||
@Test
|
||||
void resolveActiveProfilesWithDuplicatedProfiles() {
|
||||
assertResolvedProfiles(DuplicatedProfiles.class, "bar", "baz", "foo");
|
||||
assertResolvedProfiles(DuplicatedProfiles.class, "foo", "bar", "baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveActiveProfilesWithLocalAndInheritedDuplicatedProfiles() {
|
||||
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "bar", "baz", "cat", "dog", "foo");
|
||||
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "foo", "bar", "baz", "cat", "dog");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,12 +92,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
|
||||
|
||||
@Test
|
||||
void resolveActiveProfilesWithLocalAndInheritedAnnotations() {
|
||||
assertResolvedProfiles(LocationsBar.class, "bar", "foo");
|
||||
assertResolvedProfiles(LocationsBar.class, "foo", "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveActiveProfilesWithOverriddenAnnotation() {
|
||||
assertResolvedProfiles(Animals.class, "cat", "dog");
|
||||
assertResolvedProfiles(Animals.class, "dog", "cat");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +129,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
|
||||
*/
|
||||
@Test
|
||||
void resolveActiveProfilesWithLocalAndInheritedMetaAnnotations() {
|
||||
assertResolvedProfiles(MetaLocationsBar.class, "bar", "foo");
|
||||
assertResolvedProfiles(MetaLocationsBar.class, "foo", "bar");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +137,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
|
||||
*/
|
||||
@Test
|
||||
void resolveActiveProfilesWithOverriddenMetaAnnotation() {
|
||||
assertResolvedProfiles(MetaAnimals.class, "cat", "dog");
|
||||
assertResolvedProfiles(MetaAnimals.class, "dog", "cat");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
|
||||
*/
|
||||
@Test
|
||||
void resolveActiveProfilesWithMergedInheritedResolver() {
|
||||
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "bar", "foo");
|
||||
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "foo", "bar");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+14
-5
@@ -38,16 +38,25 @@ import org.springframework.transaction.TransactionDefinition;
|
||||
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
|
||||
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
|
||||
* convert the data to the latter class, so that Spring's transaction support code
|
||||
* does not have to know about annotations. If no rules are relevant to the exception,
|
||||
* it will be treated like
|
||||
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
|
||||
* (rolling back on {@link RuntimeException} and {@link Error} but not on checked
|
||||
* exceptions).
|
||||
* does not have to know about annotations. If no custom rollback rules apply,
|
||||
* the transaction will roll back on {@link RuntimeException} and {@link Error}
|
||||
* but not on checked exceptions.
|
||||
*
|
||||
* <p>For specific information about the semantics of this annotation's attributes,
|
||||
* consult the {@link org.springframework.transaction.TransactionDefinition} and
|
||||
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
|
||||
*
|
||||
* <p>This annotation commonly works with thread-bound transactions managed by
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager}, exposing a
|
||||
* transaction to all data access operations within the current execution thread.
|
||||
* <b>Note: This does NOT propagate to newly started threads within the method.</b>
|
||||
*
|
||||
* <p>Alternatively, this annotation may demarcate a reactive transaction managed
|
||||
* by {@link org.springframework.transaction.ReactiveTransactionManager} which
|
||||
* uses the Reactor context instead of thread-local attributes. As a consequence,
|
||||
* all participating data access operations need to execute within the same
|
||||
* Reactor context in the same reactive pipeline.
|
||||
*
|
||||
* @author Colin Sampaleanu
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* TransactionAttribute implementation that works out whether a given exception
|
||||
* should cause transaction rollback by applying a number of rollback rules,
|
||||
* both positive and negative. If no rules are relevant to the exception, it
|
||||
* both positive and negative. If no custom rollback rules apply, this attribute
|
||||
* behaves like DefaultTransactionAttribute (rolling back on runtime exceptions).
|
||||
*
|
||||
* <p>{@link TransactionAttributeEditor} creates objects of this class.
|
||||
|
||||
@@ -480,9 +480,9 @@ public class MediaType extends MimeType implements Serializable {
|
||||
|
||||
|
||||
@Override
|
||||
protected void checkParameters(String attribute, String value) {
|
||||
super.checkParameters(attribute, value);
|
||||
if (PARAM_QUALITY_FACTOR.equals(attribute)) {
|
||||
protected void checkParameters(String parameter, String value) {
|
||||
super.checkParameters(parameter, value);
|
||||
if (PARAM_QUALITY_FACTOR.equals(parameter)) {
|
||||
value = unquote(value);
|
||||
double d = Double.parseDouble(value);
|
||||
Assert.isTrue(d >= 0D && d <= 1D,
|
||||
|
||||
+2
-1
@@ -128,7 +128,8 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
|
||||
message.getHeaders().setContentLength(buffer.readableByteCount());
|
||||
return message.writeWith(Mono.just(buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
|
||||
});
|
||||
})
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
|
||||
if (isStreamingMediaType(contentType)) {
|
||||
|
||||
+6
-1
@@ -18,6 +18,7 @@ package org.springframework.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -55,6 +56,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.TypeUtils;
|
||||
|
||||
/**
|
||||
@@ -308,7 +310,9 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
JsonEncoding encoding = getJsonEncoding(contentType);
|
||||
|
||||
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
|
||||
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
|
||||
try {
|
||||
writePrefix(generator, object);
|
||||
|
||||
Object value = object;
|
||||
@@ -343,6 +347,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
||||
|
||||
writeSuffix(generator, object);
|
||||
generator.flush();
|
||||
generator.close();
|
||||
}
|
||||
catch (InvalidDefinitionException ex) {
|
||||
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
|
||||
|
||||
+22
-4
@@ -203,10 +203,28 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
|
||||
// We must resolve value first however, for a chance to handle potential error.
|
||||
if (body instanceof Mono) {
|
||||
return ((Mono<? extends DataBuffer>) body)
|
||||
.flatMap(buffer -> doCommit(() ->
|
||||
writeWithInternal(Mono.fromCallable(() -> buffer)
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release))))
|
||||
.doOnError(t -> getHeaders().clearContentHeaders());
|
||||
.flatMap(buffer -> {
|
||||
AtomicReference<Boolean> subscribed = new AtomicReference<>(false);
|
||||
return doCommit(
|
||||
() -> {
|
||||
try {
|
||||
return writeWithInternal(Mono.fromCallable(() -> buffer)
|
||||
.doOnSubscribe(s -> subscribed.set(true))
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
})
|
||||
.doOnError(ex -> DataBufferUtils.release(buffer))
|
||||
.doOnCancel(() -> {
|
||||
if (!subscribed.get()) {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
});
|
||||
})
|
||||
.doOnError(t -> getHeaders().clearContentHeaders())
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
else {
|
||||
return new ChannelSendOperator<>(body, inner -> doCommit(() -> writeWithInternal(inner)))
|
||||
|
||||
+6
-2
@@ -54,7 +54,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
this.headers.add(key, value);
|
||||
if (value != null) {
|
||||
this.headers.add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +71,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
this.headers.set(key, value);
|
||||
if (value != null) {
|
||||
this.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -112,6 +112,8 @@ public @interface CrossOrigin {
|
||||
* {@code Expires}, {@code Last-Modified}, or {@code Pragma},
|
||||
* <p>Exposed headers are listed in the {@code Access-Control-Expose-Headers}
|
||||
* response header of actual CORS requests.
|
||||
* <p>The special value {@code "*"} allows all headers to be exposed for
|
||||
* non-credentialed requests.
|
||||
* <p>By default no headers are listed as exposed.
|
||||
*/
|
||||
String[] exposedHeaders() default {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -254,13 +254,11 @@ public class CorsConfiguration {
|
||||
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
|
||||
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}) that an
|
||||
* actual response might have and can be exposed.
|
||||
* <p>Note that {@code "*"} is not a valid exposed header value.
|
||||
* <p>The special value {@code "*"} allows all headers to be exposed for
|
||||
* non-credentialed requests.
|
||||
* <p>By default this is not set.
|
||||
*/
|
||||
public void setExposedHeaders(@Nullable List<String> exposedHeaders) {
|
||||
if (exposedHeaders != null && exposedHeaders.contains(ALL)) {
|
||||
throw new IllegalArgumentException("'*' is not a valid exposed header value");
|
||||
}
|
||||
this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null);
|
||||
}
|
||||
|
||||
@@ -276,12 +274,10 @@ public class CorsConfiguration {
|
||||
|
||||
/**
|
||||
* Add a response header to expose.
|
||||
* <p>Note that {@code "*"} is not a valid exposed header value.
|
||||
* <p>The special value {@code "*"} allows all headers to be exposed for
|
||||
* non-credentialed requests.
|
||||
*/
|
||||
public void addExposedHeader(String exposedHeader) {
|
||||
if (ALL.equals(exposedHeader)) {
|
||||
throw new IllegalArgumentException("'*' is not a valid exposed header value");
|
||||
}
|
||||
if (this.exposedHeaders == null) {
|
||||
this.exposedHeaders = new ArrayList<>(4);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -42,8 +42,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return Map.class.isAssignableFrom(parameter.getParameterType()) &&
|
||||
parameter.getParameterAnnotations().length == 0;
|
||||
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
|
||||
parameter.getParameterAnnotations().length == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,8 +70,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
|
||||
}
|
||||
else if (returnValue != null) {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException("Unexpected return type: " +
|
||||
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
|
||||
throw new UnsupportedOperationException("Unexpected return type [" +
|
||||
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -70,8 +70,8 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
|
||||
}
|
||||
else {
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException("Unexpected return type: " +
|
||||
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
|
||||
throw new UnsupportedOperationException("Unexpected return type [" +
|
||||
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -224,7 +224,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
if (this.forwardedHeaderTransformer != null) {
|
||||
request = this.forwardedHeaderTransformer.apply(request);
|
||||
try {
|
||||
request = this.forwardedHeaderTransformer.apply(request);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to apply forwarded headers to " + formatRequest(request), ex);
|
||||
}
|
||||
response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
return response.setComplete();
|
||||
}
|
||||
}
|
||||
ServerWebExchange exchange = createExchange(request, response);
|
||||
|
||||
|
||||
+4
-1
@@ -27,6 +27,7 @@ import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
|
||||
@@ -209,7 +210,9 @@ public class ContentCachingResponseWrapper extends HttpServletResponseWrapper {
|
||||
if (this.content.size() > 0) {
|
||||
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
|
||||
if ((complete || this.contentLength != null) && !rawResponse.isCommitted()) {
|
||||
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
|
||||
if (rawResponse.getHeader(HttpHeaders.TRANSFER_ENCODING) == null) {
|
||||
rawResponse.setContentLength(complete ? this.content.size() : this.contentLength);
|
||||
}
|
||||
this.contentLength = null;
|
||||
}
|
||||
this.content.writeTo(rawResponse.getOutputStream());
|
||||
|
||||
@@ -578,7 +578,7 @@ public class UrlPathHelper {
|
||||
return requestUri;
|
||||
}
|
||||
String start = requestUri.substring(0, index);
|
||||
for (int i = key.length(); i < requestUri.length(); i++) {
|
||||
for (int i = index + key.length(); i < requestUri.length(); i++) {
|
||||
char c = requestUri.charAt(i);
|
||||
if (c == ';' || c == '/') {
|
||||
return start + requestUri.substring(i);
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util.pattern;
|
||||
|
||||
/**
|
||||
* Used to represent a subsection of an array, useful when wanting to pass that subset of data
|
||||
* to another method (e.g. a java regex matcher) but not wanting to create a new string object
|
||||
* to hold all that data.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
class SubSequence implements CharSequence {
|
||||
|
||||
private final char[] chars;
|
||||
|
||||
private final int start;
|
||||
|
||||
private final int end;
|
||||
|
||||
|
||||
SubSequence(char[] chars, int start, int end) {
|
||||
this.chars = chars;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return (this.end - this.start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return this.chars[this.start + index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return new SubSequence(this.chars, this.start + start, this.start + end);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String(this.chars, this.start, this.end - this.start);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user