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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
No AOP? Achieve Zero‑Intrusion Parameter Auto‑Filling in Spring Boot

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentSpring BootAnnotationFormatterFactoryParameter Auto-Fill
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.