springMVC之@ControllerAdvice和ResponseBodyAdvice

前言

当使用HandlerInterceptor拦截器的postHandle()对controller的返回值进行拦截处理时,如果controller的方法被@ResponseBody修饰,则无法拦截controller方法的返回值,因为在postHandle()执行之前,controller方法的返回值就已经提交了。

详情见spring-framework SPR-9226

Response is committed before Interceptor postHandle invoked

In certain circumstances, the response is committed before the Interceptor postHandle method is invoked. This appears to be caused when the HandlerMethod is annotated with @ResponseBody.

Steps to reproduce:
1. Create a Controller method annotated with @ResponseBody.
2. Configure an Interceptor for this path (or all path mappings)
3. Submit a request to the path above, with a breakpoint set in the Interceptor's postHandle method.
4. Check the value of response.isCommitted()

Expected:
Response should not be committed

Actual:
Response is committed.

Implications:

Interceptors are unable to modify the response for @ResponseBody HandlerMethods. This prevents Interceptors from being able to add headers to the response.


@ControllerAdvice

@ControllerAdvice,是spring3.2提供的新注解。@ControllerAdvice注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。

我们要做的就是创建一个用@ControllerAdvice注释的类,并创建相应的三个方法,这三个方法分别用@ExceptionHandler注释以进行全局异常处理,@InitBinder用于全局init绑定,而@ModelAttribute用于全局model属性添加。

当请求达到Controller类中带@RequestMapping注解的方法时,如果没有本地定义的@ExceptionHandler,@InitBinder和@ModelAttribute时,将使用由@ControllerAdvice注解标记的类中的相应方法。

默认情况下,在@ControllerAdvice的方法会应用到所有的Controller中,但是你可以使用@ControllerAdvice的basePackages属性来限制应用到特定包路径下的controller。

指定单个包路径:

@ControllerAdvice("org.my.pkg")  相当于@ControllerAdvice(basePackages="org.my.pkg")

指定一组包路径:

@ControllerAdvice(basePackages={"org.my.pkg","org.my.other.pkg"})

代码示例:

@ControllerAdvice(basePackages = {"com.concretepage.controller"} )
public class GlobalControllerAdvice {
	@InitBinder
	public void dataBinding(WebDataBinder binder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
		dateFormat.setLenient(false);
		binder.registerCustomEditor(Date.class, "dob", new CustomDateEditor(dateFormat, true));
	}
	@ModelAttribute
        public void globalAttributes(Model model) {
		model.addAttribute("msg", "Welcome to My World!");
        }
	@ExceptionHandler(FileNotFoundException.class)
        public ModelAndView myError(Exception exception) {
	    ModelAndView mav = new ModelAndView();
	    mav.addObject("exception", exception);
	    mav.setViewName("error");
	    return mav;
	}
} 

创建一个@RequestMapping方法的Controller,在该Controller我们也定义了一个本地的@InitBinder方法

@Controller
@RequestMapping("/myworld")
public class MyWorldController {
	@Autowired
	private UserValidator userValidator;
	@RequestMapping(value="signup", method = RequestMethod.GET)
	public ModelAndView user(){
		return new ModelAndView("user","user",new User());
	}
	@InitBinder
	public void dataBinding(WebDataBinder binder) {
		binder.addValidators(userValidator);
	}
	@RequestMapping(value="save", method = RequestMethod.POST)
	public String createUser(@ModelAttribute("user") @Valid User user,BindingResult result, ModelMap model) 
			                                                throws FileNotFoundException {
	    if(result.hasErrors()) {
	    	return "user";
	    }
	    if(user.getName().equals("exception")) {
	        throw new FileNotFoundException("Error found.");	
	    }
	    System.out.println("Name:"+ user.getName());
	    System.out.println("Date of Birth:"+ user.getDob());
	    return "success";
	}	
} 

上面Controller中抛出的FileNotFoundException异常没被处理,所以它会被@ControllerAdvice注解标记的类的异常处理方法处理。


@ControllerAdvice和ResponseBodyAdvice 

先引用spring-framework 4.3官方文档的22.4节的这句话:

Intercepting requests with a HandlerInterceptor

Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with @ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an @ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.

ResponseBody接口官方API也有描述到:

public interface ResponseBodyAdvice<T>
Allows customizing the response after the execution of an @ResponseBody or a ResponseEntity controller method but before the body is written with an HttpMessageConverter.

Implementations may be registered directly with RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver or more likely annotated with @ControllerAdvice in which case they will be auto-detected by both.

也就是说,通过实现ResponseBodyAdvice接口,则带@ResponseBody或者ResponseEntity的控制器方法可以在执行完成之后,response写入输出流(通过HttpMessageConverter)之前,完成对response的拦截。

使用示例如下:

package com.test.yhgl.framework.interceptor;

import java.util.HashMap;

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice(basePackages="com.test.yhgl.controller")
public class RegisterReturnedInterceptor implements ResponseBodyAdvice{

	@Override
	public boolean supports(MethodParameter returnType, Class converterType) {
		// TODO Auto-generated method stub
		 //获取当前处理请求的controller的方法
        String methodName=returnType.getMethod().getName(); 
        String method= "egisteAccount"; 
		return method.equals(methodName); //只有当请求的方法为“egisteAccount”才进行拦截,返回true才能调用beforeBodyWrite()方法
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
		// TODO Auto-generated method stub
		HashMap<String,Object>  resp = (HashMap<String, Object>) body;  //获取controller方法的返回值
		boolean flag = (boolean) resp.get("flag");
		if(flag){ //如果用户注册成功
			
		}
		return resp;
	}

	

}

Controller如下:

package com.test.yhgl.controller;


import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequestMapping(value = "/regist")
public class RegistController {

private IUserService userService;

 @RequestMapping(value = "/egisteAccount.do")
	public @ResponseBody Map<String,Object> egisteAccount(HttpServletRequest request, HttpServletResponse response) throws IOException {
		Map<String,Object> result = new HashMap<String,Object>();
		
		boolean su = userService.registUser(request.getParameter("yhxx"));
		if(su){
			result.put("msg", "注册成功");
			result.put("flag", true);
		}else{
			result.put("msg", "注册失败");
			result.put("flag", false);
		}
		return result;
	}


}

参考:spring 4.3 官方文档-HandlerInterceptor

@ControllerAdvice官方API

Spring MVC @ControllerAdvice Annotation Example

spring SPR-9226 Response is committed before Interceptor postHandle invoked

@ControllerAdvice,ResponseBodyAdvice 统一处理返回值/响应体

ResponseBodyAdvice官方API

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值