Springの自動設定とスターター依存

📢 この記事は gemini-2.5-flash によって翻訳されました

設定

yaml、yml、propertiesファイルで設定できるよ。Javaのシステムプロパティやコマンドライン引数でも設定できるんだ。

優先順位は、コマンドライン引数 > Javaシステムプロパティ > properties > yml > yaml だよ。

コマンドラインで使うには、まずMavenのパッケージングコマンドを実行してから、コマンドラインで動かすんだ。

1
2
3
4
5
java -jar path_to_jar.jar
# Java システムプロパティ、ポートを例に
java -Dserver.port=9000 -jar path_to_jar.jar
# コマンドライン引数、ポートを例に
java -jar path_to_jar.jar --server.port=9000

SpringBootプロジェクトをパッケージングするときは、spring-boot-maven-pluginプラグインを組み込む必要があるよ (公式のひな形からプロジェクトを作ると、このプラグインは自動で追加されるんだ)。

Beanの管理

Beanの取得

デフォルトのシングルトンで遅延ロードされないbeanの場合、Springプロジェクトが起動するときに、beanは全部作られてIOCコンテナに入れられるんだ (@Lazyアノテーションを付けると、初めて使われるときにインスタンス化されるよ)。

もしこれらのbeanを能動的に取得したいなら、以下の方法でできるよ。

  1. 名前で取得
1
Object getBean(String name)
  1. 型で取得
1
<T> T getBean(Class<T> requiredType)
  1. 名前と型で取得 (型変換)
1
<T> T getBean(String name, Class<T> requiredType)

このメソッドを使うためには、まずIOCコンテナオブジェクトを取得する必要があるんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Autowired
private ApplicationContext applicationContext; //IOCコンテナオブジェクト

public void testGetBean(){
    // beanの名前で取得する
    DeptController beanl = (DeptController) applicationContext.getBean("deptController");
    
    // beanの型で取得する
    DeptController bean2 = applicationContext.getBean(DeptController.class);
    
    // beanの名前と型で取得する
    DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
}

Beanのスコープ

Springは5種類のスコープをサポートしているよ。後ろの3つはWeb環境でのみ有効になるんだ。

スコープ説明
singletonコンテナ内に同じ名前のbeanのインスタンスは1つだけ (シングルトン)
prototypeこのbeanを使うたびに新しいインスタンスが作成される (非シングルトン)
request各リクエストの範囲内で新しいインスタンスが作成される
session各セッションの範囲内で新しいインスタンスが作成される
application各アプリケーションの範囲内で新しいインスタンスが作成される

@Scopeアノテーションを使ってスコープを設定するよ。

1
2
3
4
5
6
// 非シングルトンに設定
@Scope("prototype")
@RestController
public class xxxController{
    
}

実際の開発では、ほとんどのBeanがシングルトンだよ。つまり、ほとんどのBeanでscope属性を設定する必要はないんだ。

サードパーティのBean

もし管理したいbeanオブジェクトがサードパーティ製 (自分で作ったものではない) の場合、@Componentやその派生アノテーションでbeanを宣言することはできないんだ。その場合は@Beanアノテーションを使う必要があるよ。例えばXMLファイルを解析するdom4jみたいにね。

1
2
3
4
5
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>

依存関係はこんな感じ。

1
2
3
4
5
6
7
@SpringBootApplication
public class xxxApplication{
    @Bean // メソッドの戻り値をIOCコンテナに管理させ、IOCコンテナのbeanオブジェクトにする
    public SAXReader saxReader(){
        return new SAXReader;
    }
}

だけど、もしサードパーティのbeanオブジェクトを管理するなら、これらのbeanをまとめて分類して設定することをおすすめするよ。@Configurationアノテーションを使って設定クラスを宣言できるんだ。

1
2
3
4
5
6
7
@Configuration
public class CommonConfig {
    @Bean
    public SAXReader saxReader(){
        return new SAXReader;
    }
}

@Beanアノテーションのnameまたはvalue属性でbeanの名前を宣言できるよ。指定しない場合、デフォルトのbean名はメソッド名になるんだ。もしサードパーティのbeanが他のbeanオブジェクトに依存している場合、bean定義メソッドの仮引数に直接設定するだけでいいよ。コンテナが型に基づいて自動的にインジェクションしてくれるんだ。

