CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-micronaut

Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.

Pending
Overview
Eval results
Files

aop.mddocs/

Aspect-Oriented Programming

Micronaut's AOP system provides compile-time aspect weaving with method and constructor interception, around advice, and introduction support without runtime proxy generation.

Capabilities

Method Interception

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);
    }
}

Cache Interception

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
    }
}

Introduction Advice

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);
}

Retry and Circuit Breaker

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);
    }
}

Validation Interception

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);
    }
}

Security Interception

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);
    }
}

Types

// 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

docs

aop.md

configuration.md

dependency-injection.md

functions.md

http-client.md

http-server.md

index.md

management.md

messaging.md

reactive.md

retry.md

scheduling.md

websocket.md

tile.json