Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.
—
Micronaut's AOP system provides compile-time aspect weaving with method and constructor interception, around advice, and introduction support without runtime proxy generation.
Intercept method calls with custom logic using around advice.
/**
* Custom interceptor annotation
*/
@Around
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Timed {
String value() default "";
}
/**
* Method interceptor implementation
*/
@Singleton
public class TimedInterceptor implements MethodInterceptor<Object, Object> {
@Override
public Object intercept(InvocationContext<Object, Object> context) {
long start = System.currentTimeMillis();
try {
return context.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
String methodName = context.getExecutableMethod().getName();
log.info("Method {} took {}ms", methodName, duration);
}
}
}
/**
* Using the interceptor
*/
@Singleton
public class UserService {
@Timed
public User findById(Long id) {
return userRepository.findById(id);
}
@Timed("user-creation")
public User createUser(User user) {
return userRepository.save(user);
}
}Built-in caching aspects for method-level caching.
/**
* Cacheable methods
*/
@Singleton
public class ProductService {
@Cacheable("products")
public Product findById(Long id) {
return productRepository.findById(id);
}
@Cacheable(value = "products", parameters = {"category", "active"})
public List<Product> findByCategory(String category, boolean active) {
return productRepository.findByCategoryAndActive(category, active);
}
@CacheInvalidate("products")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CacheInvalidate(value = "products", all = true)
public void clearProductCache() {
// Cache will be cleared automatically
}
}Create interface implementations dynamically using introduction advice.
/**
* Introduction annotation
*/
@Introduction
@Bean
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface Repository {
String value() default "";
}
/**
* Introduction interceptor
*/
@Singleton
public class RepositoryIntroduction implements MethodInterceptor<Object, Object> {
@Override
public Object intercept(InvocationContext<Object, Object> context) {
String methodName = context.getExecutableMethod().getName();
if (methodName.startsWith("find")) {
return handleFindMethod(context);
} else if (methodName.startsWith("save")) {
return handleSaveMethod(context);
} else if (methodName.startsWith("delete")) {
return handleDeleteMethod(context);
}
return context.proceed();
}
private Object handleFindMethod(InvocationContext<Object, Object> context) {
// Auto-implement find methods
return null;
}
}
/**
* Interface that will be implemented automatically
*/
@Repository
public interface CustomerRepository {
Customer findById(Long id);
List<Customer> findByName(String name);
Customer save(Customer customer);
void deleteById(Long id);
}Built-in resilience patterns using AOP.
/**
* Retry interceptor
*/
@Singleton
public class ExternalService {
@Retryable(attempts = "3", delay = "1s", multiplier = "2.0")
public String callExternalApi() {
// This method will be retried up to 3 times
// with exponential backoff (1s, 2s, 4s)
return restClient.call();
}
@CircuitBreaker(attempts = "5", openStatusTimeout = "1m", resetTimeout = "30s")
public Data fetchData() {
// Circuit breaker will open after 5 failures
// Stay open for 1 minute, then try to reset
return dataProvider.fetch();
}
}
/**
* Fallback methods
*/
@Singleton
public class UserService {
@Retryable
public User getUserFromPrimary(Long id) {
return primaryDataSource.getUser(id);
}
@Fallback
public User getUserFromPrimary(Long id, Exception ex) {
log.warn("Failed to get user from primary source", ex);
return fallbackDataSource.getUser(id);
}
}Automatic validation using Bean Validation annotations.
/**
* Validation interceptor (automatically applied)
*/
@Singleton
public class OrderService {
public Order createOrder(@Valid @NotNull OrderRequest request) {
// Parameters will be validated automatically
return new Order(request.getCustomerId(), request.getItems());
}
@Validated
public void updateOrderStatus(@NotNull Long orderId,
@NotBlank String status) {
// Method-level validation
orderRepository.updateStatus(orderId, status);
}
}Method-level security using annotations.
/**
* Security annotations
*/
@Singleton
@Secured("isAuthenticated()")
public class AdminService {
@Secured({"ROLE_ADMIN"})
public void deleteUser(Long userId) {
userRepository.delete(userId);
}
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public Report generateReport(ReportType type) {
return reportGenerator.generate(type);
}
@PreAuthorize("@securityService.canAccessUser(authentication, #userId)")
public User getUser(Long userId) {
return userRepository.findById(userId);
}
}// Core AOP interfaces
public interface MethodInterceptor<T, R> extends Interceptor<T, R> {
R intercept(InvocationContext<T, R> context);
}
public interface ConstructorInterceptor<T> extends Interceptor<T, T> {
T intercept(InvocationContext<T, T> context);
}
public interface InvocationContext<T, R> {
T getTarget();
ExecutableMethod<T, R> getExecutableMethod();
Map<String, Object> getAttributes();
Object[] getParameterValues();
R proceed() throws RuntimeException;
R proceed(Interceptor<T, R> from) throws RuntimeException;
}
public interface MethodInvocationContext<T, R> extends InvocationContext<T, R> {
MutableArgumentValue<?>[] getArguments();
<A> Optional<A> getAttribute(CharSequence name, Class<A> type);
void setAttribute(CharSequence name, Object value);
}
// Interceptor binding
public interface InterceptorRegistry {
<T> Interceptor<T, ?>[] resolveInterceptors(ExecutableMethod<T, ?> method,
InterceptorKind kind);
<T> List<BeanRegistration<Interceptor<T, ?>>> findInterceptors(InterceptorKind kind,
BeanDefinition<?> beanDefinition);
}
// Proxy interfaces
public interface InterceptedProxy<T> {
T interceptedTarget();
ExecutableMethod<?, ?> findInterceptedMethod();
}
public interface Intercepted {
// Marker interface for intercepted beans
}
// Cache annotations (built-in AOP)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Around
@InterceptorBinding
public @interface Cacheable {
String[] value() default {};
String[] parameters() default {};
boolean atomic() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Around
@InterceptorBinding
public @interface CacheInvalidate {
String[] value() default {};
String[] parameters() default {};
boolean all() default false;
boolean async() default false;
}
// Retry annotations
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Around
@InterceptorBinding
public @interface Retryable {
String attempts() default "3";
String delay() default "1s";
String multiplier() default "1.0";
String maxDelay() default "";
Class<? extends Throwable>[] includes() default {};
Class<? extends Throwable>[] excludes() default {};
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Around
@InterceptorBinding
public @interface CircuitBreaker {
String attempts() default "20";
String openStatusTimeout() default "1m";
String resetTimeout() default "20s";
Class<? extends Throwable>[] includes() default {};
Class<? extends Throwable>[] excludes() default {};
}Install with Tessl CLI
npx tessl i tessl/maven-micronaut