// Core Invocation
org.springframework.boot.actuate.endpoint.invoke.OperationInvoker
org.springframework.boot.actuate.endpoint.invoke.reflect.ReflectiveOperationInvoker
org.springframework.boot.actuate.endpoint.InvocationContext
// Parameter Handling
org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper
org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper
org.springframework.boot.actuate.endpoint.invoke.OperationParameter
org.springframework.boot.actuate.endpoint.invoke.OperationParameters
// Caching
org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvoker
org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor
// Argument Resolution
org.springframework.boot.actuate.endpoint.invoke.OperationArgumentResolverHTTP Request
↓
Extract Parameters (URL params, path variables, body)
↓
Create InvocationContext (security + arguments)
↓
Apply Advisors (logging, caching, etc.)
↓
Parameter Value Mapping (String → typed values)
↓
Argument Resolution (SecurityContext, etc.)
↓
ReflectiveOperationInvoker (method.invoke())
↓
ResponseShould operation be cached?
│
├─ Has parameters?
│ └─ NO → Cacheable if configured
│
├─ Is READ operation?
│ └─ NO (WRITE/DELETE) → Never cached
│
├─ Parameters are ApiVersion, SecurityContext, or WebServerNamespace?
│ └─ YES → Cacheable
│
└─ Other parameters?
└─ NO → Not cacheable| Source Type | Target Types | Converter |
|---|---|---|
| String | Primitives (int, long, boolean) | Default |
| String | Enums | Default |
| String | OffsetDateTime | IsoOffsetDateTimeConverter |
| String | LogLevel | Custom |
| Any | Same type | Passthrough |
Cache read operations when:
Don't cache when:
PATTERN: Configure caching per endpoint
// ✓ Selective caching based on endpoint
@Bean
public CachingOperationInvokerAdvisor cachingAdvisor() {
return new CachingOperationInvokerAdvisor(endpointId -> {
return switch (endpointId.toLowerCaseString()) {
case "health" -> 10_000L; // 10 seconds
case "info" -> 60_000L; // 60 seconds
case "metrics" -> 5_000L; // 5 seconds
default -> 0L; // No caching
};
});
}ANTI-PATTERN: Cache everything
// ❌ Wrong - caches operations that shouldn't be cached
@Bean
public CachingOperationInvokerAdvisor cachingAdvisor() {
return new CachingOperationInvokerAdvisor(endpointId -> 60_000L);
// Caches write operations, parameterized reads, etc!
}PATTERN: Custom parameter converter
// ✓ Add domain-specific converters
@Configuration
public class ConverterConfiguration {
@Bean
public ParameterValueMapper parameterValueMapper() {
DefaultConversionService service = new DefaultConversionService();
// Add custom converter
service.addConverter(new StringToLogLevelConverter());
return new ConversionServiceParameterValueMapper(service);
}
}
public class StringToLogLevelConverter implements Converter<String, LogLevel> {
@Override
public LogLevel convert(String source) {
return LogLevel.valueOf(source.toUpperCase());
}
}ANTI-PATTERN: Manual parameter mapping
// ❌ Manual mapping in operation (should use converter)
@ReadOperation
public void setLogLevel(@Selector String name, String level) {
LogLevel logLevel = LogLevel.valueOf(level.toUpperCase());
// Should let framework convert String → LogLevel
}
// ✓ Let framework handle conversion
@ReadOperation
public void setLogLevel(@Selector String name, LogLevel level) {
// level already converted by framework
}PATTERN: Argument resolver for custom types
// ✓ Resolve custom types via argument resolver
@Configuration
public class ResolverConfiguration {
@Bean
public OperationArgumentResolver requestInfoResolver(HttpServletRequest request) {
return OperationArgumentResolver.of(
RequestInfo.class,
() -> new RequestInfo(request.getRemoteAddr())
);
}
}
@ReadOperation
public Data getData(RequestInfo requestInfo) {
// requestInfo automatically resolved
}The operation invocation framework provides the infrastructure for invoking endpoint operations with parameter mapping, reflection-based invocation, caching support, and argument resolution.
/**
* Invokes endpoint operations.
*
* Thread-safe: Implementations must be thread-safe
* Since: Spring Boot 2.0+
*/
@FunctionalInterface
public interface OperationInvoker {
/**
* Invoke operation with context.
*
* @param context Invocation context (non-null)
* @return Operation result (nullable)
* @throws MissingParametersException if required parameters missing
* @throws ParameterMappingException if parameter mapping fails
*/
Object invoke(InvocationContext context);
}
/**
* Advisor that wraps an OperationInvoker to add cross-cutting concerns.
* Advisors can add functionality like caching, logging, metrics, or security checks.
*
* Thread-safe: Implementations must be thread-safe
* Package: org.springframework.boot.actuate.endpoint.invoke
* @since 2.0.0
*/
@FunctionalInterface
public interface OperationInvokerAdvisor {
/**
* Apply this advisor to an operation invoker.
* Returns the original invoker if no advice is needed, or a wrapped invoker.
*
* @param endpointId the endpoint ID (never null)
* @param operationType the operation type (never null)
* @param parameters the operation parameters (never null)
* @param invoker the operation invoker to wrap (never null)
* @return the original or wrapped invoker (never null)
*/
OperationInvoker apply(EndpointId endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker);
}
/**
* Context for operation invocation.
*
* Thread-safe: Immutable after construction
* Since: Spring Boot 2.0+
*/
public class InvocationContext {
/**
* Create invocation context.
*
* @param securityContext Security context (non-null)
* @param arguments Operation arguments (non-null)
* @param argumentResolvers Additional resolvers (varargs)
*/
public InvocationContext(SecurityContext securityContext,
Map<String, Object> arguments,
OperationArgumentResolver... argumentResolvers);
/**
* Get operation arguments.
*
* @return Immutable map of arguments
*/
public Map<String, Object> getArguments();
/**
* Resolve argument by type.
*
* @param argumentType Type to resolve
* @return Resolved argument or null
*/
public <T> @Nullable T resolveArgument(Class<T> argumentType);
/**
* Check if type can be resolved.
*
* @param type Type to check
* @return true if resolvable
*/
public boolean canResolve(Class<?> type);
}/**
* A collection of {@link OperationParameter operation parameters}.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*/
public interface OperationParameters extends Iterable<OperationParameter> {
/**
* Return {@code true} if there is at least one parameter.
*
* @return if there are parameters
*/
default boolean hasParameters() {
return getParameterCount() > 0;
}
/**
* Return the total number of parameters.
*
* @return the total number of parameters
*/
int getParameterCount();
/**
* Return if any of the contained parameters are
* {@link OperationParameter#isMandatory() mandatory}.
*
* @return if any parameters are mandatory
*/
default boolean hasMandatoryParameter() {
return stream().anyMatch(OperationParameter::isMandatory);
}
/**
* Return the parameter at the specified index.
*
* @param index the parameter index
* @return the parameter (never null)
*/
OperationParameter get(int index);
/**
* Return a stream of the contained parameters.
*
* @return a stream of the parameters (never null)
*/
Stream<OperationParameter> stream();
}
/**
* Maps parameter values to target types.
*
* Thread-safe: Implementations should be thread-safe
* Since: Spring Boot 2.0+
*/
public interface ParameterValueMapper {
/**
* A {@link ParameterValueMapper} that does nothing.
*/
ParameterValueMapper NONE = (parameter, value) -> value;
/**
* Map parameter value to target type.
*
* @param parameter Parameter definition (non-null)
* @param value Value to map (nullable)
* @return Mapped value
* @throws ParameterMappingException if mapping fails
*/
Object mapParameterValue(OperationParameter parameter, @Nullable Object value);
}
/**
* Parameter mapper using Spring ConversionService.
*
* Thread-safe: Yes
* Since: Spring Boot 2.0+
*/
public class ConversionServiceParameterValueMapper implements ParameterValueMapper {
/**
* Create with shared ApplicationConversionService.
*/
public ConversionServiceParameterValueMapper();
/**
* Create with custom conversion service.
*
* @param conversionService Conversion service (non-null)
*/
public ConversionServiceParameterValueMapper(ConversionService conversionService);
@Override
public Object mapParameterValue(OperationParameter parameter, @Nullable Object value);
}/**
* {@link OperationInvoker} that uses reflection to invoke the underlying operation method.
* Standard implementation used by the framework for invoking endpoint operations.
*
* Thread-safe: Yes (immutable after construction)
* Since: Spring Boot 2.0+
*/
public class ReflectiveOperationInvoker implements OperationInvoker {
/**
* Create a new {@link ReflectiveOperationInvoker} instance.
*
* @param target the target object containing the operation method (non-null)
* @param operationMethod the operation method to invoke (non-null)
* @param parameterValueMapper the parameter value mapper (non-null)
*/
public ReflectiveOperationInvoker(Object target,
OperationMethod operationMethod,
ParameterValueMapper parameterValueMapper);
@Override
public Object invoke(InvocationContext context);
}
/**
* A method on an endpoint that can be used as an operation.
* Wraps a Java {@link Method} with additional metadata about the operation type and parameters.
*
* Thread-safe: Immutable
* Since: Spring Boot 2.0+
*/
public class OperationMethod {
/**
* Create a new {@link OperationMethod} instance.
*
* @param method the Java method (non-null)
* @param operationType the type of operation (non-null)
*/
public OperationMethod(Method method, OperationType operationType);
/**
* Return the Java method.
*
* @return the method (never null)
*/
public Method getMethod();
/**
* Return the operation type.
*
* @return the operation type (never null)
*/
public OperationType getOperationType();
/**
* Return the operation parameters.
*
* @return the parameters (never null)
*/
public OperationParameters getParameters();
}/**
* Advisor that adds caching to read operations.
*
* Thread-safe: Yes
* Caching Rules:
* - Only READ operations
* - No parameters OR only ApiVersion/SecurityContext/WebServerNamespace
* - TTL > 0
*
* Since: Spring Boot 2.0+
*/
public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
/**
* Create caching advisor.
*
* @param endpointIdTimeToLive Function mapping endpoint ID to TTL (ms)
* Returns null or 0 for no caching
*/
public CachingOperationInvokerAdvisor(
Function<EndpointId, @Nullable Long> endpointIdTimeToLive);
@Override
public OperationInvoker apply(EndpointId endpointId,
OperationType operationType,
OperationParameters parameters,
OperationInvoker invoker);
}
/**
* Advisor for operation invokers.
*
* Since: Spring Boot 2.0+
*/
public interface OperationInvokerAdvisor {
/**
* Apply advice to invoker.
*
* @param endpointId Endpoint ID
* @param operationType Operation type
* @param parameters Operation parameters
* @param invoker Invoker to advise
* @return Advised invoker (may wrap original)
*/
OperationInvoker apply(EndpointId endpointId,
OperationType operationType,
OperationParameters parameters,
OperationInvoker invoker);
}
/**
* {@link OperationInvoker} that caches the result of an operation invocation.
* Wraps another invoker and caches its result for a configurable time-to-live period.
* Only caches READ operations without parameters (or with special parameters like ApiVersion).
* Supports reactive types (Mono, Flux) when Project Reactor is present.
*
* Thread-safe: Yes
* Package: org.springframework.boot.actuate.endpoint.invoker.cache
* Since: Spring Boot 2.0+
*
* Note: This class has a package-private constructor and is typically created by
* {@link CachingOperationInvokerAdvisor} rather than being instantiated directly.
*/
public class CachingOperationInvoker implements OperationInvoker {
/**
* Create caching invoker (package-private constructor).
* Note: Typically created by framework via CachingOperationInvokerAdvisor.
*
* @param invoker delegate invoker (non-null)
* @param timeToLive cache TTL in milliseconds
*/
CachingOperationInvoker(OperationInvoker invoker, long timeToLive);
/**
* Get the time-to-live for the cache.
*
* @return the time-to-live in milliseconds
*/
public long getTimeToLive();
/**
* Determine if caching is applicable for operations with the given parameters.
* Caching is only applicable if all parameters are PATH parameters (no query parameters).
*
* @param parameters Operation parameters to check
* @return true if caching can be applied, false otherwise
*/
static boolean isApplicable(OperationParameters parameters);
@Override
public Object invoke(InvocationContext context);
}/**
* Resolves additional arguments for operation invocation.
* Allows custom argument types to be injected into operation methods beyond standard parameters.
*
* Thread-safe: Implementations should be thread-safe
* Package: org.springframework.boot.actuate.endpoint
* Since: Spring Boot 2.0+
*/
@FunctionalInterface
public interface OperationArgumentResolver {
/**
* Check if this resolver can resolve the given type.
*
* @param type Type to resolve
* @return true if this resolver can resolve the type
*/
default boolean canResolve(Class<?> type) {
return false;
}
/**
* Resolve argument of given type.
*
* @param type Type to resolve (non-null)
* @return Resolved argument or null if not resolvable
*/
<T> @Nullable T resolve(Class<T> type);
/**
* Factory method to create resolver for specific type.
*
* @param type Type this resolver handles
* @param supplier Supplier providing the value
* @return New resolver instance
*/
static OperationArgumentResolver of(Class<?> type, Supplier<?> supplier);
}
/**
* Argument resolver for {@link Producible} enum types used in content negotiation.
* Resolves producible enums based on HTTP Accept header or defaults to the highest ordinal value.
*
* Thread-safe: Yes
* Package: org.springframework.boot.actuate.endpoint
* Since: Spring Boot 2.5.0
*
* Example: Resolving ApiVersion.V2 or ApiVersion.V3 based on client's Accept header.
*/
public class ProducibleOperationArgumentResolver implements OperationArgumentResolver {
/**
* Create resolver with supplier for accepted MIME types.
*
* @param accepts Supplier returning list of accepted MIME types from Accept header
* Returns null or empty list for wildcard (*\/*) or missing Accept header
*/
public ProducibleOperationArgumentResolver(Supplier<@Nullable List<String>> accepts);
/**
* Check if type is a Producible enum.
*
* @param type Type to check
* @return true if type implements both Producible and Enum
*/
@Override
public boolean canResolve(Class<?> type);
/**
* Resolve Producible enum value based on Accept header.
*
* Resolution rules:
* 1. If Accept header is *\/* or missing: return default value (or highest ordinal)
* 2. Parse Accept header MIME types
* 3. Match against enum's getProducedMimeType()
* 4. Return most recent (highest ordinal) match
* 5. Return null if no match found
*
* @param type Producible enum type
* @return Resolved enum value or null
*/
@Override
public <T> @Nullable T resolve(Class<T> type);
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* Caching configuration for endpoints.
*
* Thread-safe: Yes
* Performance: Reduces computation for cached operations
* Since: Application 1.0
*/
@Configuration
public class EndpointCachingConfiguration {
/**
* Configure caching per endpoint.
*
* Caching rules:
* - Only READ operations without parameters
* - Cached for configured TTL
* - Cache key includes SecurityContext if present
*
* @return Caching advisor
*/
@Bean
public CachingOperationInvokerAdvisor cachingAdvisor() {
// Define cache TTLs per endpoint (in milliseconds)
Map<String, Long> cacheTtls = Map.of(
"health", 10_000L, // 10 seconds
"info", 60_000L, // 60 seconds
"metrics", 5_000L, // 5 seconds
"beans", 30_000L, // 30 seconds
"configprops", 60_000L // 60 seconds
);
return new CachingOperationInvokerAdvisor(endpointId ->
cacheTtls.getOrDefault(endpointId.toLowerCaseString(), 0L)
);
}
}
// Configuration in application.yml
/*
management:
endpoint:
health:
cache:
time-to-live: 10s
info:
cache:
time-to-live: 60s
metrics:
cache:
time-to-live: 5s
*/package com.example.actuator;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.logging.LogLevel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
/**
* Configuration for custom parameter converters.
*
* Thread-safe: Yes
* Since: Application 1.0
*/
@Configuration
public class ParameterMappingConfiguration {
/**
* Configure parameter value mapper with custom converters.
*
* @return Configured parameter mapper
*/
@Bean
public ParameterValueMapper parameterValueMapper() {
DefaultConversionService conversionService = new DefaultConversionService();
// Add custom converters
conversionService.addConverter(new StringToLogLevelConverter());
conversionService.addConverter(new StringToOffsetDateTimeConverter());
conversionService.addConverter(new StringToSeverityConverter());
return new ConversionServiceParameterValueMapper(conversionService);
}
/**
* Converter for log levels (case-insensitive).
*/
public static class StringToLogLevelConverter implements Converter<String, LogLevel> {
@Override
public LogLevel convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
try {
return LogLevel.valueOf(source.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Invalid log level: " + source +
". Valid values: TRACE, DEBUG, INFO, WARN, ERROR, OFF"
);
}
}
}
/**
* Converter for ISO-8601 date-time strings.
*/
public static class StringToOffsetDateTimeConverter
implements Converter<String, OffsetDateTime> {
@Override
public OffsetDateTime convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
return OffsetDateTime.parse(source, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
/**
* Converter for custom severity enum.
*/
public static class StringToSeverityConverter implements Converter<String, Severity> {
@Override
public Severity convert(String source) {
if (source == null || source.isEmpty()) {
return Severity.MEDIUM;
}
return Severity.valueOf(source.toUpperCase());
}
}
public enum Severity {
LOW, MEDIUM, HIGH, CRITICAL
}
}
// Usage in endpoint:
@ReadOperation
public void setLogLevel(
@Selector String loggerName,
LogLevel level // Automatically converted from string
) {
// level is already a LogLevel enum
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.invoke.OperationArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
/**
* Configuration for custom argument resolvers.
*
* Thread-safe: Yes
* Since: Application 1.0
*/
@Configuration
public class ArgumentResolverConfiguration {
/**
* Resolver for RequestInfo (custom type).
*
* @return Argument resolver
*/
@Bean
public OperationArgumentResolver requestInfoResolver() {
return OperationArgumentResolver.of(
RequestInfo.class,
() -> {
ServletRequestAttributes attrs =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null) {
return null;
}
HttpServletRequest request = attrs.getRequest();
return new RequestInfo(
request.getRemoteAddr(),
request.getHeader("User-Agent")
);
}
);
}
/**
* Request information holder.
*/
public record RequestInfo(String remoteAddress, String userAgent) {}
}
// Usage in endpoint:
@Endpoint(id = "custom")
@Component
public class CustomEndpoint {
/**
* Operation with custom argument type.
*
* @param requestInfo Automatically resolved by framework
* @return Data with request info
*/
@ReadOperation
public Map<String, Object> getData(RequestInfo requestInfo) {
return Map.of(
"remoteAddress", requestInfo.remoteAddress(),
"userAgent", requestInfo.userAgent(),
"timestamp", Instant.now()
);
}
}package com.example.actuator;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.InvocationContext;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Advisor that adds logging to all operations.
*
* Thread-safe: Yes
* Performance: Minimal overhead (~1ms)
* Since: Application 1.0
*/
@Component
public class LoggingOperationInvokerAdvisor implements OperationInvokerAdvisor {
private static final Logger logger =
LoggerFactory.getLogger(LoggingOperationInvokerAdvisor.class);
@Override
public OperationInvoker apply(EndpointId endpointId,
OperationType operationType,
OperationParameters parameters,
OperationInvoker invoker) {
// Wrap invoker with logging
return new LoggingOperationInvoker(invoker, endpointId, operationType);
}
/**
* Invoker wrapper that adds logging.
*/
private static class LoggingOperationInvoker implements OperationInvoker {
private final OperationInvoker delegate;
private final EndpointId endpointId;
private final OperationType operationType;
LoggingOperationInvoker(OperationInvoker delegate,
EndpointId endpointId,
OperationType operationType) {
this.delegate = delegate;
this.endpointId = endpointId;
this.operationType = operationType;
}
@Override
public Object invoke(InvocationContext context) {
logger.debug("Invoking {} operation on endpoint {}",
operationType, endpointId);
long startTime = System.currentTimeMillis();
try {
Object result = delegate.invoke(context);
long duration = System.currentTimeMillis() - startTime;
logger.debug("Operation completed in {}ms", duration);
if (duration > 1000) {
logger.warn("Slow operation: {} {} took {}ms",
operationType, endpointId, duration);
}
return result;
} catch (Exception e) {
logger.error("Operation failed: {} {} - {}",
operationType, endpointId, e.getMessage());
throw e;
}
}
}
}package com.example.actuator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.logging.LogLevel;
import static org.assertj.core.api.Assertions.*;
class StringToLogLevelConverterTest {
private final ParameterMappingConfiguration.StringToLogLevelConverter converter =
new ParameterMappingConfiguration.StringToLogLevelConverter();
@Test
void convert_ValidLevel_ReturnsLogLevel() {
assertThat(converter.convert("DEBUG")).isEqualTo(LogLevel.DEBUG);
assertThat(converter.convert("debug")).isEqualTo(LogLevel.DEBUG);
assertThat(converter.convert("INFO")).isEqualTo(LogLevel.INFO);
}
@Test
void convert_InvalidLevel_ThrowsException() {
assertThatThrownBy(() -> converter.convert("INVALID"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid log level");
}
@Test
void convert_Null_ReturnsNull() {
assertThat(converter.convert(null)).isNull();
}
}Problem: Parameter conversion fails
Causes:
Solutions:
// Solution 1: Add custom converter
@Bean
public ParameterValueMapper parameterValueMapper() {
DefaultConversionService service = new DefaultConversionService();
service.addConverter(new MyCustomConverter());
return new ConversionServiceParameterValueMapper(service);
}
// Solution 2: Validate input format
@ReadOperation
public void operation(
@Selector String id,
OffsetDateTime date // Expects ISO-8601 format
) {
// Use: 2024-01-15T10:30:00Z
}
// Solution 3: Make parameter nullable
@ReadOperation
public void operation(@Nullable String optionalParam) {
// Handles missing parameter
}Problem: Required parameter not provided
Causes:
Solutions:
// Ensure @Selector for path variables
@ReadOperation
public Data get(@Selector String id) { // <-- @Selector required
// Maps to /actuator/endpoint/{id}
}
// Make optional parameters nullable
@ReadOperation
public Data search(@Nullable String filter) {
// filter can be null
}// Caching works for:
// ✓ READ operations without parameters
@ReadOperation
public Data getData() {
return expensiveComputation(); // Cached
}
// ✓ READ operations with only special parameters
@ReadOperation
public Data getData(SecurityContext ctx) {
return expensiveComputation(); // Cached per security context
}
// ✗ Not cached:
@ReadOperation
public Data search(String query) {
return search(query); // Has query parameter - not cached
}
@WriteOperation
public void update(String value) {
// Write operations never cached
}// Operation invocation overhead:
// - Parameter mapping: ~0.1-0.5ms
// - Reflection call: ~0.1ms
// - Advisor chain: ~0.1ms per advisor
// - Total: ~0.3-1ms typical
// For high-frequency endpoints, this is negligible
// compared to actual operation execution