Simplify registration of Jackson mixin types

See gh-30152
pull/30472/head
Guirong Hu 3 years ago committed by Andy Wilkinson
parent 45f393b76b
commit df417bf317

@ -41,11 +41,14 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties.ConstructorDetectorStrategy;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jackson.JsonComponentModule;
import org.springframework.boot.jackson.JsonMixinModule;
import org.springframework.boot.jackson.JsonMixinScanPackages;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -93,6 +96,15 @@ public class JacksonAutoConfiguration {
return new JsonComponentModule();
}
@Bean
public JsonMixinModule jsonMixinModule(ApplicationContext context) {
List<String> packages = JsonMixinScanPackages.get(context).getPackageNames();
if (packages.isEmpty() && AutoConfigurationPackages.has(context)) {
packages = AutoConfigurationPackages.get(context);
}
return new JsonMixinModule(context, packages);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -52,6 +52,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonMixinModule;
import org.springframework.boot.jackson.JsonObjectSerializer;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -90,6 +91,11 @@ class JacksonAutoConfigurationTests {
});
}
@Test
void jsonMixinModuleShouldBeAutoconfigured() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(JsonMixinModule.class));
}
@Test
void noCustomDateFormat() {
this.contextRunner.run((context) -> {

@ -0,0 +1,59 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Provides a mixin class implementation that registers with Jackson when using
* {@link JsonMixinModule}.
*
* @author Guirong Hu
* @see JsonMixinModule
* @since 2.7.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonMixin {
/**
* Alias for the {@link #type()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @JsonMixin(MyType.class)} instead of
* {@code @JsonMixin(type=MyType.class)}.
* @return the mixed-in classes
* @since 2.7.0
*/
@AliasFor("type")
Class<?>[] value() default {};
/**
* The types that are handled by the provided mix-in class. {@link #value()} is an
* alias for (and mutually exclusive with) this attribute.
* @return the mixed-in classes
* @since 2.7.0
*/
@AliasFor("value")
Class<?>[] type() default {};
}

@ -0,0 +1,107 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.io.IOException;
import java.util.Collection;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Spring Bean and Jackson {@link Module} to register {@link JsonMixin @JsonMixin}
* annotated beans.
*
* @author Guirong Hu
* @since 2.7.0
* @see JsonMixin
*/
public class JsonMixinModule extends SimpleModule implements InitializingBean {
private final ApplicationContext context;
private final Collection<String> basePackages;
/**
* Create a new {@link JsonMixinModule} instance.
* @param context the source application context
* @param basePackages the packages to check for annotated classes
*/
public JsonMixinModule(ApplicationContext context, Collection<String> basePackages) {
Assert.notNull(context, "Context must not be null");
this.context = context;
this.basePackages = basePackages;
}
@Override
public void afterPropertiesSet() throws Exception {
if (ObjectUtils.isEmpty(this.basePackages)) {
return;
}
JsonMixinComponentScanner scanner = new JsonMixinComponentScanner();
scanner.setEnvironment(this.context.getEnvironment());
scanner.setResourceLoader(this.context);
for (String basePackage : this.basePackages) {
if (StringUtils.hasText(basePackage)) {
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
addJsonMixin(ClassUtils.forName(candidate.getBeanClassName(), this.context.getClassLoader()));
}
}
}
}
private void addJsonMixin(Class<?> mixinClass) {
MergedAnnotation<JsonMixin> annotation = MergedAnnotations
.from(mixinClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(JsonMixin.class);
Class<?>[] targetTypes = annotation.getClassArray("type");
if (ObjectUtils.isEmpty(targetTypes)) {
return;
}
for (Class<?> targetType : targetTypes) {
setMixInAnnotation(targetType, mixinClass);
}
}
static class JsonMixinComponentScanner extends ClassPathScanningCandidateComponentProvider {
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
return true;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().hasAnnotation(JsonMixin.class.getName());
}
}
}

@ -0,0 +1,75 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
/**
* Configures the base packages used by auto-configuration when scanning for mix-in
* classes. One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias
* {@link #value()} may be specified to define specific packages to scan. If specific
* packages are not defined scanning will occur from the package of the class with this
* annotation.
*
* @author Guirong Hu
* @since 2.7.0
* @see JsonMixinScanPackages
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(JsonMixinScanPackages.Registrar.class)
public @interface JsonMixinScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @JsonMixinScan("org.my.pkg")} instead of
* {@code @JsonMixinScan(basePackages="org.my.pkg")}.
* @return the base packages to scan
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* Base packages to scan for mix-in classes. {@link #value()} is an alias for (and
* mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the base packages to scan
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for mix-in classes. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return classes from the base packages to scan
*/
Class<?>[] basePackageClasses() default {};
}

@ -0,0 +1,185 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Class for storing {@link JsonMixinScan @JsonMixinScan} specified packages for reference
* later.
*
* @author Guirong Hu
* @since 2.7.0
* @see JsonMixinScan
* @see JsonMixinModule
*/
public class JsonMixinScanPackages {
private static final String BEAN = JsonMixinScanPackages.class.getName();
private static final JsonMixinScanPackages NONE = new JsonMixinScanPackages();
private final List<String> packageNames;
JsonMixinScanPackages(String... packageNames) {
List<String> packages = new ArrayList<>();
for (String name : packageNames) {
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packageNames = Collections.unmodifiableList(packages);
}
/**
* Return the package names specified from all {@link JsonMixinScan @JsonMixinScan}
* annotations.
* @return the mix-in classes scan package names
*/
public List<String> getPackageNames() {
return this.packageNames;
}
/**
* Return the {@link JsonMixinScanPackages} for the given bean factory.
* @param beanFactory the source bean factory
* @return the {@link JsonMixinScanPackages} for the bean factory (never {@code null})
*/
public static JsonMixinScanPackages get(BeanFactory beanFactory) {
// Currently, we only store a single base package, but we return a list to
// allow this to change in the future if needed
try {
return beanFactory.getBean(BEAN, JsonMixinScanPackages.class);
}
catch (NoSuchBeanDefinitionException ex) {
return NONE;
}
}
/**
* Register the specified mix-in classes scan packages with the system.
* @param registry the source registry
* @param packageNames the package names to register
*/
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
Assert.notNull(registry, "Registry must not be null");
Assert.notNull(packageNames, "PackageNames must not be null");
register(registry, Arrays.asList(packageNames));
}
/**
* Register the specified mix-in classes scan packages with the system.
* @param registry the source registry
* @param packageNames the package names to register
*/
public static void register(BeanDefinitionRegistry registry, Collection<String> packageNames) {
Assert.notNull(registry, "Registry must not be null");
Assert.notNull(packageNames, "PackageNames must not be null");
if (registry.containsBeanDefinition(BEAN)) {
JsonMixinScanPackagesBeanDefinition beanDefinition = (JsonMixinScanPackagesBeanDefinition) registry
.getBeanDefinition(BEAN);
beanDefinition.addPackageNames(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new JsonMixinScanPackagesBeanDefinition(packageNames));
}
}
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar {
private final Environment environment;
Registrar(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, getPackagesToScan(metadata));
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(JsonMixinScan.class.getName()));
Set<String> packagesToScan = new LinkedHashSet<>();
for (String basePackage : attributes.getStringArray("basePackages")) {
String[] tokenized = StringUtils.tokenizeToStringArray(
this.environment.resolvePlaceholders(basePackage),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(packagesToScan, tokenized);
}
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packagesToScan.add(this.environment.resolvePlaceholders(ClassUtils.getPackageName(basePackageClass)));
}
if (packagesToScan.isEmpty()) {
String packageName = ClassUtils.getPackageName(metadata.getClassName());
Assert.state(StringUtils.hasLength(packageName),
"@JsonMixinScan cannot be used with the default package");
return Collections.singleton(packageName);
}
return packagesToScan;
}
}
static class JsonMixinScanPackagesBeanDefinition extends GenericBeanDefinition {
private final Set<String> packageNames = new LinkedHashSet<>();
JsonMixinScanPackagesBeanDefinition(Collection<String> packageNames) {
setBeanClass(JsonMixinScanPackages.class);
setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
addPackageNames(packageNames);
}
@Override
public Supplier<?> getInstanceSupplier() {
return () -> new JsonMixinScanPackages(StringUtils.toStringArray(this.packageNames));
}
private void addPackageNames(Collection<String> additionalPackageNames) {
this.packageNames.addAll(additionalPackageNames);
}
}
}

