or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-config.mdaot.mdcaching.mdcontext-lifecycle.mdevents.mdformatting.mdi18n.mdindex.mdjmx.mdresilience.mdscheduling.mdstereotypes.mdvalidation.md
tile.json

annotation-config.mddocs/

Component Scanning and Annotation Configuration

@Component Scan

@ComponentScan(
    basePackages = {"com.example.service", "com.example.repository"},
    basePackageClasses = {ServiceMarker.class},
    includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Impl"),
    excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Deprecated.class),
    lazyInit = false,
    scopeResolver = CustomScopeMetadataResolver.class,
    resourcePattern = "**/*.class"
)
public class AppConfig {}

FilterType options: ANNOTATION, ASSIGNABLE_TYPE, ASPECTJ, REGEX, CUSTOM

@Configuration

@Configuration
@PropertySource("classpath:application.properties")
@Import(DataSourceConfig.class)
public class AppConfig {

    @Bean
    @Profile("dev")
    @Scope("prototype")
    @Lazy
    public DataSource dataSource(@Value("${db.url}") String url) {
        return new HikariDataSource(url);
    }

    @Bean
    @DependsOn("dataSource")
    @Primary
    public UserService userService(DataSource ds) {
        return new UserServiceImpl(ds);
    }
}

@Conditional

@Bean
@Conditional(OnDatabaseCondition.class)
public DatabaseService databaseService() {
    return new DatabaseService();
}

public class OnDatabaseCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().containsProperty("database.enabled");
    }
}

Common @Conditional variants:

  • @ConditionalOnClass - Class exists on classpath
  • @ConditionalOnMissingClass - Class not on classpath
  • @ConditionalOnBean - Bean exists in context
  • @ConditionalOnMissingBean - Bean doesn't exist
  • @ConditionalOnProperty - Property has specific value
  • @ConditionalOnExpression - SpEL expression evaluates to true

@Import and ImportSelector

@Configuration
@Import({DataConfig.class, SecurityConfig.class, MyImportSelector.class})
public class AppConfig {}

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        return new String[]{"com.example.ConditionalConfig"};
    }
}

// DeferredImportSelector for conditional ordering
public class DeferredSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        return new String[]{"com.example.LateConfig"};
    }
}

@Profile

@Configuration
@Profile("prod")
public class ProductionConfig {

    @Bean
    @Profile("!dev")
    public SecurityService securityService() {
        return new ProductionSecurityService();
    }
}

// Activate: -Dspring.profiles.active=prod
// Or: System.setProperty("spring.profiles.active", "prod");

Environment and PropertySources

@Configuration
@PropertySource("classpath:app.properties")
@PropertySource(value = "file:/etc/app.properties", ignoreResourceNotFound = true)
public class Config {

    @Autowired
    private Environment env;

    @Bean
    public DataSource dataSource() {
        String url = env.getProperty("db.url");
        String username = env.getRequiredProperty("db.username");
        Integer poolSize = env.getProperty("db.pool.size", Integer.class, 10);
        return createDataSource(url, username, poolSize);
    }
}

@Value Injection

@Component
public class AppProperties {

    @Value("${app.name}")
    private String appName;

    @Value("${app.timeout:30}")
    private int timeout;  // default 30

    @Value("#{systemProperties['user.home']}")
    private String userHome;

    @Value("#{${app.map}}")  // Injects Map from property
    private Map<String, String> configMap;

    @Value("#{'${app.list}'.split(',')}")
    private List<String> items;
}

@Lookup Method Injection

@Component
public abstract class CommandManager {

    public void process() {
        Command cmd = createCommand();
        cmd.execute();
    }

    @Lookup
    protected abstract Command createCommand();
}

@Component
@Scope("prototype")
public class AsyncCommand implements Command {
    @Override
    public void execute() {
        // Prototype-scoped execution
    }
}

@Primary and @Qualifier

@Configuration
public class Config {

    @Bean
    @Primary
    public DataSource primaryDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @Qualifier("read-only")
    public DataSource readOnlyDataSource() {
        return new HikariDataSource();
    }
}

@Service
public class UserService {

    private final DataSource primary;
    private final DataSource readOnly;

    public UserService(DataSource primary,  // Injects @Primary
                      @Qualifier("read-only") DataSource readOnly) {
        this.primary = primary;
        this.readOnly = readOnly;
    }
}

Programmatic Registration

GenericApplicationContext context = new GenericApplicationContext();

// Register bean definitions
context.registerBean(UserService.class);
context.registerBean("customName", CustomService.class,
    () -> new CustomService(context.getBean(DataSource.class)));

// Register with customizer
context.registerBean(DataSource.class, () -> new HikariDataSource(), bd -> {
    bd.setScope(BeanDefinition.SCOPE_SINGLETON);
    bd.setLazyInit(true);
});

context.refresh();