1、Filter
1.1、什么是Filter?
1.2、举例说明Filter
1.3、Filter的生命周期(与Servlet的生命周期比较)
1.4、FilterConfig类
1.5、FilterChain 过滤器链
2.ThreadLocal的使用
1、Filter
1.1、什么是Filter?
1、首先Filter是一个接口。
2、Filter是java Web三大组件之一。 javaWeb三大组件分别是:Servlet小程序、Filter过滤器、Listener监听器
3、Filter是服务器专门用来过滤请求,拦截响应的。
Filter的常见作用:
1、检查用户访问权限。
2、设置请求响应编码,解决乱码问题。
1.2、举例说明Filter
需求:
现在在WebContent目录下有一个目录admin。这个目录是管理员操作的目录。这个目录里有jsp文件,有html文件,还有图片资源文件。现在我们要让这些资源都在用户登录才能被访问。那么我们要怎么实现这样的需求。
思路:
Session的局限:有人可能会想,我们可以在用户登录之后。把用户的信息保存在Session域对象中。然后在jsp页面里通过Session域对象获取用户的信息,如果用户信息存在,说明用户已登录。否则就重定向到登录页面。这个方案可行。可是html页面呢? html页面是没有Session域对象的。或者访问一张图片呢?如何在访问之前拦截呢?
解决方案:
这就需要我们使用Filter过滤器来进行请求的拦截。然后判断Session域对象中是否包含用户的信息。
现在我们以admin目录下user.jsp为例进行讲解。
1、首先,我们需要创建一个类来实现Filter接口,用来检查Session中是否包含用户信息。
2、实现Filter中的doFilter方法
3、然后到web.xml文件中去配置Filter的过滤信息。
4、然后重启服务器访问测试
登录页面:
<body>
这是登录页面
<form action="userServlet" method="post">
用户名:<input name="username" /><br/>
密 码:<input name="password" type="password" /><br/>
<input type="submit" />
</form>
</body>
Filter过滤器
public class AdminFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 需要强转后使用。
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 获取Session对象
HttpSession session = httpRequest.getSession();
// 从Session中获取用户登录的信息
Object user = session.getAttribute("user");
if (user == null) {
// 说明用户还没有登录
System.out.println("Filter中检查,用户没有登录。跳回登录页面");
httpResponse.sendRedirect(httpRequest.getContextPath()
+ "/login.html");
return;
} else {
// 检查通过。放行
chain.doFilter(request, response);
}
}
}
web.xml中的配置:
<filter>
<filter-name>AdminFilter</filter-name>
<filter-class>com.tcent.filter.AdminFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
1.3、Filter的生命周期(与Servlet的生命周期比较)
Servlet的生命周期
1.先执行构造方法
2.执行init方法做初始化操作
3.执行Service方法
4.销毁的时候调用destory方法
1)第一次访问的时候初始化 构造方法与init方法,且只进行一次
2)在访问servlet的时候调用Service方法
3)Tomcat关闭Service被销毁的时候调用destory方法
Filter生命周期:
1、先执行Filter的构造方法
2、然后执行Filter的init方法
3、执行Filter的doFilter方法,每次访问资源,只要匹配过滤的地址,就会调用。
4、执行Filter的destroy方法
1)Filter在工程启动的时候初始化。构造方法与init方法,且只进行一次
2)在访问过滤的时候调用doFilter方法
3)Tomcat关闭Filter被销毁的时候调用destory方法
创建一个Filter2类。代码如下:
public class Filter2 implements Filter {
public Filter2() {
System.out.println("Filter2 构造 方法 被调用");
}
/**
* Filter初始化方法
*/
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter2 init 方法被调用。初始化……");
}
/**
* Filter的过滤方法
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("Filter2 doFilter 方法被调用 ");
// 一定要调用此方法,否则用户访问的资源会访问不到。
chain.doFilter(request, response);
}
/**
* Filter销毁的方法
*/
public void destroy() {
System.out.println("Filter2 的destroy方法被调用……");
}
}
1.4、FilterConfig类
作用:FilterConfig类和ServletConfig类是一样的。可以获取Filter在web.xml文件中的配置信息,做初始化之用。
我们可以在web.xml文件中给Filter添加初始化参数。然后在init初始化方法中使用FilterConfig类获取到初始化的参数。
FilterConfig类,一般有三个作用:
1、获取Filter在web.xml文件中配置的名称
2、获取Filter在web.xml文件中配置的初始化参数
3、通过FilterConfig类获取ServletContext对象实例
1.4.1、修改Filter2在web.xml中的配置信息
<!-- 配置Filter2 -->
<filter>
<!-- 给Filter2起一个名字 -->
<filter-name>Filter2</filter-name>
<!-- 是哪一个Filter类 -->
<filter-class>com.tcent.filter.Filter2</filter-class>
<!-- 配置初始化参数 -->
<init-param>
<!-- 初始化参数的名称 -->
<param-name>username</param-name>
<!-- 初始化参数的值 -->
<param-value>root</param-value>
</init-param>
</filter>
1.4.2、修改Filter2中init方法的代码如下:
/**
* Filter初始化方法
*/
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter2 init 方法被调用。初始化……");
// 获取Filter的名称
String filterName = filterConfig.getFilterName();
System.out.println("Filter name ==>>> " + filterName);
// 获取初始化参数。username的值
String username = filterConfig.getInitParameter("username");
System.out.println("username ==>> " + username);
// 获取ServletContext的对象实例
ServletContext ctx = filterConfig.getServletContext();
System.out.println(ctx);
}
1.4.3、然后重启Tomcat服务器,控制台打印如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9ae08f93a6cbe345162586c6250dbbc1.png)
1.5、FilterChain 过滤器链
FilterChain 过滤器链
Filter是过滤器
chain是链
FilterChain是过滤器链
FilterChain是整个Filter过滤器的调用者。Filter与Filter之间的传递,或者Filter与请求资源之间的传递都靠FilterChain.doFilter方法。
一般Filter.doFilter中的代码分为三段。
第一段是FilterChain.doFilter之前的代码。一般用来做请求的拦截,检查用户访问的权限,访问日记的记录。参数编码的设置等等操作。
第二段是FilterChain.doFilter方法。此方法可以将代码的执行传递到下一个Filter中。或者是传递到用户最终访问的资源中。
第三段是FilterChain.doFilter之后的代码。主要用过做一些日记操作。我们很少会在第三段中做太多复杂的操作。
在每一个Filter类的doFilter方法中,一定要调用chain.doFilter方法,除非你想要阻止用户继续往下面访问。否则一定要调用FilterChain的doFilter方法。
注:多个过滤器执行的先后顺序,是由它们在web.xml中从上到下到配置顺序决定。
图解:多个Filter过滤器的代码流转
![](https://i-blog.csdnimg.cn/blog_migrate/7ad456d7c20cc5bdabf8aefd46720bb1.png)
Filter的拦截路径
--精确匹配
比如: /admin/a.jsp 它表示只有请求地址是:http://ip:port/工程名/admin/a.jsp的时候,Filter过滤器才会拦截。
--目录匹配
比如: /admin/* 它表示只有请求地址是: http://ip:port/工程名/admin/* 的时候,Filter过滤器才会拦截。
--后缀名匹配
比如:
*.html 表示请求地址必须以html结尾,Filter才会拦截到。
*.jsp 表示请求地址必须以jsp结尾。filter才会拦截到。
*.action 表示请求地址必须以 action结尾。Filter才会拦截到。
*.do 表示请求地址必须以do结尾。Filter过滤器才会拦截到
*.abc 请求地址必须以abc结尾,Filter过滤器才会拦截到。
Filter只关心请求地址。不关闭资源是不是存在。
千万要注意:在Filter类的doFilter方法中,除非你要拦截请求的资源,否则一定要调用FilterChain参数的doFilter方法让代码的执行传递到下一个Filter或访问的资源中
2.ThreadLocal的使用
1、它可以像map一样存取数据。key永远是当前线程对象。
2、一般情况ThreadLocal类实例的时候,都是static类型。
3、在ThreadLocal对象实例中保存的数据。只要线程销毁了。虚拟机JVM会自动的释放对应的数据。
注:Threadlocal可以像map一样存取数据。在一个线程中,不管代码调用层级有多少层。只要是在ThreadLocal,同一个线程中都可以取出之前在ThreadLocal中保存的数
我们先来看一下。在线程里保存变量,然后在线程中取自己保存的变量的情况
1)map来实现线程保存变量:
public class TestThreadLocal1 {
// 定义一个整型
private static Map<String, Integer> map = new HashMap<String, Integer>();
// 随机数对象
private static Random random = new Random(System.currentTimeMillis());
static class MyTask implements Runnable {
Map<String, Integer> map;
public MyTask(Map<String, Integer> map) {
super();
this.map = map;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " -- begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 生成随机数,用于相加
int num = random.nextInt(1000);
//
System.out.println(Thread.currentThread().getName() + "生成一个随机数:" + num);
// 把随机数保存到map中
map.put(Thread.currentThread().getName(), num);
// 用线程做key获取自己的变量
System.out.println("获取" + Thread.currentThread().getName() + " -- "
+ map.get(Thread.currentThread().getName()));
System.out.println(Thread.currentThread().getName() + " -- end");
}
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new MyTask(map));
Thread t2 = new Thread(new MyTask(map));
t1.start();
t2.start();
}
}
2)ThreadLocal实现线程保存变量
public class TestThreadLocal2 {
// 定义一个整型
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
// 随机数对象
private static Random random = new Random(System.currentTimeMillis());
static class MyTask implements Runnable {
ThreadLocal<Integer> i;
public MyTask(ThreadLocal<Integer> i) {
this.i = i;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " -- begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 生成随机数,用于相加
int num = random.nextInt(1000);
//
System.out.println(Thread.currentThread().getName() + "生成一个随机数:" + num);
// 把随机数保存到map中
i.set(num);
// 用线程做key获取自己的变量
System.out.println("获取" + Thread.currentThread().getName() + " -- " + i.get());
System.out.println(Thread.currentThread().getName() + " -- end");
}
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new MyTask(threadLocal));
Thread t2 = new Thread(new MyTask(threadLocal));
t1.start();
t2.start();
}
}
经过上面两个小示例的代码,我们可以知道,,在多个线程里我们可以使用ThreadLocal保存线程自己需要的变量,而不需要担心线程安全的问题。所以我们只可以使用ThreadLocal来保存数据库的连接(与filter组合管理事务),这样在一次请求中。是一个线程处理所有的操作。使用Filter和ThreadLocal组合来控制事务,这样可以保正一个请求,使用相同的Connection对象。可以确保多个操作在一个连接的一个事务中完成。来达到。要么都成功 。要么都失败的效果。