← Назад к вопросам
Как создать сервис, возвращающий представление в виде 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 приложений.