SpringMVC+自定义过滤器+方式一
1.自定义过滤器
package com.demo.thymeleafstudy.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FilterTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----FilterTest过滤器初始化----");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 对request和response进行一些预处理,比如说对报文的加解密、字符集的变更
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
System.out.println("FilterTest执行前!!!");
// 让目标资源执行,放行
filterChain.doFilter(request, response);
System.out.println("RequestFilter执行后!!!");
}
@Override
public void destroy() {
System.out.println("----FilterTest过滤器销毁----");
}
}
2.web.xml配置
<!-- 配置过滤器 -->
<filter>
<filter-name>filterTest</filter-name>
<filter-class>com.demo.thymeleafstudy.filter.FilterTest</filter-class>
</filter>
<!-- 映射过滤器 -->
<filter-mapping>
<!-- <filter-mapping>和<filter>标签里面的<filter-name>必须要一致!! -->
<filter-name>filterTest</filter-name>
<!-- “/*”表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
3.缺点
采用这种方式配置,如果FilterTest类需要一些Sring容器中的一些实例(bean),无法通过Spring直接注入!
SpringMVC+自定义过滤器+方式二(自定义过滤器交给spring管理)
1.自定义过滤器
package com.demo.thymeleafstudy.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FilterTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----FilterTest过滤器初始化----");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 对request和response进行一些预处理,比如说对报文的加解密、字符集的变更
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
System.out.println("FilterTest执行前!!!");
// 让目标资源执行,放行
filterChain.doFilter(request, response);
System.out.println("RequestFilter执行后!!!");
}
@Override
public void destroy() {
System.out.println("----FilterTest过滤器销毁----");
}
}
2.自定义过滤器注册为bean
<bean name="filterTest" class="com.demo.thymeleafstudy.filter.FilterTest"></bean>
3.web.xml配置
<filter>
<!-- Spring中配置的bean的name要和这里<filter-name>一样 -->
<filter-name>filterTest</filter-name>
<!-- <filter-class>使用DelegatingFilterProxy -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 如果要保留自定义Filter原有的init,destroy方法的调用,需要配置初始化参数targetFilterLifecycle为true,该参数默认为false -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 其它的初始化参数 -->
<init-param>
<param-name>excludedPages</param-name>
<param-value>/login</param-value>
</init-param>
</filter>
<!-- 映射过滤器 -->
<filter-mapping>
<!-- <filter-mapping>和<filter>标签里面的<filter-name>必须要一致!! -->
<filter-name>filterTest</filter-name>
<!-- “/*”表示拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
4.注意
- 1.
<filter-class>使用org.springframework.web.filter.DelegatingFilterProxy - 2.如果自定义filter要使用
init,destroy方法,需要配置web.xml中的targetFilterLifecycle参数为true - 3.自定义filter要注册为bean
SpringBoot+自定义过滤器+方式一
1.新建一个TimeFilter类,加入注解
@WebFilter(urlPatterns = "/*", filterName = "FilterTest")
public class FilterTest implements Filter {
}
2.在SpringBoot加上如下注解,@ServletComponentScan
@SpringBootApplication
@ServletComponentScan
public class ThymeleafstudyApplication {
}
3.注意:多个自定义filter使用@Order无法指定顺序
经过测试,发现 @Order 注解指定 int 值没有起作用,是无效的。为啥?因为看源码发现 @WebFilter 修饰的过滤器在加载时,没有使用 @Order 注解,而是使用的类名来实现自定义Filter顺序。详情参考这个博客、博客
SpringBoot+自定义过滤器+方式二(多个过滤器并且指定顺序)
1.新建一个或者多个自定义过滤器类,不要加任何注解
2.项目中增加一个配置类
@Configuration
public class MyFilterConfig {
@Bean
public FilterRegistrationBean<FilterTest> filterTestRegistration() {
FilterRegistrationBean<FilterTest> registration = new FilterRegistrationBean<FilterTest>();
registration.setFilter(new FilterTest());
registration.addUrlPatterns("/*");
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setOrder(1);
return registration;
}
@Bean
public FilterRegistrationBean<AsyncFilterTest> asyncFilterTestRegistration() {
FilterRegistrationBean<AsyncFilterTest> registration = new FilterRegistrationBean<AsyncFilterTest>();
registration.setFilter(new AsyncFilterTest());
registration.addUrlPatterns("/*");
registration.setAsyncSupported(true);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
registration.setOrder(2);
return registration;
}
}
setDispatcherType属性详解请点我
异步servlet+异步Filter
参考这篇文章点我
拓展
SpringMVC和Servlet区别
-
1.SpringMVC是框架,或者说SpringMVC封装了Servlet;
-
2.Servlet可以认为是一套处理网络请求的规范
比如说我们要实现一个能够响应“Hello Word”的servlet,那么我们最终是实现Servlet这个接口。我们自己写的servlet只有部署到容器中(最常用的是tomcat)才能起作用。tomcat才是与客户端直接打交道的家伙,他监听了端口,请求过来后,根据url等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,tomcat再把这个response返回给客户端。参考1、参考2
Controller和Servlet区别
SpringMVC中经常看到如下配置
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvcServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Spring MVC是基于servlet的,它有一个DispatherServlet,然后DispatherServlet负责处理请求,并且调用了你的controller。
HTTP请求获取请求内容的方法
在平时开发过程中,在过滤器filter中如果我们想要获取http post请求的报文主要有三种方式,request.getParameter()、request.getInputStream()和request.getReader()。参考资料资料1
-
1.
request.getParameter()主要参考资料request.getParameter()源码分析1、request.getParameter()源码分析2、tomcat的Request源码
该方法只能用于post请求并且
content-type=application/x- www-form-urlencoded。下面看一下request.getParameter()的源码,其具体实现类是org.apache.catalina.connector.Request/** * @return the value of the specified request parameter, if any; otherwise, * return <code>null</code>. If there is more than one value defined, * return only the first one. * * @param name Name of the desired request parameter */ @Override public String getParameter(String name) { if (!parametersParsed) { parseParameters(); } return coyoteRequest.getParameters().getParameter(name); }parametersParsed默认为false的一个全局变量,针对当前线程parseParameters()方法只会执行一次,下面看下parseParameters的关键代码。String contentType = getContentType(); if (contentType == null) { contentType = ""; } int semicolon = contentType.indexOf(';'); if (semicolon >= 0) { contentType = contentType.substring(0, semicolon).trim(); } else { contentType = contentType.trim(); } if ("multipart/form-data".equals(contentType)) { parseParts(false); success = true; return; } if( !getConnector().isParseBodyMethod(getMethod()) ) { success = true; return; } if (!("application/x-www-form-urlencoded".equals(contentType))) { success = true; return; }1.1当contentType有多种时,只会取第一种。比如contentType为application/json;application/x-www-form-urlencoded,最终是取application/json
1.2当contentType不为application/x-www-form-urlencoded退出,不在执行下面的关键代码
1.3当contenttype为multipart/form-data时,parseParts()方法里使用的解析文件的框架是apache自带的fileupload
1.4当contentType为application/x-www-form-urlencoded时,关键代码
try { if (readPostBody(formData, len) != len) { parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE); return; } }readPostBody()代码如下/** * Read post body in an array. * * @param body The bytes array in which the body will be read * @param len The body length * @return the bytes count that has been read * @throws IOException if an IO exception occurred */ protected int readPostBody(byte[] body, int len) throws IOException { int offset = 0; do { int inputLen = getStream().read(body, offset, len - offset); if (inputLen <= 0) { return offset; } offset += inputLen; } while ((len - offset) > 0); return len; }readPostBody()方法中用到了getStream()方法,很明显这里读了一次流(详细源码解读可以参考这篇博客点我,因为流不能重复读取(流不能回写),所以使用了request.getParameter()方法后再使用request.getinputstream()就无法获取报文了。那么同学可能会有这个疑问,那为什么我可以多次使用
request.getParameter()方法呢?接着看下面代码parameters.processParameters(formData, 0, formData.length);,processParameters调用了同名的重载方法processParameters(bytes, start, len, charset);,这个方法的工作就是将key和value拆分,然后调用java/org/apache/tomcat/util/http/Parameters.java的addParameter()方法将key和value保存到一个变量名为paramHashValues的map中,所以调用多少次request.getParameter()获取参数都没有问题,因为它已经将请求流中的数据封装到了map中了。addParameter()关键代码如下:
ArrayList<String> values = paramHashValues.get(key); if (values == null) { values = new ArrayList<>(1); paramHashValues.put(key, values); } values.add(value);总结: 1.当contentType为application/x-www-form-urlencoded时,request.getParameter()才能获取到参数。为其他contentType时,不执行下面的关键代码,不会读request的输入流;
2.request.getParameter()可以多次重复使用的原因是此方法将request的输入流数据封装到map中去了,所以可以重复获取;
3.request.getParameter()、request.getInputStream、request.getReader()三个方法之间是互斥的,原因是流是不能重复读取的。而不仅request.getInputStream会读取request的inputStream,request.getParameter()方法内部也会读取request的inputStream。
-
2.如何解决request.getInputStream流只能读取一次的问题
实际开发过程中,需要读去流最多的就是在过滤器和拦截器中。如果多个人开发同一个项目,每个人都写了一个过滤器,都使用了request.getInputStream获取报文显然是不行的。
网上有很多中解决办法,这里提供一种简单思路,但是具体情况要具体分析,即读取一次流后把报文放到request.setAttribute()中,使用有局限性。
多个filter的初始化顺序
如果定义了多个filter,并且指定了多个filter的执行顺序,但是并不代表多个filter的初始化顺序(filter执行init()方法的顺序)与执行顺序相同
filter开发过程中遇到的坑
- 1.doFilter之后再设置setHeader无效,具体原因有时间再分析,可暂时参考这篇文章点我