目录
一、什么是 Filter 过滤器?
当浏览器向服务器发送请求想要浏览某个资源时,首先会经过 Filter 过滤器,判断是否符合权限
1、Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器
二、应用举例
假设我们在 web 目录下有一个 admin 目录,里面有一些资源【图片,html,页面....】 ,我们希望登录过后才能访问,没有登录就跳到登录界面。
那么按照我们之前学习的内容:应该是在登录成功的时候,将 用户 保存到 session 域中,在跳转资源的时候,判断session域中用户是否为空,为空说明还没有登录,则跳转到登录界面。不为空可以访问资源。
<%
/*判断,如果user为空,则跳转到登录页面。*/
Object user = session.getAttribute("user");
if (user == null){
request.getRequestDispatcher("/login.jsp").forward(request,response);
return;
}
%>
当我们访问 jsp 页面时,没有登录他会跳转到登录页面。
当我们没有登录时,仍然能访问 a.html 页面还有图片,所以说这种方法是有局限性的。
它仅仅只能在 jsp 页面上判断,html,图片就无法判断 session域是否为空了。所以我们需要使用 Filter 过滤器。
三、使用过滤器的步骤
1、 写一个类实现 javax.servlet.Filter 接口,并且实现 Filter接口中的三个方法。init,doFilter,destroy 。
2、在 doFilter方法中,编写拦截操作。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//
HttpServletRequest request = (HttpServletRequest) servletRequest;
Object user = request.getSession().getAttribute("user");
if (user == null){
//拦截操作:如果user为空,说明没有登录。
//跳转到登录界面servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else{
//可以访问资源
//具体可以访问哪些资源,在 web.xml 文件中配置
// 这段代码很重要,如果不写就算登陆成功也访问不了任何资源。
filterChain.doFilter(servletRequest,servletResponse);
}
}
3、在 web.xml 文件中配置可以访问哪些资源。与配置Servlet类似。
拦截的路径可以写多个地址。
<filter>
<!--给 filter 起一个别名-->
<filter-name>FilterTest</filter-name>
<!--完整类名-->
<filter-class>yangzhaoguang.FilterTest</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterTest</filter-name>
<!--
配置拦截路径:
/ : 表示:http;//ip:port/工程路径/
/admin/* : 表示对admin目录下的所有资源都进行拦截操作。
-->
<url-pattern>/admin/*</url-pattern>
<url-pattern>/admin/b.jsp</url-pattern>
</filter-mapping>
这个时候我们在去访问admin下的资源:都可以拦截到了。如果还能访问到资源,说明浏览器还有缓存,清除一下就好了。
以上是未登录成功拦截到的情况,下面演示以下登录成功后,能不能访问到。
提供一个登录页面和 LoginServlet :
<form action="/filter/loginServlet" method="post">
用户名:<input type="text" name="username" ><br>
密码:<input type="password" name="password" ><br>
<input type="submit" value="登录">
</form>
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("admin".equals(username) && "admin".equals(password)){
//登录成功,将用户信息保存到 session 域中
request.getSession().setAttribute("user",username);
request.getRequestDispatcher("/login_success.jsp").forward(request,response);
}else{
//没有登录成功,调回登录界面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
}
登录成功后,admin下的所有资源都能访问到,就没有问题了。
,
四、Filter 的生命周期
类似于Servlet 中的 init ,service ,destroy 方法。
五、FilterConfig 类
<!--在 filter 标签里,配置FilterConfig参数。-->
<init-param>
<param-name>name1</param-name>
<param-value>value1</param-value>
</init-param>
<!--也可以配置多个-->
<init-param>
<param-name>name2</param-name>
<param-value>value2</param-value>
</init-param>
filterConfig.getFilterName()
filterConfig.getInitParameter("name")
filterConfig.getServletContext()
初始化时获取参数:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2 init初始化方法执行");
// 1、获取 Filter 的名称 filter-name 的内容
System.out.println("获取 filter-name:"+ filterConfig.getFilterName());
// 2、获取在 Filter 中配置的 init-param 初始化参数
System.out.println("init-param中的 name1 的value" + filterConfig.getInitParameter("name1"));
System.out.println("init-param中的 name2 的value" + filterConfig.getInitParameter("name2"));
// 3、获取 ServletContext 对象
System.out.println("ServletContext对象:"+filterConfig.getServletContext());
}
六、FilterChain 类
我们可以看到执行顺序和图中的顺序一样
1、多个过滤器先后执行顺序是由 web.xml 文件决定的,先配置哪个过滤器,哪个过滤器就会先执行。
如果先配置 Filter2 。
先配置 Filter2,Filter2就会先执行。
2、 Filter中 靠 filterChain.doFilter(); 来继续访问下一步的资源,如果没有这段代码,后面的资源将不会被访问,代码也不会被执行。
将Filter1中的 filterChain.doFilter() 去掉,那么后面的 Filter2 和 b.jsp 页面都不会执行到。
3、从图中也可以看出来,这一整个流程都是在一个请求中完成的,也就是他们共享同一个请求域。
七、Filter 的拦截路径
八、 ThreadLocal 的介绍
ThreadLocal 是 JDK 中的一个类,ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量【(可以是普通变量,可以是对象,也可以是数组,集合】与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。
与synchronized的关系:
有些文章拿ThreadLocal和synchronized比较,其实它们的实现思想不一样。
- synchronized是同一时间最多只有一个线程执行,所以变量只需要存一份,算是一种时间换空间的思想
- ThreadLocal是多个线程互不影响,所以每个线程存一份变量,算是一种空间换时间的
构造方法摘要 | |
---|---|
ThreadLocal() 创建一个线程本地变量。 |
T | get() 取出当前线程池中的变量值 |
void | remove() 移除此线程局部变量当前线程的值。 |
void | set(T value) 存入 线程池 的变量值为value |
使用 ThreadLocal 是不需要设置 key 的值,key 就是当前线程的名字。
public class ThreadLocalTest {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
//创建多个线程
new Thread(new Task()).start();
}
}
/* <> 指的是 value的类型*/
public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
private static Random random = new Random();
//创建一个线程任务
public static class Task implements Runnable {
@Override
public void run() {
//获取当前线程的名字
String name = Thread.currentThread().getName();
//获取一个随机数
int i = random.nextInt(200);
// 将当前线程与 一个值 相关联。
threadLocal.set(i);
System.out.println("存入线程[ " + name + " ] 关联的值:" + threadLocal.get());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//取出相关联的值
System.out.println("取出线程[ " + name + " ] 关联的值:" + threadLocal.get());
}
}
}
我们可以看到 ThreadLocal 不仅是安全的,并且它并不影响多个线程的使用。并且每个线程都会保存一个变量。