@ -0,0 +1,107 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jackson.scan.a.RenameMixInClass;
import org.springframework.boot.jackson.scan.b.RenameMixInAbstractClass;
import org.springframework.boot.jackson.scan.c.RenameMixInInterface;
import org.springframework.boot.jackson.scan.d.EmptyMixInClass;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link JsonMixinModule}.
*
* @author Guirong Hu
*/
public class JsonMixinModuleTests {
private AnnotationConfigApplicationContext context;
@AfterEach
void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
void createWhenContextIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> new JsonMixinModule(null, Collections.emptyList()))
.withMessageContaining("Context must not be null");
}
@Test
void jsonWithModuleWithRenameMixInClassShouldBeMixedIn() throws Exception {
load(RenameMixInClass.class);
JsonMixinModule module = this.context.getBean(JsonMixinModule.class);
assertMixIn(module, new Name("spring"), "{\"username\":\"spring\"}");
assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
@Test
void jsonWithModuleWithEmptyMixInClassShouldNotBeMixedIn() throws Exception {
load(EmptyMixInClass.class);
JsonMixinModule module = this.context.getBean(JsonMixinModule.class);
assertMixIn(module, new Name("spring"), "{\"name\":\"spring\"}");
assertMixIn(module, new NameAndAge("spring", 100), "{\"name\":\"spring\",\"age\":100}");
}
@Test
void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception {
load(RenameMixInAbstractClass.class);
JsonMixinModule module = this.context.getBean(JsonMixinModule.class);
assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
@Test
void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception {
load(RenameMixInInterface.class);
JsonMixinModule module = this.context.getBean(JsonMixinModule.class);
assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}");
}
private void load(Class<?>... basePackageClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
List<String> basePackages = Arrays.stream(basePackageClasses).map(ClassUtils::getPackageName)
.collect(Collectors.toList());
context.registerBean(JsonMixinModule.class, () -> new JsonMixinModule(context, basePackages));
context.refresh();
this.context = context;
}
private void assertMixIn(Module module, Name value, String expectedJson) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
String json = mapper.writeValueAsString(value);
assertThat(json).isEqualToIgnoringWhitespace(expectedJson);
}
}

