Springフルアノテーション開発ガイド

アノテーション駆動の意義と落とし穴

アノテーション駆動とは、XML の冗長な記述を Java のメタデータで置き換える手法である。設定ファイルが消滅し、IDE の補完やリファクタリング恩恵を受けられる一方、次のようなトレードオフもある。

  • 単純な定義ほどコードが膨張しがち
  • 外部ライブラリの Bean を登録する際、ソース修正ができないため専用のファクトリメソッドが必要

頻出アノテーション早見表

用途アノテーション対象主な属性
スキャン起点@ComponentScanクラスbasePackages, includeFilters, excludeFilters
Bean 登録@Component 及び派生3種クラスvalue (Bean ID)
ライフサイクル@PostConstruct / @PreDestroyメソッド
スコープ@Scopeクラスvalue (singleton, prototype…)
値注入@Valueフィールド / メソッド値、${}、#{}
参照注入@Autowired + @Qualifierフィールド / メソッドrequired, name
優先順位@Primaryクラス
外部リソース@PropertySourceクラスvalue, ignoreResourceNotFound
JavaConfig@Configurationクラス
遅延初期化@Lazyクラス / メソッド
依存関係@DependsOnクラス / メソッドvalue (依存 Bean ID)

Bean 定義例

@Repository("accountRepo")
@Scope("prototype")
public class AccountRepositoryImpl implements AccountRepository {

    @PostConstruct
    public void warmCache() {
        // キャッシュ初期化
    }

    @PreDestroy
    public void release() {
        // リソース解放
    }
}

プロパティ値の注入

@Component
@PropertySource("classpath:app.properties")
public class DataSourceProps {

    @Value("${db.url}")
    private String url;

    @Value("${db.user}")
    private String user;

    @Value("${db.password}")
    private String password;
}

JavaConfig による完全脱 XML

@Configuration
@ComponentScan("com.example.app")
@Import({DataSourceConfig.class, MyBatisConfig.class})
public class AppRootConfig {

    @Bean
    @Lazy
    public MailService mailService() {
        return new MailServiceImpl();
    }
}

コンテキスト起動は次の一行で完結する。

ApplicationContext ctx =
        new AnnotationConfigApplicationContext(AppRootConfig.class);

外部ライブラリ Bean の取り込み

ライブラリ内部に手を加えられない場合、@Bean メソッドでラップする。

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl("jdbc:h2:mem:test");
        cfg.setUsername("sa");
       .cfg.setPassword("");
        return new HikariDataSource(cfg);
    }
}

条件付き登録とフィルタ

@Conditional 実装例

public class OnWindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata md) {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }
}

@Configuration
public class OsSpecificConfig {

    @Bean
    @Conditional(OnWindowsCondition.class)
    public FilePathResolver windowsResolver() {
        return new WindowsFilePathResolver();
    }
}

カスタム TypeFilter

public class ExcludeImplFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader mr, MetadataReaderFactory mrf) {
        String className = mr.getClassMetadata().getClassName();
        return className.endsWith("Impl");
    }
}

@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = ExcludeImplFilter.class)
)

MyBatis との統合

@Configuration
public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource ds) {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(ds);
        fb.setTypeAliasesPackage("com.example.domain");
        return fb;
    }

    @Bean
    public MapperScannerConfigurer mapperScanner() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.example.mapper");
        return msc;
    }
}

JUnit 連携

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppRootConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService service;

    @Test
    public void transferShouldCommit() {
        service.transfer("A001", "B002", BigDecimal.valueOf(100));
    }
}

Bean 初期化フックポイント

  • BeanFactoryPostProcessor:全 Bean 生成前にファクトリをカスタマイズ
  • BeanPostProcessor:各 Bean の初期化前後に横断処理
  • InitializingBean:特定 Bean の初期化ロジック
  • FactoryBean:複雑な生成手順をカプセル化
@Component
public class EncryptDataSourceFactory implements FactoryBean<DataSource> {

    @Override
    public DataSource getObject() {
        // 暗号化されたパスワードを復号して DataSource を構築
        return new EncryptedHikariDataSource();
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

Bean ロード順序制御

@Configuration
@DependsOn("dataSource")
public class CacheConfig {

    @Bean
    @Order(1)
    public CacheManager l1Cache() {
        return new ConcurrentMapCacheManager();
    }

    @Bean
    @Order(2)
    public CacheManager l2Cache() {
        return new RedisCacheManager();
    }
}

タグ: Spring Annotation JavaConfig MyBatis IoC

6月28日 18:26 投稿