No AOP? Achieve Zero‑Intrusion Parameter Auto‑Filling in Spring Boot
This article demonstrates how to leverage Spring Boot's type‑conversion and AnnotationFormatterFactory to implement elegant, zero‑intrusion parameter auto‑filling, covering a basic string‑to‑object conversion example and a remote‑service lookup scenario with complete code and test results.
Overview
Using Spring Boot 3.5.0, the article shows how to exploit Spring's powerful type‑conversion and formatting framework to create a zero‑intrusion, annotation‑driven parameter auto‑filling mechanism. By defining custom @interface annotations and corresponding AnnotationFormatterFactory implementations, developers can automatically convert request parameters into complex objects without writing boilerplate code or using AOP.
Case 1 – Basic Conversion
Define Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ToUser {}Formatter Factory
public class ToUserAnnotationFormatterFactory implements AnnotationFormatterFactory<ToUser> {
@Override
public Set<Class<?>> getFieldTypes() {
return Set.of(User.class);
}
@Override
public Printer<User> getPrinter(ToUser annotation, Class<?> fieldType) {
return (value, locale) -> "【id = %s, name = %s, age = %s]".formatted(
value.getId(), value.getName(), value.getAge());
}
@Override
public Parser<User> getParser(ToUser annotation, Class<?> fieldType) {
return new Parser<User>() {
@Override
public User parse(String text, Locale locale) throws ParseException {
if (!StringUtils.hasLength(text)) return null;
String[] data = text.split(",");
if (data.length != 3) return null;
return new User(Long.valueOf(data[0]), data[1], Integer.valueOf(data[2]));
}
};
}
}Register Formatter
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new ToUserAnnotationFormatterFactory());
}
}Test Controllers
@GetMapping
public User getUser(@ToUser User user) { return user; }
@GetMapping("/profile")
public Profile getProfile(Profile profile) { return profile; }
public class User { private Long id; private String name; private Integer age; }
public class Profile { @ToUser private User user; }Running the endpoints with a request like GET /getUser?user=1,zhangsan,30 returns a populated User object, as shown in the accompanying screenshots.
Case 2 – Automatic Remote Service Invocation
In micro‑service architectures, DTOs often contain only an ID (e.g., createdById). Manually calling a remote API to fetch the full object is error‑prone. The article extends the same concept to resolve IDs via a remote service.
Define Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ResolveRemoteUser { boolean required() default true; }Remote Formatter Factory
@Component
public class RemoteUserAnnotationFormatterFactory implements AnnotationFormatterFactory<ResolveRemoteUser> {
private final RemoteUserService remoteUserService;
public RemoteUserAnnotationFormatterFactory(RemoteUserService remoteUserService) {
this.remoteUserService = remoteUserService;
}
@Override
public Set<Class<?>> getFieldTypes() { return Set.of(RemoteUser.class); }
@Override
public Printer<RemoteUser> getPrinter(ResolveRemoteUser annotation, Class<?> fieldType) {
return (user, locale) -> user != null ? user.getId() : "";
}
@Override
public Parser<RemoteUser> getParser(ResolveRemoteUser annotation, Class<?> fieldType) {
return new Parser<RemoteUser>() {
@Override
public RemoteUser parse(String text, Locale locale) throws ParseException {
if (!StringUtils.hasLength(text)) return null;
RemoteUser resolved = remoteUserService.findUserById(text);
if (resolved == null) {
if (annotation.required()) {
throw new ParseException("Could not resolve RemoteUser with ID: " + text, 0);
} else {
RemoteUser placeholder = new RemoteUser();
placeholder.setId(text);
return placeholder;
}
}
return resolved;
}
};
}
}Register Remote Formatter
private final RemoteUserAnnotationFormatterFactory userAnnotationFormatterFactory;
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(userAnnotationFormatterFactory);
}Test Controller
@GetMapping("/review")
public ReviewerRequest getProjectReviewer(ReviewerRequest request) { return request; }
public class RemoteUser { private String id; private String username; private String email; }
public class ReviewerRequest {
@ResolveRemoteUser(required = true)
private RemoteUser reviewer;
private String title;
}When the endpoint receives a request with reviewer=123, Spring automatically calls RemoteUserService.findUserById("123") and injects the full RemoteUser object. The result screenshot confirms the behavior.
Conclusion
By harnessing Spring's AnnotationFormatterFactory, developers can achieve clean, zero‑intrusion parameter auto‑filling for both simple string parsing and complex remote lookups, eliminating repetitive boilerplate and keeping business logic untouched.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
