I want to get data form repository to controller and send them into view so, I want to send data from controller to view using Hashmap(K,V). But value of Hashmap(V) is List<>, I got error like that…
Stack trace
Cannot render error page for request [/meetzen/] and exception [An error happened during template parsing (template: «class path resource [templates/post.jsp]»)] as the response has already been committed. As a result, the response may have the wrong status code.
Query
@Query(nativeQuery = true, value="SELECT * FROM request_master WHERE sender_id = ? AND status = ?")
List<Request> getAcceptRequestFrnd(Integer Sender_id, String Status);
@Query(nativeQuery = true, value="SELECT rsm.receiver_id,pm.profile, rgm.username, upm.post_id, upm.post, upm.date FROM registration_master AS rgm INNER JOIN profile_master AS pm ON rgm.u_id = pm.user_id INNER JOIN uploadpost_master AS upm ON rgm.u_id = upm.user_id INNER JOIN request_master AS rsm ON rgm.u_id = rsm.receiver_id WHERE rsm.receiver_id = ? ORDER BY upm.date DESC LIMIT 1")
List<ProfileJoin> getPostWithAccount(Integer Receiver_id);
Controller
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Model mdl, HttpSession session, Request request) {
Integer sessionId = Integer.parseInt(session.getAttribute("uid").toString());
Map<Integer, List<ProfileJoin>> userUploadPost = new HashMap<>();
List<Request> getUser =
this.service.getAcceptRequestFrnd(sessionId, "Accept");
for(Request getUserForPost : getUser)
{
List<ProfileJoin> getUserWithPost = this.service.getPostWithAccount(getUserForPost.getReceiver_id());
userUploadPost.put(getUserForPost.getReceiver_id(), getUserWithPost);
}
mdl.addAttribute("userWithPost", userUploadPost);
mdl.addAttribute("getUser", getUser);
return "index";
}
Thymeleaf:
<div th:each="getData: ${getUserWithPost.value}">
<span th:text="${getData.username}"></span>
<span th:text="${getData.post}"></span>
</div>
I want to read data from services to controler and display on the UI on Spring boot, but it gives the below error
ERROR 20984 — [nio-8080-exec-2] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/countries] as the response has already been committed. As a result, the response may have the wrong status code.
The database has also the below data
mysql> select * from country;
+----+-------------+------+---------------+---------------+-------------+
| id | capital | code | continent | description | nationality |
+----+-------------+------+---------------+---------------+-------------+
| 1 | Washington | 01 | North America | United States | American |
| 2 | Addis Ababa | 01 | Africa | Ethiopia | Ethiopian |
+----+-------------+------+---------------+---------------+-------------+
Model
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Country {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String code;
private String capital;
private String description;
private String nationality;
private String continent;
@OneToMany(mappedBy="country")
private List<State> states;
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.kindsonthegenius.FleetApp.models.Country;
@Repository
public interface CountryRepository extends JpaRepository<Country, Integer> {
}
Service
@Service
public class CountryService {
@Autowired
private CountryRepository countryRepository;
public List<Country> getCountries(){
return countryRepository.findAll();
}
}
Controller
@Controller
public class CountryController {
@Autowired
private CountryService countryService;
@GetMapping("/countries")
public String goCountry(Model model) {
List<Country> countryList = countryService.getCountries();
model.addAttribute("countries", countryList);
return "country";
}
}
UI
<table class="table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">Description</th>
<th scope="col">Capital</th>
</tr>
</thead>
<tbody>
<tr th:each="country : ${countries}">
<td th:text="${country.id}"></td>
<td th:text="${country.description}"></td>
<td th:text="${country.capital}"></td>
</tr>
</tbody>
</table>
Please help me to fix this error?
Comments
wilkinsona
changed the title
ErrorPageFilter cause the request to non-existent resource returned 200 instead of 404
ErrorPageFilter causes a forwarded request that sends an error to actually send a 200 OK response instead
Jan 29, 2018
wilkinsona
added a commit
to wilkinsona/spring-boot
that referenced
this issue
Apr 9, 2018
Previously, the error page filter used sendError to set the response
status when handling an exception and before forwarding the request
to the error controller. Following the fix for spring-projectsgh-11814, this meant
that the error controller was unable to write its response and the
containers default error page was returned instead.
This commit updates the error page filter to use setStatus rather than
sendError. This ensures that the response has the correct status code
while allowing the error controller to write its body. Tests have
been added to the Tomcat deployment test suite to verify that the
error page filter behaves as intended when dealing with a sent error
and an exception for requests accepting HTML, JSON, or anything.
Closes spring-projectsgh-12787
в настоящее время используюSpring Boot Появится если есть ошибкаWhitelabel Error Page Пейдж, этоSpring Boot Страница, которая обрабатывает ошибки по умолчанию, создается в жестко запрограммированной форме. Мы можем заменить тональный сигнал, использовать нашу собственную страницу ошибки и украсить ее.
В Интернете есть много похожих статей, но многие из них являются неполными, а некоторые ошибочными (например, настройкиserver.error.whitelabel.enabled=falseдобавить вerror.html, Это предпосылка), сегодня мы анализируем это с точки зрения исходного кода и даем решение.
Первый выборSpring Boot Если возникает ошибка, например:500、503、404И т.п.org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController Мы также можем переписать его (если вы переписываете его, вы будете удивлены, обнаружив, что следующий контент является избыточным, ха-ха) для реализации вашей собственной логики;
Как вы можете видеть на картинкеSpring Boot Я думаю, очень хорошо, как синхронная, так и асинхронная обработка, здесь мы обсуждаем только проблему синхронизации, потому что будет происходить только синхронизацияWhitelabel Error Page
Мы находим код, который анализирует представление
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
resolveErrorView Родительский классAbstractErrorControllersдостигать
ErrorViewResolver Реализация по умолчаниюDefaultErrorViewResolver
ИзresolveВ методе мы можем разделить несколько шагов
1. Найдите доступного поставщика шаблонизатора
Если ошибка
500Тогда на этот разerrorViewName="error/500"
-
Найти логику
TemplateAvailabilityProvidersкатегорияgetProviderМетод, этот метод будет кэшировать существующий поставщик шаблонизатора, в частностиfindProviderЧтобы найти, еслиfindProviderЯ не нашел его и зарегистрировал бы по умолчаниюNoTemplateAvailabilityProvider
-
Внимание
this.providersэтоSpring BootОн будет добавлен при запуске контейнера. Будет ли добавлен поставщик механизма шаблонов, определяется наличием имени класса механизма шаблонов.Spring BootЗарегистрируем несколько поставщиков шаблонных движков;
-
Загрузить провайдер шаблонного движка
loadFactoriesЗагрузка естьspring-boot-autoconfigure-2.xxxx.jarНижеMETA-INF/spring.factoriesЭтот метод может получить все классы реализации родительского класса.
-
Вот
ThymeleafTemplateAvailabilityProviderНапример
Из рисунка видно, что вы ищетеclasspath:/templates/error/500.html
Так что если оно появится500Мы можем ошибатьсяsrc/main/resources/Новый каталогtemplates/errorСоздать новыйthymeleafиз500.htmlШаблон достаточно;404сопереживание -
Отсюда нетрудно понять, что если вы хотите успешно найти неправильный шаблон, вы должны соответствовать следующим условиям
- Путь к классу существует в шаблонизаторе
jarclasspathСуществует текущий шаблон ошибки под(Пример: 500.html)
2. Если на шаге 1providerЕсли вы найдете его, вам не нужно переходить к следующему шагу, вы можете напрямую вернуться к просмотру
3. Если на шаге 1 не выполнены условияproviderТогда сдайresolveResourceРучка, давайте посмотримresolveResourceлогика
Этот шаг в основном предназначен для непосредственного получения статикиhtml;Общее500с участием404Вы можете напрямую использовать статическийHTMLРесурс может отображать дружественную подсказку, поэтому здесь вы можете получить статический файл ресурса напрямую
Местоположение поиска по умолчанию следующее
Может быть изменено по конфигурации
Если ошибка500, Результат поиска показан на рисунке, нам нужно только создать файл ресурса для доступа<staticLocations>/error/5xx.html
4. Что если вышеуказанные три шага не выполнены? ИзBasicErrorController Вы можете увидеть, что будетerrorВид по умолчанию, вы найдетеclasspath:/templates/error.html, Логика обработки такая же, как указано выше, если она все еще не может быть найденаSpring Boot По умолчанию будетErrorMvcAutoConfigurationсреднийStaticView Оказывать
Предпосылка
server.error.whitelabel.enabled=true
private static class StaticView implements View {
private static final Log logger = LogFactory.getLog(StaticView.class);
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
StringBuilder builder = new StringBuilder();
Date timestamp = (Date) model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=")
.append(htmlEscape(model.get("error"))).append(", status=")
.append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>")
.append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
private String htmlEscape(Object input) {
return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message += " and exception [" + model.get("message") + "]";
}
message += " as the response has already been committed.";
message += " As a result, the response may have the wrong status code.";
return message;
}
@Override
public String getContentType() {
return "text/html";
}
}
Это Whitelabel Error Page источник
В итоге, есть следующие решения
Если в проекте используется шаблонизатор, например
thymeleaffreemarker
в это времяserver.error.whitelabel.enabledВыключить и включить
- в
classpathНовый шаблон/templates/error/5xx.html/templates/error/4xx.html - в
classpathСоздать новый статический ресурс/<staticlocation>/error/5xx.html/<staticlocation>/error/4xx.html - в
classpathНапрямую создайте новый шаблон/templates/error.html
Если в проекте нет шаблонного движка
- Настроить
server.error.whitelabel.enabled=trueИспользуйте встроенныйviewRender, появитьсяWhitelabel Error Page - Если
server.error.whitelabel.enabled=falseТам не будет никакой информации
Если у вас есть какие-либо вопросы или ошибки, вы можете оставить сообщение
Код не прост, пожалуйста, укажите источник
Обратите внимание на публичную учетную запись WX, вы не можете пропустить это
common.ftl
<#macro page>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Twit</title>
<link rel="stylesheet" href="/static/style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<#-- если не подключен интернет-->
<link rel="stylesheet" href="static/bootstrap/css/bootstrap.min.css"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
</head>
<body>
<#include "navbar.ftl">
<#--Данная секция будет принимать некоторый вложенный код.-->
<div class="container mt-5">
<#nested>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></script>
</body>
</html>
</#macro>
main.ftl
<#import "parts/common.ftl" as c>
<@c.page>
<#-- форма поиска-->
<div class="form-row">
<div class="form-group col-md-6">
<form method="get" action="/main" class="form-inline">
<input type="text" name="filter" class="form-control"
value="${filter?ifExists}"
placeholder="Search by tag">
<#--ml-2 - оnступ слева на 2-->
<button type="submit" class="btn btn-primary ml-2">Search</button>
</form>
</div>
</div>
<#--Скрытие или показ формы-->
<a class="btn btn-primary" data-toggle="collapse" href="#collapseform">
Add new message
</a>
<#--id="collapseExample" - по данному id будет доступна анимация (сркрытиепоказ)-->
<#--ввод сообщения-->
<#-- (textError??) - если в поле есть ошибка, тогда получим true и полученное значение
нужно привести к строке
?string - приводим к строке значение, которое содержится в (textError??)
'is-invalid' - если ошибка есть, тогда добавляем в данный атрибут class, еще один
селектор, по которому bootstap будет знать, как оформить сообщение
'' - селектор не добавляется, если ошибки нет
#value = ...if message??-- Если пользователь ввел сообщение и у нас есть ошибка,
чтобы сообщение не потерялось, мы его сохраняем в поле-->
<div id="collapseform" class="collapse">
<div class="form-group">
<form method="post" enctype="multipart/form-data" >
<div class="form-group col-3 mt-3">
<input type="text"
class="form-control ${(textError??)?string('is-invalid', '')}"
name="text"
value="<#if message??>
${message.text}
</#if>
"
placeholder="Введите сообщение"/>
<#-- Валидация поля - ввод сообщения-->
<#if textError??>
<div class="invalid-feedback">
${textError}
</div>
</#if>
</div>
<div class="form-group col-3">
<input type="text" class="form-control" name="tag" placeholder="Tag">
</div>
<div class="form-group col-3">
<div class="custom-file">
<input type="file" name="file" class="custom-file-input" id="customFile">
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
</div>
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<button type="submit" class="btn btn-primary">Добавить</button>
</form>
</div>
</div>
<div>Список сообщений</div>
<div class="card-columns">
<#-- mu-3 - отступ слева и снизу-->
<div class="card my-3">
<#list messages as message>
<!--?? - указывает, что нужно привести данное выражени к булевому типу
- Те есть провекра указывает на то, если получим значение поля filename у объекта message
занчит это true иначе false-->
<#if message.filename??>
<img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">
</#if>
<#--m-2 - оступ со всех 4-х сторон-->
<div class="m-2">
<span>${message.text}</span>
<i>${message.tag}</i>
</div>
<div class="card-footer text-muted">
${message.authorName}
</div>
</div>
<#else >
No message
</#list>
</div>
</div>
</@c.page>
вот в это месте появляется ошибка
<div>Список сообщений</div>
<div class="card-columns">
<#-- mu-3 - отступ слева и снизу-->
<div class="card my-3">
<#list messages as message>
<!--?? - указывает, что нужно привести данное выражени к булевому типу
- Те есть провекра указывает на то, если получим значение поля filename у объекта message
занчит это true иначе false-->
<#if message.filename??>
<img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">
</#if>
<#list messages as message>
вот контроллер
@PostMapping("/main")
public String add(
@AuthenticationPrincipal User user,
@Valid Message message,
BindingResult bindingResult,
Model model,
@RequestParam("file") MultipartFile file
) {
message.setAuthor(user);
if (bindingResult.hasErrors()) {
Map<String, String> errorsMap = getListValidationErr(bindingResult);
model.mergeAttributes(errorsMap);
model.addAttribute("message", message);
} else {
prepareFileForSave(file, message);
saveImgFileAndMessageToBase(message, model);
}
return "main";
}
При попытке обработки ошибки в контроллере, когда добваляю сообщение, появляется вот такая ошибка
2019-10-21 18:06:55.312 ERROR 14524 — [nio-8080-exec-5] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page
for request [/main] and exception [The following has evaluated to null
or missing:
==> messages [in template «main.ftl» at line 80, column 20]—- Tip: If the failing expression is known to legally refer to something that’s sometimes null or missing, either specify a default
value like myOptionalVar!myDefault, or use <#if
myOptionalVar??>when-present<#else>when-missing. (These only
cover the last step of the expression; to cover the whole expression,use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
—- FTL stack trace («~» means nesting-related):
— Failed at: #list-#else-container [in template «main.ftl» at line 80, column 13] ~ Reached through: #nested [in template
«parts/common.ftl» in macro «page» at line 27, column 9] ~ Reached
through: @c.page [in template «main.ftl» at line 3, column 1]
—-] as the response has already been committed. As a result, the response may have the wrong status code.
Если кто знает, подскажите в чем дело.
Решение
<#if messages??> <#--Проверка - есть ли список сообщений или нет, если список пустой, тогда ничего не рисуется-->
<#list messages as message>
<#if message.filename??>
<img style="width: 150px" src="/static/img/${message.filename}" class="card-img-top">
</#if>
<#--m-2 - оступ со всех 4-х сторон-->
<div class="m-2">
<span>${message.text}</span>
<i>${message.tag}</i>
</div>
<div class="card-footer text-muted">
${message.authorName}
</div>
</div>
<#else >
No message
</#list>
</#if>
нужно было добавить проверку, приходит ли на страницу переменная, которая содержить спискок элементов, если переменная null, тогда таблица для списка не рисуется.
Problem:
Product.java
package com.example.springdemo.model.entity;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table
@Data
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Product parent;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Product> children = new ArrayList<>();
}
Table of Product:
Error Logs:
java.lang.IllegalStateException: Cannot call sendError() after the response has been committed ... java.lang.StackOverflowError: null ... ERROR 9816 --- [nio-8080-exec-5] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/rest/product/list] and exception [Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) ...
Solution 1 – @JsonIdentityInfo – Example Project:
Project Structure
ProductController.java
package com.example.springdemo.controller;
import com.example.springdemo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/rest/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(path = "/list", method = RequestMethod.GET)
public ResponseEntity getProducts() {
return ResponseEntity.ok(productService.getProducts());
}
}
ProductRepository.java
package com.example.springdemo.dao;
import com.example.springdemo.model.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("select distinct p from Product p " +
"left join fetch p.parent pp " +
"left join fetch p.children ch " +
"where pp is null")
List<Product> findAll();
}
ProductService.java
package com.example.springdemo.service;
import com.example.springdemo.dao.ProductRepository;
import com.example.springdemo.model.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public List<Product> getProducts() {
return productRepository.findAll();
}
}
Product.java
package com.example.springdemo.model.entity;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Data;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table
@Data
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Product parent;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Product> children = new ArrayList<>();
}
SpringdemoApplication.java
package com.example.springdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringdemoApplication.class, args);
}
}
application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/demo spring.datasource.username=postgres spring.datasource.password=postgres spring.jpa.hibernate.ddl-auto=update logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE spring.jackson.serialization.indent-output=true spring.jackson.default-property-inclusion=non_empty
pom.xml
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<vaadin.version>14.0.10</vaadin.version>
<spring.version>5.2.0</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Solution 2 – @JsonIgnore
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
@JsonIgnore
// or: @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@ManyToOne(fetch = FetchType.LAZY)
private Product parent;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Product> children = new ArrayList<>();
}
Output:
У меня есть панель навигации с LOGIN , PROFILE и LOGOUT . Моя цель — удалить ЛОГИН и показать только ПРОФИЛЬ и ВЫХОД. после того, как пользователь вошел в систему. У меня есть контроллер, проверяющий аутентифицированных пользователей:
@Controller
public class LoginController {
@GetMapping("/login")
private String loginRender(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || authentication instanceof AnonymousAuthenticationToken){
return "login";
}else {
return "redirect:/";
}
}
}
Шаблон Thymeleaf выглядит так:
<ul class="logout-ul top-links-container">
<li class="top-links-item text-center" sec:authorize="!isAuthenticated()"><a href="/login"> <i class="icon-user4" style="color: #e35f5f"></i>Login</a></li>
<li class="top-links-item text-center" sec:authorize="isAuthenticated()"><a href="/user/profile"><i class="icon-user4" style="color: #e35f5f"></i>Profile</a></li>
<li class="text-center logout" sec:authorize="isAuthenticated()"><form th:action="@{/logout}" th:method="post"><button title="LOGOUT" class="btn" type="submit"><i class="icon-off"></i></button></form></li>
</ul>
Конфигурация:
http
.authorizeRequests()
.antMatchers("/login", "/", "register",
"/custom-transfers/**", "/about-us", "/information/**", "/support/**").permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/admin").hasRole(UserRoleEnum.ADMIN.name())
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
.passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)
.defaultSuccessUrl("/user/profile")
//TODO validation page
.failureForwardUrl("/login")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
ПРОБЛЕМА Все это работает, когда пользователь впервые входит в систему. При выходе из системы и повторном входе в систему Thymleaf показывает исключение TemplateInputException.
Исключение:
ERROR 26304 --- [nio-8080-exec-9] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/login] and exception [An error happened during template parsing (template: "class path resource [templates/login.html]")] as the response has already been committed. As a result, the response may have the wrong status code.
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/login.html]")
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) ~[thymeleaf-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:366) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:190) ~[thymeleaf-spring5-3.0.12.RELEASE.jar:3.0.12.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1400) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.10.jar:5.3.10]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.10.jar:5.3.10]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.10.jar:5.3.10]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.53.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.53.jar:9.0.53]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:121) ~[spring-security-web-5.5.2.jar:5.5.2]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115) ~[spring-security-web-5.5.2.jar:5.5.2]
Я попытался разместить sec: authorize = «! IsAuthenticated ()» снаружи в <div>, попытался передать boolean в шаблон с RedirectAttribute и Model, но я все еще не могу решить проблему. Это работает, когда я добавляю: <li class="top-links-item" sec:authorize="!isAuthenticated()">TEST</li> поэтому я предполагаю, что это потому, что я использую sec:authorize для страницы входа, которая управляется Spring Security. Может быть какая-то связь?
ОБНОВЛЕНИЕ . Я обнаружил, что после выхода из системы cookie уничтожается и не создает новый для второго входа в систему. Также проблема заключается только в форме выхода из системы в html, когда я добавляю sec:authorize=isAuthenticated(). Поэтому проблема связана с выходом из системы, а не со страницей входа. Почему это могло произойти?
Это происходит только тогда, когда я использую isAuthenticated() в Thyemleaf.
Что я делаю неправильно? Буду очень признателен за вашу помощь. Спасибо.
1 ответ
Лучший ответ
Я думаю, что здесь происходит следующее:
Ваша форма выхода управляется Thymeleaf, что означает, что под капотом Thymeleaf попытается создать скрытое поле с токеном CSRF внутри. Обычно это работает, и для этого требуется существующий или новый сеанс.
Проблема в том, что Thymeleaf разработан для минимизации задержек, создавая выходные данные шаблона по мере обработки вашего шаблона. Таким образом, когда страница становится длиннее по размеру (как в вашем случае) к тому времени, когда движок достигает вашего выхода из системы, <form> помечает, что часть вывода МОЖЕТ БЫТЬ передана клиенту и сервер начинает считать ваш ответ подтвержденным . Как только ответ будет помечен как совершенный, Thymeleaf не сможет получить доступ / создать сеанс. Я предполагаю, что это архитектурное ограничение.
Так что я не уверен, что именно вам здесь посоветовать. Возможно, попробуйте вывести кнопку выхода раньше в ваших шаблонах или, при желании, попытаться инициализировать токен CSRF. Этот параметр в HttpSecurity, возможно, сделает это:
.and()
.csrf()
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
1
Lachezar Balev
12 Ноя 2021 в 16:37
