1
2
3
4
5
6
7
@Configuration
public class CommonConfig {
    @Bean
    public SAXReader saxReader(XxService xxService){
        return new SAXReader;
    }
}

スターター依存

開発中にSpringを直接使う場合、関連する依存関係を組み込んで、バージョンの整合性を保つ必要があるんだ。だけどSpringBootを使うなら、スターター依存を組み込むだけでいいよ。原理はMavenの推移的依存だよ。他の依存関係は、Mavenの依存関係の推移によって自動的に組み込まれるんだ。

自動設定

SpringBootの自動設定とは、Springコンテナが起動した後に、一部の設定クラスやbeanオブジェクトが自動的にIOCコンテナに保存されることだよ。僕たちが手動で宣言する必要はないんだ。これによって開発が簡略化され、面倒な設定作業が省けるんだ。

設定クラスの@Configurationは、その基盤が@Componentなんだ。そしてコンテナ内の一つのbeanオブジェクトでもあるんだよ。

依存関係を組み込んだ後、どのようにして依存するjarファイルに定義されている設定クラスやbeanをSpringIOCコンテナに読み込むんだろうね。

@ComponentScan

@ComponentScanを使うと、スキャンしたいパッケージを指定できるよ。例えば、依存関係でcom.exampleパッケージがインポートされた場合みたいにね。

1
2
@SpringBootApplication
@ComponentScan({"net.yexca","com.example"})

だけど、たくさんのサードパーティの依存関係を組み込む必要がある場合、上のようにたくさんのパッケージを設定しなきゃいけないんだ。広範囲のスキャンはパフォーマンスも低いんだよね。

@Import

普通のクラス、設定クラス、それからImportSelectorインターフェースの実装クラスをインポートできるよ。

普通のクラス

1
2
@Import(TokenParser.class) //普通のクラスをインポート
@SpringBootApplication

設定クラス

設定クラスの内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
public class HeaderConfig {
    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}

起動クラス

1
2
@Import(HeaderConfig.class) //設定クラスをインポート
@SpringBootApplication

ImportSelectorインターフェースの実装クラス

ImportSelectorインターフェースの実装クラスの内容

1
2
3
4
5
6
public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //戻り値は文字列の配列 (配列には完全修飾名のクラスがカプセル化されている)
        return new String[]{"com.example.HeaderConfig"};
    }
}

起動クラス

1
2
@Import(MyImportSelector.class) //ImportSelectorインターフェース実装クラスをインポート
@SpringBootApplication

@EnableXxxxx

上の@Importは、まずサードパーティの依存関係にどんな設定クラスやbeanがあるかを知っている必要があるんだ。だけどサードパーティの依存関係は@EnableXxxxxアノテーションを提供できるよ。@Importアノテーションをカプセル化して、よく使うbeanを提供できるんだ。使うときは@EnableXxxxxアノテーションを付けるだけでいいんだよ。

上の@Importの設定クラスのように、@EnableHeaderConfigアノテーションをカプセル化してみようか。

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//どのbeanオブジェクトや設定クラスをインポートするか指定する
public @interface EnableHeaderConfig { 
}

そして、起動クラスに@EnableHeaderConfigアノテーションを付けるだけで、対応するbeanをインポートできるんだ。

1
2
@EnableHeaderConfig  //サードパーティの依存が提供するEnableで始まるアノテーションを使用
@SpringBootApplication

この方法もSpringBootが採用しているやり方だよ。

SpringBootの自動設定

@SpringBootApplicationアノテーションの中には@EnableAutoConfigurationがあるんだ。その@Import({AutoConfigurationImportSelector.class})は、ImportSelectorインターフェースの実装クラスAutoConfigurationImportSelector.classをインポートしているんだ。

この実装クラスでは、selectImports()メソッドがオーバーライドされているよ。

1
2
3
4
5
6
7
8
9
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 自動設定される設定クラス情報の集合を取得する
        AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

