Sorry for the title, i have no idea to get better title.
I create a simple servlet and implements CDI interceptor.
This is my servlet
#Inject
UserManagement user;
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
user.setUsername(request.getParameter("username"));
response.getWriter().println(user.getMessage());
}
This is UserManagement bean:
#SessionScoped
public class UserManagement implements Serializable{
private String username;
private String message = "";
#UsernameSet
public void setUsername(String username){
this.username = username;
}
}
This is interceptor binding (#UsernameSet):
#InterceptorBinding
#Inherited
#Target({ TYPE, METHOD })
#Retention(RUNTIME)
#Documented
public #interface UsernameSet {
}
This is the interceptor:
#UsernameSet
#Interceptor
public class UserInterceptor implements Serializable {
#Inject
UserManagement user;
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
LOGGER.info("Entering method: " + ic.getMethod().getName());
user.setMessage("Hello " + user.getUsername());
return ic.proceed();
}
}
The interceptor does work but the servlet return UserManagement bean from previous request, although the logger shows immediately.
example
First Request:
localhost?username=smitty
Browser screen:
Hello
Second Request:
localhost?username=werben
Browser screen:
Hello smitty
Third Request:
localhost?username=jensen
Browser screen:
Hello werben
When you create an interceptor, it's invoked either before or after your method is called. With the way you have written yours, it is invoked before your method is called. InvocationContext.proceed will effectively call your method (and other interceptors if others are wired in).
To get the behavior you're looking for, you can switch to this:
#UsernameSet
#Interceptor
public class UserInterceptor implements Serializable {
#Inject
UserManagement user;
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
LOGGER.info("Entering method: " + ic.getMethod().getName());
Object result = ic.proceed();
user.setMessage("Hello " + user.getUsername());
return result;
}
}
Related
I'm trying to add some custom bean validation in a Spring Boot REST controller, extending the ResponseEntityExceptionHandler class with a #ControllerAdvice annotation and overriding the #handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) method. In this method I'm trying to convert the given FieldError via the messageSource into a localized message. Although I'm receiving a NumberFormatException when trying to using message parameters which are be possible via the Hibernate validator.
I'm using the following dependencies:
org.hibernate.validator:hibernate-validator (6.0.11.Final)
org.springframework:spring-web (5.0.8.RELEASE)
org.springframework:spring-webmvc (5.0.8.RELEASE)
All included via org.springframework.boot:spring-boot-starter-web (2.0.4.RELEASE).
Consider using the following REST controller:
#RestController
public class FooController {
#PostMapping(value = "/foo")
public void submitFooRequest(#Validated #RequestBody FooRequest fooRequest) {
// ....
}
}
The FooRequest bean has a custom bean validation annotation & constraint validator:
The bean FooRequest:
#Getter
#Setter
#ValidBarRequest
public class FooRequest {
private String fieldFoo;
private BarRequest barRequest;
}
The bean BarRequest:
#Getter
#Setter
public class BarRequest {
private String fieldBar;
}
The validation annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BarRequestValidator.class)
#Documented
public #interface ValidBarRequest {
String message() default "{org.example.validation.constraints.ValidBarRequest.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String fieldFoo() default "fieldFoo";
String barRequestFieldBar() default "barRequest.fieldBar";
}
The validation constraint validator:
#Log4j2
public class BarRequestValidator implements ConstraintValidator<ValidBarRequest, Object> {
// ....
#Override
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
if (/* some condition */) {
HibernateConstraintValidatorContext hibernateValidatorContext = constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
hibernateValidatorContext.disableDefaultConstraintViolation();
hibernateValidatorContext.addMessageParameter("fieldFoo", "some value...").buildConstraintViolationWithTemplate("{org.example.validation.constraints.ValidBarRequest.message}")
.addPropertyNode("barRequest.fieldBar").addConstraintViolation();
return false;
}
return true;
}
}
However via a #ControllerAdvice annotated bean and using a Spring messageSource a NumberFormatException is thrown on the following message (in messages.properties):
ValidBarRequest.fooRequest.barRequest.fieldBar=must be lower or equal than {fieldFoo}
The #ControllerAdvice bean:
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired private MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<ErrorDetails> errorDetails = new ArrayList<>();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
errorDetails.add(new ErrorDetails(fieldError.getField(), messageSource.getMessage(fieldError, Locale.getDefault())));
}
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
#Getter
#AllArgsConstructor
class ErrorDetails {
private String field;
private String message;
}
}
This causes the following exception: Caused by: java.lang.NumberFormatException: For input string: "fieldFoo"
What am I doing wrong? Also I included the following bean in my #SpringBootApplication:
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setValidationMessageSource(messageSource);
return localValidatorFactory;
}
The populate the variables in the message source MessageFormat.format is and there you must use numbers in curly braces.
ValidBarRequest.fooRequest.barRequest.fieldBar=must be lower or equal than {0}
I am trying to update an audit entry using the response body advice but as far as I can tell it never gets executed. I see the bean in the logs:
{"timestamp":"2018-08-21T15:48:08.349Z","level":"INFO","thread":"main",
"logger":"org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter",
"message":"Detected ResponseBodyAdvice bean in responseAuditAdvice","context":"default"}
My controller method looks like this:
#PostMapping(path = "/stage", consumes = {
"application/json"
}, produces = {
"application/json"
})
#ResponseBody
public ResponseEntity<?> stage(#Valid #RequestBody StagingDto stagingDto,
#RequestHeader(HttpHeaders.USER_AGENT) String userAgent,
BindingResult bindingResult) {
I have a RequestAuditAdvice that extends RequestBodyAdviceAdapter and it is working fine. Also if the error flow occurs I see the exception advice executing as well. it is only the response advice that is failing to trigger. Any suggestions?
here is the advice bean:
#Slf4j
#RequiredArgsConstructor(onConstructor_ = #Inject)
#ControllerAdvice
public class ResponseAuditAdvice implements ResponseBodyAdvice<Object> {
private final RequestService requestService;
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
log.info("Updating audit for response.");
String ip = new String (request.getRemoteAddress().getAddress().getAddress());
requestService.auditResponse(ip, 200);
return body;
}
}
I'd like to return 404 when the response object is null for every response automatically in spring boot.
I need suggestions.
I don't want to check object in controller that it is null or not.
You need more than one Spring module to accomplish this. The basic steps are:
Declare an exception class that can be used to throw an exception when a repository method does not return an expected value.
Add a #ControllerAdvice that catches the custom exception and translates it into an HTTP 404 status code.
Add an AOP advice that intercepts return values of repository methods and raises the custom exception when it finds the values not matching expectations.
Step 1: Exception class
public class ResourceNotFoundException extends RuntimeException {}
Step 2: Controller advice
#ControllerAdvice
public class ResourceNotFoundExceptionHandler
{
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public void handleResourceNotFound() {}
}
Step 3: AspectJ advice
#Aspect
#Component
public class InvalidRepositoryReturnValueAspect
{
#AfterReturning(pointcut = "execution(* org.example.data.*Repository+.findOne(..))", returning = "result")
public void intercept(final Object result)
{
if (result == null)
{
throw new ResourceNotFoundException();
}
}
}
A sample application is available on Github to demonstrate all of this in action. Use a REST client like Postman for Google Chrome to add some records. Then, attempting to fetch an existing record by its identifier will return the record correctly but attempting to fetch one by a non-existent identifier will return 404.
Simplest way to do this in Spring is write your own exception class like below
#ResponseStatus(value = HttpStatus.NOT_FOUND)
class ResourceNotFoundException extends RuntimeException{
}
Then just throw the ResourceNotFoundException from anywhere.
if (something == null) throw new ResourceNotFoundException();
For more -> Read
Similar to #manish's answer (https://stackoverflow.com/a/43891952/986160) but without the AspectJ pointcut and using another #ControllerAdvice instead:
Step 1: NotFoundException class:
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
}
Step 2: Check if body returned in endpoint is null and throw NotFoundException:
#ControllerAdvice
public class NotFoundAdvice implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
#SuppressWarnings("unchecked")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
throw new NotFoundException("resource not found");
}
return body;
}
}
Step 3: handle NotFoundException and make the response have a status of 404
#ControllerAdvice
public class GlobalExceptionAdvice {
#Data
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
#ExceptionHandler(NotFoundException.class)
public final ResponseEntity<ErrorDetails> notFoundHandler(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
Alternative to Step 3:
You can just annotate your NotFoundException with #ResponseStatus and override fillInStackTrace() (from https://stackoverflow.com/a/31263942/986160) so that it has similar effect to GlobalExceptionAdvice and doesn't show stacktrace like this:
#ResponseStatus(value = HttpStatus.NOT_FOUND,reason = "resource not found")
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
#Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
There is no need to throw exceptions, now ResponseBodyAdvice does the trick:
#ControllerAdvice
public class NullTo404 implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (body == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
}
return body;
}
}
Similarly, you can implement ResponseBodyAdvice<Optional<?>>, and check for Optional.isEmpty() before setting the response status. It has the added benefit of working nicely with CrudRepository. Most controller methods eventually ends like this:
public Optional<Product> getProductBySku(#PathVariable String sku) {
// logic goes here...
return productRepository.findBySku(sku);
}
I am adding rate-limiting to a restful webservice using Spring MVC 4.1.
I created a #RateLimited annotation that I can apply to controller methods. A Spring AOP aspect intercepts calls to these methods and throws an exception if there have been too many requests:
#Aspect
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
#Autowired
private RateLimitService rateLimitService;
#Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& #annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
This all works perfectly, except when the #RateLimited controller method has parameters marked as #Valid, e.g.:
#RateLimited
#RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
#Valid #RequestBody CreateAccountRequestDto dto) {
...
}
The problem: if validation fails, the validator throws MethodArgumentNotValidException, which is handled by an #ExceptionHandler, which returns an error response to the client, never triggering my #Before and therefore bypassing the rate-limiting.
How can I intercept a web request like this in a way that takes precedence over parameter validation?
I've thought of using Spring Interceptors or plain servlet Filters, but they are mapped by simple url-patterns and I need to differentiate by GET/POST/PUT/etc.
I eventually gave up on trying to find an AOP solution and created a Spring Interceptor instead. The interceptor preHandles all requests and watches for requests whose handler is #RateLimited.
#Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
#Autowired
private final RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
Add the following controller advice in your application.
#ControllerAdvice
public class ApplicationControllerAdvice {
#InitBinder
#RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
The #RateLimited should call the class RateLimitingAspect. So, after this all the constraints validator will be called.
See if it's feasible for you to implement similar logic for ##AfterThrowing advice as well which will have similar pointcut.
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
#Controller
public ModeAndView createUser(User user){
}
how can get controller method createUser's parameter user value
in interceptor 's postHandle ???
You cannot directly.
As you see in its parameters, an Interceptor has direct access to the request, the response and the ModelAndView. If you need to have access to the method parameter user, the simplest is to put it in model.
public ModeAndView createUser(User user){
ModelAndView mav = new ModelAndView();
mav.addAttribute("user", user);
...
return mav;
}
Then in interceptor postHandle method, you simply do
User user = (User) modelAndView.getAttribute("user");
You could write an org.springframework.web.servlet.HandlerInterceptor. (or its convenience subclass HandlerInterceptorAdapter)
#See: Spring Reference chapter: 15.4.1 Intercepting requests - the HandlerInterceptor interface
It has the method:
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception;
This method is invoked after the controller is done and before the view is rendered. So you can use it, to add some properties to the ModelMap
An example:
public class VersionAddingHandlerInterceptor extends HandlerInterceptorAdapter {
/**
* The name under which the version is added to the model map.
*/
public static final String VERSION_MODEL_ATTRIBUTE_NAME =
"VersionAddingHandlerInterceptor_version";
/**
* it is my personal implmentation
* I wanted to demonstrate something usefull
*/
private VersionService versionService;
public VersionAddingHandlerInterceptor(final VersionService versionService) {
this.versionService = versionService;
}
#Override
public void postHandle(final HttpServletRequest request,
final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.getModelMap().
addAttribute(VERSION_MODEL_ATTRIBUTE_NAME,
versionService.getVersion());
}
}
}
webmvc-config.xml
<mvc:interceptors>
<bean class="demo.VersionAddingHandlerInterceptor" autowire="constructor" />
</mvc:interceptors>