Spring Boot

Javaエンタープライズアプリケーション開発の事実上の標準フレームワーク。自動設定と豊富なエコシステムにより高速開発を実現。

JavaフレームワークBackendWebSpringEnterpriseMicroservices

GitHub概要

spring-projects/spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.

スター78,081
ウォッチ3,343
フォーク41,398
作成日:2012年10月19日
言語:Java
ライセンス:Apache License 2.0

トピックス

frameworkjavaspringspring-boot

スター履歴

spring-projects/spring-boot Star History
データ取得日時: 2025/8/13 01:43

フレームワーク

Spring Boot

概要

Spring Bootは、Javaエコシステムにおける最も人気の高いWebアプリケーションフレームワークです。

詳細

Spring Boot(スプリング ブート)は、Spring Frameworkをベースにした、迅速かつ簡単にスタンドアロンで本番級のSpringベースアプリケーションを作成するためのフレームワークです。2014年にリリースされて以来、Javaのデファクトスタンダードとして確固たる地位を築いています。自動設定(Auto-configuration)機能により、従来のSpringで必要だった複雑なXML設定を大幅に削減し、最小限のコードでWebアプリケーションを構築できます。組み込まれたTomcatやJettyサーバーにより、単一のJARファイルとして実行可能なアプリケーションを作成でき、デプロイメントが簡素化されています。依存性注入(DI)とアスペクト指向プログラミング(AOP)により、保守性と拡張性の高いアプリケーション開発が可能です。現在ではマイクロサービスアーキテクチャやクラウドネイティブアプリケーション開発の分野で広く使用されており、Spring Cloud、Spring Securityなどの豊富なエコシステムと組み合わせて企業システムの構築に活用されています。

メリット・デメリット

メリット

  • 自動設定: 複雑な設定を自動化し、開発効率を大幅に向上
  • 組み込みサーバー: TomcatやJettyが内蔵されており、簡単にWebアプリケーションを実行
  • スターター依存関係: 必要な機能をまとめてパッケージ化し、依存関係管理を簡素化
  • 本番対応機能: Actuatorによる監視・管理機能で運用を支援
  • 豊富なエコシステム: Spring Cloud、Spring Securityなど多彩な関連技術
  • マイクロサービス対応: 軽量で高速起動のためマイクロサービスに最適
  • 企業サポート: VMware Tanzuによる長期サポートと継続的開発
  • 高い市場価値: 企業システムでの需要が高く、平均単価67万円(2024年調査)

デメリット

  • 学習コスト: Springエコシステム全体の理解が必要で習得に時間が必要
  • メモリ使用量: フレームワークのオーバーヘッドでメモリ消費が多い傾向
  • 起動時間: DIコンテナ初期化やAOP処理により起動時間が長くなることがある
  • 過剰な機能: シンプルなアプリケーションには機能が過多になる場合がある
  • ブラックボックス化: 自動設定により内部動作が見えにくくなる問題
  • Java依存: Java言語とJVMに依存するため他言語では使用不可

主要リンク

書き方の例

Hello World(基本的なWebアプリケーション)

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

    @GetMapping("/")
    public String hello() {
        return "Hello, Spring Boot!";
    }

    @GetMapping("/api/status")
    public String status() {
        return "Application is running!";
    }

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

REST API開発

package com.example.demo.controller;

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.ArrayList;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private List<User> users = new ArrayList<>();

    @GetMapping
    public List<User> getAllUsers() {
        return users;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = users.stream()
            .filter(u -> u.getId().equals(id))
            .findFirst()
            .orElse(null);
        
        if (user != null) {
            return ResponseEntity.ok(user);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        user.setId((long) (users.size() + 1));
        users.add(user);
        return ResponseEntity.ok(user);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
        User user = users.stream()
            .filter(u -> u.getId().equals(id))
            .findFirst()
            .orElse(null);
        
        if (user != null) {
            user.setName(updatedUser.getName());
            user.setEmail(updatedUser.getEmail());
            return ResponseEntity.ok(user);
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        users.removeIf(u -> u.getId().equals(id));
        return ResponseEntity.noContent().build();
    }
}

// User エンティティクラス
class User {
    private Long id;
    private String name;
    private String email;

    // コンストラクタ、ゲッター、セッター
    public User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

データベース操作(Spring Data JPA)

package com.example.demo.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column
    private String description;
    
    @Column(nullable = false)
    private Double price;

    // コンストラクタ、ゲッター、セッター
    public Product() {}

    public Product(String name, String description, Double price) {
        this.name = name;
        this.description = description;
        this.price = price;
    }

    // ゲッター・セッターは省略
}

// Repository インターフェース
package com.example.demo.repository;

import com.example.demo.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface ProductRepository extends JpaRepository<Product, Long> {
    
    List<Product> findByNameContaining(String name);
    
    List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
    
    @Query("SELECT p FROM Product p WHERE p.price > :price")
    List<Product> findExpensiveProducts(@Param("price") Double price);
}

// Service クラス
package com.example.demo.service;

import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }

    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }

    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }

    public List<Product> searchProducts(String keyword) {
        return productRepository.findByNameContaining(keyword);
    }
}

依存性注入とBean管理

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class AppConfig {

    @Bean
    @Primary
    public EmailService emailService() {
        return new EmailServiceImpl();
    }

    @Bean
    public SmsService smsService() {
        return new SmsServiceImpl();
    }
}

