简介
- 举个例子:如果一个系统有多种功能,但是大部分功能都要登录才能操作,否则跳登录页面,那么每个控制器都判断下不方便,即便抽出来作为共有的校验方法也不方便。
过滤器即可将共有的逻辑提取出来,并通过路径来校验,就比调用逻辑方法方便
过滤器类需要实现
Filter
接口,并实现doFilter
方法- 其实
**Filter**
就是一个特殊的**httpservlet**
对象,但是它不能对请求和响应进行处理。所以它的作用就是预处理(存疑,设置重定向难道不是对响应进行处理)
- 其实
@WebFilter
设置过滤链接,所有经过该设置参数的请求都会被进行校验FilterChain
是过滤器链,即过滤器可以有很多个,被过滤的请求会根据配置顺序被每一个过滤器筛选doFilter(request, response)
表示继续处理,即走下一个过滤。- 如果重写的
doFilter
什么都不做,那么将看到一个空白页,因为请求没有继续处理,默认响应是200+空白输出
- 如果重写的
- springboot中因为
**@WebFilter**
非springboot注解,所以需要添加被扫描。- 在main上增加
**@ServletComponentScan**
```java @WebFilter(urlPatterns = “/*”) //对所有请求都进行过滤 public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(“EncodingFilter:doFilter”);
request.setCharacterEncoding(“UTF-8”); response.setCharacterEncoding(“UTF-8”); chain.doFilter(request, response); } }//将输出和输入都定位utf-8
- 在main上增加
@WebFilter(“/user/*”) public class AuthFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(“AuthFilter: check authentication”); HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getSession().getAttribute(“user”) == null) { // 未登录,自动跳转到登录页。不再走剩下的过滤器和servlet控制器方法 System.out.println(“AuthFilter: not signin!”); resp.sendRedirect(“/signin”); } else { // 已登录,继续处理: chain.doFilter(request, response); } } }
<a name="N2AwB"></a>
# Springboot实现Filter
- spring中实现bean和servlet实现差不多,不同的是需要配置xml将spring的bean注册到servlet中。而在springboot中凭借自动配置就更加简单
- 也可以继续采用上面这种方法,但是需要添加`@ServletComponentScan`,**而如下方式除了简化外,还可以指定过滤器的顺序**
---
- 首先还是创建Filter的具体实现过滤器,无需再加`@WebFilter`
- 创建一个过滤器工厂,继承`FilterRegistrationBean`即可。该接口可以看成是Filter的工厂,在工厂可**以对传入的bean进行额外配置,最终所以过滤器bean都可以被**`**FilterRegistrationBean**`**统一管理**
- **一个过滤器就创建一个对应工厂类**
- Spring Boot会自动扫描所有的`FilterRegistrationBean`类型的Bean,然后,将它们返回的Filter自动注册到Servlet容器中,无需任何配置。
- **不设置过滤路径时过滤一切url,过滤器order越小优先级越高**
```java
@Component
public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {
@PostConstruct
public void init() {
setOrder(20); //设置优先级
setFilter(new ApiFilter());
List<String> list= Arrays.asList("/xx/*");
setUrlPatterns(list); //设置过滤路径
}
}
或者--------------------------------------------
public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {
@Override
public Filter getFilter() {
setOrder(30);
setFilter(new ApiFilter());
List<String> list= Arrays.asList("/xx/*");
return new AuthFilter();
}
}
@Bean
public FilterRegistrationBean xx() {
FilterRegistrationBean registration = new FilterRegistrationBean(new MyFilter());
registration.addUrlPatterns("/test/*"); //可选
registration.setName("MyFilter1"); //可选
registration.setOrder(1); //可选
//添加不过滤路径
registration.addInitParameter("excludedUris","/app/login,/app/test/");
return registration;
}
...
xml配置过滤顺序
- FilterChain是存放很多过滤器的集合,不同的过滤顺序可能会影响程序的运行效果,如本意先过滤
/x1
通后再过滤x1/y
,如果x1/y在在x1前就很有可能不一样- 配置过滤器顺序只能通过
web.xml
配置
- 配置过滤器顺序只能通过
springboot中可以通过setOrder设置过滤器顺序
过滤器修改请求与设置响应
过滤器要修改请求/响应,最好的方法是创建一个
HttpServletRequestWrapper/HttpServletResponseWrapper
的子类。这2者是ServletRespon/ServletRequest
间接子类,并实现了HttpServletRespon/HttpServletRequest
- 因此
...Wrapper
可以使用HttpServlet...
的很多方法,如设置请求头,获取请求头 ...Wrapper
不需要过于了解,它使用起来更像是个工具类
- 因此
- 下面的响应修改有点问题,使用
**HttpServletResponseWrapper**
构建输出流返回的内容是空的 - 我们可以使用缓存将固定的响应缓存起来,键为url。即在过滤器中创建个
ConcurrentHashMap<Map<String, byte[]> >
,对于固定内容的响应内容就存到该map中,之后直接从map取,内容即wrapper.getContent()
```java //—————修改请求————————————— public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){ RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); String a= requestWrapper.getHeader(“?”); //获取请求头 requestWrapper.addHeader(“?”,”?”); //修改或增加请求头 //对于删除请求头Wrapper增加map元素删除方法即可 chain.doFilter(requestWrapper, servletResponse); }
//——————-修改响应,这个没啥好说的,一般就构建响应后直接返回给前端(跟Servlet返回是一样的),而修改响应很多时候是修改后给后续操作继续使用—————————-
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){
// 构建响应主体,这里也可以使用PrintWriter构建响应,但是注意getOutputStream与getWriter()不能同时使用,否则会报响应getOutputStream() has already been called for this response
ServletOutputStream out =servletResponse.getOutputStream();
resp.setContentType(“text/html”); //设置响应类型
resp.setCharacterEncoding(“UTF-8”);
out.write(“错误请求”.getBytes(“UTF-8”));
out.flush();
out.close();
}
```java
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
//存储请求头信息的map
private Map<String,String> headers=new HashMap<>();
//存储请求体信息
private String tempBody;
public RequestWrapper(HttpServletRequest request) {
super(request);
tempBody = getBodyString(request);
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(tempBody.getBytes(Charset.forName("UTF-8")));
return new ServletInputStream() {
@Override
public boolean isReady() {
return false;
}
@Override
public int readLine(byte[] b, int off, int len) throws IOException {
return super.readLine(b, off, len);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
public void addHeader(String name,String value){
headers.put(name, value);
}
@Override
public String getHeader(String name) {
String value=super.getHeader(name);
if (headers.containsKey(name)){
value=headers.get(name);
}
return value;
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.tempBody;
}
public void setBody(String body) {
this.tempBody = body;
}
/**
* 获取请求Body
*
* @param request request
* @return String
*/
public String getBodyString(final ServletRequest request) {
try {
return inputStream2String(request.getInputStream());
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
}
}
/**
* 将inputStream里的数据读取出来并转换成字符串
*
* @param inputStream inputStream
* @return String
*/
private String inputStream2String(InputStream inputStream) {
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("", e);
}
}
}
return sb.toString();
}
}
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public ResponseWrapper(HttpServletResponse response) {
super(response);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream()
throws IOException
{
return out;
}
@Override
public void flushBuffer()
throws IOException
{
if (out != null)
{
out.flush();
}
}
public byte[] getContent()
throws IOException
{
flushBuffer();
return buffer.toByteArray();
}
class WrapperOutputStream extends ServletOutputStream
{
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos)
{
this.bos = bos;
}
@Override
public void write(int b)
throws IOException
{
bos.write(b);
}
@Override
public boolean isReady()
{
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0)
{
// TODO Auto-generated method stub
}
}
}