一、拦截器
上篇博文中已经说到过可以继承HandlerInterceptorAdapter类或者实现HandlerInterceptor接口。
这里想说的是对于其方法中一个参数的说明。
- /**
- * Controller之前执行
- */
- @Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response, Object handler) throws Exception {}
request、response自不必多说。这里有个handler,据我多方了解,可以确定此handler为下一步要执行的拦截器或者Controller。我们都知道拦截器可以配置多个,就有个拦截器链,按照顺序去执行这么多拦截器(不知是不是按照你配置的先后顺序来执行的),如果你正在执行的拦截器完成后,下面还有个拦截器等待执行,那么handler就是那个拦截器类;如果这个拦截器执行完了,就执行controller,那么这个handler就是那个Controller类了。
二、错误异常(2种方式)
目前大家常用的错误异常捕捉可能是在web.xml中配置:
- <error-page>
- <error-code></error-code>
- <location></location>
- </error-page>
- 或者
- <error-page>
- <exception-type></exception-type>
- <location></location>
- </error-page>
当springMVC内部抛出自定义的运行时异常,如:NoSuchRequestHandlingMethodException(错误代码404),无法根据web.xml配置的404代码跳转到相应页面,那么我们就需要配置springMVC提供的错误异常日志处理类。
第一种,配置文件形式:看配置文件:
- <!-- 系统错误转发配置[并记录错误日志] -->
- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
- <property name="defaultErrorView" value="error"></property> <!-- 默认为500,系统错误(error.jsp) -->
- <property name="defaultStatusCode" value="500"></property>
- <property name="statusCodes"><!-- 配置多个statusCode -->
- <props>
- <prop key="error">500</prop> <!-- error.jsp -->
- <prop key="error1">404</prop> <!-- error1.jsp -->
- </props>
- </property>
- <property name="exceptionMappings">
- <props>
- <!-- 这里你可以根据需要定义N多个错误异常转发 -->
- <prop key="java.sql.SQLException">dbError</prop> <!-- 数据库错误(dbError.jsp) -->
- <prop key="org.springframework.web.bind.ServletRequestBindingException">bizError</prop> <!-- 参数绑定错误(如:必须参数没传递)(bizError.jsp) -->
- <prop key="java.lang.IllegalArgumentException">bizError</prop> <!-- 参数错误(bizError.jsp) -->
- <prop key="org.springframework.validation.BindException">bizError</prop> <!-- 参数类型有误(bizError.jsp) -->
- <prop key="java.lang.Exception">unknowError</prop> <!-- 其他错误为'未定义错误'(unknowError.jsp) -->
- </props>
- </property>
- </bean>
注意:
1、这里需要说明一点的是,它只能转发到jsp页面,不能转发到Controller;
2、如果错误不能转发到对应的错误页面,请查看你的错误类是否写正确了,如org.springframework.validation.BindException是否写正确。
3、传递到错误页面不能传递参数,如<prop key="java.lang.Exception">unknowError?flag=1</prop>,这么写就不会转发到unknowError.jsp页面了。
4、如果错误页面没有记录错误日志,那么你的log4j日志文件也是不会记录错误日志的,那么我们需要自己手动在错误页面中记录,代码如下:
- <%@ page language="java" import="org.apache.log4j.Logger" pageEncoding="UTF-8" contentType="text/html; charset=utf-8"%>
- <%
- Exception exception = (Exception)request.getAttribute("exception");
- final Logger logger = Logger.getRootLogger();
- logger.error(exception.getMessage(),exception);
- %>
说明:根据SimpleMappingExceptionResolver类的源码可知,它将错误日志放在了request的属性变量中,变量名为exception,类型为Exception,需要引入org.apache.log4j.Logger包,这样的话,log4j日志就会记录错误日志了。
5、当然也需要这个配置文件(定义jsp文件的位置):
- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/views/"></property>
- <property name="suffix" value=".jsp"></property>
- </bean>
第二种,注解形式:
以上基于配置文件形式配置异常处理机制,适用于项目全局。而注解形式主要适用于局部,如果想适用于全局,需要首先声明一个BaseController类,加入注解异常处理机制,其他Controller继承该BaseController。具体使用方式,看代码:
- @ExceptionHandler(Exception.class) //在Controller类中添加该注解方法即可(注意:添加到某个controller,只针对该controller起作用)
- public void exceptionHandler(Exception ex,HttpServletResponse response,HttpServletRequest request) throws IOException{
- log.error(ex.getMessage(), ex);
- if(ex.getClass() == NoSuchRequestHandlingMethodException.class){
- response.sendRedirect(request.getContextPath()+"/common/view/404.jsp");
- }else{
- response.sendRedirect(request.getContextPath()+"/common/view/500.jsp");
- }
- }
spring自定义的异常类对应的错误代码如下:
- *** SpringMVC自定义异常对应的status code
- Exception HTTP Status Code
- ConversionNotSupportedException 500 (Internal Server Error)
- HttpMediaTypeNotAcceptableException 406 (Not Acceptable)
- HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
- HttpMessageNotReadableException 400 (Bad Request)
- HttpMessageNotWritableException 500 (Internal Server Error)
- HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
- MissingServletRequestParameterException 400 (Bad Request)
- NoSuchRequestHandlingMethodException 404 (Not Found)
- TypeMismatchException 400 (Bad Request)
三、数据绑定
上篇博文中有提到在BaseController中定义全局的数据转换(如String转换为Date或者Calendar;如果String转换为JavaBean等),只要注册一个方法protected void ininBinder(WebDataBinder binder){ },并且添加注解@InitBinder,就可以实现全局Controller的数据转换。
下面详细介绍下两种方式实现数据的绑定:
1、全局数据绑定
第一种方式,定义一个BaseController,在里面注册一个protected void ininBinder(WebDataBinder binder){},添加注解@InitBinder。【注解式】
第二种方式,定义一个类MyBinder实现WebBindingInitializer接口,同时实现其方法public void initBinder(WebDataBinder binder, WebRequest arg1) {}。【声明式】
接下来需要在spring-mvc.xml中配置,这里要多说一点。
一般大家可能省事,直接写了<mvc:annotation-driven/>来激活@Controller模式,它默认会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter两个bean,它们是springMVC为@Controllers分发请求所必须的。但是如果你想用声明式注册一个数据绑定,你需要手动注册AnnotationMethodHandlerAdapter和DefaultAnnotationHandlerMapping。
- <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"></bean> <!-- 这个类里面你可以注册拦截器什么的 -->
- <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
- <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
- <property name="webBindingInitializer">
- <bean class="packagename.MyBinder"></bean> <!-- 这里注册自定义数据绑定类 -->
- </property>
- <property name="messageConverters">
- <list>
- <ref bean="jacksonMessageConverter"/> <!-- 注册JSON Converter-->
- </list>
- </property>
- </bean>
说明:这里我还引用了org.springframework.http.converter.json.MappingJacksonHttpMessageConverter,也在这里说明一下。项目中我使用了springMVC为人津津乐道的可以直接返回JSON字符串的功能,就是在Controller的方法前面加@ResponseBody。但是我发现我引用了自定义数据绑定类,运行时候不会返回JSON字符串,并且报错(好像就是缺少了个XXXConverter),经过多方寻找,原来是需要手动注册一个json的Converter。如果你想用它的这个功能,需要引入两个jar包。jackson-core-asl.jar和jackson-mapper-asl.jar。
2、局部数据绑定
这个就简单了,直接在需要的绑定的Controller中,添加protected void ininBinder(WebDataBinder binder){ },并且添加注解@InitBinder就可以实现。例如:
- @Controller
- public class TestController{
- //日期转换器,这样就会作用到这个Controller里所有方法上
- @InitBinder
- public void initBinder(WebDataBinder binder) {
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- dateFormat.setLenient(false);
- binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
- }
- ...各种增删改查方法
- }
四、转换器
Spring提供了很多默认转换器,如StringToBooleanConverter,ObjectToStringConverter,NumberToCharacterConverter,ArrayToCollectionConverter,StringToArrayConverter,ObjectToCollectionConverter,ObjectToObjectConverter等等,如果需要自定义转换器,需要实现接口
- public interface Converter<S, T> { //S是源类型 T是目标类型
- T convert(S source); //转换S类型的source到T目标类型的转换方法
- }
我目前用到的转换器和数据绑定,基本都是对字段类型转换,两种方式都可以实现字符串到日期的转换。如:
- public class CustomDateConverter implements Converter<String, Date> {
- private String dateFormatPattern; //转换的格式
- public CustomDateConverter(String dateFormatPattern) {
- this.dateFormatPattern = dateFormatPattern;
- }
- @Override
- public Date convert(String source) {
- if(!StringUtils.hasLength(source)) {
- return null;
- }
- DateFormat df = new SimpleDateFormat(dateFormatPattern);
- try {
- return df.parse(source);
- } catch (ParseException e) {
- throw new IllegalArgumentException(String.format("类型转换失败,需要格式%s,但格式是[%s]", dateFormatPattern, source));
- }
- }
- }
配置文件有两种方式,
第一种比较简单:
- <mvc:annotation-driven conversion-service="conversionService"/>
- <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
- <property name="converters">
- <list>
- <bean class="packagename.CustomDateConverter">
- <constructor-arg value="yyyy-MM-dd"></constructor-arg>
- </bean>
- lt;/list>
- </property>
- </bean>
第二种,稍微麻烦点:
- <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
- <property name="converters">
- <list>
- <bean class="packagename.CustomDateConverter">
- <constructor-arg value="yyyy-MM-dd"></constructor-arg>
- </bean>
- </list>
- </property>
- </bean>
- <bean id="myBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
- <property name="conversionService" ref="conversionService"/>
- </bean>
- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
- <property name="webBindingInitializer" ref="myBinder"></property>
- </bean>
五、属性编辑器
1、在第三条中说了数据绑定,那么怎么能做到将String转换为Calendar或者Date呢。这里需要说的就是Spring的属性编辑器,感觉跟struts2的那个数据转换差不多。先看一段代码:
- public void initBinder(WebDataBinder binder, WebRequest arg1) {
- //这里我定义了一个匿名属性编辑器将String转为为Calendar
- binder.registerCustomEditor(Calendar.class, new PropertyEditorSupport(){
- @Override
- public void setAsText(String text) throws IllegalArgumentException {
- Calendar cal = null;
- Date date = Util.convertDate(text);
- if(date != null){
- cal = new GregorianCalendar();
- cal.setTime(date);
- }
- setValue(cal);
- }
- });
- }
说明:用binder.registerCustomEditor()注册一个属性编辑器,来进行数据的转换操作。它有2种方式。
第一种binder.registerCustomEditor(Class clz,String field,PropertyEditor propertyEditor);这种方式可以针对bean中的某一个属性进行转换操作。clz为类的class,field为bean中的某一个属性,propertyEditor为编辑器。
第二种binder.registerCustomEditor(Class clz,PropertyEditor propertyEditor)。这种方式可以针对某种数据类型进行数据转换操作。如:将传递过来的String字符串转换为Calendar,这里就需要将clz设置为Calendar.class,propertyEditor为编辑器。
2、默认spring提供了很多种属性编辑器,可以在Spring-beans-3.0.5.jar的org.springframework.beans.propertyeditors包中看到。直接使用即可,如将String转换为Date类型:
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, false));
当然也可以自定义属性编辑器,你只需继承PropertyEditorSupport类(推荐)或者实现PropertyEditor接口;然后实现其setAsText(String text)方法做自己的操作。