相信中文乱码问题每一个刚学习web开发的程序员都遇到过,像我最开始写demo的时候我就会给每一个请求都设置request的encode,后来发现每一个servlet都要加一句实在是太麻烦了,那么有没有什么简单的方法可以统一设置呢?后来知道有了过滤器可以拦截请求,就可以实现在过滤器中写一次然后就给所有的request设置encode。再后来学习了SpringMVC(当初学Struts的时候是怎么搞的已经忘记了。。。),只需要配置一下SpringMVC的CharacterEncodingFilter类,省去了自己写过滤器的麻烦。那么CharacterEncodingFilter具体为我们做了什么又是怎样实现的呢?本章就来详细说一下CharacterEncodingFilter的实现原理(好像也没啥说的。。不管了,强行说)
首先说一句题外话,在这里给大家的建议是,在项目中能用成熟框架已经完成的实现尽量使用框架的实现,一个来说省去了自己编写代码的麻烦,二来框架已经流行多年,该有的bug也被修复的差不多了,自己写的代码有时候难免会出一些问题,这里也包括我们平常使用的工具类,除非不能满足需求,非要自己写不可,一般推荐使用成熟的工具,例如apache-common与guava。这里说一个小技巧(也不算技巧,基本大家都知道),一些框架都会封装自己的一些工具类,然而这些工具类一般不在文档中出现,只有看源码或者API才能发现,如果项目里用到了这些框架可以直接使用它们了,例如spring的AnnotationUtils啦、spring各个jar包里的util包啦等等。
总览
最初开发的时候会在web.xml中进行配置,我为了偷懒就直接使用SpringBoot了。SpringBoot对于编码过滤器进行了自动的配置
注入CharacterEncodingFilter如图,如果未进行手动配置,SpringBoot自动帮你注入了CharacterEncodingFilter类,接下来我们看一下继承结构。
继承结构图如图,相信对于Spring比较熟悉的同学对于最长的一行接口应该都比较熟悉,这里就不介绍了,主要从GenericFilterBean开始说起:
GenericFilterBean
首先他是一个抽象类,是springmvc中所有过滤器的基础类。该类实现了各种aware接口和初始化bean与销毁bean的生命周期接口。他把所实现的Filter接口中的doFilter方法留给子类去实现。这里主要注意两个方法:
InitializingBean接口中的方法第一个是他实现的InitializingBean接口中的afterPropertiesSet方法,该方法在bean初始化并设置完属性值后执行。方法里面有个initFilterBean(),而继续点击进去发现该方法是一个空实现,说明他也是留给子类实现的,子类可以选择是否复写该方法来用于拦截器初始化时候的一些自定义逻辑。
Filter接口中的方法第二个重要的方法就是实现了Filter接口中的init方法,在这里主要做的工作就是将一些初始化参数设置进属性中(记得web.xml中配置CharacterEncodingFilter的步骤吗?里面的init标签参数的设置就是在这里完成的)。
OncePerRequestFilter
接下来分析一下CharacterEncodingFilter的直接父类OncePerRequestFilter:顾名思义,该类保证每个请求在任何servlet容器上执行一次,他也是一个抽象类,spring中大部分的过滤器也继承了该类。里面主要的方法是实现了Filter接口的doFilter方法。
如图,该方法里判断是否需要对该请求拦截或者直接放行。首先看最长的if判断,判断他判断是否对其进行拦截过或者是否应该进行跳过。
spring自己的判断逻辑其中skipDispatch(httpRequest)方法中主要对异步servlet,和异常情况下的判断,默认如果是异步servlet的请求默认返回true,shouldNotFilterErrorDispatch方法判断在异常情况下是否过滤该请求,什么是异常情况呢?比如当请求发生错误的映射-例如404,该方法也默认返回true,意味着发生异常时不进行拦截。
提到异常,这里就多说一下spring的异常处理,对于运行时异常或者自定义异常,spring提供了@ExceptionHandler和@ControllerAdvice注解进行处理(具体使用比较简单,和@RequestMapping使用差不多,不会的百度一下好了)。前者可以单独使用但是代表只能处理当前controller异常,后者则是全局的(原理是利用spring的异常处理器。。等以后有时间总结springmvc的创建过程时候再说异常处理器吧)。
spring扩展点shouldNotFilter方法是spring留给我们的扩展,在这里我们可以自定义自己的过滤逻辑。
最后当以上判断走完发现当不需要拦截处理的时候直接放行,确实需要进行拦截调用时,会调用doFilterInternal(httpRequest, httpResponse, filterChain),点进去发现该方法是一个抽象方法,留给子类去实现,这样写的好处是每个子类可以根据自身不同的情况去写相应的处理逻辑,spring源码很多地方使用了这种模式。
CharacterEncodingFilter
最后终于来到了我们的主类了。。感觉过了好几年。。。不过Spring源码就是这样一层层的进行抽象,当理解了之后才能发觉他这样做的好处(理解中。。。)
前面也说了,OncePerRequestFilter留了doFilterInternal给子类实现,则CharacterEncodingFilter中最重要的方法就是他了。
具体实现逻辑逻辑到这里是不是终于看到了熟悉的request.setCharacterEncoding(encoding)了!!这里注意一下,其中force***代表了是否对其强制进行制定字符集编码,默认为false。
end。
网友评论