Filter
执行任何request,response的操作
请求与响应时都会经过Filter
在chain.doFilter方法之前的在请求时执行,在chain.doFilter方法之后在响应时执行
Tomcat启动时就会创建,关闭时销毁
Filter配置
1.配置web.xml
方式同Servlet
<!-- 配置过滤器-->
<filter>
<!-- filter名称-->
<filter-name>MyFilter</filter-name>
<!-- 全限定名-->
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping>
<!-- 映射名,与filter名称对应-->
<filter-name>MyFilter</filter-name>
<!-- 过滤拦截路径-->
<url-pattern>/hello.jsp</url-pattern>
</filter-mapping>
使用注解
路径中 不可使用 /
过滤器链
多个Filter可拦截同一个网页
请求时先1后2,响应时先2后1
请求时先执行的,响应时会后执行
拦截优先级
1.如果为web.xml,按照filter-mapping注册顺序,从上往下。谁写在前面谁优先级高
2.使用注解时,是按照类全名称的字符串顺序决定作用顺序。
web.xml配置优先级高于注解配置
如果注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次。
参数配置
可在初始方法init方法中获得,方法同Servlet
web.xml中配置
<init-param>
<param-name>namespace</param-name>
<param-value>chichi</param-value>
</init-param>
注解中配置
方式同Servlet
大坑大坑:看别人代码时要注意
metadata-complete属性表示部署时当前的配置是否完全,值为true,表示完全,只会应用web.xml的配置,而不会去扫描类似@WebServlet,@WebFilter,@WebListener等注解和web-frame.xml配置。
默认值是metadata-complete=false,即不完全,会对项目中类进行扫描,是否有相关的注解配置,同时也会加载web-frame.xml等插件配置。
Filter应用
正常流程:
客户端第一次请求资源,服务端会把资源发送给服务端,并把ETag和Last-Modified发送给客户端,当客户端再次请求时,会带着这个去请求服务端,如果和服务端的信息没差别,服务端就发送304,告诉客户端上自己的缓存里去找。
禁止缓存缓存动态页面
通过下面的设置可让游览器不能直接使用缓存,必须要先请求服务器,服务器返给游览器302,游览器再从本地缓存中得到数据,目前的游览器都默认此功能
Cache-Control:no-cache:指示请求或响应消息不能缓存,实际上是可以存储在本地缓存区中的,只是在与原始服务器进行新鲜度验证之前,缓存不能将其提供给客户端使用。
通过以上设置可禁止游览器缓存动态页面
Filter设置响应头,直接使用本地缓存
第一次请求响应时,设置响应头,再次请求相同数据时直接从本地缓存返回,不再向服务器提交请求(仅IE游览器有效,Chrome会忽略这个设置)
在doFilter方法中加入
response.setHeader("Cache-Control","max-age:600");
response.addDateHeader("Expires",System.currentTimeMillis()+600000);
max-age 缓存储存的时间 单位时间为s
Expires:缓存过期时间 单位ms
自动登录
第一次访问登录后,将账户密码放在cookie中返回。为首页加入Filter。
下次访问首页时(刷新或再次打开游览器访问),
1.先检查当前用户是否在登录状态(Session中有没有用户信息),若Session存在用户信息,即刷新情况,直接进入首页
2.若不存在,再验证cookie中有正确的账户密码,正确则直接进入首页,并把数据放入Session中,不再需要登录,否则转入登录界面
// 1.判断当前是否处于登录状态
if (user!=null){
// 已登录,去首页
chain.doFilter(req,resp);
return;
}
Cookie[] cookies = request.getCookies();
if (cookies!=null){
for (Cookie cookie : cookies) {
if (cookie.getName().equals("userinfo")){
String value = cookie.getValue();
String[] split = value.split("#");
if (split[0].equals("admin")&&split[1].equals("admin")){
request.getSession().setAttribute("user",split[0]);
}else {
// cookie被篡改了,无用了,使其失效
Cookie cookie1 = new Cookie("userinfo", "");
cookie1.setPath("/0904web1");
cookie1.setMaxAge(0);
response.addCookie(cookie1);
}
}
}
}
chain.doFilter(req, resp);
脏词过滤
表单输入内容中含有脏词,为表单接收页加入Filter,将它变为**
自定义MyRequest,重写getParameter方法,实现对表单输入内容的改变
在chain.doFilter方法中传入自定义MyRequest,这样在服务器中使用getParameter方法获得表单数据时,得到的就是我们进行过滤之后的内容。
static class MyRequest extends HttpServletRequestWrapper {
// 脏词集合
private List<String> dirtyWords;
private HttpServletRequest request;
public MyRequest(HttpServletRequest request) {
super(request);
this.request = request;
dirtyWords = new ArrayList<>();
dirtyWords.add("猪");
dirtyWords.add("狗");
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
for (String dirtyWord : dirtyWords) {
value = value.replaceAll(dirtyWord, "***");
}
return value;
}
}
使用时将此MyRequest传入chain.doFilter中即可
JSP页面压缩
仅对页面压缩,并不对其中的图片等需再次请求的压缩
JSP是一个文本页面,响应时会固定调用getWriter方法的流写入到response中,若其中有图片等的,服务器会返回一个重定向地址,让游览器再次请求图片,这次使用二进制流,不归我们管。我们仅对response重写时仅需重写getWriter方法
在Filter中:
doFilter方法前:传给服务器一个自定义MyResponse,重写getWriter方法,让服务器将响应内容写入其中内存流 ByteArrayOutputStream中
doFilter方法后:将服务器传回响应内容通过压缩流GZIPOutputStream压缩至内存流,最后通过响应字节流将内容输出。
此方法能达到约3:1的压缩比
@WebFilter(filterName = "GipFilter",value = "/news.jsp")
public class GipFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
MyResponse myResponse = new MyResponse((HttpServletResponse) resp);
chain.doFilter(req,myResponse);
ByteArrayOutputStream bos = myResponse.getBos();
System.out.println("原始大小"+bos.size());
// 内容通过压缩流压缩后再次写入内存流
ByteArrayOutputStream newBos = new ByteArrayOutputStream();
GZIPOutputStream gs =new GZIPOutputStream(newBos);
gs.write(bos.toByteArray());
// 之后要记得 flush() 和finish() ,不然压缩包的东西是空的
gs.flush();;
gs.close();
System.out.println("压缩后大小"+newBos.size());
// 响应给客户端
// 设置响应头,告知返回内容为压缩的
myResponse.setHeader("Content-Encoding","gzip");
myResponse.getOutputStream().write(newBos.toByteArray());
}
public void init(FilterConfig config) throws ServletException {
}
static class MyResponse extends HttpServletResponseWrapper {
private PrintWriter pw;
private HttpServletResponse response;
// 将服务器响应写入内存流中
private ByteArrayOutputStream bos;
public MyResponse(HttpServletResponse response) {
super(response);
this.response = response;
bos = new ByteArrayOutputStream();
try {
// 将Printer流中内容写入内存流
pw = new PrintWriter(new OutputStreamWriter(bos,"utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public PrintWriter getWriter() throws IOException {
return pw;
}
public ByteArrayOutputStream getBos(){
// 刷新,防止缓冲
if (pw != null) {
pw.flush();
}
return bos;
}
}
}
实际图片防盗链
HTTP协议中,如果从一个网页跳到另一个网页,HTTP头字段里面会带个Referer。图片服务器通过检测Referer是否来自规定域名,来进行防盗链。
作用:防止其它网站盗用你的图片。
只有本网站才能访问的资源(照片),如果别的网站访问这个照片,请求是别的网站的,这就是盗取。
Listener监听器
监听器类型
三大类
感知型要让被监听的对象的类实现接口,监听的是对象,当这个对象被放入session中时,会触发监听器。
感知型监控器不需要注册
钝化指序列化,内存到硬盘,活化指反序列化硬盘到内存,
对象的类需要同时实现HttpSessionActivationListener接口和Serializable接口
生命周期监听器应用
application:创建必要初始化
session:通过创建来统计网站的用户访问量
配置方式
实例
继承接口,实现对应方法
package listener; /**
* 2020/9/4
* 21:28
* zmx
*/
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.HttpSessionBindingEvent;
@WebListener()
public class LifeCycleListener implements ServletContextListener,
HttpSessionListener, HttpSessionAttributeListener {
// Public constructor is required by servlet spec
public LifeCycleListener() {
}
// -------------------------------------------------------
// ServletContextListener implementation
// -------------------------------------------------------
public void contextInitialized(ServletContextEvent sce) {
/* This method is called when the servlet context is
initialized(when the Web application is deployed).
You can initialize servlet context related data here.
*/
}
public void contextDestroyed(ServletContextEvent sce) {
/* This method is invoked when the Servlet Context
(the Web application) is undeployed or
Application Server shuts down.
*/
}
// -------------------------------------------------------
// HttpSessionListener implementation
// -------------------------------------------------------
public void sessionCreated(HttpSessionEvent se) {
/* Session is created. */
}
public void sessionDestroyed(HttpSessionEvent se) {
/* Session is destroyed. */
}
// -------------------------------------------------------
// HttpSessionAttributeListener implementation
// -------------------------------------------------------
public void attributeAdded(HttpSessionBindingEvent sbe) {
/* This method is called when an attribute
is added to a session.
*/
}
public void attributeRemoved(HttpSessionBindingEvent sbe) {
/* This method is called when an attribute
is removed from a session.
*/
}
public void attributeReplaced(HttpSessionBindingEvent sbe) {
/* This method is invoked when an attribute
is replaced in a session.
*/
}
}
扩展
自定义的request和response
创建自定义的request和response。分别继承HttpServletRequestWrapper,HttpServletResponseWrapper.(这是两个包装类)
重写其中的某些方法,来达到我们自己的目的,如重写其@Override public String getParameter(String name) 方法
我们可以对表单内容进行先进行操作。当调用自定义request该方法时,就可以获得我们想要的结果。而不是表单传过来的内容。
如利用Filter实现脏词过滤
内存操作流
ByteArrayInputStream和ByteArrayOutputStream
ByteArrayOutputStream
将内容输出到内存中,而不是文件中
其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据
内存流无法使用close关闭,不会产生任何 IOException