getAutoConfigurationEntry()メソッドを呼び出して、自動設定される設定クラス情報の集合を取得しているんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 設定ファイルに設定されているすべての自動設定クラスの集合を取得する
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}

その中でも、getCandidateConfigurations(annotationMetadata, attributes)メソッドは、設定ファイルに設定されているすべての自動設定クラスの集合を取得するんだ。

1
2
3
4
5
6
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

これは、META-INF/spring.factoriesとMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsファイルにある設定クラスの集合を取得しているのがわかるね。

これらの2つのファイルは、通常、組み込まれたスターター依存の中にあるんだ。

つまり、SpringBootプログラムが起動するときに、設定ファイルに定義されている設定クラスが読み込まれるんだ。そして、これらの設定クラス情報 (クラスの完全修飾名) がString型の配列にカプセル化されて、最終的に@Importアノテーションを通じて、これらの設定クラスがすべてSpringのIOCコンテナに読み込まれ、IOCコンテナに管理されるんだ。

@Conditional

でもファイルの中に設定クラスがたくさんあるけど、すべてのbeanがIOCコンテナに登録されるのかな?そうじゃないんだ、@Conditionalアノテーションを使うと、beanオブジェクトを条件に応じてインジェクションできるんだよ。

@Conditionalは親アノテーションで、たくさんの子アノテーションがあるんだ。

@ConditionalOnClass

環境に対応するバイトコードファイルがある場合にのみ、beanをIOCコンテナに登録するんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class HeaderConfig {

    @Bean
    //環境に指定されたこのクラスが存在する場合にのみ、このbeanがIOCコンテナに追加される
    @ConditionalOnClass(name="io.jsonwebtoken.Jwts")
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
    
}

上記のbeanは、JWTトークンの依存関係を組み込んだ場合にのみIOCコンテナにインジェクションされるんだ。

1
2
3
4
5
6
<!--JWTトークン-->
<dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.1</version>
</dependency>

テスト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testHeaderParser(){
        System.out.println(applicationContext.getBean(HeaderParser.class));
    }

}

@ConditionalOnMissingBean

環境に対応するbean (型または名前) がない場合にのみ、beanをIOCコンテナに登録するんだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class HeaderConfig {

    @Bean
    //この型のbeanが存在しない場合にのみ、このbeanがIOCコンテナに追加される
    @ConditionalOnMissingBean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
    
}

上のように、IOCにHeaderConfig型のbeanがない場合にのみ作成されるんだ。

アノテーションで他のbeanの名前を指定することもできるよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class HeaderConfig {

    @Bean
    //指定された名前のbeanが存在しない場合にのみ、このbeanがIOCコンテナに追加される
    @ConditionalOnMissingBean(name="deptController2")
    public HeaderParser headerParser(){
        return new HeaderParser();
    }
    
}

上の例では、deptController2という名前のbeanオブジェクトが存在しない場合にのみ、HeaderConfigオブジェクトが作成されてIOCに登録されるんだ。

型を指定することもできるよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class HeaderConfig {

    @Bean
    //指定された型のbeanが存在しない場合にのみ、beanがIOCコンテナに追加される
    @ConditionalOnMissingBean(HeaderConfig.class)
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

}

上の例を実行してこのbeanを呼び出すと、NoSuchBeanDefinitionException例外が発生するんだ。@Configurationの中には@Componentがあるから、自動的にHeaderConfigのbeanが作成されちゃうんだ。だからHeaderParserのbeanは作成されないんだよ。

@ConditionalOnProperty

設定ファイルに対応するプロパティと値がある場合にのみ、beanをIOCコンテナに登録するんだ。

設定ファイル

1
name: header

設定クラス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Configuration
public class HeaderConfig {

    @Bean
    //設定ファイルに指定されたプロパティ名と値が存在する場合にのみ、beanがIOCコンテナに追加される
    @ConditionalOnProperty(name ="name",havingValue = "header")
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

}

カスタムスターター依存

例えば、自分だけのAlibaba Cloud OSSのスターター依存を作ってみようか。

