← Назад к вопросам

Как создать сервис, возвращающий представление в виде HTML-страницы?

2.0 Middle🔥 151 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как создать сервис, возвращающий представление в виде HTML-страницы?

В Spring приложениях часто нужно возвращать HTML-представления (views) вместо JSON. Это важно для рендеринга web-страниц, отчётов, шаблонов. Расскажу о различных подходах.

1. Spring MVC с Thymeleaf (рекомендуется)

Thymeleaf — это современный шаблонизатор для Java, интегрированный со Spring.

Зависимость (pom.xml)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Контроллер

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
@RequestMapping("/pages")
public class PageController {
    
    @GetMapping("/home")
    public String home(Model model) {
        // Передаём данные в представление
        model.addAttribute("title", "Welcome to Home");
        model.addAttribute("message", "Hello, Spring!");
        
        // Возвращаем имя шаблона (ищет templates/home.html)
        return "home";
    }
    
    @GetMapping("/product/{id}")
    public String productDetails(@PathVariable Long id, Model model) {
        Product product = new Product(id, "Laptop", 999.99);
        model.addAttribute("product", product);
        
        return "product-details";
    }
}

Шаблон Thymeleaf (templates/home.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="${title}">Default Title</title>
</head>
<body>
    <h1 th:text="${title}">Title</h1>
    <p th:text="${message}">Default message</p>
    
    <a href="/pages/product/1">View Product</a>
</body>
</html>

Шаблон для деталей продукта (templates/product-details.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Product Details</title>
</head>
<body>
    <h1 th:text="${product.name}">Product Name</h1>
    <p>ID: <span th:text="${product.id}">0</span></p>
    <p>Price: <span th:text="${product.price}">0.00</span> USD</p>
    
    <a href="/pages/home">Back to Home</a>
</body>
</html>

2. REST контроллер с HTML ответом

Можно возвращать HTML прямо из REST контроллера.

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/api/v1/html")
public class HtmlRestController {
    
    @GetMapping("/report")
    public ResponseEntity<String> getHtmlReport() {
        String html = """<!DOCTYPE html>
        <html>
        <head><title>Report</title></head>
        <body>
            <h1>Sales Report</h1>
            <table border="1">
                <tr><th>Month</th><th>Sales</th></tr>
                <tr><td>January</td><td>$10000</td></tr>
                <tr><td>February</td><td>$15000</td></tr>
            </table>
        </body>
        </html>""";
        
        return ResponseEntity
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .body(html);
    }
}

3. FreeMarker шаблонизатор

FreeMarker — альтернатива Thymeleaf.

Зависимость

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

Контроллер

@Controller
@RequestMapping("/freemarker")
public class FreemarkerController {
    
    @GetMapping("/invoice")
    public String invoice(Model model) {
        model.addAttribute("invoiceId", "INV-2024-001");
        model.addAttribute("items", List.of(
            new Item("Product A", 100),
            new Item("Product B", 200)
        ));
        
        return "invoice"; // templates/invoice.ftl
    }
}

Шаблон FreeMarker (templates/invoice.ftl)

<!DOCTYPE html>
<html>
<head>
    <title>Invoice</title>
</head>
<body>
    <h1>Invoice ${invoiceId}</h1>
    <table border="1">
        <tr><th>Product</th><th>Price</th></tr>
        <#list items as item>
            <tr>
                <td>${item.name}</td>
                <td>${item.price}</td>
            </tr>
        </#list>
    </table>
</body>
</html>

4. Динамическое генерирование HTML с StringBuilder

@RestController
public class DynamicHtmlController {
    
    @GetMapping("/dynamic-list")
    public ResponseEntity<String> getDynamicList(@RequestParam(defaultValue = "5") int count) {
        StringBuilder html = new StringBuilder();
        
        html.append("<!DOCTYPE html>\n");
        html.append("<html>\n");
        html.append("<head><title>Dynamic List</title></head>\n");
        html.append("<body>\n");
        html.append("<h1>Items</h1>\n");
        html.append("<ul>\n");
        
        for (int i = 1; i <= count; i++) {
            html.append("<li>Item #").append(i).append("</li>\n");
        }
        
        html.append("</ul>\n");
        html.append("</body>\n");
        html.append("</html>\n");
        
        return ResponseEntity
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .body(html.toString());
    }
}

5. Возврат HTML файла

@RestController
public class FileController {
    
    @GetMapping("/download-html")
    public ResponseEntity<Resource> downloadHtmlFile() throws IOException {
        Path path = Paths.get("files/report.html");
        Resource resource = new FileSystemResource(path);
        
        return ResponseEntity
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.html\"")
            .body(resource);
    }
}

6. Сервис для генерирования HTML

@Service
public class HtmlGenerationService {
    
    public String generateUserCard(User user) {
        return String.format(
            """<!DOCTYPE html>
            <html>
            <head>
                <title>User Card</title>
                <style>
                    .card { border: 1px solid #ddd; padding: 20px; border-radius: 5px; }
                </style>
            </head>
            <body>
                <div class="card">
                    <h2>%s</h2>
                    <p>Email: %s</p>
                    <p>Phone: %s</p>
                </div>
            </body>
            </html>""",
            user.getName(),
            user.getEmail(),
            user.getPhone()
        );
    }
    
    public String generateTable(List<Map<String, String>> data, List<String> headers) {
        StringBuilder html = new StringBuilder();
        html.append("<table border='1'>");
        
        // Заголовки
        html.append("<tr>");
        for (String header : headers) {
            html.append("<th>").append(header).append("</th>");
        }
        html.append("</tr>");
        
        // Строки
        for (Map<String, String> row : data) {
            html.append("<tr>");
            for (String header : headers) {
                html.append("<td>").append(row.get(header)).append("</td>");
            }
            html.append("</tr>");
        }
        
        html.append("</table>");
        return html.toString();
    }
}

@RestController
public class UserController {
    @Autowired
    private HtmlGenerationService htmlService;
    
    @GetMapping("/user/{id}/html")
    public ResponseEntity<String> getUserHtml(@PathVariable Long id) {
        User user = new User(id, "John Doe", "john@example.com", "+1234567890");
        String html = htmlService.generateUserCard(user);
        
        return ResponseEntity
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .body(html);
    }
}

7. Конфигурация ViewResolver

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    
    @Bean
    public ViewResolver htmlViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

8. Content-Type и Headers

@RestController
public class HeaderController {
    
    @GetMapping("/styled-html")
    public ResponseEntity<String> getStyledHtml() {
        String html = """<!DOCTYPE html>
        <html>
        <head>
            <title>Styled Page</title>
            <style>
                body { font-family: Arial, sans-serif; margin: 20px; }
                .header { background-color: #007bff; color: white; padding: 10px; }
            </style>
        </head>
        <body>
            <div class="header">
                <h1>Welcome</h1>
            </div>
            <p>This is styled HTML</p>
        </body>
        </html>""";
        
        return ResponseEntity
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .header("Cache-Control", "no-cache")
            .body(html);
    }
}

Best Practices

  • Используй Thymeleaf для complex views — он безопаснее и функциональнее
  • Разделяй бизнес-логику и представление — контроллеры возвращают Model, шаблоны рендерят
  • Установи правильный Content-Type — MediaType.TEXT_HTML
  • Избегай конкатенации строк для HTML — используй шаблонизаторы
  • Экранируй пользовательский ввод — избегай XSS уязвимостей
  • Кэшируй шаблоны — настраивай кеширование для production
  • Используй static файлы для CSS/JS — не генерируй их динамически

В production коде я обычно использую Thymeleaf для SSR (Server-Side Rendering), а фронтенд отделяю REST API для SPA приложений.

Как создать сервис, возвращающий представление в виде HTML-страницы? | PrepBro