@ -0,0 +1,182 @@
/*
* Copyright 2012-2022 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.boot.jackson;
import java.util.Collection;
import java.util.Collections;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationConfigurationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link JsonMixinScanPackages}.
*
* @author Guirong Hu
*/
class JsonMixinScanPackagesTests {
private AnnotationConfigApplicationContext context;
@AfterEach
void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
void getWhenNoneRegisteredShouldReturnNone() {
this.context = new AnnotationConfigApplicationContext();
this.context.refresh();
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages).isNotNull();
assertThat(packages.getPackageNames()).isEmpty();
}
@Test
void getShouldReturnRegisterPackages() {
this.context = new AnnotationConfigApplicationContext();
JsonMixinScanPackages.register(this.context, "a", "b");
JsonMixinScanPackages.register(this.context, "b", "c");
this.context.refresh();
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly("a", "b", "c");
}
@Test
void registerFromArrayWhenRegistryIsNullShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> JsonMixinScanPackages.register(null))
.withMessageContaining("Registry must not be null");
}
@Test
void registerFromArrayWhenPackageNamesIsNullShouldThrowException() {
this.context = new AnnotationConfigApplicationContext();
assertThatIllegalArgumentException()
.isThrownBy(() -> JsonMixinScanPackages.register(this.context, (String[]) null))
.withMessageContaining("PackageNames must not be null");
}
@Test
void registerFromCollectionWhenRegistryIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> JsonMixinScanPackages.register(null, Collections.emptyList()))
.withMessageContaining("Registry must not be null");
}
@Test
void registerFromCollectionWhenPackageNamesIsNullShouldThrowException() {
this.context = new AnnotationConfigApplicationContext();
assertThatIllegalArgumentException()
.isThrownBy(() -> JsonMixinScanPackages.register(this.context, (Collection<String>) null))
.withMessageContaining("PackageNames must not be null");
}
@Test
void jsonMixinScanAnnotationWhenHasValueAttributeShouldSetupPackages() {
this.context = new AnnotationConfigApplicationContext(JsonMixinScanValueConfig.class);
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly("a");
}
@Test
void jsonMixinScanAnnotationWhenHasValueAttributeShouldSetupPackagesAsm() {
this.context = new AnnotationConfigApplicationContext();
this.context.registerBeanDefinition("jsonMixinScanValueConfig",
new RootBeanDefinition(JsonMixinScanValueConfig.class.getName()));
this.context.refresh();
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly("a");
}
@Test
void jsonMixinScanAnnotationWhenHasBasePackagesAttributeShouldSetupPackages() {
this.context = new AnnotationConfigApplicationContext(JsonMixinScanBasePackagesConfig.class);
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly("b");
}
@Test
void jsonMixinScanAnnotationWhenHasValueAndBasePackagesAttributeShouldThrow() {
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.context = new AnnotationConfigApplicationContext(
JsonMixinScanValueAndBasePackagesConfig.class));
}
@Test
void jsonMixinScanAnnotationWhenHasBasePackageClassesAttributeShouldSetupPackages() {
this.context = new AnnotationConfigApplicationContext(JsonMixinScanBasePackageClassesConfig.class);
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly(getClass().getPackage().getName());
}
@Test
void jsonMixinScanAnnotationWhenNoAttributesShouldSetupPackages() {
this.context = new AnnotationConfigApplicationContext(JsonMixinScanNoAttributesConfig.class);
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly(getClass().getPackage().getName());
}
@Test
void jsonMixinScanAnnotationWhenLoadingFromMultipleConfigsShouldCombinePackages() {
this.context = new AnnotationConfigApplicationContext(JsonMixinScanValueConfig.class,
JsonMixinScanBasePackagesConfig.class);
JsonMixinScanPackages packages = JsonMixinScanPackages.get(this.context);
assertThat(packages.getPackageNames()).containsExactly("a", "b");
}
@Configuration(proxyBeanMethods = false)
@JsonMixinScan("a")
static class JsonMixinScanValueConfig {
}
@Configuration(proxyBeanMethods = false)
@JsonMixinScan(basePackages = "b")
static class JsonMixinScanBasePackagesConfig {
}
@Configuration(proxyBeanMethods = false)
@JsonMixinScan(value = "a", basePackages = "b")
static class JsonMixinScanValueAndBasePackagesConfig {
}
@Configuration(proxyBeanMethods = false)
@JsonMixinScan(basePackageClasses = JsonMixinScanPackagesTests.class)
static class JsonMixinScanBasePackageClassesConfig {
}
@Configuration(proxyBeanMethods = false)
@JsonMixinScan
static class JsonMixinScanNoAttributesConfig {
}
}

