You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现带参数的自定义Spring Boot测试注解(类似@LocalPort)?

Implementing a Custom @LocalSomePort Annotation with Protocol Parameters

Great question! Let's break down how to build your custom annotation that works like @LocalPort but accepts additional protocol parameters—since @Value alone can't handle the extra enum input you need. Here are two robust, Spring-aligned approaches:

Option 1: Custom AutowireCandidateResolver (Your Initial Idea)

This is actually the official approach Spring uses for @LocalPort—so you're already on the right track! It gives you tight integration with Spring's autowiring lifecycle. Here's how to implement it:

  • Define your custom annotation
    Start by creating the annotation itself, which accepts your Protocol enum as a parameter:

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LocalSomePort {
        Protocol value(); // Your enum: HTTP, HTTPS, etc.
    }
    
  • Create a custom AutowireCandidateResolver
    Extend Spring's ContextAnnotationAutowireCandidateResolver and override getSuggestedValue to add your port lookup logic based on the protocol:

    public class LocalSomePortAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver {
        @Override
        @Nullable
        public Object getSuggestedValue(DependencyDescriptor descriptor) {
            LocalSomePort annotation = descriptor.getAnnotation(LocalSomePort.class);
            if (annotation != null) {
                Protocol protocol = annotation.value();
                // Replace with your actual port lookup logic (e.g., from environment, server beans)
                String portPropertyKey = switch(protocol) {
                    case HTTP -> "local.server.port";
                    case HTTPS -> "local.management.port"; // Or your custom property key
                    default -> throw new IllegalArgumentException("Unsupported protocol: " + protocol);
                };
                // Return the property key wrapped in ${} to let Spring resolve it, or return the direct value
                return "${" + portPropertyKey + "}";
            }
            return super.getSuggestedValue(descriptor);
        }
    }
    
  • Register the resolver with Spring
    Configure Spring to use your resolver via a @TestConfiguration class (for test contexts):

    @TestConfiguration
    public class LocalSomePortConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
            beanFactory.setAutowireCandidateResolver(new LocalSomePortAutowireCandidateResolver());
        }
    }
    

    Include this config in your tests with @Import(LocalSomePortConfig.class).

Option 2: SpEL with Custom Static Logic (Lighter Alternative)

If you want a quicker implementation without overriding resolver logic, use Spring Expression Language (SpEL) paired with a meta-annotation wrapping @Value:

  • Create a static port resolver helper
    Encapsulate your port lookup logic in a static class:

    public class PortResolver {
        public static String getPort(Protocol protocol) {
            // Same port lookup logic as before
            return switch(protocol) {
                case HTTP -> System.getProperty("local.server.port", "8080");
                case HTTPS -> System.getProperty("local.management.port", "8443");
                default -> throw new IllegalArgumentException("Unsupported protocol: " + protocol);
            };
        }
    }
    
  • Define your annotation as a @Value meta-annotation
    Use SpEL to call your static resolver method, passing the annotation's protocol parameter directly:

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Value("#{T(com.yourpackage.PortResolver).getPort(#root.annotation.value())}")
    public @interface LocalSomePort {
        Protocol value();
    }
    

    This works out of the box in Spring contexts—no extra resolver registration needed.

Which Option Should You Choose?

  • Option 1 is ideal if you need to integrate with Spring's bean lifecycle (e.g., handling scoped beans, or port lookup depends on bean state). It's fully aligned with how Spring implements built-in annotations like @LocalPort.
  • Option 2 is perfect for simple, stateless port resolution. It's quicker to implement and requires less boilerplate.

Either approach will let you create a clean, reusable @LocalSomePort(Protocol.HTTP) annotation for your tests!

内容的提问来源于stack exchange,提问作者WonChul Heo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:13:22