Skip to content

Latest commit

 

History

47 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring Chain Of Responsibility

CI Maven Central Javadocs Coverage

Library for building Chain of Responsibility in Spring applications without manual wiring of dependencies.

Automatically links beans into a chain using @Order and @ChainNext.

Chain Flow

sequenceDiagram
    participant Client
    participant ChainA
    participant ChainB
    participant ChainC

    Client->>ChainA: execute()
    ChainA->>ChainB: next.handle()
    ChainB->>ChainC: next.handle()
Loading

Features

  • Automatic chain building via Spring context
  • Supports @Order for execution order
  • No manual wiring of chain dependencies
  • Fully compatible with Spring AOP (proxies supported)
  • Last class of chain contains Proxy implementation on chain interface when the class has field with @ChainNext

How it works

  • All beans implementing the chain interface are collected from the Spring context
  • Beans are sorted using @Order
  • The next element is injected via @ChainNext

Why not just @Autowired List<Interface>?

The common way to build an ordered chain in plain Spring, without this library, looks like this:

@Component
public class ManualChain {
    private final List<ChainInterface> handlers;

    public ManualChain(List<ChainInterface> handlers) {
        this.handlers = new ArrayList<>(handlers);
        this.handlers.sort(AnnotationAwareOrderComparator.INSTANCE);
    }

    public void handle() {
        for (ChainInterface handler : handlers) {
            handler.handle();
        }
    }
}

That works for handlers that always run start to finish, but every handler has to know about the whole list, and there's no clean way for a handler to stop the chain or skip the rest based on its own logic. spring-chain-of-responsibility wires each bean directly to the next one via @ChainNext, so a handler decides on its own whether to continue — a real Chain of Responsibility, not just an ordered loop.

Requirements

  • Java 17+
  • Spring Boot 3.x+ for automatic autoconfiguration via AutoConfiguration.imports (see Without Spring Boot otherwise)
  • Built and tested against Spring Framework 7.0.x (via spring-framework-bom)

Add dependency

Add library in pom

<dependency>
    <groupId>io.github.evmetatron</groupId>
    <artifactId>spring-chain-of-responsibility</artifactId>
    <version>0.2.0</version>
</dependency>

Add library in gradle

dependencies {
    implementation("io.github.evmetatron:spring-chain-of-responsibility:0.2.0")
}

Create bean by chain interface

With Spring Boot

ChainFactory is registered automatically via Spring Boot autoconfiguration — just autowire it.

import io.github.evmetatron.spring.cor.ChainFactory;
import org.springframework.context.annotation.Bean;

@Bean
public ChainInterface chain(@Autowired ChainFactory chainFactory) {
    return chainFactory.createChain(ChainInterface.class);
}

Without Spring Boot (plain Spring context)

The autoconfiguration file only gets picked up by Spring Boot's autoconfiguration mechanism. In a plain Spring application, register ChainFactory yourself — either by importing the provided configuration:

import io.github.evmetatron.spring.cor.ChainAutoConfiguration;
import org.springframework.context.annotation.Import;

@Import(ChainAutoConfiguration.class)
class AppConfig {}

or by declaring the bean directly:

import io.github.evmetatron.spring.cor.ChainFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@Bean
public ChainFactory chainFactory(ApplicationContext context) {
    return new ChainFactory(context);
}

Create interface and classes

@ChainNext

Injects the next element in the chain.

The order is defined using @Order.

Important

  • Do not forget @Component or @Service
  • @Order defines execution order
  • Every bean must have a field annotated with @ChainNext — otherwise ChainFactory throws ChainNextFieldNotFoundException
import io.github.evmetatron.spring.cor.ChainNext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

public interface ChainInterface {
    void handle();
}

@Component // or @Service
@Order(1)
public class ChainA implements ChainInterface {
    @ChainNext
    private ChainInterface next;

    @Override
    public void handle() {
        /* Some code */
        System.out.println("Chain A");
        
        if (shouldContinue()) {
            next.handle();
        }
    }
}

@Component
@Order(2)
public class ChainB implements ChainInterface {
    @ChainNext
    private ChainInterface next;

    @Override
    public void handle() {
        /* Some code */
        System.out.println("Chain B");

        if (shouldContinue()) {
            next.handle();
        }
    }
}

Call chains

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ExampleClass {
    @Autowired
    private ChainInterface chain;

    public void execute() {
        chain.handle(); // call all chains
        
        // Result:
        // Chain A
        // Chain B
    }
}

About

Zero-boilerplate Chain of Responsibility for Spring Boot — auto-wire ordered beans via Order and ChainNext annotations, no manual dependency wiring

Topics

Resources

Contributing

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages