Как получить доступ к bean-компоненту с областью запроса в обработчике исключений Spring?

Мне нужно регистрировать информацию трассировки с помощью bean-компонента Tracer, который ограничен областью запроса, как в моем методе REST, так и в обработчике исключений. Компонент Tracer используется в методе REST для регистрации информации трассировки в начале обработки запроса, и я хочу использовать одно и то же сообщение трассировки в обработчике исключений при возникновении исключения.

Однако я столкнулся с трудностями при доступе к bean-компоненту Tracer области запроса в моем обработчике исключений. Я пробовал обе аннотации контроллера rest @ExceptionHhandler в контроллере REST и @ControllerAdvice, но ни одна из них не поддерживала доступ к bean-компоненту области действия.

Вот моя установка:

@RestController
public class MyController {

    @Bean
    @RequestScope
    Tracer tracer(){
        return new Tracer();
    }
    @GetMapping(path = "list")
    public String list(Tracer trace){
        return  trace.getCorrelationId();
    }

    @GetMapping(path = "throw")
    public String throwEx(Tracer trace,HttpServletRequest req){
         throw new RuntimeException("Ops");
    }


    @ExceptionHandler({Exception.class})
    ResponseEntity<String> defaultExceptionHandler(Tracer tracer, HttpServletRequest req, Exception ex) {
    
        return ResponseEntity
                .internalServerError()
                .body("ERROR: " + tracer.getCorrelationId());

    }
}

Трассировщик (для полноты):

@Getter
public class Tracer {
    private final String requestId;
    private final String correlationId;


    public Tracer() {
        requestId = null;
        correlationId = UUID.randomUUID().toString();
    }
    public Tracer(String requestId, String correlationId) {
        this.requestId = requestId;
        this.correlationId = correlationId;
    }
}

Вот тест:

@SpringBootTest
@AutoConfigureMockMvc
class MyControllerTest {
    @Autowired
    protected MockMvc mockMvc;

    @Test
    void testExceptionHandler() throws Exception {
  
        mockMvc
                .perform(MockMvcRequestBuilders.get("/throw")
                )
                .andDo(MockMvcResultHandlers.print())
                .andReturn()
                .getResponse()
                .getContentAsString();            
    }
}

И вот проблема:

Не удалось разрешить параметр [0] в org.springframework.http.ResponseEntity<java.lang.String> com.baeldung.scopes.MyController.defaultExceptionHandler(com.baeldung.scopes.Tracer,jakarta.servlet.http.HttpServletRequest,java. lang.Exception): Нет подходящего преобразователя

Кажется, я не могу напрямую внедрить компонент Tracer в метод обработчика исключений.

Я что-то здесь упускаю? Есть ли другой подход к решению этой проблемы?

Любая помощь или предложения будут очень признательны!

Вы действительно прочитали исключение? Вы не можете автоматически связывать запросы bean-компонентов с ограниченной областью действия (или вообще с любой областью действия) в подобные методы. Это будет работать только для методов @Bean в файлах @Configuration. Просто подключите его как поле в контроллере или классе, которому оно нужно.

M. Deinum 25.06.2024 15:34

Компонент работает, если его вставить в параметр метода, и при каждом http-запросе создается новый экземпляр, поэтому я предположил, что он правильно вставлен в среду Spring. В любом случае попробую проверить.

MiguelSlv 25.06.2024 16:35

Он работает неправильно, он создает новый экземпляр, если вы используете его в аргументе метода, он не будет использовать экземпляр области запроса. Кроме того, @BEan для Tracer должен находиться в @Configuration, а не в самом контроллере.

M. Deinum 25.06.2024 16:36

Да, ты прав. Я никогда не подозревал, что не смогу внедрить компонент изнутри контроллера. Он работает так, как должен, если объявлен в конфигурации и подключен автоматически. Кроме того, автоматическое подключение не имеет особого смысла, но я могу понять, как это работает.

MiguelSlv 25.06.2024 23:19

Это имеет смысл (и вам следовало прочитать документацию, а не делать предположения, поскольку там объясняется, как работают прокси с ограниченной областью действия). То же самое работает и в CDI (обычное внедрение Java).

M. Deinum 26.06.2024 08:34

Извините, если я прозвучал оскорбительно, но это не было моим намерением. Спасибо за вашу помощь.

MiguelSlv 26.06.2024 10:23
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

ОБНОВЛЕНИЕ: Самый правильный способ это

Может быть, и «грязно», но, по крайней мере, это работает:

@Getter
public class YourException extends RuntimeException {

  private final Tracer tracer;
  
  //Your constructors
}

@GetMapping(path = "throw")
public String throwEx(Tracer trace,HttpServletRequest req){
  throw new YourException("Ops", tracer);  -- pass your tracer to the constructor
}


@ExceptionHandler({YourException.class})
ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, YourException ex) {
    
  return ResponseEntity
        .internalServerError()
        .body("ERROR: " + ex.getTracer().getCorrelationId());

}

Другое решение - ThreadLocal (spring web-mvc использует модель потока на запрос)

public class TracerInfo {
  private static final ThreadLocal<Tracer> TRACER = new ThreadLocal<>();

  public static void set(Tracer tracer) {
    TRACER.set(tracer);
  }

  public static Tracer get() {
    return TRACER.get();
  }
}

@GetMapping(path = "throw")
public String throwEx(Tracer trace,HttpServletRequest req) {
  TracerInfo.set(tracer);
  throw new YourException("Ops");
}

@ExceptionHandler({Exception.class})
ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, Exception ex) {
    
  return ResponseEntity
        .internalServerError()
        .body("ERROR: " + TracerInfo.get().getCorrelationId());

}

Ps: исправьте ошибку компиляции, если таковая имеется, так как сейчас у меня нет IDE

Спасибо. Судя по тому, что я читал об этом, bean-компоненты с областью действия хранятся как атрибут потока, поэтому любой способ доступа к ним «под капотом» будет одинаковым.

MiguelSlv 25.06.2024 14:56

Слишком сложный. Просто добавьте Tracer в качестве поля в компоненты, которые вы хотите использовать, это вставит прокси, который будет делегировать доступ к экземпляру, доступному в запросе. Это также описано в справочном руководстве Spring. Это решение добавляет слишком много сложности и ненужного кода.

M. Deinum 25.06.2024 16:38

Другой подход. Добавьте аннотации @RequestScope и @Component к Tracer:

@Getter
@Component
@RequestScope
public class Tracer {
    private final String requestId;
    private final String correlationId;

    public Tracer() {
        requestId = null;
        correlationId = UUID.randomUUID().toString();
    }

    public Tracer(String requestId, String correlationId) {
        this.requestId = requestId;
        this.correlationId = correlationId;
    }
}

Вставьте его в свой контроллер и удалите параметры метода trace.

@RestController
@Log4j2
public class MyController {

    private Tracer tracer; // use instance variable

    public MyController(Tracer tracer) {
        this.tracer = tracer;
    }

    @GetMapping(path = "list")
    public String list() {
        return tracer.getCorrelationId();
    }

    @GetMapping(path = "throw")
    public String throwEx(HttpServletRequest req) {
        log.info(tracer.getCorrelationId());
        throw new RuntimeException("Ops");
    }

    @ExceptionHandler({Exception.class})
    ResponseEntity<String> defaultExceptionHandler(HttpServletRequest req, Exception ex) {

        return ResponseEntity
                .internalServerError()
                .body("ERROR: " + tracer.getCorrelationId()); // use the instance variable

    }
}

Это не другой подход, это единственно правильный подход.

M. Deinum 25.06.2024 16:37

Другие вопросы по теме