I want to create an exception handler which will intercept all controllers in my project. Is that possible to do? Looks like I have to put a handler method in each controller. Thanks for your help. I have a spring controller that sends Json response. So if an exception happens I want to send an error response which can be controlled from one place.
(I found a way to implement it in Spring 3.1, this is described in the second part of this answer)
See chapter 16.11 Handling exceptions of Spring Reference
There are some more ways than using #ExceptionHandler (see gouki's answer)
You could implement a HandlerExceptionResolver (use the servlet not the portlet package) - that is some kind of global #ExceptionHandler
If you do not have a specific logic for the exception, but only specifc view then you could use the SimpleMappingExceptionResolver, which is at least an implementation of the HandlerExceptionResolver where you can specify an Exception name pattern and the view (jsp) which is shown when the exception is thrown. For example:
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="uncaughtException">
<property name="exceptionMappings">
<props>
<prop key=".DataAccessException">dataAccessFailure</prop>
<prop key=".TypeMismatchException">resourceNotFound</prop>
<prop key=".AccessDeniedException">accessDenied</prop>
</props>
</property>
</bean>
In Spring 3.2+ one can annotate a class with #ControllerAdvice, all #ExceptionHandler methods in this class work in a global way.
In Spring 3.1 there is no #ControllerAdvice. But with a little hack one could have a similar feature.
The key is the understanding of the way #ExceptionHandler works. In Spring 3.1 there is a class ExceptionHandlerExceptionResolver. This class implements (with help of its superclasses) the interface HandlerExceptionResolver and is responsible invoking the #ExceptionHandler methods.
The HandlerExceptionResolver interface has only one Method:
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);`.
When the request was handled by a Spring 3.x Controller Method, then this method (represented by org.springframework.web.method.HandlerMethod) is the handler parameter.
The ExceptionHandlerExceptionResolver uses the handler (HandlerMethod) to obtain the Controller class and scan it for methods annotated with #ExceptionHandler. If one of this methods matches the exception (ex) then this methods get invoked in order to handle the exception. (else null get returned in order to signal that this exception resolver feels no responsible).
The first idea would be to implement an own HandlerExceptionResolver that behaves like ExceptionHandlerExceptionResolver, but instead of search for #ExceptionHandler in the controller class, it should search for them in one special bean. The drawback would be, that one has to (copy (or subclass ExceptionHandlerExceptionResolver) and must) configure all nice message converters, argument resolvers and return value handlers by hand (the configuration of the real one and only ExceptionHandlerExceptionResolver is done by spring automatically). So I came up with another idea:
Implement a simple HandlerExceptionResolver that "forwards" the exception to THE (already configured) ExceptionHandlerExceptionResolver, BUT with an modified handler which points to the bean that contains the global Exception handlers (I call them global, because they do the work for all controllers).
And this is the implementation: GlobalMethodHandlerExeptionResolver
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
public class GlobalMethodHandlerExeptionResolver
implements HandlerExceptionResolver, Ordered {
#Override
public int getOrder() {
return -1; //
}
private ExceptionHandlerExceptionResolver realExceptionResolver;
private List<GlobalMethodExceptionResolverContainer> containers;
#Autowired
public GlobalMethodHandlerExeptionResolver(
ExceptionHandlerExceptionResolver realExceptionResolver,
List<GlobalMethodExceptionResolverContainer> containers) {
this.realExceptionResolver = realExceptionResolver;
this.containers = containers;
}
#Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
for (GlobalMethodExceptionResolverContainer container : this.containers) {
ModelAndView result = this.realExceptionResolver.resolveException(
request,
response,
handlerMethodPointingGlobalExceptionContainerBean(container),
ex);
if (result != null)
return result;
}
// we feel not responsible
return null;
}
protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
GlobalMethodExceptionResolverContainer container) {
try {
return new HandlerMethod(container,
GlobalMethodExceptionResolverContainer.class.
getMethod("fakeHanderMethod"));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
}
The global Handler has to implement this interface (in order to get found and to implement the fakeHanderMethod used for the handler
public interface GlobalMethodExceptionResolverContainer {
void fakeHanderMethod();
}
And example for an global Handler:
#Component
public class JsonGlobalExceptionResolver
implements GlobalMethodExceptionResolverContainer {
#Override
public void fakeHanderMethod() {
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDto handleMethodArgumentNotValidException(
MethodArgumentNotValidException validationException,
Locale locale) {
...
/* map validationException.getBindingResult().getFieldErrors()
* to ValidationErrorDto (custom class) */
return validationErrorDto;
}
}
BTW: You do not need to register the GlobalMethodHandlerExeptionResolver because spring automatically register all beans that implements HandlerExceptionResolver for exception resolvers. So a simple <bean class="GlobalMethodHandlerExeptionResolver"/> is enough.
Since Spring 3.2 you can use #ControllerAdvice annotation.
You can declare an #ExceptionHandler method within an #ControllerAdvice class
in which case it handles exceptions from #RequestMapping methods from all controllers.
#ControllerAdvice
public class MyGlobalExceptionHandler {
#ExceptionHandler(value=IOException.class)
public #ResponseBody String iOExceptionHandler(Exception ex){
//
//
}
// other exception handler methods
// ...
}
An abstract class where you define the exception handlers will do. And then make your controllers inherit it.
Related
My Spring boot app is standalone application which don't have controller. I am calling service layer from main method of spring boot application.
I have tried to use #ExceptionHandler #ControllerAdvice annotations in my class as below. but control never comes to My Exception Handler method
package com.test.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(NullPointerException.class)
public void handleNullpointerExcetion() {
System.out.println("Handling Null pointer exception");
}
}
Tried with package name as well which i need to scan in #ControllerAdvice but it is not working
#ControllerAdvice("com.test.utility")
public class MyExceptionHandler {
#ExceptionHandler(NullPointerException.class)
public void handleNullpointerExcetion() {
System.out.println("Handling Null pointer exception");
}
}
Are we not able to handle exception at centralized place if i we don't have controller class which we annotate with #RestContoller or #Controller
I don't think so.
ControllerAdvice is only used whenever a controller would return an exception,
It is a layer that exists after your endpoint in the controller returns and before actually returning the response, to interject and globally handle the exception and is meant to be global only for controllers.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
since you don't have a controller, I'd imagine the ControllerAdvice will not help you there.
If you want a centralized error handling mechanism, you'd have to implement something similar to the controller advice yourself around your "Main" function, to which you can have unified exception handling responses.
I am developing a set of reusable stuff like filters, global exception handlers and other spring webflux components, that would be used by reactive spring apps (webflux)
Since, this is a library (a jar), that will be consumed by apps, I dont want to annotate the global exception handler class with #Configuration and instead would like to let the applications programmatically configure the exception handler as needed. I see from the docs, that the way to configure the WebExceptionHandler is through the HttpHandler.
#Component
#AllArgsConstructor
public class TestApplicationConfig {
private ApplicationContext applicationContext;
#Bean
public HttpHandler routeHandler() {
return WebHttpHandlerBuilder.applicationContext(applicationContext)
.exceptionHandler(new ServiceExceptionHandler())
.build();
}
}
//exception handler
#NoArgsConstructor
#Order(-2)
#Slf4j
public class ServiceExceptionHandler implements ErrorWebExceptionHandler {
#SuppressWarnings("NullableProblems")
#Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable err) {
}
}
The routeHandler bean gets called by the framework, but the exception handler is not called for any exception. If I have #Configuration in the exception handler class, then it is getting called. Wondering if I making a mistake in the way I am exposing the HttpHandler as a bean
I want to measure time of sql execution which will be run by MyBatis (Spring Boot project) and bind that with other request parameters, so I can get full info about performance issues regarding specific requests. For that case I have used MyBatis Interceptor on following way:
#Intercepts({
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
stopwatch.stop();
logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
return result;
}
}
Now when it come to binding with request, I want to store those metrics in request as attribute. I have tried this simple solution to get request, but that was not working since request was always null (I have read that this solution won't work in async methods, but with MyBatis Interceptor and its methods I think that's not the case):
#Autowired
private HttpServletRequest request;
So, the question is how properly get request within MyBatis interceptor?
One important note before I answer your question: it is a bad practice to access UI layer in the DAO layer. This creates dependency in the wrong direction. Outer layers of your application can access inner layers but in this case this is other way round. Instead of this you need to create a class that does not belong to any layer and will (or at least may) be used by all layers of the application. It can be named like MetricsHolder. Interceptor can store values to it, and in some other place where you planned to get metrics you can read from it (and use directly or store them into request if it is in UI layer and request is available there).
But now back to you question. Even if you create something like MetricsHolder you still will face the problem that you can't inject it into mybatis interceptor.
You can't just add a field with Autowired annotation to interceptor and expect it to be set. The reason for this is that interceptor is instantiated by mybatis and not by spring. So spring does not have chance to inject dependencies into interceptor.
One way to handle this is to delegate handling of the interception to a spring bean that will be part of the spring context and may access other beans there. The problem here is how to make that bean available in interceptor.
This can be done by storing a reference to such bean in the thread local variable. Here's example how to do that. First create a registry that will store the spring bean.
public class QueryInterceptorRegistry {
private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();
public static QueryInterceptor getQueryInterceptor() {
return queryInterceptor.get();
}
public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
}
public static void clear() {
queryInterceptor.remove();
}
}
Query interceptor here is something like:
public interface QueryInterceptor {
Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
Then you can create an interceptor that will delegate processing to spring bean:
#Intercepts({
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
public class QueryInterceptorPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
if (interceptor == null) {
return invocation.proceed();
} else {
return interceptor.interceptQuery(invocation);
}
}
#Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
#Override
public void setProperties(Properties properties) {
}
}
You need to create an implementation of the QueryInterceptor that does what you need and make it a spring bean (that's where you can access other spring bean including request which is a no-no as I wrote above):
#Component
public class MyInterceptorDelegate implements QueryInterceptor {
#Autowired
private SomeSpringManagedBean someBean;
#Override
public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
// do whatever you did in the mybatis interceptor here
// but with access to spring beans
}
}
Now the only problem is to set and cleanup the delegate in the registry.
I did this via aspect that was applied to my service layer methods (but you can do it manually or in spring mvc interceptor). My aspect looks like this:
#Aspect
public class SqlSessionCacheCleanerAspect {
#Autowired MyInterceptorDelegate myInterceptorDelegate;
#Around("some pointcut that describes service methods")
public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
try {
return proceedingJoinPoint.proceed();
} finally {
QueryInterceptorRegistry.clear();
}
}
}
I want to create an annotation that I will use on controller methods to validate access to the resource. I have written interceptor to intercept the request and also written code to create an annotation for their independent scenarios. Now I want intercept the request as well as take values provided in anotation to further processing.
Ideally
#RequestMapping("/release")
#ValidateAction("resource","release") //custom annotation that will accept two strings
public ResponseEntity releaseSoftware(Request request){
}
From the above I have to take those two values from #ValidateAction and send a request to another authorization server to authorize the action if the user have access to it (request contains oauth access token that will be used to authorize) and return true if the user have access otherwise throw AcceeDenied exception.
Can anybody point me in the right direction of doing it in Spring boot environment
Best way to achieve this is using Spring AOP Aspects.
Let us assume you have an Annotation like this
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAction {
String resource();
String release();
}
Then write an Aspect like this
#Aspect
#Component
public class AspectClass {
#Around(" #annotation(com.yourpackage.ValidateAction)")
public Object validateAspect(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
ValidateAction validateAction = method.getAnnotation(ValidateAction.class);
String release = validateAction.release();
String resource = validateAction.resource();
// Call your Authorization server and check if all is good
if( hasAccess)
pjp.proceed();
.......
}
}
Control will come to validateAspect method when any method which is annotated with #ValidateAction is called. Here you capture the annotation values as shown and do the necessary check.
Make sure you have the right dependency needed and these imports
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
You can do it with Spring AOP:
First, add spring-aop dependency:
compile 'org.springframework.boot:spring-boot-starter-aop' // Mine using gradle
In your #Configuration class, add #EnableAspectJAutoProxy:
#Configuration
#EnableAspectJAutoProxy
public class Application {
....
}
Create an annotation handler:
#Aspect
#Component // This #Component is required in spring aop
public class ValidateActionHandler {
#Around("execution(#your.path.ValidateAction * *(..)) && #annotation(validateAction)")
public Object doValidate(ProceedingJoinPoint pjp, ValidateAction retryConfig) throws Throwable {
// Your logic here
// then
return pjp.proceed();
}
}
I can't seem to get Spring Pre/Post method security annotations to work. I've read every related stackoverflow question on the topic, and the main suggestion is to ensure that global-method-security is enabled in the same context as the beans which you wish to secure. I have the following my dispatcher-servlet.xml:
<context:component-scan base-package="com.package.path" />
<context:annotation-config />
<security:global-method-security pre-post-annotations="enabled" />
The beans in question are in "com.package.path". I know that Spring is creating instances of them correctly, as injection is working just fine and requests are being serviced by the intended classes.
So, here's an example service class in "com.package.path":
#Controller
#RequestMapping("/article")
public class ArticleServiceImpl extends GWTController implements ArticleService {
#Autowired
public ArticleServiceImpl(DataSource ds) {
}
#Override
#PreAuthorize("hasRole('ROLE_BASIC_USER')")
public Article save(Article article) {
}
}
The annotation on the save method does not work. A few important notes:
I'm using GWT, though from what I've read, that shouldn't matter much.
I have method security working perfectly well in another, similar project. The only difference is that there is a DAO layer in the other project, which is not present in this one. It's in this layer that I have annotation security working. However, it shouldn't matter what "layer" this is, as long as Spring is responsible for creation of the beans, right?
The interface "ArticleService" above is a GWT service interface. I've tried putting the annotation there, but that doesn't work either.
Here's my GWTController class, referenced above, if needed:
package com.areahomeschoolers.baconbits.server.spring;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.ServletConfigAware;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import com.areahomeschoolers.baconbits.server.util.ServerContext;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
/**
* Spring controller class that handles all requests and passes them on to GWT. Also initializes server context.
*/
public class GWTController extends RemoteServiceServlet implements ServletConfigAware, ServletContextAware, Controller, RemoteService {
private static final long serialVersionUID = 1L;
protected ServletContext servletContext;
#Override
public ServletContext getServletContext() {
return servletContext;
}
// Call GWT's RemoteService doPost() method and return null.
#Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// load our ServerContext with current request, response, session, user, appContext, etc.
ServerContext.loadContext(request, response, servletContext);
try {
doPost(request, response);
} finally {
ServerContext.unloadContext();
}
return null; // response handled by GWT RPC over XmlHttpRequest
}
#Override
public void setServletConfig(ServletConfig conf) {
try {
super.init(conf);
} catch (ServletException e) {
e.printStackTrace();
}
}
#Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
#Override
protected void checkPermutationStrongName() throws SecurityException {
return;
}
#Override
protected void doUnexpectedFailure(Throwable e) {
e.printStackTrace();
super.doUnexpectedFailure(e);
}
}
Security aspect provided by Spring Security inherits all limitations of Spring Framework proxy-based AOP support. In particular, aspects are not applied to calls that happen "inside" the objects (unless you use AspectJ weaving), see 7.6.1 Understanding AOP proxies.
So, if you want to use security aspect this way, you need to use GWT integration mechanism that make calls to your service from the outside, i.e. a mechanism that doesn't require your services to extend RemoteServiceServlet.
Something like spring4gwt should work fine.