JAX-RS Resource mit javax.ws.rs
Die JAX-RS-Ressource greift direkt auf ein Singleton-Repository zu.
Vollständiger Legacy-Modern-Vergleich aller Java-Klassen, Tests, Maven-POMs und relevanten Ressourcen dieses Moduls. Jede Datei ist genau einmal einer fachlich passenden Vergleichsgruppe zugeordnet.
Die JAX-RS-Ressource greift direkt auf ein Singleton-Repository zu.
Der Controller delegiert über Constructor Injection an eine Service Layer.
Die Ähnlichkeit ist eine technische Orientierung auf normalisiertem Quelltext. Sie ersetzt keine fachliche Bewertung.
| Vergleichsgruppe | Legacy-Dateien | Modern-Dateien | Legacy-Zeilen | Modern-Zeilen | Ähnlichkeit |
|---|---|---|---|---|---|
| Build-Konfiguration | 1 | 1 | 46 | 46 | 35% |
| Bootstrap und Laufzeitkonfiguration | 1 | 2 | 21 | 13 | 21% |
| Domänenmodell | 1 | 1 | 29 | 6 | 22% |
| Datenzugriff | 2 | 1 | 43 | 28 | 54% |
| HTTP-Adapter, Service und Test | 1 | 3 | 28 | 78 | 4% |
Das Pair-POM bindet die Legacy- und Modern-Submodule zusammen und gehört deshalb ebenfalls zur vollständigen Codeabdeckung.
JAX-RS- und Spring-Web-Abhängigkeiten werden verglichen.
| Legacy-Dateien | Modern-Dateien | Einordnung |
|---|---|---|
| pom.xml | pom.xml | Direktvergleich Semantische Ähnlichkeit: 35% |
<?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>at.aydin.academy</groupId>
<artifactId>03rest-pair</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>rest-legacy</artifactId>
<packaging>jar</packaging>
<name>REST Legacy (JAX-RS 2.1)</name>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
</project><?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>at.aydin.academy</groupId>
<artifactId>03rest-pair</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>rest-modern</artifactId>
<packaging>jar</packaging>
<name>REST Modern (Spring Boot)</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>Application-Setup wird auf Spring Boot und externe Konfiguration umgestellt.
| Legacy-Dateien | Modern-Dateien | Einordnung |
|---|---|---|
| RestLegacyApplication.java | RestModernApplication.java application.properties | Direktvergleich Semantische Ähnlichkeit: 21% |
package at.aydin.lab.rest.legacy;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import java.net.URI;
public final class RestLegacyApplication {
private RestLegacyApplication() {
}
public static void main(String[] args) throws Exception {
ResourceConfig config = new ResourceConfig(BookResource.class, JacksonFeature.class);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:8081/api/"), config);
System.out.println("Legacy JAX-RS: http://localhost:8081/api/books");
System.out.println("ENTER beendet den Server.");
System.in.read();
server.shutdownNow();
}
}package at.aydin.lab.rest.modern;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RestModernApplication {
public static void main(String[] args) {
SpringApplication.run(RestModernApplication.class, args);
}
}server.port=8082
management.endpoints.web.exposure.include=health,infoRequest-/Response-Modell wird direkt gegenübergestellt.
| Legacy-Dateien | Modern-Dateien | Einordnung |
|---|---|---|
| Book.java | Book.java | Direktvergleich Semantische Ähnlichkeit: 22% |
package at.aydin.lab.rest.legacy;
public class Book {
private long id;
private String title;
public Book() {
}
public Book(long id, String title) {
this.id = id;
this.title = title;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}package at.aydin.lab.rest.modern;
import jakarta.validation.constraints.NotBlank;
public record Book(long id, @NotBlank String title) {
}Singleton-Repository wird als injizierbare Komponente verwendet; Testverantwortung verschiebt sich.
| Legacy-Dateien | Modern-Dateien | Einordnung |
|---|---|---|
| BookRepository.java BookRepositoryTest.java | BookRepository.java | Direktvergleich Semantische Ähnlichkeit: 54% |
package at.aydin.lab.rest.legacy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
// Design Pattern: Repository
// Zweck: Datenhaltung wird von der REST-Ressource getrennt.
public final class BookRepository {
private static final BookRepository INSTANCE = new BookRepository();
private final ConcurrentHashMap<Long, Book> books = new ConcurrentHashMap<>();
private final AtomicLong sequence = new AtomicLong();
private BookRepository() {
save(new Book(0, "Enterprise Java verstehen"));
}
public static BookRepository instance() {
return INSTANCE;
}
public List<Book> findAll() {
return new ArrayList<>(books.values());
}
public Book save(Book book) {
long id = book.getId() == 0 ? sequence.incrementAndGet() : book.getId();
Book stored = new Book(id, book.getTitle());
books.put(id, stored);
return stored;
}
}package at.aydin.lab.rest.legacy;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
class BookRepositoryTest {
@Test
void containsSeedBook() {
assertFalse(BookRepository.instance().findAll().isEmpty());
}
}package at.aydin.lab.rest.modern;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Repository
// Design Pattern: Repository
// Zweck: Der Controller kennt keine Details der Datenhaltung.
public class BookRepository {
private final ConcurrentHashMap<Long, Book> books = new ConcurrentHashMap<>();
private final AtomicLong sequence = new AtomicLong();
public BookRepository() {
save(new Book(0, "Modernes Enterprise Java"));
}
public List<Book> findAll() {
return List.copyOf(books.values());
}
public Book save(Book input) {
long id = input.id() == 0 ? sequence.incrementAndGet() : input.id();
Book stored = new Book(id, input.title());
books.put(id, stored);
return stored;
}
}Eine gekoppelte Resource wird in Controller, Service und Webtest aufgeteilt.
| Legacy-Dateien | Modern-Dateien | Einordnung |
|---|---|---|
| BookResource.java | BookController.java BookService.java BookControllerTest.java | Direktvergleich Semantische Ähnlichkeit: 4% |
package at.aydin.lab.rest.legacy;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;
@Path("books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
private final BookRepository repository = BookRepository.instance();
@GET
public List<Book> all() {
return repository.findAll();
}
@POST
public Response create(Book book) {
Book created = repository.save(book);
return Response.created(URI.create("/api/books/" + created.getId())).entity(created).build();
}
}package at.aydin.lab.rest.modern;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService service;
public BookController(BookService service) {
this.service = service;
}
@GetMapping
public List<Book> all() {
return service.findAll();
}
@PostMapping
public ResponseEntity<Book> create(@Valid @RequestBody Book book, UriComponentsBuilder uriBuilder) {
Book created = service.create(book);
return ResponseEntity.created(uriBuilder.path("/api/books/{id}").build(created.id())).body(created);
}
}package at.aydin.lab.rest.modern;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
// Design Pattern: Service Layer
// Zweck: Fachlogik bleibt unabhängig von HTTP.
public class BookService {
private final BookRepository repository;
public BookService(BookRepository repository) {
this.repository = repository;
}
public List<Book> findAll() {
return repository.findAll();
}
public Book create(Book book) {
return repository.save(book);
}
}package at.aydin.lab.rest.modern;
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.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(BookController.class)
class BookControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
BookService service;
@Test
void returnsBooks() throws Exception {
when(service.findAll()).thenReturn(List.of(new Book(1, "Testbuch")));
mockMvc.perform(get("/api/books")).andExpect(status().isOk()).andExpect(jsonPath("$[0].title").value("Testbuch"));
}
}