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服务器,控制台打印如下:
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过滤器的代码流转
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对象。可以确保多个操作在一个连接的一个事务中完成。来达到。要么都成功 。要么都失败的效果。