Мне нужно регистрировать информацию трассировки с помощью 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 в метод обработчика исключений.
Я что-то здесь упускаю? Есть ли другой подход к решению этой проблемы?
Любая помощь или предложения будут очень признательны!
Компонент работает, если его вставить в параметр метода, и при каждом http-запросе создается новый экземпляр, поэтому я предположил, что он правильно вставлен в среду Spring. В любом случае попробую проверить.
Он работает неправильно, он создает новый экземпляр, если вы используете его в аргументе метода, он не будет использовать экземпляр области запроса. Кроме того, @BEan для Tracer должен находиться в @Configuration, а не в самом контроллере.
Да, ты прав. Я никогда не подозревал, что не смогу внедрить компонент изнутри контроллера. Он работает так, как должен, если объявлен в конфигурации и подключен автоматически. Кроме того, автоматическое подключение не имеет особого смысла, но я могу понять, как это работает.
Это имеет смысл (и вам следовало прочитать документацию, а не делать предположения, поскольку там объясняется, как работают прокси с ограниченной областью действия). То же самое работает и в CDI (обычное внедрение Java).
Извините, если я прозвучал оскорбительно, но это не было моим намерением. Спасибо за вашу помощь.




ОБНОВЛЕНИЕ: Самый правильный способ это
Может быть, и «грязно», но, по крайней мере, это работает:
@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-компоненты с областью действия хранятся как атрибут потока, поэтому любой способ доступа к ним «под капотом» будет одинаковым.
Слишком сложный. Просто добавьте Tracer в качестве поля в компоненты, которые вы хотите использовать, это вставит прокси, который будет делегировать доступ к экземпляру, доступному в запросе. Это также описано в справочном руководстве Spring. Это решение добавляет слишком много сложности и ненужного кода.
Другой подход. Добавьте аннотации @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
}
}
Это не другой подход, это единственно правильный подход.
Вы действительно прочитали исключение? Вы не можете автоматически связывать запросы bean-компонентов с ограниченной областью действия (или вообще с любой областью действия) в подобные методы. Это будет работать только для методов
@Beanв файлах@Configuration. Просто подключите его как поле в контроллере или классе, которому оно нужно.