// サービスインターフェース
package com.example.demo.service;

public interface NotificationService {
    void sendNotification(String message, String recipient);
}

// Email実装
package com.example.demo.service.impl;

import com.example.demo.service.NotificationService;
import org.springframework.stereotype.Service;

@Service
public class EmailServiceImpl implements NotificationService {
    
    @Override
    public void sendNotification(String message, String recipient) {
        System.out.println("Email sent to " + recipient + ": " + message);
    }
}

// SMS実装
@Service
public class SmsServiceImpl implements NotificationService {
    
    @Override
    public void sendNotification(String message, String recipient) {
        System.out.println("SMS sent to " + recipient + ": " + message);
    }
}

// 依存性注入の使用例
package com.example.demo.controller;

import com.example.demo.service.NotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/notifications")
public class NotificationController {

    @Autowired
    private NotificationService emailService; // @Primaryが注入される

    @Autowired
    @Qualifier("smsServiceImpl")
    private NotificationService smsService;

    @PostMapping("/email")
    public String sendEmail(@RequestParam String message, @RequestParam String recipient) {
        emailService.sendNotification(message, recipient);
        return "Email notification sent";
    }

    @PostMapping("/sms")
    public String sendSms(@RequestParam String message, @RequestParam String recipient) {
        smsService.sendNotification(message, recipient);
        return "SMS notification sent";
    }
}

アノテーションベース設定

package com.example.demo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "app")
public class ApplicationProperties {
    
    private String name;
    private String version;
    private Database database = new Database();
    
    // ゲッター・セッター
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getVersion() { return version; }
    public void setVersion(String version) { this.version = version; }
    
    public Database getDatabase() { return database; }
    public void setDatabase(Database database) { this.database = database; }
    
    public static class Database {
        private String host;
        private int port;
        private String name;
        
        // ゲッター・セッター
        public String getHost() { return host; }
        public void setHost(String host) { this.host = host; }
        
        public int getPort() { return port; }
        public void setPort(int port) { this.port = port; }
        
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }
}

// application.properties
/*
app.name=My Spring Boot App
app.version=1.0.0
app.database.host=localhost
app.database.port=5432
app.database.name=mydb
*/

// 設定値を使用するコンポーネント
package com.example.demo.component;

import com.example.demo.config.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;

@Component
public class AppInfoComponent {

    @Autowired
    private ApplicationProperties appProperties;

    @PostConstruct
    public void printAppInfo() {
        System.out.println("Application Name: " + appProperties.getName());
        System.out.println("Version: " + appProperties.getVersion());
        System.out.println("Database: " + appProperties.getDatabase().getHost() + 
                          ":" + appProperties.getDatabase().getPort());
    }
}

テスト(JUnitとMockito)

package com.example.demo.controller;

import com.example.demo.service.ProductService;
import com.example.demo.entity.Product;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.Optional;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(ProductController.class)
public class ProductControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ProductService productService;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void testGetAllProducts() throws Exception {
        // Given
        Product product1 = new Product("Product 1", "Description 1", 100.0);
        Product product2 = new Product("Product 2", "Description 2", 200.0);
        when(productService.getAllProducts()).thenReturn(Arrays.asList(product1, product2));

        // When & Then
        mockMvc.perform(get("/api/products"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$.length()").value(2))
                .andExpect(jsonPath("$[0].name").value("Product 1"))
                .andExpect(jsonPath("$[1].name").value("Product 2"));

        verify(productService).getAllProducts();
    }

    @Test
    public void testGetProductById() throws Exception {
        // Given
        Product product = new Product("Test Product", "Test Description", 150.0);
        when(productService.getProductById(1L)).thenReturn(Optional.of(product));

        // When & Then
        mockMvc.perform(get("/api/products/1"))
                .andExpect(status().isOk())
                .andExpected(jsonPath("$.name").value("Test Product"))
                .andExpected(jsonPath("$.price").value(150.0));

        verify(productService).getProductById(1L);
    }

    @Test
    public void testCreateProduct() throws Exception {
        // Given
        Product newProduct = new Product("New Product", "New Description", 300.0);
        Product savedProduct = new Product("New Product", "New Description", 300.0);
        savedProduct.setId(1L);
        
        when(productService.saveProduct(any(Product.class))).thenReturn(savedProduct);

        // When & Then
        mockMvc.perform(post("/api/products")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(newProduct)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.name").value("New Product"));

        verify(productService).saveProduct(any(Product.class));
    }
}

// 統合テスト例
package com.example.demo;

import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
public class ProductIntegrationTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ProductRepository productRepository;

    @Test
    public void testCreateAndRetrieveProduct() {
        // Given
        Product product = new Product("Integration Test Product", "Test Description", 99.99);

        // When - Create product
        ResponseEntity<Product> createResponse = restTemplate.postForEntity(
            "http://localhost:" + port + "/api/products", 
            product, 
            Product.class
        );

        // Then - Verify creation
        assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(createResponse.getBody().getName()).isEqualTo("Integration Test Product");

        // When - Retrieve product
        Long productId = createResponse.getBody().getId();
        ResponseEntity<Product> getResponse = restTemplate.getForEntity(
            "http://localhost:" + port + "/api/products/" + productId,
            Product.class
        );

        // Then - Verify retrieval
        assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(getResponse.getBody().getName()).isEqualTo("Integration Test Product");
    }
}