Filter(过滤器)也是在服务器,负责检查过滤,也可以执行任何request,response的操作。
过滤器是在服务器开启时,就已经创建了。而servlet是可以改变创建时机的。,能过滤服务器的一切资源。切记,能执行request,response的一切操作和修改request,response的操作。还能转向。
配置Filter(web.xml)
在web.xml中
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.qf.servlet.MyFilter</filter-class>
</filter>
//决定了拦截的顺序。先写的那个mapping,那个mapping就先执行
<filter-mapping>
<filter-name>MyFilter</filter-name>
<!-- 拦截的路径-->
<url-pattern>/hello.jsp</url-pattern>
</filter-mapping>
Filter在服务器启动时,Filter就创建好了。执行顺寻与创建顺序无关。
注解(@webfilter)
在web.xml的顶部需要配置 metadata-complete=“false”,默认为false,如果改为true,不会去扫描注解,只会查看配置文件
采用注解的形式,过滤器的执行顺序与全限定名有关。会根据全限定名进行比较。
过滤器链
Filter的优先级 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/904b613cf1bfd506374dff997b91049b.png)
过滤器配置参数
web.xml
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.qf.servlet.MyFilter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>xiaoxuan</param-value>
</init-param>
</filter>
客户端第一次请求资源,服务端会把资源发送给服务端,并把ETag和Last-Modified发送给客户端,当客户端再次请求时,会带着这个去请求服务端,如果和服务端的信息没差别,服务端就发送304,告诉客户端上自己的缓存里去找。
过滤器的应用
浏览器缓存
cache-cotrol:缓存存活时间,单位是秒,如果没有缓存,no-cache
expires:过期时间,单位是毫秒,没有缓存,0
pargma:控制缓存,没有缓存 nocache
现在的大部分浏览器不会缓存动态页面,都会去请求服务端。
禁止缓存
现在浏览器已经没有问题了。
HttpServletResponse response = (HttpServletResponse)resp;
//设置无缓存
response.setHeader("cache-control", "no-cache");
//设置过期时间
response.setDateHeader("expires", 0);
response.setHeader("pragma", "no-cache");
chain.doFilter(req, resp);
允许缓存
HttpServletResponse response = (HttpServletResponse)resp;
//单位为秒,缓存的时间
response.setHeader("cache-control", "max-age=600");
//单位毫秒,记录过期的时间
response.setDateHeader("expires", System.currentTimeMillis()+600000);
chain.doFilter(req, resp);
这样就可以不需要访问服务器,浏览器自己去找自己的缓存。
自动登录
当成功登录之后,应该把自己的用户名和密码保存起来,下次能直接访问数据。
总的来说,就是在登录成功,检查是否选择自动登录按钮,如果选择了,就把账号和密码通过加密,存放到cookie,设置有效时间,当下次进入项目时,先检查当前用户是否在登录,如果没有,就把cookie中的数据解密,验证账号和密码是否正确,如果正确,就把数据存放到session中。
UserService userService = new UserServiceImpl();
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
//判断当前是否登录
String username = (String) request.getSession().getAttribute("username");
if(username != null) {
chain.doFilter(req,resp);
return;
}
//获取cookie
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if(cookie.getName().equals("userInfo")){
String value = cookie.getValue();
//解密
byte[] decode = Base64.getDecoder().decode(value);
String info = new String(decode);
String[] infos = info.split("#");
User user = userService.queryByUserNameAndPassword(infos[0], infos[1]);
if(user != null) {
request.getSession().setAttribute("username", infos[0]);
}else {
Cookie cookie1 = new Cookie(infos[0],"");
cookie1.setPath(request.getContextPath());
cookie1.setMaxAge(0);
response.addCookie(cookie1);
}
}
}
}
chain.doFilter(req, resp);
过滤敏感词汇
一般我们都是将数据发送给服务器,服务器在响应回来,我们只要知道服务器用的什么方法,只要在过滤器中重载某些方法,并把自己创建的requset或者response传给目标资源,目标资源就调用的我们的重载的方法,依次达到不同的效果。
比如我们将评论提交给服务器,服务器需要把敏感词进行过滤。
这就是目标资源,它通过使用request.getParameter()方法得到数据。所以我们就在过滤器中把这个方法进行重载
@WebServlet(name = "CommentServlet", value = "/comment")
public class CommentServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String comment = request.getParameter("comment");
response.getWriter().write(comment);
}
过滤器:
@WebFilter(filterName = "CleanDirtyFilter", value = "/comment")
public class CleanDirtyFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest realRequest = (HttpServletRequest)req;
//把req中的getParameter()方法进行重载,把自己创建的request传给下一个链或者目标资源
MyRequest request = new MyRequest(realRequest);
chain.doFilter(request, resp);
}
public void init(FilterConfig config) throws ServletException {
}
//HttpServletRequestWrapper只是包装了request,里面存了request的引用,里面的方法都是和request相同的,方法里面是调用的request的方法
class MyRequest extends HttpServletRequestWrapper {
//需要把脏词保存起来
private List<String> dirtyWords;
public MyRequest(HttpServletRequest request) {
super(request);
dirtyWords = new ArrayList<>();
dirtyWords.add("笨猪");
dirtyWords.add("好兄弟");
dirtyWords.add("好朋友");
dirtyWords.add("铁子");
}
@Override
public String getParameter(String name) {
String comment = super.getParameter(name);
for (String dirtyWord : dirtyWords) {
comment = comment.replaceAll(dirtyWord, "***");
}
return comment;
}
}
}
结果:
压缩
一般都是压缩文本,比如我们去访问一个新闻
显示为4669字节,数据太大,影响效率。所以我们需要进行压缩。
我们是用jsp显示的这条内容。
@WebFilter(filterName = "NewsFilter",value = "/news.jsp")
public class NewsFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//修改resp
HttpServletResponse realResponse = (HttpServletResponse)resp;
MyResponse response = new MyResponse(realResponse);
chain.doFilter(req, response);
//得到内存流
ByteArrayOutputStream byteOut = response.getByteOut();
//因为内存输出流的toByteArray(),toString就可以把缓冲区的数据给读出来,这个和普通的输出流是不一样的。
//创建压缩流
ByteArrayOutputStream newByteOut = new ByteArrayOutputStream();
System.out.println("压缩之前的大小:"+byteOut.size());
GZIPOutputStream gzip = new GZIPOutputStream(newByteOut);
gzip.write(byteOut.toByteArray());
gzip.finish();
gzip.close();
//告诉浏览器改内容已经被压缩
response.setHeader("Content-Encoding", "gzip");
System.out.println("压缩之后的内容:"+newByteOut.size());
response.getOutputStream().write(newByteOut.toByteArray() );
}
public void init(FilterConfig config) throws ServletException {
}
static class MyResponse extends HttpServletResponseWrapper{
//打印流
private PrintWriter pw;
//内存输出操作流
private ByteArrayOutputStream byteOut;
public MyResponse(HttpServletResponse response) {
super(response);
try {
byteOut = new ByteArrayOutputStream();
//将打印流写入内存
pw = new PrintWriter(new OutputStreamWriter(byteOut,"utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//重载方法,让目标资源用我创建的打印流
@Override
public PrintWriter getWriter() throws IOException {
//返回的是我创建的流
return pw;
}
//等响应回来我们还需要从内存流里读出数据。所以我们还需要获取内存流
public ByteArrayOutputStream getByteOut(){
//不为空,需要刷新缓冲区
if(pw != null) {
pw.flush();
}
return byteOut;
}
}
}
实现图片防盗链
只有本网站才能访问的资源(照片),如果别的网站访问这个照片,请求是别的网站的,这就是
盗取。这个也可以通过过滤器来过滤。
@WebFilter(filterName = "StealFilter", value = "*.png")
public class StealFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)req;
//请求服务器的客户端的地址
String referer = request.getHeader("referer");
System.out.println(referer);
//图片的地址
String path = "http://localhost:8080"+request.getContextPath();
//refer何时为空,在浏览器上面写上该地址去访问,refer就为null
if(referer != null && referer.startsWith(path)){
HttpServletResponse response = (HttpServletResponse)resp;
//相同说明是正常访问
chain.doFilter(req, resp);
return;
}
//如果是非正常访问
request.getRequestDispatcher("/upload/steal.jpg").forward(request,resp);
System.out.println(request.getRequestURI());
chain.doFilter(req,resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
监听器
监听器的类型
监听某个的对象放入session中或者移出
是对象要实现这个接口
监听的是这个对象
不需要注解或者配置web.xml文件
public class User implements HttpSessionBindingListener {
private String name;
private String color;
public User() {
}
public User(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("对象进入了session");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("对象移出了session");
}
}
<%
User user = new User("小红","绿色");
session.setAttribute("user", user);
%>
<body>
${pageContext.session.removeAttribute("user")}
</body>
对象的钝化与活化
钝化:内存到硬盘
活化:硬盘到内存
还需要实现序列化接口
配置文件:
管理session
因为在服务器中创建的session默认是30分钟清除,也就是失效,但是我们可以自己设置有效时间。这就用到了监听器。
@WebListener
public class SessionManager implements ServletContextListener, HttpSessionListener {
//容器,存放session
private ConcurrentHashMap<String, HttpSession/> map;
//定时器
private Timer timer;
@Override
public void contextInitialized(ServletContextEvent sce) {
//初始化
map = new ConcurrentHashMap<>();
timer = new Timer();
//定时器开始
timer.schedule(new TimerTask() {
@Override
public void run() {
if(map.size()>0){
for (Map.Entry<String, HttpSession> entry : map.entrySet()) {
HttpSession value = entry.getValue();
//比较时间
if(System.currentTimeMillis()-value.getLastAccessedTime()>60000) {
//移出
map.remove(entry.getValue());
System.out.println("过期了"+value.hashCode());
//失效
value.invalidate();
}
}
}
}
},0,10000);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
map = null;
timer.cancel();
}
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建了session"+se.getSource().hashCode());
HttpSession session = (HttpSession)se.getSource();
map.put(session.getId(),session);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session销毁了");
}
}