@ -0,0 +1,33 @@
/*
* Copyright 2012-2022 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.boot.jackson.scan.a;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.Name;
import org.springframework.boot.jackson.NameAndAge;
@JsonMixin(type = { Name.class, NameAndAge.class })
public class RenameMixInClass {
@JsonProperty("username")
String getName() {
return null;
}
}

@ -0,0 +1,30 @@
/*
* Copyright 2012-2022 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.boot.jackson.scan.b;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.NameAndAge;
@JsonMixin(type = NameAndAge.class)
public abstract class RenameMixInAbstractClass {
@JsonProperty("username")
abstract String getName();
}

@ -0,0 +1,30 @@
/*
* Copyright 2012-2022 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.boot.jackson.scan.c;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.NameAndAge;
@JsonMixin(type = NameAndAge.class)
public interface RenameMixInInterface {
@JsonProperty("username")
String getName();
}

@ -0,0 +1,26 @@
/*
* Copyright 2012-2022 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.boot.jackson.scan.d;
import org.springframework.boot.jackson.JsonMixin;
import org.springframework.boot.jackson.Name;
import org.springframework.boot.jackson.NameAndAge;
@JsonMixin(type = { Name.class, NameAndAge.class })
public class EmptyMixInClass {
}
Loading…
Cancel
Save