Als PHP-Entwickler mit Symfony-Erfahrung steht man Java und Spring Boot oft mit gemischten Gefühlen gegenüber. Der Ruf: kompliziert, langsam im Setup, viel Boilerplate. Die Realität mit Spring Boot ist anders. Die Konzepte sind vertraut — es ist im Wesentlichen Symfony, nur in Java.
Konzept-Mapping: Symfony ↔ Spring Boot
| Symfony | Spring Boot | Beschreibung |
|---|---|---|
services.yaml |
@Component, @Service |
Service-Registrierung |
#[Route] |
@RequestMapping, @GetMapping |
Routing |
AbstractController |
@RestController |
Controller-Basis |
| Doctrine ORM | Spring Data JPA / Hibernate | ORM |
| Repository-Interface | JpaRepository<Entity, ID> |
Datenbankzugriff |
EventSubscriber |
@EventListener |
Events |
.env |
application.properties |
Konfiguration |
| Composer | Maven / Gradle | Dependency Management |
bin/console |
./mvnw, ./gradlew |
CLI |
Projekt-Setup mit Spring Initializr
Das Äquivalent zu composer create-project symfony/skeleton ist start.spring.io. Dort wählt man:
- Project: Maven oder Gradle
- Language: Java
- Spring Boot Version: 3.x (aktuell)
- Dependencies: Spring Web, Spring Data JPA, H2 Database (für Entwicklung)
Das generierte Projekt hat folgende Struktur:
my-api/
├── src/main/java/com/example/myapi/
│ ├── MyApiApplication.java # Einstiegspunkt (wie public/index.php)
│ ├── controller/
│ ├── service/
│ ├── repository/
│ └── entity/
├── src/main/resources/
│ └── application.properties # Konfiguration (wie .env)
├── src/test/java/ # Tests
└── pom.xml # Composer-äquivalent
Dependency Injection — wie Symfony
Symfony und Spring Boot nutzen beide Dependency Injection als Kernkonzept. In Symfony schreibt man:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Service\ProductService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ProductController extends AbstractController
{
public function __construct(
private readonly ProductService $productService
) {}
}
In Spring Boot ist es fast identisch:
package com.example.myapi.controller;
import com.example.myapi.service.ProductService;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
private final ProductService productService;
// Constructor Injection — analog zu Symfony
public ProductController(ProductService productService) {
this.productService = productService;
}
}
Spring erkennt @Service-, @Component- und @Repository-Annotierte Klassen automatisch und injiziert sie. Genau wie Symfony Service-Autowiring.
REST-Controller
Symfony:
<?php
declare(strict_types=1);
namespace App\Controller\Api;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
#[Route('/api/products', name: 'api_products_')]
class ProductController extends AbstractController
{
#[Route('', name: 'list', methods: ['GET'])]
public function list(): JsonResponse
{
return $this->json(['products' => []]);
}
#[Route('/{id}', name: 'show', methods: ['GET'])]
public function show(int $id): JsonResponse
{
return $this->json(['id' => $id]);
}
}
Spring Boot:
package com.example.myapi.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<Product> list() {
return productService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Product> show(@PathVariable Long id) {
return productService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> create(@RequestBody @Valid ProductDto dto) {
Product created = productService.create(dto);
return ResponseEntity.status(201).body(created);
}
}
Entity und Repository (analog Doctrine)
In Symfony definiert man eine Doctrine-Entity:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $name;
}
In Spring Boot (JPA/Hibernate):
package com.example.myapi.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 255)
private String name;
// Getter und Setter
public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Repository
Symfony mit Doctrine:
class ProductRepository extends ServiceEntityRepository
{
public function findByCategory(string $category): array;
}
Spring Boot mit JPA:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
// Automatisch generierte Methode aufgrund des Namens!
List<Product> findByCategory(String category);
}
Spring Data JPA generiert die Query automatisch aus dem Methodennamen — ähnlich wie Doctrine QueryBuilder, aber noch magischer.
application.properties — das .env-Äquivalent
# Datenbankverbindung (analog DATABASE_URL)
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=mypassword
# JPA
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
# Server-Port
server.port=8080
# Logging
logging.level.com.example=DEBUG
Tests — JUnit statt PHPUnit
package com.example.myapi.controller;
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.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void listProductsReturnsEmptyArray() throws Exception {
mockMvc.perform(get("/api/products"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$").isArray());
}
}
Fazit
Spring Boot ist für Symfony-Entwickler überraschend zugänglich. Dependency Injection, Annotationen statt XML, ein ORM mit Repository-Pattern und eine konventionsbasierte Konfiguration — das sind vertraute Konzepte. Die Hauptunterschiede liegen in der statischen Typisierung (was tatsächlich hilft), dem Build-Prozess (Maven/Gradle statt Composer) und dem Deployment (JAR-Datei statt PHP-Dateien auf einem Webserver).
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.