Consistent and efficient access to BeanDefinition argument values
Issue: SPR-16192
This commit is contained in:
parent
e2bb06edbd
commit
b5cedd43eb
|
@ -318,7 +318,7 @@ public class MutablePropertyValues implements PropertyValues, Serializable {
|
|||
*/
|
||||
public void registerProcessedProperty(String propertyName) {
|
||||
if (this.processedProperties == null) {
|
||||
this.processedProperties = new HashSet<>();
|
||||
this.processedProperties = new HashSet<>(4);
|
||||
}
|
||||
this.processedProperties.add(propertyName);
|
||||
}
|
||||
|
|
|
@ -219,6 +219,14 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
|
|||
*/
|
||||
ConstructorArgumentValues getConstructorArgumentValues();
|
||||
|
||||
/**
|
||||
* Return if there are constructor argument values defined for this bean.
|
||||
* @since 5.0.2
|
||||
*/
|
||||
default boolean hasConstructorArgumentValues() {
|
||||
return !getConstructorArgumentValues().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property values to be applied to a new instance of the bean.
|
||||
* <p>The returned instance can be modified during bean factory post-processing.
|
||||
|
@ -226,6 +234,14 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
|
|||
*/
|
||||
MutablePropertyValues getPropertyValues();
|
||||
|
||||
/**
|
||||
* Return if there are property values values defined for this bean.
|
||||
* @since 5.0.2
|
||||
*/
|
||||
default boolean hasPropertyValues() {
|
||||
return !getPropertyValues().isEmpty();
|
||||
}
|
||||
|
||||
|
||||
// Read-only attributes
|
||||
|
||||
|
|
|
@ -81,10 +81,14 @@ public class BeanDefinitionVisitor {
|
|||
visitFactoryBeanName(beanDefinition);
|
||||
visitFactoryMethodName(beanDefinition);
|
||||
visitScope(beanDefinition);
|
||||
visitPropertyValues(beanDefinition.getPropertyValues());
|
||||
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
|
||||
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
|
||||
visitGenericArgumentValues(cas.getGenericArgumentValues());
|
||||
if (beanDefinition.hasPropertyValues()) {
|
||||
visitPropertyValues(beanDefinition.getPropertyValues());
|
||||
}
|
||||
if (beanDefinition.hasConstructorArgumentValues()) {
|
||||
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
|
||||
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
|
||||
visitGenericArgumentValues(cas.getGenericArgumentValues());
|
||||
}
|
||||
}
|
||||
|
||||
protected void visitParentName(BeanDefinition beanDefinition) {
|
||||
|
|
|
@ -723,7 +723,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
Class<?> commonType = null;
|
||||
Method uniqueCandidate = null;
|
||||
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
|
||||
int minNrOfArgs =
|
||||
(mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0);
|
||||
Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
|
||||
for (Method factoryMethod : candidates) {
|
||||
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
|
||||
|
@ -1277,10 +1278,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
* @param bw BeanWrapper with bean instance
|
||||
*/
|
||||
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
|
||||
PropertyValues pvs = mbd.getPropertyValues();
|
||||
|
||||
if (bw == null) {
|
||||
if (!pvs.isEmpty()) {
|
||||
if (mbd.hasPropertyValues()) {
|
||||
throw new BeanCreationException(
|
||||
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
|
||||
}
|
||||
|
@ -1311,6 +1310,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
return;
|
||||
}
|
||||
|
||||
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
|
||||
|
||||
if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
|
||||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
|
||||
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
|
||||
|
@ -1332,6 +1333,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
|
||||
|
||||
if (hasInstAwareBpps || needsDepCheck) {
|
||||
if (pvs == null) {
|
||||
pvs = mbd.getPropertyValues();
|
||||
}
|
||||
PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
|
||||
if (hasInstAwareBpps) {
|
||||
for (BeanPostProcessor bp : getBeanPostProcessors()) {
|
||||
|
@ -1349,7 +1353,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
}
|
||||
}
|
||||
|
||||
applyPropertyValues(beanName, mbd, bw, pvs);
|
||||
if (pvs != null) {
|
||||
applyPropertyValues(beanName, mbd, bw, pvs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -173,11 +173,14 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
@Nullable
|
||||
private String factoryMethodName;
|
||||
|
||||
@Nullable
|
||||
private ConstructorArgumentValues constructorArgumentValues;
|
||||
|
||||
@Nullable
|
||||
private MutablePropertyValues propertyValues;
|
||||
|
||||
private MethodOverrides methodOverrides = new MethodOverrides();
|
||||
@Nullable
|
||||
private MethodOverrides methodOverrides;
|
||||
|
||||
@Nullable
|
||||
private String initMethodName;
|
||||
|
@ -212,8 +215,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
* constructor argument values and property values.
|
||||
*/
|
||||
protected AbstractBeanDefinition(@Nullable ConstructorArgumentValues cargs, @Nullable MutablePropertyValues pvs) {
|
||||
this.constructorArgumentValues = (cargs != null ? cargs : new ConstructorArgumentValues());
|
||||
this.propertyValues = (pvs != null ? pvs : new MutablePropertyValues());
|
||||
this.constructorArgumentValues = cargs;
|
||||
this.propertyValues = pvs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,8 +232,6 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
setLazyInit(original.isLazyInit());
|
||||
setFactoryBeanName(original.getFactoryBeanName());
|
||||
setFactoryMethodName(original.getFactoryMethodName());
|
||||
this.constructorArgumentValues = new ConstructorArgumentValues(original.getConstructorArgumentValues());
|
||||
this.propertyValues = new MutablePropertyValues(original.getPropertyValues());
|
||||
setRole(original.getRole());
|
||||
setSource(original.getSource());
|
||||
copyAttributesFrom(original);
|
||||
|
@ -240,6 +241,15 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
if (originalAbd.hasBeanClass()) {
|
||||
setBeanClass(originalAbd.getBeanClass());
|
||||
}
|
||||
if (originalAbd.hasConstructorArgumentValues()) {
|
||||
setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));
|
||||
}
|
||||
if (originalAbd.hasPropertyValues()) {
|
||||
setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));
|
||||
}
|
||||
if (originalAbd.hasMethodOverrides()) {
|
||||
setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
|
||||
}
|
||||
setAutowireMode(originalAbd.getAutowireMode());
|
||||
setDependencyCheck(originalAbd.getDependencyCheck());
|
||||
setDependsOn(originalAbd.getDependsOn());
|
||||
|
@ -249,7 +259,6 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
setInstanceSupplier(originalAbd.getInstanceSupplier());
|
||||
setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed());
|
||||
setLenientConstructorResolution(originalAbd.isLenientConstructorResolution());
|
||||
setMethodOverrides(new MethodOverrides(originalAbd.getMethodOverrides()));
|
||||
setInitMethodName(originalAbd.getInitMethodName());
|
||||
setEnforceInitMethod(originalAbd.isEnforceInitMethod());
|
||||
setDestroyMethodName(originalAbd.getDestroyMethodName());
|
||||
|
@ -258,6 +267,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
setResource(originalAbd.getResource());
|
||||
}
|
||||
else {
|
||||
setConstructorArgumentValues(new ConstructorArgumentValues(original.getConstructorArgumentValues()));
|
||||
setPropertyValues(new MutablePropertyValues(original.getPropertyValues()));
|
||||
setResourceDescription(original.getResourceDescription());
|
||||
}
|
||||
}
|
||||
|
@ -294,8 +305,6 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
if (StringUtils.hasLength(other.getFactoryMethodName())) {
|
||||
setFactoryMethodName(other.getFactoryMethodName());
|
||||
}
|
||||
getConstructorArgumentValues().addArgumentValues(other.getConstructorArgumentValues());
|
||||
getPropertyValues().addPropertyValues(other.getPropertyValues());
|
||||
setRole(other.getRole());
|
||||
setSource(other.getSource());
|
||||
copyAttributesFrom(other);
|
||||
|
@ -305,6 +314,15 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
if (otherAbd.hasBeanClass()) {
|
||||
setBeanClass(otherAbd.getBeanClass());
|
||||
}
|
||||
if (otherAbd.hasConstructorArgumentValues()) {
|
||||
getConstructorArgumentValues().addArgumentValues(other.getConstructorArgumentValues());
|
||||
}
|
||||
if (otherAbd.hasPropertyValues()) {
|
||||
getPropertyValues().addPropertyValues(other.getPropertyValues());
|
||||
}
|
||||
if (otherAbd.hasMethodOverrides()) {
|
||||
getMethodOverrides().addOverrides(otherAbd.getMethodOverrides());
|
||||
}
|
||||
setAutowireMode(otherAbd.getAutowireMode());
|
||||
setDependencyCheck(otherAbd.getDependencyCheck());
|
||||
setDependsOn(otherAbd.getDependsOn());
|
||||
|
@ -314,7 +332,6 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
setInstanceSupplier(otherAbd.getInstanceSupplier());
|
||||
setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed());
|
||||
setLenientConstructorResolution(otherAbd.isLenientConstructorResolution());
|
||||
getMethodOverrides().addOverrides(otherAbd.getMethodOverrides());
|
||||
if (otherAbd.getInitMethodName() != null) {
|
||||
setInitMethodName(otherAbd.getInitMethodName());
|
||||
setEnforceInitMethod(otherAbd.isEnforceInitMethod());
|
||||
|
@ -327,6 +344,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
setResource(otherAbd.getResource());
|
||||
}
|
||||
else {
|
||||
getConstructorArgumentValues().addArgumentValues(other.getConstructorArgumentValues());
|
||||
getPropertyValues().addPropertyValues(other.getPropertyValues());
|
||||
setResourceDescription(other.getResourceDescription());
|
||||
}
|
||||
}
|
||||
|
@ -778,9 +797,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
/**
|
||||
* Specify constructor argument values for this bean.
|
||||
*/
|
||||
public void setConstructorArgumentValues(@Nullable ConstructorArgumentValues constructorArgumentValues) {
|
||||
this.constructorArgumentValues =
|
||||
(constructorArgumentValues != null ? constructorArgumentValues : new ConstructorArgumentValues());
|
||||
public void setConstructorArgumentValues(ConstructorArgumentValues constructorArgumentValues) {
|
||||
this.constructorArgumentValues = constructorArgumentValues;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -788,6 +806,9 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
*/
|
||||
@Override
|
||||
public ConstructorArgumentValues getConstructorArgumentValues() {
|
||||
if (this.constructorArgumentValues == null) {
|
||||
this.constructorArgumentValues = new ConstructorArgumentValues();
|
||||
}
|
||||
return this.constructorArgumentValues;
|
||||
}
|
||||
|
||||
|
@ -795,14 +816,14 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
* Return if there are constructor argument values defined for this bean.
|
||||
*/
|
||||
public boolean hasConstructorArgumentValues() {
|
||||
return !this.constructorArgumentValues.isEmpty();
|
||||
return (this.constructorArgumentValues != null && !this.constructorArgumentValues.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify property values for this bean, if any.
|
||||
*/
|
||||
public void setPropertyValues(@Nullable MutablePropertyValues propertyValues) {
|
||||
this.propertyValues = (propertyValues != null ? propertyValues : new MutablePropertyValues());
|
||||
public void setPropertyValues(MutablePropertyValues propertyValues) {
|
||||
this.propertyValues = propertyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -810,14 +831,25 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
*/
|
||||
@Override
|
||||
public MutablePropertyValues getPropertyValues() {
|
||||
if (this.propertyValues == null) {
|
||||
this.propertyValues = new MutablePropertyValues();
|
||||
}
|
||||
return this.propertyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if there are property values values defined for this bean.
|
||||
* @since 5.0.2
|
||||
*/
|
||||
public boolean hasPropertyValues() {
|
||||
return (this.propertyValues != null && !this.propertyValues.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify method overrides for the bean, if any.
|
||||
*/
|
||||
public void setMethodOverrides(@Nullable MethodOverrides methodOverrides) {
|
||||
this.methodOverrides = (methodOverrides != null ? methodOverrides : new MethodOverrides());
|
||||
public void setMethodOverrides(MethodOverrides methodOverrides) {
|
||||
this.methodOverrides = methodOverrides;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -826,9 +858,20 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
* <p>Never returns {@code null}.
|
||||
*/
|
||||
public MethodOverrides getMethodOverrides() {
|
||||
if (this.methodOverrides == null) {
|
||||
this.methodOverrides = new MethodOverrides();
|
||||
}
|
||||
return this.methodOverrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if there are method overrides defined for this bean.
|
||||
* @since 5.0.2
|
||||
*/
|
||||
public boolean hasMethodOverrides() {
|
||||
return (this.methodOverrides != null && !this.methodOverrides.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the initializer method.
|
||||
* <p>The default is {@code null} in which case there is no initializer method.
|
||||
|
@ -1002,7 +1045,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
* @throws BeanDefinitionValidationException in case of validation failure
|
||||
*/
|
||||
public void validate() throws BeanDefinitionValidationException {
|
||||
if (!getMethodOverrides().isEmpty() && getFactoryMethodName() != null) {
|
||||
if (hasMethodOverrides() && getFactoryMethodName() != null) {
|
||||
throw new BeanDefinitionValidationException(
|
||||
"Cannot combine static factory method with method overrides: " +
|
||||
"the static factory method must create the instance");
|
||||
|
@ -1020,9 +1063,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
|||
*/
|
||||
public void prepareMethodOverrides() throws BeanDefinitionValidationException {
|
||||
// Check that lookup methods exists.
|
||||
MethodOverrides methodOverrides = getMethodOverrides();
|
||||
if (!methodOverrides.isEmpty()) {
|
||||
Set<MethodOverride> overrides = methodOverrides.getOverrides();
|
||||
if (hasMethodOverrides()) {
|
||||
Set<MethodOverride> overrides = getMethodOverrides().getOverrides();
|
||||
synchronized (overrides) {
|
||||
for (MethodOverride mo : overrides) {
|
||||
prepareMethodOverride(mo);
|
||||
|
|
|
@ -434,9 +434,14 @@ class ConstructorResolver {
|
|||
else {
|
||||
// We don't have arguments passed in programmatically, so we need to resolve the
|
||||
// arguments specified in the constructor arguments held in the bean definition.
|
||||
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
|
||||
resolvedValues = new ConstructorArgumentValues();
|
||||
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
|
||||
if (mbd.hasConstructorArgumentValues()) {
|
||||
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
|
||||
resolvedValues = new ConstructorArgumentValues();
|
||||
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
|
||||
}
|
||||
else {
|
||||
minNrOfArgs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
LinkedList<UnsatisfiedDependencyException> causes = null;
|
||||
|
@ -447,7 +452,14 @@ class ConstructorResolver {
|
|||
if (paramTypes.length >= minNrOfArgs) {
|
||||
ArgumentsHolder argsHolder;
|
||||
|
||||
if (resolvedValues != null) {
|
||||
if (explicitArgs != null){
|
||||
// Explicit arguments given -> arguments length must match exactly.
|
||||
if (paramTypes.length != explicitArgs.length) {
|
||||
continue;
|
||||
}
|
||||
argsHolder = new ArgumentsHolder(explicitArgs);
|
||||
}
|
||||
else {
|
||||
// Resolved constructor arguments: type conversion and/or autowiring necessary.
|
||||
try {
|
||||
String[] paramNames = null;
|
||||
|
@ -472,14 +484,6 @@ class ConstructorResolver {
|
|||
}
|
||||
}
|
||||
|
||||
else {
|
||||
// Explicit arguments given -> arguments length must match exactly.
|
||||
if (paramTypes.length != explicitArgs.length) {
|
||||
continue;
|
||||
}
|
||||
argsHolder = new ArgumentsHolder(explicitArgs);
|
||||
}
|
||||
|
||||
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
|
||||
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
|
||||
// Choose this factory method if it represents the closest match.
|
||||
|
@ -522,7 +526,7 @@ class ConstructorResolver {
|
|||
argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null");
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (resolvedValues != null){
|
||||
Set<ValueHolder> valueHolders = new LinkedHashSet<>(resolvedValues.getArgumentCount());
|
||||
valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());
|
||||
valueHolders.addAll(resolvedValues.getGenericArgumentValues());
|
||||
|
@ -645,7 +649,7 @@ class ConstructorResolver {
|
|||
* given the resolved constructor argument values.
|
||||
*/
|
||||
private ArgumentsHolder createArgumentArray(
|
||||
String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,
|
||||
String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
|
||||
BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
|
||||
boolean autowiring) throws UnsatisfiedDependencyException {
|
||||
|
||||
|
@ -660,13 +664,15 @@ class ConstructorResolver {
|
|||
Class<?> paramType = paramTypes[paramIndex];
|
||||
String paramName = (paramNames != null ? paramNames[paramIndex] : "");
|
||||
// Try to find matching constructor argument value, either indexed or generic.
|
||||
ConstructorArgumentValues.ValueHolder valueHolder =
|
||||
resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
|
||||
// If we couldn't find a direct match and are not supposed to autowire,
|
||||
// let's try the next generic, untyped argument value as fallback:
|
||||
// it could match after type conversion (for example, String -> int).
|
||||
if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
|
||||
valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
ConstructorArgumentValues.ValueHolder valueHolder = null;
|
||||
if (resolvedValues != null) {
|
||||
valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
|
||||
// If we couldn't find a direct match and are not supposed to autowire,
|
||||
// let's try the next generic, untyped argument value as fallback:
|
||||
// it could match after type conversion (for example, String -> int).
|
||||
if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
|
||||
valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
}
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
// We found a potential match - let's give it a try.
|
||||
|
|
|
@ -37,7 +37,7 @@ import org.springframework.lang.Nullable;
|
|||
*/
|
||||
public class MethodOverrides {
|
||||
|
||||
private final Set<MethodOverride> overrides = Collections.synchronizedSet(new LinkedHashSet<>(0));
|
||||
private final Set<MethodOverride> overrides = Collections.synchronizedSet(new LinkedHashSet<>(2));
|
||||
|
||||
private volatile boolean modified = false;
|
||||
|
||||
|
|
|
@ -190,7 +190,9 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
|||
* @param cargs the constructor argument values to apply
|
||||
* @param pvs the property values to apply
|
||||
*/
|
||||
public RootBeanDefinition(@Nullable Class<?> beanClass, ConstructorArgumentValues cargs, @Nullable MutablePropertyValues pvs) {
|
||||
public RootBeanDefinition(@Nullable Class<?> beanClass, @Nullable ConstructorArgumentValues cargs,
|
||||
@Nullable MutablePropertyValues pvs) {
|
||||
|
||||
super(cargs, pvs);
|
||||
setBeanClass(beanClass);
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
|||
@Override
|
||||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
|
||||
// Don't override the class with CGLIB if no overrides.
|
||||
if (bd.getMethodOverrides().isEmpty()) {
|
||||
if (!bd.hasMethodOverrides()) {
|
||||
Constructor<?> constructorToUse;
|
||||
synchronized (bd.constructorArgumentLock) {
|
||||
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
|
||||
|
@ -72,8 +72,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
|||
try {
|
||||
if (System.getSecurityManager() != null) {
|
||||
constructorToUse = AccessController.doPrivileged(
|
||||
(PrivilegedExceptionAction<Constructor<?>>) () ->
|
||||
clazz.getDeclaredConstructor());
|
||||
(PrivilegedExceptionAction<Constructor<?>>) () -> clazz.getDeclaredConstructor());
|
||||
}
|
||||
else {
|
||||
constructorToUse = clazz.getDeclaredConstructor();
|
||||
|
@ -107,7 +106,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
|||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
final Constructor<?> ctor, @Nullable Object... args) {
|
||||
|
||||
if (bd.getMethodOverrides().isEmpty()) {
|
||||
if (!bd.hasMethodOverrides()) {
|
||||
if (System.getSecurityManager() != null) {
|
||||
// use own privileged to change accessibility (when security is on)
|
||||
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.
|
||||
|
@ -41,7 +41,6 @@ import org.springframework.context.annotation.Configuration;
|
|||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class JCacheCustomInterceptorTests {
|
||||
|
@ -52,6 +51,7 @@ public class JCacheCustomInterceptorTests {
|
|||
|
||||
protected Cache exceptionCache;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
ctx = new AnnotationConfigApplicationContext(EnableCachingConfig.class);
|
||||
|
@ -61,9 +61,12 @@ public class JCacheCustomInterceptorTests {
|
|||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ctx.close();
|
||||
if (ctx != null) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onlyOneInterceptorIsAvailable() {
|
||||
Map<String, JCacheInterceptor> interceptors = ctx.getBeansOfType(JCacheInterceptor.class);
|
||||
|
@ -130,6 +133,7 @@ public class JCacheCustomInterceptorTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A test {@link org.springframework.cache.interceptor.CacheInterceptor} that handles special exception
|
||||
* types.
|
||||
|
|
|
@ -291,24 +291,24 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
String resourceCache = element.getAttribute("resource-cache");
|
||||
if ("true".equals(resourceCache)) {
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
|
||||
RootBeanDefinition cachingResolverDef = new RootBeanDefinition(CachingResourceResolver.class);
|
||||
cachingResolverDef.setSource(source);
|
||||
cachingResolverDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
cachingResolverDef.setConstructorArgumentValues(cavs);
|
||||
cachingResolverDef.setConstructorArgumentValues(cargs);
|
||||
|
||||
RootBeanDefinition cachingTransformerDef = new RootBeanDefinition(CachingResourceTransformer.class);
|
||||
cachingTransformerDef.setSource(source);
|
||||
cachingTransformerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
cachingTransformerDef.setConstructorArgumentValues(cavs);
|
||||
cachingTransformerDef.setConstructorArgumentValues(cargs);
|
||||
|
||||
String cacheManagerName = element.getAttribute("cache-manager");
|
||||
String cacheName = element.getAttribute("cache-name");
|
||||
if (StringUtils.hasText(cacheManagerName) && StringUtils.hasText(cacheName)) {
|
||||
RuntimeBeanReference cacheManagerRef = new RuntimeBeanReference(cacheManagerName);
|
||||
cavs.addIndexedArgumentValue(0, cacheManagerRef);
|
||||
cavs.addIndexedArgumentValue(1, cacheName);
|
||||
cargs.addIndexedArgumentValue(0, cacheManagerRef);
|
||||
cargs.addIndexedArgumentValue(1, cacheName);
|
||||
}
|
||||
else {
|
||||
ConstructorArgumentValues cacheCavs = new ConstructorArgumentValues();
|
||||
|
@ -317,7 +317,7 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
|||
cacheDef.setSource(source);
|
||||
cacheDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
cacheDef.setConstructorArgumentValues(cacheCavs);
|
||||
cavs.addIndexedArgumentValue(0, cacheDef);
|
||||
cargs.addIndexedArgumentValue(0, cacheDef);
|
||||
}
|
||||
resourceResolvers.add(cachingResolverDef);
|
||||
resourceTransformers.add(cachingTransformerDef);
|
||||
|
@ -386,12 +386,12 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
|||
String[] patterns = StringUtils.commaDelimitedListToStringArray(beanElement.getAttribute("patterns"));
|
||||
Object strategy = null;
|
||||
if (FIXED_VERSION_STRATEGY_ELEMENT.equals(beanElement.getLocalName())) {
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, beanElement.getAttribute("version"));
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, beanElement.getAttribute("version"));
|
||||
RootBeanDefinition strategyDef = new RootBeanDefinition(FixedVersionStrategy.class);
|
||||
strategyDef.setSource(source);
|
||||
strategyDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
strategyDef.setConstructorArgumentValues(cavs);
|
||||
strategyDef.setConstructorArgumentValues(cargs);
|
||||
strategy = strategyDef;
|
||||
}
|
||||
else if (CONTENT_VERSION_STRATEGY_ELEMENT.equals(beanElement.getLocalName())) {
|
||||
|
|
|
@ -105,16 +105,16 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
private interface HandlerMappingStrategy {
|
||||
|
||||
void addMapping(Element mappingElement, ManagedMap<String, Object> map, ParserContext context);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class WebSocketHandlerMappingStrategy implements HandlerMappingStrategy {
|
||||
|
||||
private final RuntimeBeanReference handshakeHandlerReference;
|
||||
|
||||
private final ManagedList<?> interceptorsList;
|
||||
|
||||
private WebSocketHandlerMappingStrategy(RuntimeBeanReference handshakeHandler, ManagedList<?> interceptors) {
|
||||
public WebSocketHandlerMappingStrategy(RuntimeBeanReference handshakeHandler, ManagedList<?> interceptors) {
|
||||
this.handshakeHandlerReference = handshakeHandler;
|
||||
this.interceptorsList = interceptors;
|
||||
}
|
||||
|
@ -125,10 +125,10 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
List<String> mappings = Arrays.asList(StringUtils.tokenizeToStringArray(pathAttribute, ","));
|
||||
RuntimeBeanReference handlerReference = new RuntimeBeanReference(element.getAttribute("handler"));
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, handlerReference);
|
||||
cavs.addIndexedArgumentValue(1, this.handshakeHandlerReference);
|
||||
RootBeanDefinition requestHandlerDef = new RootBeanDefinition(WebSocketHttpRequestHandler.class, cavs, null);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, handlerReference);
|
||||
cargs.addIndexedArgumentValue(1, this.handshakeHandlerReference);
|
||||
RootBeanDefinition requestHandlerDef = new RootBeanDefinition(WebSocketHttpRequestHandler.class, cargs, null);
|
||||
requestHandlerDef.setSource(context.extractSource(element));
|
||||
requestHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
requestHandlerDef.getPropertyValues().add("handshakeInterceptors", this.interceptorsList);
|
||||
|
@ -141,12 +141,12 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SockJsHandlerMappingStrategy implements HandlerMappingStrategy {
|
||||
|
||||
private final RuntimeBeanReference sockJsService;
|
||||
|
||||
|
||||
private SockJsHandlerMappingStrategy(RuntimeBeanReference sockJsService) {
|
||||
public SockJsHandlerMappingStrategy(RuntimeBeanReference sockJsService) {
|
||||
this.sockJsService = sockJsService;
|
||||
}
|
||||
|
||||
|
@ -156,11 +156,11 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
|
|||
List<String> mappings = Arrays.asList(StringUtils.tokenizeToStringArray(pathAttribute, ","));
|
||||
RuntimeBeanReference handlerReference = new RuntimeBeanReference(element.getAttribute("handler"));
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, this.sockJsService, "SockJsService");
|
||||
cavs.addIndexedArgumentValue(1, handlerReference, "WebSocketHandler");
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, this.sockJsService, "SockJsService");
|
||||
cargs.addIndexedArgumentValue(1, handlerReference, "WebSocketHandler");
|
||||
|
||||
RootBeanDefinition requestHandlerDef = new RootBeanDefinition(SockJsHttpRequestHandler.class, cavs, null);
|
||||
RootBeanDefinition requestHandlerDef = new RootBeanDefinition(SockJsHttpRequestHandler.class, cargs, null);
|
||||
requestHandlerDef.setSource(context.extractSource(element));
|
||||
requestHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
String requestHandlerName = context.getReaderContext().registerWithGeneratedName(requestHandlerDef);
|
||||
|
|
|
@ -243,14 +243,14 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
}
|
||||
ConstructorArgumentValues argValues = new ConstructorArgumentValues();
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
if (executor != null) {
|
||||
executor.getPropertyValues().add("threadNamePrefix", name + "-");
|
||||
String executorName = name + "Executor";
|
||||
registerBeanDefByName(executorName, executor, context, source);
|
||||
argValues.addIndexedArgumentValue(0, new RuntimeBeanReference(executorName));
|
||||
cargs.addIndexedArgumentValue(0, new RuntimeBeanReference(executorName));
|
||||
}
|
||||
RootBeanDefinition channelDef = new RootBeanDefinition(ExecutorSubscribableChannel.class, argValues, null);
|
||||
RootBeanDefinition channelDef = new RootBeanDefinition(ExecutorSubscribableChannel.class, cargs, null);
|
||||
ManagedList<? super Object> interceptors = new ManagedList<>();
|
||||
if (element != null) {
|
||||
Element interceptorsElement = DomUtils.getChildElementByTagName(element, "interceptors");
|
||||
|
@ -288,11 +288,11 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
stompHandlerDef.getPropertyValues().add("errorHandler", errorHandlerRef);
|
||||
}
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, inChannel);
|
||||
cavs.addIndexedArgumentValue(1, outChannel);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, inChannel);
|
||||
cargs.addIndexedArgumentValue(1, outChannel);
|
||||
|
||||
RootBeanDefinition handlerDef = new RootBeanDefinition(SubProtocolWebSocketHandler.class, cavs, null);
|
||||
RootBeanDefinition handlerDef = new RootBeanDefinition(SubProtocolWebSocketHandler.class, cargs, null);
|
||||
handlerDef.getPropertyValues().addPropertyValue("protocolHandlers", stompHandlerDef);
|
||||
registerBeanDefByName(WEB_SOCKET_HANDLER_BEAN_NAME, handlerDef, context, source);
|
||||
RuntimeBeanReference result = new RuntimeBeanReference(WEB_SOCKET_HANDLER_BEAN_NAME);
|
||||
|
@ -329,10 +329,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
element, SCHEDULER_BEAN_NAME, cxt, source);
|
||||
|
||||
if (sockJsService != null) {
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, sockJsService);
|
||||
cavs.addIndexedArgumentValue(1, subProtoHandler);
|
||||
beanDef = new RootBeanDefinition(SockJsHttpRequestHandler.class, cavs, null);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, sockJsService);
|
||||
cargs.addIndexedArgumentValue(1, subProtoHandler);
|
||||
beanDef = new RootBeanDefinition(SockJsHttpRequestHandler.class, cargs, null);
|
||||
|
||||
// Register alias for backwards compatibility with 4.1
|
||||
cxt.getRegistry().registerAlias(SCHEDULER_BEAN_NAME, SOCKJS_SCHEDULER_BEAN_NAME);
|
||||
|
@ -344,10 +344,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
String allowedOrigins = element.getAttribute("allowed-origins");
|
||||
List<String> origins = Arrays.asList(StringUtils.tokenizeToStringArray(allowedOrigins, ","));
|
||||
interceptors.add(new OriginHandshakeInterceptor(origins));
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, subProtoHandler);
|
||||
cavs.addIndexedArgumentValue(1, handler);
|
||||
beanDef = new RootBeanDefinition(WebSocketHttpRequestHandler.class, cavs, null);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, subProtoHandler);
|
||||
cargs.addIndexedArgumentValue(1, handler);
|
||||
beanDef = new RootBeanDefinition(WebSocketHttpRequestHandler.class, cargs, null);
|
||||
beanDef.getPropertyValues().add("handshakeInterceptors", interceptors);
|
||||
}
|
||||
return new RuntimeBeanReference(registerBeanDef(beanDef, cxt, source));
|
||||
|
@ -361,16 +361,16 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
Element simpleBrokerElem = DomUtils.getChildElementByTagName(brokerElement, "simple-broker");
|
||||
Element brokerRelayElem = DomUtils.getChildElementByTagName(brokerElement, "stomp-broker-relay");
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, inChannel);
|
||||
cavs.addIndexedArgumentValue(1, outChannel);
|
||||
cavs.addIndexedArgumentValue(2, brokerChannel);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, inChannel);
|
||||
cargs.addIndexedArgumentValue(1, outChannel);
|
||||
cargs.addIndexedArgumentValue(2, brokerChannel);
|
||||
|
||||
RootBeanDefinition brokerDef;
|
||||
if (simpleBrokerElem != null) {
|
||||
String prefix = simpleBrokerElem.getAttribute("prefix");
|
||||
cavs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
||||
brokerDef = new RootBeanDefinition(SimpleBrokerMessageHandler.class, cavs, null);
|
||||
cargs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
||||
brokerDef = new RootBeanDefinition(SimpleBrokerMessageHandler.class, cargs, null);
|
||||
if (brokerElement.hasAttribute("path-matcher")) {
|
||||
String pathMatcherRef = brokerElement.getAttribute("path-matcher");
|
||||
brokerDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
|
||||
|
@ -386,7 +386,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
else if (brokerRelayElem != null) {
|
||||
String prefix = brokerRelayElem.getAttribute("prefix");
|
||||
cavs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
||||
cargs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
|
||||
|
||||
MutablePropertyValues values = new MutablePropertyValues();
|
||||
if (brokerRelayElem.hasAttribute("relay-host")) {
|
||||
|
@ -431,7 +431,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
values.add("systemSubscriptions", map);
|
||||
}
|
||||
Class<?> handlerType = StompBrokerRelayMessageHandler.class;
|
||||
brokerDef = new RootBeanDefinition(handlerType, cavs, values);
|
||||
brokerDef = new RootBeanDefinition(handlerType, cargs, values);
|
||||
}
|
||||
else {
|
||||
// Should not happen
|
||||
|
@ -487,9 +487,9 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
converters.add(jacksonConverterDef);
|
||||
}
|
||||
}
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, converters);
|
||||
RootBeanDefinition messageConverterDef = new RootBeanDefinition(CompositeMessageConverter.class, cavs, null);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, converters);
|
||||
RootBeanDefinition messageConverterDef = new RootBeanDefinition(CompositeMessageConverter.class, cargs, null);
|
||||
String name = MESSAGE_CONVERTER_BEAN_NAME;
|
||||
registerBeanDefByName(name, messageConverterDef, context, source);
|
||||
return new RuntimeBeanReference(name);
|
||||
|
@ -498,9 +498,9 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
private RuntimeBeanReference registerMessagingTemplate(Element element, RuntimeBeanReference brokerChannel,
|
||||
RuntimeBeanReference messageConverter, ParserContext context, @Nullable Object source) {
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, brokerChannel);
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(SimpMessagingTemplate.class,cavs, null);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, brokerChannel);
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(SimpMessagingTemplate.class, cargs, null);
|
||||
if (element.hasAttribute("user-destination-prefix")) {
|
||||
beanDef.getPropertyValues().add("userDestinationPrefix", element.getAttribute("user-destination-prefix"));
|
||||
}
|
||||
|
@ -515,17 +515,17 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
RuntimeBeanReference converter, RuntimeBeanReference messagingTemplate,
|
||||
ParserContext context, @Nullable Object source) {
|
||||
|
||||
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
|
||||
cavs.addIndexedArgumentValue(0, inChannel);
|
||||
cavs.addIndexedArgumentValue(1, outChannel);
|
||||
cavs.addIndexedArgumentValue(2, messagingTemplate);
|
||||
ConstructorArgumentValues cargs = new ConstructorArgumentValues();
|
||||
cargs.addIndexedArgumentValue(0, inChannel);
|
||||
cargs.addIndexedArgumentValue(1, outChannel);
|
||||
cargs.addIndexedArgumentValue(2, messagingTemplate);
|
||||
|
||||
MutablePropertyValues values = new MutablePropertyValues();
|
||||
String prefixAttribute = messageBrokerElement.getAttribute("application-destination-prefix");
|
||||
values.add("destinationPrefixes", Arrays.asList(StringUtils.tokenizeToStringArray(prefixAttribute, ",")));
|
||||
values.add("messageConverter", converter);
|
||||
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(WebSocketAnnotationMethodMessageHandler.class, cavs, values);
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(WebSocketAnnotationMethodMessageHandler.class, cargs, values);
|
||||
if (messageBrokerElement.hasAttribute("path-matcher")) {
|
||||
String pathMatcherRef = messageBrokerElement.getAttribute("path-matcher");
|
||||
beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
|
||||
|
|
Loading…
Reference in New Issue