まず命名についてだけど、SpringBoot公式のスターターはspring-boot-starter-xxxという名前にするんだ。サードパーティが提供するものはxxx-spring-boot-starterという名前にするよ。

次にモジュールだけど、規範に従って2つのモジュールを定義する必要があるんだ。

  1. starterモジュールは、依存関係の管理をするんだ。プログラム開発に必要な依存関係をすべてstarter依存の中に定義するよ。
  2. autoconfigureモジュールは、自動設定に使うんだ。

この2つのモジュールを定義したら、他のプロジェクトはスターター依存を組み込むだけでいいんだ。自動設定モジュールが依存関係を推移的に渡してくれるからね。

モジュールのpomファイル

aliyun-oss-spring-boot-starterモジュール

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
    </properties>
    
    <dependencies>
        <!-- autoconfigureモジュールを組み込む -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>

aliyun-oss-spring-boot-autoconfigureモジュール

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <!-- webスターター依存を組み込む -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- Alibaba Cloud OSS -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.1</version>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!-- no more than 2.3.3-->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.3</version>
        </dependency>
    </dependencies>

</project>

自動設定

AliOSSAutoConfigurationクラス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
//AliOSSPropertiesクラスをインポートし、SpringIOCに管理させる
@EnableConfigurationProperties(AliOSSProperties.class)
public class AliOSSAutoConfiguration {

    //AliOSSUtilsオブジェクトを作成し、SpringIOCコンテナに渡す
    @Bean
    public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){
        AliOSSUtils aliOSSUtils = new AliOSSUtils();
        aliOSSUtils.setAliOSSProperties(aliOSSProperties);
        return aliOSSUtils;
    }
}

AliOSSPropertiesクラス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* Alibaba Cloud OSS関連設定 */
@Data
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
    //リージョン
    private String endpoint;
    //アクセスキーID
    private String accessKeyId ;
    //アクセスキーシークレット
    private String accessKeySecret ;
    //バケット名
    private String bucketName;
}

AliOSSUtilsクラス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Data 
public class AliOSSUtils {
    private AliOSSProperties aliOSSProperties;

    /**
     * 画像をOSSにアップロードする処理を実装
     */
    public String upload(MultipartFile multipartFile) throws IOException {
        // アップロードされたファイルの入力ストリームを取得
        InputStream inputStream = multipartFile.getInputStream();

        // ファイルの上書きを防ぐ
        String originalFilename = multipartFile.getOriginalFilename();
        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        //ファイルをOSSにアップロード
        OSS ossClient = new OSSClientBuilder().build(aliOSSProperties.getEndpoint(),
                aliOSSProperties.getAccessKeyId(), aliOSSProperties.getAccessKeySecret());
        ossClient.putObject(aliOSSProperties.getBucketName(), fileName, inputStream);

        //ファイルアクセスパス
        String url =aliOSSProperties.getEndpoint().split("//")[0] + "//" + aliOSSProperties.getBucketName() + "." + aliOSSProperties.getEndpoint().split("//")[1] + "/" + fileName;

        // ossClientをクローズ
        ossClient.shutdown();
        return url;// OSSにアップロードされたパスを返す
    }
}

自動設定ファイルMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsを新規作成するよ。

1
com.aliyun.oss.AliOSSAutoConfiguration

使い方

依存関係の組み込み

1
2
3
4
5
6
<!-- Alibaba Cloud OSSスターター依存を組み込む -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

上記の例のAlibaba Cloud OSS関連設定は、設定ファイルから読み込む必要があるんだ。

1
2
3
4
5
6
7
# Alibaba Cloud OSSパラメータ設定
aliyun:
  oss:
    endpoint: your_oss_region
    accessKeyId: your_key_id
    accessKeySecret: your_key_secret
    bucketName: your_bucker_name

テスト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
public class UploadController {

    @Autowired
    private AliOSSUtils aliOSSUtils;

    @PostMapping("/upload")
    public String upload(MultipartFile image) throws Exception {
        //ファイルをAlibaba Cloud OSSにアップロード
        String url = aliOSSUtils.upload(image);
        return url;
    }

}

Visits Since 2025-02-28

Hugo で構築されています。 | テーマ StackJimmy によって設計されています。