一、Filter过滤器
1、Filter什么是过滤器
1) Filter过滤器是JavaWeb的三大组件之一;三大组件分别是Servlet程序、Listener监听器、Filter过滤器
2) Filter过滤器是JavaEE的规范,也就是接口
3) Filter过滤器作用是:拦截请求、过滤响应。
拦截请求常见的应用场景有:
① 权限检查
② 日记操作
③ 事务管理
... ...等
2、Filter操作
要求:在web工程下,有一个admin目录,这个admin目录下的所有资源(html页面、jpg图片、jsp文件等)都必须是用户登陆之后才允许访问。
用户登录才允许访问的实现思路 ①:
根据之前学过的内容,可以知道,用户登录之后都会把用户登录的信息保存到Session域中。所以要检查用户是否登录,可以判断Session中是否包含有用户登录的信息即可!!!
存在局限性,只能在jsp页面去写,html页面或者图片不能实现。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
Object user = session.getAttribute("user");
//如果等于null,说明还没登录
if (user==null){
request.getRequestDispatcher("/login.jsp").forward(request,response);
return;
}
%>
b.jsp文件
</body>
</html>
用户登录才允许访问的实现思路 ②:
采用Filter过滤器可以来实现
过滤器的功能实现如下所示:
类似于Servlet
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
// doFilter方法,专门用于拦截请求(可以做权限检查),过滤响应
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明还没登录
if (user==null){
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else {
//让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
在web.xml文件中的配置如下: - - 其中 /admin/* 表示拦截此路径下的所有。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--同Servlet类似-->
<!--filter标签用于配置一个Filter过滤器-->
<filter>
<!--给filter起一个别名-->
<filter-name>AdminFilter</filter-name>
<!--配置filter的全类名-->
<filter-class>filter.AdminFilter</filter-class>
</filter>
<!--filter-mapping配置Filter过滤器的拦截路径-->
<filter-mapping>
<!--filter-name表示当前的拦截路径给哪个filter使用-->
<filter-name>AdminFilter</filter-name>
<!--url-pattern配置拦截路径
/ 表示请求地址为:http://ip:port/工程路径/ 映射到IDEA的web目录
/admin/* 表示请求地址为:http://ip:port/工程路径/admin/* 拦截全部
-->
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
</web-app>
3、完整的用户登录
AdminFilter实现类中doFilter方法拦截请求如下: /admin/* 拦截此路径下的所有
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
// doFilter方法,专门用于拦截请求(可以做权限检查),过滤响应
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明还没登录
if (user==null){
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else {
//让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
login.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
这是login登录界面<br/>
<form action="http://localhost:8080/15_filter/loginServlet" method="get">
用户名:<input type="txet" name="username"><br/>
密 码:<input type="password" name="password"><br/>
<input type="submit" name="提交">
</form>
</body>
</html>
登录的LoginServlet代码如下:
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
response.setContentType("text/html;charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("wzg168".equals(username) && "123456".equals(password)){
request.getSession().setAttribute("user",username);
response.getWriter().write("登录 成功!");
}else {
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
}
解释如下:
由于拦截了admin路径下的所有,因此在不登录的前提下是不可以访问路径下的文件的。
访问login,jsp页面,进行登录请求,跳转到LoginServlet程序,获取请求参数,并判断用户名和密码是否与要求的一致,不一致重新输入,一致就登录成功,并赋值给Session。
当用户登录之后就可以访问之前被拦截的文件了,例如:http://localhost:8080/15_filter/admin/a.html http://localhost:8080/15_filter/admin/b.jsp
4、Filter的生命周期
Filter的生命周期包含几个方法:
① 构造器方法
② init初始化方法
第①、②步,在web工程启动的时候执行(Filter已经创建)
③ doFilter过滤方法
第③步,每次拦截到请求,就会执行
④ destroy销毁
第④步,停止web工程的时候,就会执行(停止web工程,也会销毁Filter过滤器)
public class AdminFilter implements Filter {
public AdminFilter() {
System.out.println("1、Filter构造器方法AdminFilter()");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2、Filter的init(FilterConfig filterConfig)初始化");
}
// doFilter方法,专门用于拦截请求(可以做权限检查),过滤响应
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("3、Filter的doFilter()过滤方法");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明还没登录
if (user==null){
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else {
//让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
System.out.println("4、Filter的destroy()销毁方法");
}
}
5、FilterConfig类
FilterConfig类见名知义,它是Filter过滤器的配置文件类;Tomcat每次创建Filter的时候,也会同时创建一个FilterConfig类,这里包含了Filter配置文件的配置信息。
FilterConfig类的作用是获取filter过滤器的配置内容:
① 获取Filter的名称filter-name的内容
② 获取Filter在web.xml中配置的init-param初始化参数
③ 获取ServletContext对象
web.xml文件加入init初始化参数配置如下:
<!--配置初始化参数-->
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost3306/test</param-value>
</init-param>
下面再init方法中进行演示:
public class AdminFilter implements Filter {
public AdminFilter() {
System.out.println("1、Filter构造器方法AdminFilter()");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("2、Filter的init(FilterConfig filterConfig)初始化");
// 1、获取Filter的名称filter-name的内容
System.out.println("filter-name的值是:"+filterConfig.getFilterName());
// 2、获取在web.xml中配置的init-param初始化参数
System.out.println("初始化参数username的值是:"+filterConfig.getInitParameter("username"));
System.out.println("初始化参数url的值是:"+filterConfig.getInitParameter("url"));
// 3、获取ServletContext对象
System.out.println(filterConfig.getServletContext());
}
// doFilter方法,专门用于拦截请求(可以做权限检查),过滤响应
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("3、Filter的doFilter()过滤方法");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
//如果等于null,说明还没登录
if (user==null){
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
return;
}else {
//让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
System.out.println("4、Filter的destroy()销毁方法");
}
}
6、FilterChain过滤器链
Filter 过滤器
Chain 链,链条
FilterChain 就是过滤器链(多个过滤器如何一起工作)
多个Filter过滤器的底层工作原理:
重点:
1) FilterChain.doFilter()方法的作用
① 执行下一个Filter过滤器(如果有其他Filter的前提下)
② 执行目标资源(已经没有Filter的前提下)
2) 在多个Filter过滤器执行的时候,它们的执行优先顺序是由它们在web.xml中从上到下配置的顺序决定!!!
3) 多个Filter过滤器执行的特点:
① 所有filter和目标资源默认都执行在同一个线程中
② 多个Filter共同执行的时候,它们都使用同一个Request对象
Filter1代码如下:
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter1 前置代码");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("Filter1 后置代码");
}
@Override
public void destroy() {
}
}
Filter2代码如下:
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter2 前置代码");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("Filter2 后置代码");
}
@Override
public void destroy() {
}
}
target.jsp代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
System.out.println("target.jsp页面执行了");
%>
</body>
</html>
web.xml配置文件
<filter>
<filter-name>Filter1</filter-name>
<filter-class>filter.Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>filter.Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/target.jsp</url-pattern>
</filter-mapping>
执行http://localhost:8080/15_filter/target.jsp显示结果如下:
Filter1 前置代码
Filter2 前置代码
target.jsp页面执行了
Filter2 后置代码
Filter1 后置代码
注:
情况1:如果过滤器2的filterChain.doFilter()及其后置的代码不存在
结果如下:
Filter1 前置代码
Filter2 前置代码
Filter1 后置代码
情况2:如果过滤器1的filterChain.doFilter()及其后置的代码不存在
结果如下:
Filter1 前置代码
7、Filter的拦截路径
二、ThreadLocal的使用
1、TheadLocal的介绍
该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程(例如:用户ID或事务ID)相关联。
ThreadLocal可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组、集合)。
ThreadLocal的作用:
可以解决多线程的数据安全问题
ThreadLocal的特点:
① ThreadLocal可以为当前线程关联一个数据(它可以像Map一样存取数据,key为当前线程)
② 每一个ThreadLocal对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal对象实例
③ 每个ThreadLocal对象实例定义的时候,一般都是static类型
④ ThreadLocal中保存数据,在线程销毁后,会由JVM虚拟自动释放。
测试OrderDao类代码如下:
public class OrderDao {
public void SaveOrder(){
String name = Thread.currentThread().getName();
//System.out.println("saveOrder当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.data.get(name));
System.out.println("saveOrder当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get());
}
}
测试OrderService类代码如下:
public class OrderService {
public void createOrder(){
String name = Thread.currentThread().getName();
//System.out.println("OrderService当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.data.get(name));
System.out.println("OrderService当前线程["+name+"]中保存的数据是:"+ThreadLocalTest.threadLocal.get());
new OrderDao().SaveOrder();
}
}
ThreadLocalTest类代码如下:(与普通Map赋键值进行对比)
public class ThreadLocalTest {
// public static Map<String,Object> data=new Hashtable<>();
public static ThreadLocal<Object> threadLocal=new ThreadLocal<>();
private static Random random =new Random();
public static class Task implements Runnable{
@Override
public void run(){
//在Run方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为key保存到map中
Integer i = random.nextInt(1000);
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("线程[" + name + "]生成的随机数是:" + i);
//data.put(name,i);
threadLocal.set(i);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new OrderService().createOrder();
//在Run方法结束之前,以当前线程名获取出数据并打印,查看是否可以取出操作
//Object o = data.get(name);
Object o = threadLocal.get();
System.out.println("在线程["+name+"]快结束时取出关联的数据是:"+ o);
}
}
public static void main(String[] args) {
for (int i=0;i<3;i++){
new Thread(new Task()).start();
}
}
}
2、使用Filter和TheadLocal组合管理事务
1)使用ThreadLocal来确保所有dao操作都在同一个Connection连接对象中完成。
原理分析图:
2)使用Filter过滤器统一给所有的Service方法都加上try-catch。来进行实现的管理
原理分析图:
3)将所有异常都统一交给Tomcat,让Tomcat展示友好的错误信息页面
在web.xml中可以通过错误页面配置来进行管理。
<!--error-page标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code是错误类型-->
<error-code>500</error-code>
<!--location标签表示,要跳转去的页面路径-->
<location>/pages/error/error500.jsp</location>
</error-page>