In Spring Webflux what is the prefered way of Exception Handling?
#RestControllerAdvice comes from Spring MVC whereas DefaultErrorAttributes comes from Spring Webflux.
However, in Spring Webflux someone could use #RestControllerAdvice. What would be the advantages/disadvantages?
#RestControllerAdvice
#RestControllerAdvice
public class ControllerAdvice
{
#ExceptionHandler(Throwable.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Mono<Map<String, Object>> exceptions(Throwable e)
{
return Mono.just(Map.of("message", "bad"));
}
}
Extend DefaultErrorAttributes
#Component
public class ErrorAttributes extends DefaultErrorAttributes
{
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace)
{
var ex = getError(request);
var attributes = new LinkedHashMap<String, Object>();
attributes.put("status", HttpStatus.BAD_REQUEST.value());
attributes.put("message", "bad");
return attributes;
}
}
I want to stay in the reactive world, so I tend more towards DefaultErrorAttributes (which plays well with DefaultErrorWebExceptionHandler in Webflux). However, in #RestControllerAdvice I could also use Mono.just(...).
It is same. Like WebMvc.
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(AnyException.class)
public Mono<EntityResponse<YourModel>> example(AnyException exception) {
return EntityResponse.fromObject(new YourModel()).status(HttpStatus.NOT_FOUND).build();
}
}
In Spring Webflux in case functional routes declaration, you can also implement your own ExceptionHandler instead of DefaultErrorWebExceptionHandler:
class SystemErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
return super.handle(exchange, throwable)
// debug, process
.contextWrite(...);
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
// for all routs
return route(all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> error = getErrorAttributes(request, ErrorAttributeOptions.of());
Throwable t = this.getError(request);
// map exception on response
return ServerResponse.status(status).body(...);
}
}
Then use your implementation of AbstractErrorWebExceptionHandler in the spring configuration with #AutoConfigureBefore(WebFluxAutoConfiguration.class)
Related
i wanted to ask if there is a way to enable Springs #ExceptionHandler capabilities with Joinfaces/Primefaces.
For now i'm able to handle global #ControllerAdvice beans, but not if the #ExceptionHandler is inside the #Controller class.
Are there any suggestions on how to solve this topic?
Here is the code i wrote so far
#Slf4j
public class SpringJsfExceptionHandler extends ExceptionHandlerWrapper {
public SpringJsfExceptionHandler(ExceptionHandler wrapped) {
super(wrapped);
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> queue = getUnhandledExceptionQueuedEvents().iterator();
while (queue.hasNext()) {
ExceptionQueuedEvent item = queue.next();
ExceptionQueuedEventContext exceptionQueuedEventContext = (ExceptionQueuedEventContext) item.getSource();
try {
Throwable throwable = exceptionQueuedEventContext.getException();
FacesContext context = FacesContext.getCurrentInstance();
handleException(context, (Exception) throwable);
} finally {
queue.remove();
}
}
}
private void handleException(FacesContext context, Exception throwable) {
WebApplicationContext applicationContext = resolveApplicationContext(context);
Collection<HandlerExceptionResolver> exceptionResolvers = listExceptionHandlerResolvers(applicationContext);
for (HandlerExceptionResolver resolver : exceptionResolvers) {
resolver.resolveException(request(context), response(context), null, throwable);
}
}
private Collection<HandlerExceptionResolver> listExceptionHandlerResolvers(WebApplicationContext context) {
return context.getBeansOfType(HandlerExceptionResolver.class).values();
}
private HttpServletRequest request(FacesContext context) {
return (HttpServletRequest) context.getExternalContext().getRequest();
}
private HttpServletResponse response(FacesContext context) {
return (HttpServletResponse) context.getExternalContext().getResponse();
}
private WebApplicationContext resolveApplicationContext(FacesContext context) {
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
return WebApplicationContextUtils.findWebApplicationContext(request.getServletContext());
}
}
public class SpringJsfExceptionHandlerFactory extends ExceptionHandlerFactory {
public SpringJsfExceptionHandlerFactory() {
}
public SpringJsfExceptionHandlerFactory(ExceptionHandlerFactory wrapped) {
super(wrapped);
}
#Override
public ExceptionHandler getExceptionHandler() {
return new SpringJsfExceptionHandler(getWrapped() != null ? getWrapped().getExceptionHandler() : null);
}
}
This works:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public void handleCalculationException(CalculationException e) {
FacesContext.getCurrentInstance().
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage()));
}
}
This does not work:
#Data
#Controller
#ViewScoped
public class CalculatorController implements Serializable {
#ExceptionHandler
public void handleCalculationException(CalculationException e) {
FacesContext.getCurrentInstance().
addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getMessage()));
}
[...]
Thanks in advance
TLDR: No
#ExceptionHandler is part of Spring MVC.
Spring MVC and JSF are separate web frameworks.
Joinfaces allows you to use JSF in a Spring Application, and you can also use Spring MVC in the same application. Every request will however either be handled by Spring MVC (i.e. the DispatcherServlet) or JSF (i.e. the FacesServlet).
I'm trying to define a common bean to be used for all my application so to add inside a logger and other logic. My idea would be:
public class MyRestTemplate extends RestTemplate{
Then:
#Configuration
public class RestTemplateConfig {
#Bean
public MyRestTemplate myRestTemplate(RestTemplateBuilder builder){
return (MyRestTemplate) builder.build(); //throws classcast exception!
}
}
What am I doing wrong? Is there another way? I want to be sure that people will have to use my customized class.
If you want some customizations in your restTemplate you could define a class that implements RestTemplateCustomizer and add a custom interceptor to it.
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
restTemplate.getInterceptors().add(new CustomClientHttpRequestInterceptor());
}
}
Then you have to define that custom interceptor for all the requests going out of this restTemplate with
public class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// This is where you can do a lot of thing with this request like logging
return execution.execute(request, body);
}
}
And finally, just define a bean for the custom restTemplate you have written
#Bean
public CustomRestTemplateCustomizer customRestTemplateCustomizer() {
return new CustomRestTemplateCustomizer();
}
builder.build() returns a RestTemplate, not a MyRestTemplate.
If you change your code as shown below you would create a bean named myRestTemplate. Spring use the name of the method as bean name if you don't override it in the #Bean annotation.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate myRestTemplate(RestTemplateBuilder builder){
return builder.build(); //throws classcast exception!
}
}
Please also see https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/boot-features-restclient.html
I have a spring-boot application without any controller classes.
How can I write exception handlers for this application. Exception handler classes annotated with #ControllerAdvice doesn't work.
If you are developing web applications, ErrroController is available.
#Controller
#RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
public MyErrorController(final ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return null;
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final WebRequest webRequest = new ServletWebRequest(request);
final Throwable th = errorAttributes.getError(webRequest);
// ...
// see also: BasicErrorController implementation
}
}
So i am trying to add a custom argument resolver to my Spring-Data-Rest project.
I am devolping a multi-tenant application, and need to filter data based on a users tenant-id.
So i wrote a simple annotation and ArgumentResolver to query my tenant repository and inject a tenant Object as Parameter on some needed Methods:
Handler:
#AllArgsConstructor
public class TenantInjector implements HandlerMethodArgumentResolver {
private final TenantStore tenantStore;
private final TenantRepository tenantRepository;
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
if(! methodParameter.hasParameterAnnotation(InjectTenant.class)) {
return false;
}
return true;
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
return tenantRepository.findById(tenantStore.getId()).get();
}
}
This handler queries the tenantRepository to find the current tenant by its Id, which is set when the incoming requests security token is parsed.
To register the handler, i do the following:
#Configuration
public class DispatcherContext implements WebMvcConfigurer {
private final TenantStore tenantStore;
private final TenantRepository tenantRepository;
#Autowired
public DispatcherContext(TenantStore tenantStore, TenantRepository tenantRepository) {
this.tenantStore = tenantStore;
this.tenantRepository= tenantRepository;
}
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TenantInjector(tenantStore, tenantRepository));
}
}
This works nice as long as the corrensponding Controller is annotated with either #Controller or #RestController
As the #RepositoryRestController has an other context, this configuration is ignored. How can I add the same ArgumentResolver to the Spring-Data-Rest configuration?
It might be an option to just switch the annotations, but i would like to rather stick with this approche, as links get generated by spring-data-rest.
Has anyone stumble over this to?
Your issue could be that you registered your custom argument resolver in your WebMvcConfigurer. Spring Data Rest seems to work in a different context, so you have to register your custom argument resolver in your RepositoryRestMvcConfiguration.
#Configuration
public class RepositoryConfiguration extends RepositoryRestMvcConfiguration {
public RepositoryConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService)
{
super(context, conversionService);
}
#Override
protected List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers()
{
List<HandlerMethodArgumentResolver> resolvers =
new ArrayList<>(super.defaultMethodArgumentResolvers());
resolvers.add(new TenantInjector(tenantStore, tenantRepository));
return resolvers;
}
}
Answer inspired by: https://github.com/tkaczmarzyk/specification-arg-resolver/issues/6#issuecomment-111952898
I want to create a custom of HttpMessageConverter using Rest, Json, Spring Boot 1.2.3 and Spring 4, However my custom HTTPMessageConverter its' never called.
I have preformed the following steps :
1: Created a class that extends AbstractHttpMessageConverter
#Component
public class ProductConverter extends AbstractHttpMessageConverter<Employee> {
public ProductConverter() {
super(new MediaType("application", "json", Charset.forName("UTF-8")));
System.out.println("Created ");
}
#Override
protected boolean supports(Class<?> clazz) {
return false;
}
#Override
protected Employee readInternal(Class<? extends Employee> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
InputStream inputStream = inputMessage.getBody();
System.out.println("Test******");
return null;
}
#Override
protected void writeInternal(Employee t,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// TODO Auto-generated method stu
}
}
2: I create a configuration class to register HTTPMessageConverters
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
System.out.println("Configure Message Converters");
converters.add(new ProductConverter());
super.configureMessageConverters(converters);
//super.extendMessageConverters(converters);
}
}
3: The rest class method
#RequestMapping(value="/{categoryId}" ,method=RequestMethod.POST, consumes="application/json")
#PreAuthorize("permitAll")
public ResponseEntity<ProductEntity> saveProduct(#RequestBody Employee employee , #PathVariable Long categoryId) {
logger.log(Level.INFO, "Category Id: {0}" , categoryId);
ResponseEntity<ProductEntity> responseEntity =
new ResponseEntity<ProductEntity>(HttpStatus.OK);
return responseEntity;
}
My Custom HTTPMessageCoverter it's created but is never called ? Is there a configuration or step I'm missing ? any input or advice is appreciated.
After overriding the (AbstractHttpMessageConverter) class methods, I found out there's two annotations for achieving polymorphism #JsonTypeInfo and #JsonSubTypes. For anyone who wants achieve polymorphism can use these two annotations.
I believe you want to configure these message converters using the configureMessageConverters method in a configuration class that extends WebMvcConfigurerAdapter. I've done this myself with a converter for CSV content. I've included that code below. This link shows an example as well. This link may also be helpful. It seems like with Spring configuration it is not always clear on the best place to configure things. :) Let me know if this helps.
#Configuration
public class ApplicationWebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(new CsvMessageConverter());
}
}
You will also need top modify your supports() method to return true for classes supported by the converter. See the Spring doc for AbstractHttpMessageConverter supports method.