Add AOT support for @Resource
This commit adds ahead of time support for @Resource on fields and methods. Lookup elements are discovered and code is generated to replace that introspection at runtime. Closes gh-29614
This commit is contained in:
@@ -25,6 +25,7 @@ import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -35,6 +36,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aot.generate.AccessControl;
|
||||
import org.springframework.aot.generate.GeneratedClass;
|
||||
import org.springframework.aot.generate.GeneratedMethod;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.support.ClassHintUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
@@ -43,20 +51,30 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.annotation.InjectionMetadata;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.EmbeddedValueResolver;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.AutowireCandidateResolver;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.aot.ResourceFieldValueResolver;
|
||||
import org.springframework.context.aot.ResourceMethodArgumentResolver;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.jndi.support.SimpleJndiBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
@@ -298,6 +316,37 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
metadata.checkConfigMembers(beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
BeanRegistrationAotContribution parentAotContribution = super.processAheadOfTime(registeredBean);
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
String beanName = registeredBean.getBeanName();
|
||||
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
|
||||
InjectionMetadata metadata = findResourceMetadata(beanName, beanClass,
|
||||
beanDefinition.getPropertyValues());
|
||||
Collection<LookupElement> injectedElements = getInjectedElements(metadata,
|
||||
beanDefinition.getPropertyValues());
|
||||
if (!ObjectUtils.isEmpty(injectedElements)) {
|
||||
AotContribution aotContribution = new AotContribution(beanClass, injectedElements,
|
||||
getAutowireCandidateResolver(registeredBean));
|
||||
return BeanRegistrationAotContribution.concat(parentAotContribution, aotContribution);
|
||||
}
|
||||
return parentAotContribution;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private AutowireCandidateResolver getAutowireCandidateResolver(RegisteredBean registeredBean) {
|
||||
if (registeredBean.getBeanFactory() instanceof DefaultListableBeanFactory lbf) {
|
||||
return lbf.getAutowireCandidateResolver();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Collection<LookupElement> getInjectedElements(InjectionMetadata metadata, PropertyValues propertyValues) {
|
||||
return (Collection) metadata.getInjectedElements(propertyValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
@@ -787,4 +836,144 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationAotContribution} to inject resources on fields and methods.
|
||||
*/
|
||||
private static class AotContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
private static final String REGISTERED_BEAN_PARAMETER = "registeredBean";
|
||||
|
||||
private static final String INSTANCE_PARAMETER = "instance";
|
||||
|
||||
private final Class<?> target;
|
||||
|
||||
private final Collection<LookupElement> lookupElements;
|
||||
|
||||
@Nullable
|
||||
private final AutowireCandidateResolver candidateResolver;
|
||||
|
||||
AotContribution(Class<?> target, Collection<LookupElement> lookupElements,
|
||||
@Nullable AutowireCandidateResolver candidateResolver) {
|
||||
|
||||
this.target = target;
|
||||
this.lookupElements = lookupElements;
|
||||
this.candidateResolver = candidateResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
|
||||
.addForFeatureComponent("ResourceAutowiring", this.target, type -> {
|
||||
type.addJavadoc("Resource autowiring for {@link $T}.", this.target);
|
||||
type.addModifiers(javax.lang.model.element.Modifier.PUBLIC);
|
||||
});
|
||||
GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
|
||||
method.addJavadoc("Apply resource autowiring.");
|
||||
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
|
||||
javax.lang.model.element.Modifier.STATIC);
|
||||
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
|
||||
method.addParameter(this.target, INSTANCE_PARAMETER);
|
||||
method.returns(this.target);
|
||||
method.addCode(generateMethodCode(generatedClass.getName(),
|
||||
generationContext.getRuntimeHints()));
|
||||
});
|
||||
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
|
||||
|
||||
registerHints(generationContext.getRuntimeHints());
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
for (LookupElement lookupElement : this.lookupElements) {
|
||||
code.addStatement(generateMethodStatementForElement(
|
||||
targetClassName, lookupElement, hints));
|
||||
}
|
||||
code.addStatement("return $L", INSTANCE_PARAMETER);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForElement(ClassName targetClassName,
|
||||
LookupElement lookupElement, RuntimeHints hints) {
|
||||
|
||||
Member member = lookupElement.getMember();
|
||||
if (member instanceof Field field) {
|
||||
return generateMethodStatementForField(
|
||||
targetClassName, field, lookupElement, hints);
|
||||
}
|
||||
if (member instanceof Method method) {
|
||||
return generateMethodStatementForMethod(
|
||||
targetClassName, method, lookupElement, hints);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unsupported member type " + member.getClass().getName());
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForField(ClassName targetClassName,
|
||||
Field field, LookupElement lookupElement, RuntimeHints hints) {
|
||||
|
||||
hints.reflection().registerField(field);
|
||||
CodeBlock resolver = generateFieldResolverCode(field, lookupElement);
|
||||
AccessControl accessControl = AccessControl.forMember(field);
|
||||
if (!accessControl.isAccessibleFrom(targetClassName)) {
|
||||
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
|
||||
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
|
||||
}
|
||||
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
|
||||
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
|
||||
}
|
||||
|
||||
private CodeBlock generateFieldResolverCode(Field field, LookupElement lookupElement) {
|
||||
if (lookupElement.isDefaultName) {
|
||||
return CodeBlock.of("$T.$L($S)", ResourceFieldValueResolver.class,
|
||||
"forField", field.getName());
|
||||
}
|
||||
else {
|
||||
return CodeBlock.of("$T.$L($S, $S)", ResourceFieldValueResolver.class,
|
||||
"forField", field.getName(), lookupElement.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
|
||||
Method method, LookupElement lookupElement, RuntimeHints hints) {
|
||||
|
||||
CodeBlock resolver = generateMethodResolverCode(method, lookupElement);
|
||||
AccessControl accessControl = AccessControl.forMember(method);
|
||||
if (!accessControl.isAccessibleFrom(targetClassName)) {
|
||||
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
|
||||
return CodeBlock.of("$L.resolveAndInvoke($L, $L)", resolver,
|
||||
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
|
||||
}
|
||||
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
|
||||
return CodeBlock.of("$L.$L($L.resolve($L))", INSTANCE_PARAMETER,
|
||||
method.getName(), resolver, REGISTERED_BEAN_PARAMETER);
|
||||
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodResolverCode(Method method, LookupElement lookupElement) {
|
||||
if (lookupElement.isDefaultName) {
|
||||
return CodeBlock.of("$T.$L($S, $T.class)", ResourceMethodArgumentResolver.class,
|
||||
"forMethod", method.getName(), lookupElement.getLookupType());
|
||||
}
|
||||
else {
|
||||
return CodeBlock.of("$T.$L($S, $T.class, $S)", ResourceMethodArgumentResolver.class,
|
||||
"forMethod", method.getName(), lookupElement.getLookupType(), lookupElement.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHints(RuntimeHints runtimeHints) {
|
||||
this.lookupElements.forEach(lookupElement ->
|
||||
registerProxyIfNecessary(runtimeHints, lookupElement.getDependencyDescriptor()));
|
||||
}
|
||||
|
||||
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
|
||||
if (this.candidateResolver != null) {
|
||||
Class<?> proxyClass =
|
||||
this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
|
||||
if (proxyClass != null) {
|
||||
ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.aot;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for resolvers that support injection of named beans on
|
||||
* an {@link Element}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.1
|
||||
* @see Resource
|
||||
*/
|
||||
public abstract class ResourceElementResolver {
|
||||
|
||||
protected final String name;
|
||||
|
||||
protected final boolean defaultName;
|
||||
|
||||
protected ResourceElementResolver(String name, boolean defaultName) {
|
||||
this.name = name;
|
||||
this.defaultName = defaultName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the field value for the specified registered bean.
|
||||
* @param registeredBean the registered bean
|
||||
* @return the resolved field value
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T resolve(RegisteredBean registeredBean) {
|
||||
return (T) resolveObject(registeredBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the field value for the specified registered bean.
|
||||
* @param registeredBean the registered bean
|
||||
* @return the resolved field value
|
||||
*/
|
||||
public Object resolveObject(RegisteredBean registeredBean) {
|
||||
Assert.notNull(registeredBean, "'registeredBean' must not be null");
|
||||
return resolveValue(registeredBean);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a suitable {@link DependencyDescriptor} for the specified bean.
|
||||
* @param bean the registered bean
|
||||
* @return a descriptor for that bean
|
||||
*/
|
||||
protected abstract DependencyDescriptor createDependencyDescriptor(RegisteredBean bean);
|
||||
|
||||
/**
|
||||
* Resolve the value to inject for this instance.
|
||||
* @param bean the bean registration
|
||||
* @return the value to inject
|
||||
*/
|
||||
protected Object resolveValue(RegisteredBean bean) {
|
||||
ConfigurableListableBeanFactory factory = bean.getBeanFactory();
|
||||
|
||||
Object resource;
|
||||
Set<String> autowiredBeanNames;
|
||||
DependencyDescriptor descriptor = createDependencyDescriptor(bean);
|
||||
if (this.defaultName && !factory.containsBean(this.name)) {
|
||||
autowiredBeanNames = new LinkedHashSet<>();
|
||||
resource = factory.resolveDependency(descriptor, bean.getBeanName(), autowiredBeanNames, null);
|
||||
if (resource == null) {
|
||||
throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), "No resolvable resource object");
|
||||
}
|
||||
}
|
||||
else {
|
||||
resource = factory.resolveBeanByName(this.name, descriptor);
|
||||
autowiredBeanNames = Collections.singleton(this.name);
|
||||
}
|
||||
|
||||
for (String autowiredBeanName : autowiredBeanNames) {
|
||||
if (factory.containsBean(autowiredBeanName)) {
|
||||
factory.registerDependentBean(autowiredBeanName, bean.getBeanName());
|
||||
}
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
protected static class LookupDependencyDescriptor extends DependencyDescriptor {
|
||||
|
||||
private final Class<?> lookupType;
|
||||
|
||||
public LookupDependencyDescriptor(Field field, Class<?> lookupType) {
|
||||
super(field, true);
|
||||
this.lookupType = lookupType;
|
||||
}
|
||||
|
||||
public LookupDependencyDescriptor(Method method, Class<?> lookupType) {
|
||||
super(new MethodParameter(method, 0), true);
|
||||
this.lookupType = lookupType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDependencyType() {
|
||||
return this.lookupType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.aot;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Resolver used to support injection of named beans on fields. Typically used in
|
||||
* AOT-processed applications as a targeted alternative to the
|
||||
* {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* <p>When resolving arguments in a native image, the {@link Field} being used must
|
||||
* be marked with an {@link ExecutableMode#INTROSPECT introspection} hint so
|
||||
* that field annotations can be read. Full {@link ExecutableMode#INVOKE
|
||||
* invocation} hints are only required if the
|
||||
* {@link #resolveAndSet(RegisteredBean, Object)} method of this class is being
|
||||
* used (typically to support private fields).
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.1
|
||||
*/
|
||||
public final class ResourceFieldValueResolver extends ResourceElementResolver {
|
||||
|
||||
private final String fieldName;
|
||||
|
||||
public ResourceFieldValueResolver(String name, boolean defaultName, String fieldName) {
|
||||
super(name, defaultName);
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ResourceFieldValueResolver} for the specified field.
|
||||
* @param fieldName the field name
|
||||
* @return a new {@link ResourceFieldValueResolver} instance
|
||||
*/
|
||||
public static ResourceFieldValueResolver forField(String fieldName) {
|
||||
return new ResourceFieldValueResolver(fieldName, true, fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ResourceFieldValueResolver} for the specified field and
|
||||
* resource name.
|
||||
* @param fieldName the field name
|
||||
* @param resourceName the resource name
|
||||
* @return a new {@link ResourceFieldValueResolver} instance
|
||||
*/
|
||||
public static ResourceFieldValueResolver forField(String fieldName, String resourceName) {
|
||||
return new ResourceFieldValueResolver(resourceName, false, fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DependencyDescriptor createDependencyDescriptor(RegisteredBean bean) {
|
||||
Field field = getField(bean);
|
||||
return new LookupDependencyDescriptor(field, field.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the field value for the specified registered bean and set it
|
||||
* using reflection.
|
||||
* @param registeredBean the registered bean
|
||||
* @param instance the bean instance
|
||||
*/
|
||||
public void resolveAndSet(RegisteredBean registeredBean, Object instance) {
|
||||
Assert.notNull(registeredBean, "'registeredBean' must not be null");
|
||||
Assert.notNull(instance, "'instance' must not be null");
|
||||
Field field = getField(registeredBean);
|
||||
Object resolved = resolveValue(registeredBean);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, instance, resolved);
|
||||
}
|
||||
|
||||
private Field getField(RegisteredBean registeredBean) {
|
||||
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(),
|
||||
this.fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on "
|
||||
+ registeredBean.getBeanClass().getName());
|
||||
return field;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.aot;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Resolver used to support injection of named beans to methods. Typically used in
|
||||
* AOT-processed applications as a targeted alternative to the
|
||||
* {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* <p>When resolving arguments in a native image, the {@link Method} being used
|
||||
* must be marked with an {@link ExecutableMode#INTROSPECT introspection} hint
|
||||
* so that field annotations can be read. Full {@link ExecutableMode#INVOKE
|
||||
* invocation} hints are only required if the
|
||||
* {@link #resolveAndInvoke(RegisteredBean, Object)} method of this class is
|
||||
* being used (typically to support private methods).
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.1
|
||||
*/
|
||||
public final class ResourceMethodArgumentResolver extends ResourceElementResolver {
|
||||
|
||||
private final String methodName;
|
||||
|
||||
private final Class<?> lookupType;
|
||||
|
||||
private ResourceMethodArgumentResolver(String name, boolean defaultName,
|
||||
String methodName, Class<?> lookupType) {
|
||||
super(name, defaultName);
|
||||
this.methodName = methodName;
|
||||
this.lookupType = lookupType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link ResourceMethodArgumentResolver} for the specified method
|
||||
* using a resource name that infers from the method name.
|
||||
* @param methodName the method name
|
||||
* @param parameterType the parameter type.
|
||||
* @return a new {@link ResourceMethodArgumentResolver} instance
|
||||
*/
|
||||
public static ResourceMethodArgumentResolver forMethod(String methodName, Class<?> parameterType) {
|
||||
return new ResourceMethodArgumentResolver(defaultResourceName(methodName), true,
|
||||
methodName, parameterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ResourceMethodArgumentResolver} for the specified method
|
||||
* and resource name.
|
||||
* @param methodName the method name
|
||||
* @param parameterType the parameter type
|
||||
* @param resourceName the resource name
|
||||
* @return a new {@link ResourceMethodArgumentResolver} instance
|
||||
*/
|
||||
public static ResourceMethodArgumentResolver forMethod(String methodName, Class<?> parameterType, String resourceName) {
|
||||
return new ResourceMethodArgumentResolver(resourceName, false, methodName, parameterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DependencyDescriptor createDependencyDescriptor(RegisteredBean bean) {
|
||||
return new LookupDependencyDescriptor(getMethod(bean), this.lookupType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the method argument for the specified registered bean and invoke
|
||||
* the method using reflection.
|
||||
* @param registeredBean the registered bean
|
||||
* @param instance the bean instance
|
||||
*/
|
||||
public void resolveAndInvoke(RegisteredBean registeredBean, Object instance) {
|
||||
Assert.notNull(registeredBean, "'registeredBean' must not be null");
|
||||
Assert.notNull(instance, "'instance' must not be null");
|
||||
Method method = getMethod(registeredBean);
|
||||
Object resolved = resolveValue(registeredBean);
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
ReflectionUtils.invokeMethod(method, instance, resolved);
|
||||
}
|
||||
|
||||
private Method getMethod(RegisteredBean registeredBean) {
|
||||
Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(),
|
||||
this.methodName, this.lookupType);
|
||||
Assert.notNull(method, () ->
|
||||
"Method '%s' with parameter type '%s' declared on %s could not be found.".formatted(
|
||||
this.methodName, this.lookupType.getName(),
|
||||
registeredBean.getBeanClass().getName()));
|
||||
return method;
|
||||
}
|
||||
|
||||
private static String defaultResourceName(String methodName) {
|
||||
if (methodName.startsWith("set") && methodName.length() > 3) {
|
||||
return StringUtils.uncapitalizeAsProperty(methodName.substring(3));
|
||||
}
|
||||
return methodName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.annotation;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode;
|
||||
import org.springframework.context.testfixture.context.annotation.PackagePrivateFieldResourceSample;
|
||||
import org.springframework.context.testfixture.context.annotation.PackagePrivateMethodResourceSample;
|
||||
import org.springframework.context.testfixture.context.annotation.PrivateFieldResourceSample;
|
||||
import org.springframework.context.testfixture.context.annotation.PrivateMethodResourceSample;
|
||||
import org.springframework.context.testfixture.context.annotation.PrivateMethodResourceWithCustomNameSample;
|
||||
import org.springframework.context.testfixture.context.annotation.PublicMethodResourceSample;
|
||||
import org.springframework.context.testfixture.context.annotation.subpkg.PackagePrivateFieldResourceFromParentSample;
|
||||
import org.springframework.context.testfixture.context.annotation.subpkg.PackagePrivateMethodResourceFromParentSample;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.SourceFile;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterizedTypeName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for AOT contributions of {@link CommonAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CommonAnnotationBeanRegistrationAotContributionTests {
|
||||
|
||||
private final TestGenerationContext generationContext;
|
||||
|
||||
private final MockBeanRegistrationCode beanRegistrationCode;
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private final CommonAnnotationBeanPostProcessor beanPostProcessor;
|
||||
|
||||
CommonAnnotationBeanRegistrationAotContributionTests() {
|
||||
this.generationContext = new TestGenerationContext();
|
||||
this.beanRegistrationCode = new MockBeanRegistrationCode(this.generationContext);
|
||||
this.beanFactory = new DefaultListableBeanFactory();
|
||||
this.beanPostProcessor = new CommonAnnotationBeanPostProcessor();
|
||||
this.beanPostProcessor.setBeanFactory(this.beanFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenPrivateFieldInjectionInjectsUsingReflection() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PrivateFieldResourceSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onField(PrivateFieldResourceSample.class, "one"))
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PrivateFieldResourceSample instance = new PrivateFieldResourceSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PrivateFieldResourceSample.class))
|
||||
.contains("resolveAndSet(");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void contributeWhenPackagePrivateFieldInjectionInjectsUsingFieldAssignement() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PackagePrivateFieldResourceSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onField(PackagePrivateFieldResourceSample.class, "one"))
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PackagePrivateFieldResourceSample instance = new PackagePrivateFieldResourceSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PackagePrivateFieldResourceSample.class))
|
||||
.contains("instance.one =");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void contributeWhenPackagePrivateFieldInjectionOnParentClassInjectsUsingReflection() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PackagePrivateFieldResourceFromParentSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onField(PackagePrivateFieldResourceSample.class, "one"))
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PackagePrivateFieldResourceFromParentSample instance = new PackagePrivateFieldResourceFromParentSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PackagePrivateFieldResourceFromParentSample.class))
|
||||
.contains("resolveAndSet");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenPrivateMethodInjectionInjectsUsingReflection() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PrivateMethodResourceSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onMethod(PrivateMethodResourceSample.class, "setOne").invoke())
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PrivateMethodResourceSample instance = new PrivateMethodResourceSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PrivateMethodResourceSample.class))
|
||||
.contains("resolveAndInvoke(");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenPrivateMethodInjectionWithCustomNameInjectsUsingReflection() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PrivateMethodResourceWithCustomNameSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onMethod(PrivateMethodResourceWithCustomNameSample.class, "setText").invoke())
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PrivateMethodResourceWithCustomNameSample instance = new PrivateMethodResourceWithCustomNameSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("text").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PrivateMethodResourceWithCustomNameSample.class))
|
||||
.contains("resolveAndInvoke(");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void contributeWhenPackagePrivateMethodInjectionInjectsUsingMethodInvocation() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PackagePrivateMethodResourceSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onMethod(PackagePrivateMethodResourceSample.class, "setOne").introspect())
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PackagePrivateMethodResourceSample instance = new PackagePrivateMethodResourceSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PackagePrivateMethodResourceSample.class))
|
||||
.contains("instance.setOne(");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithForkedClassLoader
|
||||
void contributeWhenPackagePrivateMethodInjectionOnParentClassInjectsUsingReflection() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = getAndApplyContribution(
|
||||
PackagePrivateMethodResourceFromParentSample.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onMethod(PackagePrivateMethodResourceSample.class, "setOne"))
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
compile(registeredBean, (postProcessor, compiled) -> {
|
||||
PackagePrivateMethodResourceFromParentSample instance = new PackagePrivateMethodResourceFromParentSample();
|
||||
postProcessor.apply(registeredBean, instance);
|
||||
assertThat(instance).extracting("one").isEqualTo("1");
|
||||
assertThat(getSourceFile(compiled, PackagePrivateMethodResourceFromParentSample.class))
|
||||
.contains("resolveAndInvoke(");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWhenMethodInjectionHasMatchingPropertyValue() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(PublicMethodResourceSample.class);
|
||||
beanDefinition.getPropertyValues().addPropertyValue("one", "from-property");
|
||||
this.beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
BeanRegistrationAotContribution contribution = this.beanPostProcessor
|
||||
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
|
||||
assertThat(contribution).isNull();
|
||||
}
|
||||
|
||||
private RegisteredBean getAndApplyContribution(Class<?> beanClass) {
|
||||
RegisteredBean registeredBean = registerBean(beanClass);
|
||||
BeanRegistrationAotContribution contribution = this.beanPostProcessor
|
||||
.processAheadOfTime(registeredBean);
|
||||
assertThat(contribution).isNotNull();
|
||||
contribution.applyTo(this.generationContext, this.beanRegistrationCode);
|
||||
return registeredBean;
|
||||
}
|
||||
|
||||
private RegisteredBean registerBean(Class<?> beanClass) {
|
||||
String beanName = "testBean";
|
||||
this.beanFactory.registerBeanDefinition(beanName,
|
||||
new RootBeanDefinition(beanClass));
|
||||
return RegisteredBean.of(this.beanFactory, beanName);
|
||||
}
|
||||
|
||||
private static SourceFile getSourceFile(Compiled compiled, Class<?> sample) {
|
||||
return compiled.getSourceFileFromPackage(sample.getPackageName());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(RegisteredBean registeredBean,
|
||||
BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) {
|
||||
Class<?> target = registeredBean.getBeanClass();
|
||||
MethodReference methodReference = this.beanRegistrationCode.getInstancePostProcessors().get(0);
|
||||
this.beanRegistrationCode.getTypeBuilder().set(type -> {
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(
|
||||
MethodReference.ArgumentCodeGenerator.of(RegisteredBean.class, "registeredBean")
|
||||
.and(target, "instance"), this.beanRegistrationCode.getClassName());
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(
|
||||
BiFunction.class, RegisteredBean.class, target, target));
|
||||
type.addMethod(MethodSpec.methodBuilder("apply")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(RegisteredBean.class, "registeredBean")
|
||||
.addParameter(target, "instance")
|
||||
.returns(target)
|
||||
.addStatement("return $L", methodInvocation)
|
||||
.build());
|
||||
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(this.generationContext).printFiles(System.out)
|
||||
.compile(compiled -> result.accept(compiled.getInstance(BiFunction.class), compiled));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -76,8 +77,11 @@ import org.springframework.context.testfixture.context.annotation.LazyAutowiredF
|
||||
import org.springframework.context.testfixture.context.annotation.LazyAutowiredMethodComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.LazyFactoryMethodArgumentComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.LazyResourceFieldComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.LazyResourceMethodComponent;
|
||||
import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration;
|
||||
import org.springframework.context.testfixture.context.annotation.QualifierConfiguration;
|
||||
import org.springframework.context.testfixture.context.annotation.ResourceComponent;
|
||||
import org.springframework.context.testfixture.context.generator.SimpleComponent;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -227,6 +231,80 @@ class ApplicationContextAotGeneratorTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ResourceAutowiring {
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenHasResourceAutowiring() {
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
registerBeanPostProcessor(applicationContext,
|
||||
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, CommonAnnotationBeanPostProcessor.class);
|
||||
registerStringBean(applicationContext, "text", "hello");
|
||||
registerStringBean(applicationContext, "text2", "hello2");
|
||||
registerIntegerBean(applicationContext, "number", 42);
|
||||
applicationContext.registerBeanDefinition("resourceComponent", new RootBeanDefinition(ResourceComponent.class));
|
||||
testCompiledResult(applicationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("resourceComponent", "text", "text2", "number");
|
||||
ResourceComponent bean = freshApplicationContext.getBean(ResourceComponent.class);
|
||||
assertThat(bean.getText()).isEqualTo("hello");
|
||||
assertThat(bean.getCounter()).isEqualTo(42);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("gh-31447")
|
||||
void processAheadOfTimeWhenHasLazyResourceAutowiringOnField() {
|
||||
testResourceAutowiringComponent(LazyResourceFieldComponent.class, (bean, generationContext) -> {
|
||||
Environment environment = bean.getEnvironment();
|
||||
assertThat(environment).isInstanceOf(Proxy.class);
|
||||
ResourceLoader resourceLoader = bean.getResourceLoader();
|
||||
assertThat(resourceLoader).isNotInstanceOf(Proxy.class);
|
||||
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||
assertThat(runtimeHints.proxies().jdkProxyHints()).satisfies(doesNotHaveProxyFor(ResourceLoader.class));
|
||||
assertThat(runtimeHints.proxies().jdkProxyHints()).anySatisfy(proxyHint ->
|
||||
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
|
||||
environment.getClass().getInterfaces())));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWhenHasLazyResourceAutowiringOnMethod() {
|
||||
testResourceAutowiringComponent(LazyResourceMethodComponent.class, (bean, generationContext) -> {
|
||||
Environment environment = bean.getEnvironment();
|
||||
assertThat(environment).isNotInstanceOf(Proxy.class);
|
||||
ResourceLoader resourceLoader = bean.getResourceLoader();
|
||||
assertThat(resourceLoader).isInstanceOf(Proxy.class);
|
||||
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||
assertThat(runtimeHints.proxies().jdkProxyHints()).satisfies(doesNotHaveProxyFor(Environment.class));
|
||||
assertThat(runtimeHints.proxies().jdkProxyHints()).anySatisfy(proxyHint ->
|
||||
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
|
||||
resourceLoader.getClass().getInterfaces())));
|
||||
});
|
||||
}
|
||||
|
||||
private <T> void testResourceAutowiringComponent(Class<T> type, BiConsumer<T, GenerationContext> assertions) {
|
||||
testResourceAutowiringComponent(type, new RootBeanDefinition(type), assertions);
|
||||
}
|
||||
|
||||
private <T> void testResourceAutowiringComponent(Class<T> type, RootBeanDefinition beanDefinition,
|
||||
BiConsumer<T, GenerationContext> assertions) {
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
applicationContext.getDefaultListableBeanFactory().setAutowireCandidateResolver(
|
||||
new ContextAnnotationAutowireCandidateResolver());
|
||||
registerBeanPostProcessor(applicationContext,
|
||||
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, CommonAnnotationBeanPostProcessor.class);
|
||||
applicationContext.registerBeanDefinition("testComponent", beanDefinition);
|
||||
TestGenerationContext generationContext = processAheadOfTime(applicationContext);
|
||||
testCompiledResult(generationContext, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
|
||||
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("testComponent");
|
||||
assertions.accept(freshApplicationContext.getBean("testComponent", type), generationContext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InitDestroy {
|
||||
|
||||
@@ -519,6 +597,14 @@ class ApplicationContextAotGeneratorTests {
|
||||
.getBeanDefinition());
|
||||
}
|
||||
|
||||
private static void registerStringBean(GenericApplicationContext applicationContext,
|
||||
String beanName, String value) {
|
||||
|
||||
applicationContext.registerBeanDefinition(beanName, BeanDefinitionBuilder
|
||||
.rootBeanDefinition(String.class).addConstructorArgValue(value)
|
||||
.getBeanDefinition());
|
||||
}
|
||||
|
||||
private static void registerIntegerBean(GenericApplicationContext applicationContext,
|
||||
String beanName, int value) {
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.aot;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceFieldValueResolver}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResourceFieldValueResolverTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
|
||||
@Test
|
||||
void resolveWhenFieldIsMissingThrowsException() {
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ResourceFieldValueResolver.forField("missing")
|
||||
.resolve(registeredBean))
|
||||
.withMessage("No field 'missing' found on " + TestBean.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveReturnsValue() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
Object resolved = ResourceFieldValueResolver.forField("one")
|
||||
.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenResourceNameAndMatchReturnsValue() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
Object resolved = ResourceFieldValueResolver.forField("test", "two")
|
||||
.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWheNoMatchFallbackOnType() {
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
Object resolved = ResourceFieldValueResolver.forField("one")
|
||||
.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenMultipleCandidatesWithNoNameMatchThrowsException() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceFieldValueResolver.forField("test")
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(NoUniqueBeanDefinitionException.class)
|
||||
.hasMessageContaining(String.class.getName())
|
||||
.hasMessageContaining("one").hasMessageContaining("two");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenNoCandidateMatchingTypeThrowsException() {
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceFieldValueResolver.forField("test")
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(NoSuchBeanDefinitionException.class)
|
||||
.hasMessageContaining(String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenInvalidMatchingTypeThrowsException() {
|
||||
this.beanFactory.registerSingleton("count", "counter");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceFieldValueResolver.forField("count")
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(BeanNotOfRequiredTypeException.class)
|
||||
.hasMessageContaining(Integer.class.getName())
|
||||
.hasMessageContaining(String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveAndSetSetsValue() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
TestBean testBean = new TestBean();
|
||||
ResourceFieldValueResolver.forField("one").resolveAndSet(registeredBean,
|
||||
testBean);
|
||||
assertThat(testBean.one).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRegistersDependantBeans() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
ResourceFieldValueResolver.forField("one").resolve(registeredBean);
|
||||
assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean");
|
||||
}
|
||||
|
||||
private RegisteredBean registerTestBean(DefaultListableBeanFactory beanFactory) {
|
||||
beanFactory.registerBeanDefinition("testBean",
|
||||
new RootBeanDefinition(TestBean.class));
|
||||
return RegisteredBean.of(beanFactory, "testBean");
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
|
||||
String one;
|
||||
|
||||
String test;
|
||||
|
||||
Integer count;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.aot;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceMethodArgumentResolver}/
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResourceMethodArgumentResolverTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
void resolveWhenMethodIsMissingThrowsException() {
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
ResourceMethodArgumentResolver resolver = ResourceMethodArgumentResolver.forMethod("missing", InputStream.class);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> resolver.resolve(registeredBean))
|
||||
.withMessage("Method 'missing' with parameter type 'java.io.InputStream' declared on %s could not be found.",
|
||||
TestBean.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveReturnsValue() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
ResourceMethodArgumentResolver resolver = ResourceMethodArgumentResolver
|
||||
.forMethod("setOne", String.class);
|
||||
Object resolved = resolver.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenResourceNameAndMatchReturnsValue() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
Object resolved = ResourceMethodArgumentResolver.forMethod("setTest", String.class, "two")
|
||||
.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWheNoMatchFallbackOnType() {
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
Object resolved = ResourceMethodArgumentResolver.forMethod("setOne", String.class)
|
||||
.resolve(registeredBean);
|
||||
assertThat(resolved).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenMultipleCandidatesWithNoNameMatchThrowsException() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
this.beanFactory.registerSingleton("two", "2");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setTest", String.class)
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(NoUniqueBeanDefinitionException.class)
|
||||
.hasMessageContaining(String.class.getName())
|
||||
.hasMessageContaining("one").hasMessageContaining("two");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenNoCandidateMatchingTypeThrowsException() {
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setTest", String.class)
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(NoSuchBeanDefinitionException.class)
|
||||
.hasMessageContaining(String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenInvalidMatchingTypeThrowsException() {
|
||||
this.beanFactory.registerSingleton("count", "counter");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setCount", Integer.class)
|
||||
.resolve(registeredBean)
|
||||
).isInstanceOf(BeanNotOfRequiredTypeException.class)
|
||||
.hasMessageContaining(Integer.class.getName())
|
||||
.hasMessageContaining(String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveAndInvokeInvokesMethod() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
TestBean testBean = new TestBean();
|
||||
ResourceMethodArgumentResolver.forMethod("setOne", String.class)
|
||||
.resolveAndInvoke(registeredBean, testBean);
|
||||
assertThat(testBean.one).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRegistersDependantBeans() {
|
||||
this.beanFactory.registerSingleton("one", "1");
|
||||
RegisteredBean registeredBean = registerTestBean(this.beanFactory);
|
||||
ResourceMethodArgumentResolver.forMethod("setOne", String.class).resolve(registeredBean);
|
||||
assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean");
|
||||
}
|
||||
|
||||
private RegisteredBean registerTestBean(DefaultListableBeanFactory beanFactory) {
|
||||
beanFactory.registerBeanDefinition("testBean",
|
||||
new RootBeanDefinition(TestBean.class));
|
||||
return RegisteredBean.of(beanFactory, "testBean");
|
||||
}
|
||||
|
||||
|
||||
static class TestBean {
|
||||
|
||||
private String one;
|
||||
|
||||
private String test;
|
||||
|
||||
private Integer count;
|
||||
|
||||
public void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public void setTest(String test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
public class LazyResourceFieldComponent {
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private Environment environment;
|
||||
|
||||
@Resource
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
public ResourceLoader getResourceLoader() {
|
||||
return this.resourceLoader;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
public class LazyResourceMethodComponent {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@Resource
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
public ResourceLoader getResourceLoader() {
|
||||
return this.resourceLoader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PackagePrivateFieldResourceSample {
|
||||
|
||||
@Resource
|
||||
String one;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PackagePrivateMethodResourceSample {
|
||||
|
||||
private String one;
|
||||
|
||||
@Resource
|
||||
void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PrivateFieldResourceSample {
|
||||
|
||||
@Resource
|
||||
private String one;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PrivateMethodResourceSample {
|
||||
|
||||
private String one;
|
||||
|
||||
@Resource
|
||||
private void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PrivateMethodResourceWithCustomNameSample {
|
||||
|
||||
private String text;
|
||||
|
||||
@Resource(name = "one")
|
||||
private void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class PublicMethodResourceSample {
|
||||
|
||||
private String one;
|
||||
|
||||
@Resource
|
||||
public void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
public class ResourceComponent {
|
||||
|
||||
private String text;
|
||||
|
||||
private Integer counter;
|
||||
|
||||
public String getText() {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Resource
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Integer getCounter() {
|
||||
return this.counter;
|
||||
}
|
||||
|
||||
@Resource
|
||||
public void setCounter(Integer counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation.subpkg;
|
||||
|
||||
import org.springframework.context.testfixture.context.annotation.PackagePrivateFieldResourceSample;
|
||||
|
||||
public class PackagePrivateFieldResourceFromParentSample extends PackagePrivateFieldResourceSample {
|
||||
|
||||
// see one from parent
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.context.testfixture.context.annotation.subpkg;
|
||||
|
||||
import org.springframework.context.testfixture.context.annotation.PackagePrivateMethodResourceSample;
|
||||
|
||||
public class PackagePrivateMethodResourceFromParentSample extends PackagePrivateMethodResourceSample {
|
||||
|
||||
// see one from parent
|
||||
}
|
||||
Reference in New Issue
Block a user