Filter 过滤器以及项目中事物的管理

目录

1.什么是 Filter 过滤器

2.Filter 的初体验

3.Filter 的生命周期

4.FilterConfig 类

5.FilterChain 过滤器链

6.Filter 的拦截路径

7.补充:ThreadLocal 的使用

8.项目中如何通过 Filter 和 ThreadLocal 组合管理事务


1.什么是 Filter 过滤器

1、 Filter 过滤器它是 JavaWeb 的三大组件之一 三大组件分别是: Servlet 程序、 Listener 监听器、 Filter 过滤器

2、 Filter 过滤器它是 JavaEE 的规范。 也就是接口

3、 Filter 过滤器它的作用是: 拦截请求, 过滤响应。

拦截请求常见的应用场景有:

1、 权限检查

2、 日记操作

3、 事务管理

……等等
 

 

2.Filter 的初体验

要求:web 工程下, 有一个 admin 目录。 这个 admin 目录下的所有资源(html 页面、 jpg 图片、 jsp 文件、 等等) 都必
须是用户登录之后才允许访问

Filter工作流程图

代码示例:

public class AdminFilter implements Filter {
        /**
         * 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);
            }
        }
    }

web.xml中的配置

<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名-->
    <filter-class>com.atguigu.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>

Filter 过滤器的使用步骤:

1、 编写一个类去实现 Filter 接口

2、 实现过滤方法 doFilter()

3、 到 web.xml 中去配置 Filter 的拦截路径

 

3.Filter 的生命周期

Filter 的生命周期包含几个方法

1、 构造器方法

2、 init 初始化方法

第 1, 2 步, 在 web 工程启动的时候执行(Filter 已经创建)

3、 doFilter 过滤方法

第 3 步, 每次拦截到请求, 就会执行

4、 destroy 销毁

第 4 步, 停止 web 工程的时候, 就会执行(停止 web 工程, 也会销毁 Filter 过滤器)

 

4.FilterConfig 类

Tomcat 每次创建 Filter 的时候, 也会同时创建一个 FilterConfig 类, 这里包含了 Filter 配置文件的配置信息。

FilterConfig 类的作用是获取 filter 过滤器的配置内容

1、 获取 Filter 的名称 filter-name 的内容

2、 获取在 Filter 中配置的 init-param 初始化参数

3、 获取 ServletContext 对象
 

代码示例:

 @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());
    }

web.xml

<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名--><filter-class>com.atguigu.filter.AdminFilter</filter-class>
    <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>
</filter>

 

5.FilterChain 过滤器链

FilterChain 就是过滤器链(多个过滤器如何一起工作)

 

6.Filter 的拦截路径

--精确匹配
<url-pattern>/target.jsp</url-pattern>
以上配置的路径, 表示请求地址必须为: http://ip:port/工程路径/target.jsp

--目录匹配
<url-pattern>/admin/*</url-pattern>
以上配置的路径, 表示请求地址必须为: http://ip:port/工程路径/admin/*

--后缀名匹配
<url-pattern>*.html</url-pattern>
以上配置的路径, 表示请求地址必须以.html 结尾才会拦截到

<url-pattern>*.do</url-pattern>
以上配置的路径, 表示请求地址必须以.do 结尾才会拦截到

<url-pattern>*.action</url-pattern>
以上配置的路径, 表示请求地址必须以.action 结尾才会拦截到

Filter 过滤器它只关心请求的地址是否匹配, 不关心请求的资源是否存在

 

7.补充:ThreadLocal 的使用

ThreadLocal 的作用, 它可以解决多线程的数据安全问题。

ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量, 可以是对象, 也可以是数组, 集合)

ThreadLocal 的特点:

1、 ThreadLocal 可以为当前线程关联一个数据。 (它可以像 Map 一样存取数据, key 为当前线程)

2、 每一个 ThreadLocal 对象, 只能为当前线程关联一个数据, 如果要为当前线程关联多个数据, 就需要使用多个ThreadLocal 对象实例。

3、 每个 ThreadLocal 对象实例定义的时候, 一般都是 static 类型

4、 ThreadLocal 中保存数据, 在线程销毁后。 会由 JVM 虚拟自动释放。

代码示例:

ThreadLocalTest类

public class ThreadLocalTest {
    // public static Map<String,Object> data = new Hashtable<String,Object>();
    public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
    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(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } n
            ew 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();
        }
    }
}

OrderService 和 OrderDao类

public class OrderService {
    public void createOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderService 当前线程[" + name + "]中保存的数据是: " +
                ThreadLocalTest.threadLocal.get());
        new OrderDao().saveOrder();
    }
} 
public class OrderDao {
    public void saveOrder(){
        String name = Thread.currentThread().getName();
        System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是: " +
                ThreadLocalTest.threadLocal.get());
    }
}

 

8.项目中如何通过 Filter 和 ThreadLocal 组合管理事务

1.JdbcUtils 工具类的修改

public class JdbcUtils {

    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();


    static {
        try {
            Properties properties = new Properties();
            //读取 jdbc.properties 属性配置文件
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //从流中加载数据
            properties.load(inputStream);
            //创建数据库连接池
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 获取数据库连接池中的连接
     * 并实现事务管理
     *
     * @return 如果返回 null 说明获取连接失败
     */
    public static Connection getConnection() {

        Connection connection = conns.get();

        if (connection == null) {
            try {

                connection = dataSource.getConnection();

                //保存到ThreadLocal中,供后面的jdbc操作
                conns.set(connection);

                //设置为手动管理
                connection.setAutoCommit(false);

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return connection;

    }

    /**
     * 提交事务,并关闭数据库
     *
     */
    public static void commitAndClose() {
        Connection connection = conns.get();
        //如果不等于null,说明以前使用过连接
        if(connection != null){

            try {
                connection.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        conns.remove();
    }

    /**
     * 回滚事务,并关闭数据库
     *
     */
    public static void rollbackAndClose() {
        Connection connection = conns.get();
        //如果不等于null,说明以前使用过连接
        if(connection != null){

            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        conns.remove();

    }


}

2.修改 BaseDao 将所有异常都抛出

Dao

/**
 * 访问数据的 DAO 接口
 * 里边定义好访问数据表的各种方法
 * @author LIXICHEN
 * @create 2020-04-20 17:43
 * @param T :DAO 处理的实体类的类型
 * @author LIXICHEN
 * @create 2020-04-13 15:59
 */
public interface DAO<T> {


    /**
     * 批量处理方法
     *
     * @param connection 数据库连接
     * @param sql        SQL 语句
     * @param args       填充占位符的Object [] 类型的可变参数
     */
    void batch(Connection connection, String sql, Object[]... args);

    /**
     * 返回具体的一个值 例如总人数,email
     *
     * @param connection 数据库连接
     * @param sql        SQL 语句
     * @param args       填充占位符的可变参数
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> E getForValue(Connection connection, String sql, Object... args);

    /**
     * 返回 T 的集合
     *
     * @param connection 数据库连接
     * @param sql        SQL 语句
     * @param args       填充占位符的可变参数
     * @return
     * @throws SQLException
     */
    List<T> getForList(Connection connection, String sql, Object... args);


    /**
     * 返回一个 T 的对象
     *
     * @param connection 数据库连接
     * @param sql        SQL 语句
     * @param args       填充占位符的可变参数
     * @return
     * @throws SQLException
     */
    T get(Connection connection, String sql, Object... args);


    /**
     * INSERT,UPDATE,DELETE
     *
     * @param connection : 数据库连接
     * @param sql        :SQL 语句
     * @param args       :填充占位符的可变参数
     * @return            如果返回-1,说明执行失败,返回其他表示影响的行数
     */
    int update(Connection connection, String sql, Object... args);

}

BaseDaoImpl

public class BaseDaoImpl<T> implements DAO<T> {

    private QueryRunner queryRunner = null;
    private Class<T> type;

    public BaseDaoImpl() {
        queryRunner = new QueryRunner();
        type = ReflectionUtils.getSuperGenericType(getClass());
    }

    @Override
    public void batch(Connection connection, String sql, Object[]... args){

        try {
            queryRunner.batch(connection, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }

    }

    @Override
    public <E> E getForValue(Connection connection, String sql, Object... args){

        try {
            return (E) queryRunner.query(connection, sql, new ScalarHandler(), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public List<T> getForList(Connection connection, String sql, Object... args){
        try {

            return (List<T>) queryRunner.query(connection, sql, new BeanListHandler<>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public T get(Connection connection, String sql, Object... args){
        try {
            return queryRunner.query(connection, sql, new BeanHandler<>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }

    @Override
    public int update(Connection connection, String sql, Object... args){

        try {
            return queryRunner.update(connection, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }



}

3.使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。 来进行实现事物的管理。

原理图

Filter类代码

public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        try{

            filterChain.doFilter(servletRequest,servletResponse);
            JdbcUtils.commitAndClose();//提交事务
        }catch(Exception e){
            JdbcUtils.rollbackAndClose();//回滚事务
            e.printStackTrace();
            //把异常抛出到Tomcat,显示错误页面
            throw new RuntimeException(e);
        }

    }

    @Override
    public void destroy() {

    }
}

web.xml代码

     <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>com.atmusic.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <!-- /* 表示当前工程下所有请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

把 BaseServlet 中的异常往外抛给 Filter 过滤器

public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //解决post请求额中文乱码问题
        req.setCharacterEncoding("UTF-8");
        String action = req.getParameter("action");

        //利用反射优化if-else代码
        try {
            //通过反射获取函数名称
            Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);

            //调用函数
            method.invoke(this,req,resp);

        } catch (Exception e) {
            e.printStackTrace();
            //把异常抛给Filter过滤器
            throw new RuntimeException(e);
        }

    }

}

4.将所有异常都统一交给 Tomcat, 让 Tomcat 展示友好的错误信息页面

web.xml的配置

    <!--服务器出错后,跳转的页面-->
    <error-page>
        <!--错误类型-->
        <error-code>500</error-code>
        <!--跳转的页面-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <!--服务器出错后,跳转的页面-->
    <error-page>
        <!--错误类型-->
        <error-code>404</error-code>
        <!--跳转的页面-->
        <location>/pages/error/error404.jsp</location>
    </error-page>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章