Search code examples
javaspringaopspring-aop

How to handle exception thrown by aspect using custom handler


I'm fighting with AOP and custom error handler. I have one controller "WorkingController" - it is working as expected.

@Controller
public class WorkingController extends BaseController {
    ​
    @RequestMapping("/welcome")
    public ModelAndView helloWorld() {
        return new ModelAndView("welcome", "message", "<h3>********** Hello World, Success!</h3>");
    }
    ​
    @RequestMapping("/success")
    public void test(HttpServletResponse response) {
        writeResponse(response, "Success!", ContentType.TEXT_JSON);
    }
    ​
    @RequestMapping("/test")
    public void test2(HttpServletResponse response) throws IOException {
        throw new IOException("This exception should be handled by custom handler.");
    }
    ​
    @InterestingMethod
    @RequestMapping("/aspect")
    public void test3(@InterestingArg @RequestParam(value = "id", required = false) String id,
                      HttpServletResponse response) throws IOException, AccessDeniedException {
        throw new IOException("This exception should also be handled by custom handler.");
    }
}
​
class BaseController {

    @ExceptionHandler(Exception.class)
    public void handleException(Throwable ex, HttpServletRequest request, HttpServletResponse response) {
        System.out.println("Exception handled by BaseController, great work!: " + ex.getMessage());
        ServletUtil.writeResponse(response, JSONObject.fromObject("Error").toString(),
                ContentType.TEXT_JSON.toString(), false, "UTF-8");
    }
    ​
    void writeResponse(HttpServletResponse response, String data, ContentType contentType) {
        ServletUtil.writeResponse(response, data, contentType.toString(), false, "UTF-8");
    }
    ​
}

My case forces me to create interface, mark my controller and Aspect will check if user has access to given endpoint - so I have MarkedController interface and NotWorkingController.

@Controller
public class NotWorkingController extends BaseController implements MarkedController {

    public void test2(HttpServletResponse response) throws IOException {
        throw new IOException("NotWorkingController, This exception should be handled by custom handler.");
    }
    ​
    @InterestingMethod
    public void test3(@InterestingArg String id,
                      HttpServletResponse response) throws IOException, AccessDeniedException {
        throw new IOException("NotWorkingController, This exception should also be handled by custom handler.");
    }
}
​
@Controller
public interface MarkedController {
    ​
    @RequestMapping("/test2")
    void test2(HttpServletResponse response) throws IOException;

    @RequestMapping("/aspect2")
    void test3(@RequestParam(value = "id", required = false) String id,
               HttpServletResponse response) throws IOException, AccessDeniedException;
}

@Aspect
public class InterestingMethodAspect {
    @Pointcut("@annotation(interestingMethod)")
    public void interestingMethodPointcut(InterestingMethod interestingMethod) {
    }

    @Before("(interestingMethodPointcut(interestingMethod))")
    public void asd(JoinPoint joinPoint, InterestingMethod interestingMethod) throws AccessDeniedException {
        processArguments(joinPoint);
    }

    private void processArguments(JoinPoint joinPoint) throws AccessDeniedException {
        throw new AccessDeniedException("You don't have access to this endpoint.");
    }
}

And this is kind of magic for me, because:

  1. In NotWorkingController exception thrown by Aspect code is not handled by custom exception handler (located in BaseController).
  2. I'm forced to move all @RequestMappings to interface - it's a little bit annoying in my case. How to prevent that?

So, where should i look, what should I check?
Thanks in advance for any suggestions.

BTW: Complete maven project is available here.


Solution

  • Yhm, and I have the answers.

    For the first problem (exception catching): HandlerExceptionResolver did all what I needed. I've removed @ExceptionHandler annotation from handleException method in BaseController and custom implementantion of HandlerExceptionResolver is catching all my exceptions).

    Second problem: I had to switch from JDK-based proxies to CGLIB-based one.