derM
derM

Reputation: 13691

Make Spring Boot Scan for different Annotations

To utilize the full power of spring magic, annotate your active components with @Component or any derived annotation, lean back and relax while Spring wires together your application.

Sofar so good.

Now I want to structure my app according to the Clean Architecture/Hexagonal Architecture pattern, and desire to keep Spring away from my inner circle, the domain. To achive that, I could simply drop the spring magic and create a couple of @Configuration-classes with some @Bean-providing methods.
That is, creating a lot of boilerplate code and the necessity of having enough domain knowledge to configure everything - so not, what I want.

What I want: I want to annotate my domain types with annotations like @UseCase, @Port, @Service, @Entity, @EntityId, @ValueObject and other meaningfull annotations, carrying the domain knowledge and intent regarding those classes - and use as much spring magic as possible in the outer layer, to automate the configuration, drawing necessary knowledge as declared in the domain layer.

I could do that, hiding the Spring Dependency by hiding an @Component-Annotation within the @UseCase-Annotation, but then I my domain part would still be spring dependent - just with some indirection.

What I would prefer is, telling Spring to not look for @Component but for @UseCase instead. My imagination tells me, Spring works somehow like this:

for (class in classpath-with-matching-package) {
    if (class has annotation '@Component') {
        createBeanDefinitionForClass(class)
    }
}
createBeansForDefinitions()

And I hope, it is possible to tell the method which does the check, whether the class has the @Component-Annotation to check wether it has some other, meaningful annotation, either by configuration or by extension.

However, I have no clue, what class and or method might be responsible for this task.

Questions in short:

Also feel free, to respectfully comment, why my idea is stupid. Thats always the most enlightening!


This is in Response to the answer by @Ghokun which seems reasonable but fails

My Example-Project has the following structure:

src/main/java/de/mycorp/group
 |-> /exampleapp
   |-> NoComponentDemoApp.java
   |-> UseCaseScan.java
 |-> /springless
   |-> SomeUseCase.java
   |-> UseCase.java

NoComponentDemoApp.java:

@SpringBootApplication
public class NoComponentDemoApp
{
    public static void main(String[] args)
    {
        SpringApplication.run(NoComponentDemoApp.class, args);
    }
}

UseCaseScane.java

@Configuration
@ComponentScan(
         useDefaultFilters = false,
         includeFilters = {
                  @ComponentScan.Filter(UseCase.class)}, // type default/annotation, value synonym of classes
         basePackages = "de.mycorp.group.springless"
)
public class UseCaseScan
{
}

SomeUseCase.java

@UseCase
public class SomeUseCase
{
    private static final Logger logger = LoggerFactory.getLogger(SomeUseCase.class);
    public SomeUseCase()
    {
        logger.info("Some Actor created");
    }
}

UseCase.java

public @interface UseCase
{
}

Ofc. when I enable default filters and mark SomeUseCase.java with @Component it works. But something fails. For brevity I removed import. Package info can be deduced from the structure. If deemed necessary, I will add it.

Upvotes: 0

Views: 310

Answers (1)

Ghokun
Ghokun

Reputation: 3465

Spring already gives you that option with the following configuration:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;


@ComponentScan(useDefaultFilters = false,
               includeFilters = {@Filter(type = FilterType.ANNOTATION, class= UseCase.class)})
public class AppConfig {}

Documentation link


Edit:

It works with following configuration. Please check your classes:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;

@SpringBootApplication
@ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(UseCase.class) })
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
}

package demo;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@UseCase
public class AnnotatedClass {
    
    private static final Logger logger = LoggerFactory.getLogger(AnnotatedClass.class);
    
    @PostConstruct
    public void init() {
        logger.info("It Works!");
    }
}

package demo;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Indexed;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface UseCase {
    
}
...
2020-09-28 03:25:02.913  INFO 6829 --- [           main] demo.AnnotatedClass: It Works!
...

Upvotes: 1

Related Questions