1. 官方文档
文档:java_ee_api_中英文对照版.chm
2. Filter 过滤器说明
2.1 为啥要过滤器-需求示意图
● 一图胜千言
2.2 过滤器介绍
- Filter 过滤器它是 JavaWeb 的三大组件之一(Servlet 程序、Listener 监听器、Filter 过滤器)
- Filter 过滤器是 JavaEE 的规范,是接口
- Filter 过滤器它的作用是:拦截请求,过滤响应。
- 应用场景
● 权限检查
● 日记操作
● 事务管理
3. Filter 过滤器基本原理
● 一图胜千言
4. Filter 过滤器快速入门
● 需求:
在 web 工程下,有后台管理目录 manage,要求该目录下所有资源(html、图片、jsp 、Servlet 等)用户登录后才能访问。
● 思路分析-程序框架图(帮助理解, 编程小技巧, 作用:清晰思路.=> 慢慢通过这样方式来锻炼自己的编程思维!!
- 一会自己先画图,(思路) ->看到老师是怎么思考,并完成 -> 2. 走代码[听得进去.]
听老师说明, 完成模块的套路/流程[多年的体会]
2. 先完成一个正确的流程-看到一个效果-> 写后面代码就可以验证. 2. 加入其它的功能[1. 加入 session,验证合法性]
3. 完善功能
- 代码实现
1.创建login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>管理后台登录</title>
</head>
<body>
<h1>管理后台登录</h1>
<%
System.out.println("request=" + request);
%>
<form action="<%=request.getContextPath() %>/loginCheckServlet" method="post">
u:<input type="text" name="username"/> <br/><br/>
p:<input type="password" name="password"/> <br/><br/>
<input type="submit" value="用户登录"/></form>
</body>
</html>
2.创建 LoginCheckServlet.java
public class LoginCheckServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取到用户名和密码->DB
//假设密码是123456, 就可以通过
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("request=" + request);
if("123456".equals(password)) {
//合法, 讲用户名,加入session
request.getSession().setAttribute("username", username);
//请求转发到admin.jsp
request.getRequestDispatcher("/manage/admin.jsp")
.forward(request,response);
} else {
//不合法, 返回登录页面
request.getRequestDispatcher("/login.jsp")
.forward(request,response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
3.创建…\manage\admin.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>后台管理</title>
<base href="<%=request.getContextPath() %>/manage/"/>
</head>
<body>
<h1>后台管理</h1>
<%
//验证request对象是和前面的filter是一个对象
System.out.println("request=" + request);
%>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
<img src="shunping.jpg" height="300"/>
</body>
</html>
4.图片自行准备,放到manage包下即可
5.创建 ManageFilter.java
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//当Tomcat 创建 Filter创建,就会调用该方法,进行初始化
//老韩提醒:回忆我们自己实现tomcat底层机制+servlet程序, 就会了然
//
System.out.println("ManageFilter init被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
//到每次调用该filter时,doFilter就会被调用
//如果这里,没有调用继续请求的方法,则就停止
//如果继续访问目标资源-> 等价于放行
//老师说明:在调用过滤器前,servletRequest对象=request已经被创建并封装
//所以:我们这里就可以通过servletRequest获取很多信息, 比如访问url , session
//比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等
//获取到session
//可以继续使用 httpServletRequest 方法.
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
System.out.println("输入密码=" + httpServletRequest.getParameter("password"));
HttpSession session = httpServletRequest.getSession();
//获取username session对象, 还可以继续使用
Object username = session.getAttribute("username");
if (username != null) {
//解读filterChain.doFilter(servletRequest, servletResponse)
//1. 继续访问目标资源url
//2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
//3. 一定要理解filter传递的两个对象,再后面的servlet/jsp 是同一个对象(指的是在一次http请求)
System.out.println("servletRequest=" + servletRequest);
System.out.println("日志信息==");
System.out.println("访问的用户名=" + username.toString());
System.out.println("访问的url=" + httpServletRequest.getRequestURL());
System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());
filterChain.doFilter(servletRequest, servletResponse);
} else {//说明没有登录过..回到登录页面
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
//当filter被销毁时,会调用该方法
System.out.println("ManageFilter destroy()被调用..");
}
}
6.在 web.xml 配置过滤器
<!--老师解读:filter一般写在其它servlet的前面
1. 观察我们发现filter 配置和 servlet 非常相似. filter也是被tomcat管理和维护
2. url-pattern 就是当请求的url 和 匹配的时候,就会调用该filter
3. /manage/* 第一个 / 解析成 http://ip:port/工程路径
4. 完整的路径就是 http://ip:port/工程路径/manage/* 当请求的资源url满足该条件时
都会调用filter , /manage/admin.jsp
-->
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.hspedu.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>LoginCheckServlet</servlet-name>
<servlet-class>com.hspedu.servlet.LoginCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginCheckServlet</servlet-name>
<url-pattern>/loginCheckServlet</url-pattern>
</servlet-mapping>
7.完成测试, 看看过滤器是否生效
拓展:
回顾手写Tomcat,完善并加深对过滤器的理解
MyTomcatV3.java
/**
* 第3版的Tomcat, 实现通过xml+反射来初始化容器
*/
public class HspTomcatV3 {
//1. 存放容器 servletMapping
// -ConcurrentHashMap
// -HashMap
// key - value
// ServletName 对应的实例
public static final ConcurrentHashMap<String, HspHttpServlet>
servletMapping = new ConcurrentHashMap<>();
//2容器 servletUrlMapping
// -ConcurrentHashMap
// -HashMap
// key - value
// url-pattern ServletName
public static final ConcurrentHashMap<String, String>
servletUrlMapping = new ConcurrentHashMap<>();
//你可以这里理解session, tomcat还维护一个容器
public static final ConcurrentHashMap<String, HttpSession>
sessionMapping = new ConcurrentHashMap<>();
//你可以这里理解filter, tomcat还维护了filter的容器
public static final ConcurrentHashMap<String, String>
filterUrlMapping = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<String, Filter>
filterMapping = new ConcurrentHashMap<>();
//变强..
public static void main(String[] args) {
HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
hspTomcatV3.init();
//启动hsptomcat容器
hspTomcatV3.run();
}
//启动HspTomcatV3容器
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("=====hsptomcatv3在8080监听======");
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
HspRequestHandler hspRequestHandler =
new HspRequestHandler(socket);
new Thread(hspRequestHandler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//直接对两个容器进行初始化
public void init() {
//读取web.xml => dom4j =>
//得到web.xml文件的路径 => 拷贝一份.
String path = HspTomcatV3.class.getResource("/").getPath();
//System.out.println("path= " + path);
//使用dom4j技术完成读取
SAXReader saxReader = new SAXReader();
//困难->真的掌握
try {
Document document = saxReader.read(new File(path + "web.xml"));
System.out.println("document= " + document);
//得到根元素
Element rootElement = document.getRootElement();
//得到根元素下面的所有元素
List<Element> elements = rootElement.elements();
//遍历并过滤到 servlet servlet-mapping
for (Element element : elements) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//这是一个servlet配置
//System.out.println("发现 servlet");
//使用反射将该servlet实例放入到servletMapping
Element servletName = element.element("servlet-name");
Element servletClass = element.element("servlet-class");
servletMapping.put(servletName.getText(),
(HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//这是一个servlet-mapping
//System.out.println("发现 servlet-mapping");
Element servletName = element.element("servlet-name");
Element urlPatter = element.element("url-pattern");
servletUrlMapping.put(urlPatter.getText(), servletName.getText());
}
}
} catch (Exception e) {
e.printStackTrace();
}
//验证,这两个容器是否初始化成功
System.out.println("servletMapping= " + servletMapping);
System.out.println("servletUrlMapping= " + servletUrlMapping);
}
}
MyRequestHandler.java
//有了filter机制,可以理解再调用servlet之前,先匹配filter
//1. 根据request对象封装的uri
//2. 到 filterUrlMapping 去匹配
//3. 如果匹配上就调用 filterMapping 对应的filer对象doFilter()
//4. 如果没有匹配上,就直接走我们后的servlet/jsp/html.
String servletName = HspTomcatV3.servletUrlMapping.get(uri);
if (servletName == null) {
servletName = "";
}
5. Filter 过滤器 url-pattern
1、url-pattern : Filter 的拦截路径, 即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤
2.、精确匹配 <url-pattern>/a.jsp</url-pattern>
对应的 请求地址 http://ip[域名]:port/工程路径/a.jsp 会拦截
3、目录匹配 <url-pattern>/manage/*</url-pattern>
对应的 请求地址 http://ip[域名]:port/工程路径/manage/xx , 即 web 工程 manage 目录下所有资源 会拦截
4、后缀名匹配 <url-pattern>*.jsp</url-pattern>
后缀名可变,比如 *.action *.do
等等对应的 请求地址 http://ip[域名]:port/工程路径/xx.jsp , 后缀名为 .jsp 请求会拦截
5、Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在
6. Filter 过滤器生命周期
● Filter 生命周期图解
● 验证一把
/**
* 解读
* 1. filter在web项目启动时, 由tomcat 来创建filter实例, 只会创建一个
* 2. 会调用filter默认的无参构造器, 同时会调用 init方法, 只会调用一次
* 3. 在创建filter实例时,同时会创建一个FilterConfig对象,并通过init方法传入
* 4. 通过FilterConfig对象,程序员可以获取该filter的相关配置信息
* 5. 当一个http请求和该filter的的url-patter匹配时,就会调用doFilter方法
* 6. 在调用doFilter方法时,tomcat会同时创建ServletRequest 和 ServletResponse 和 FilterChain对象
* , 并通过doFilter传入.
* 7. 如果后面的请求目标资源(jsp,servlet..) 会使用到request,和 response,那么会继续传递
* 8. 老师的提醒:到javaweb - ssm - springboot , 有 浏览器和 web服务器(tomcat)参与, 而这两个部分不是我们
* 程序员自己写,所以理解起来比 java se要困难!!!
*/
public class ManageFilter implements Filter {
private int count = 0;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//当Tomcat 创建 Filter创建,就会调用该方法,进行初始化
//提醒:回忆我们自己实现tomcat底层机制+servlet程序, 就会了然
//
System.out.println("ManageFilter init被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println("ManageFilter doFilter() 被调用=" + (++count));
//到每次调用该filter时,doFilter就会被调用
//如果这里,没有调用继续请求的方法,则就停止
//如果继续访问目标资源-> 等价于放行
//老师说明:在调用过滤器前,servletRequest对象=request已经被创建并封装
//所以:我们这里就可以通过servletRequest获取很多信息, 比如访问url , session
//比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等
//获取到session
//可以继续使用 httpServletRequest 方法.
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
System.out.println("输入密码=" + httpServletRequest.getParameter("password"));
HttpSession session = httpServletRequest.getSession();
//获取username session对象, 还可以继续使用
Object username = session.getAttribute("username");
if (username != null) {
//解读filterChain.doFilter(servletRequest, servletResponse)
//1. 继续访问目标资源url
//2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
//3. 一定要理解filter传递的两个对象,再后面的servlet/jsp 是同一个对象(指的是在一次http请求)
System.out.println("servletRequest=" + servletRequest);
System.out.println("日志信息==");
System.out.println("访问的用户名=" + username.toString());
System.out.println("访问的url=" + httpServletRequest.getRequestURL());
System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());
filterChain.doFilter(servletRequest, servletResponse);
} else {//说明没有登录过..回到登录页面
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest, servletResponse);
}
}
@Override
public void destroy() {
//当filter被销毁时,会调用该方法
System.out.println("ManageFilter destroy()被调用..");
}
}
7. FilterConfig
● FilterConfig 接口图
● FilterConfig 说明
- FilterConfig 是 Filter 过滤器的配置类
- Tomcat 每次创建 Filter 的时候,也会创建一个 FilterConfig 对象,这里包含了 Filter 配置文件的配置信息。
- FilterConfig 对象作用是获取 filter 过滤器的配置内容
● 应用实例
web.xml
<filter>
<filter-name>HspFilterConfig</filter-name>
<filter-class>com.hspedu.filter.HspFilterConfig</filter-class>
<!--这里就是给该filter配置的参数-有程序员根据业务逻辑来设置-->
<init-param>
<param-name>ip</param-name>
<param-value>127.0</param-value>
</init-param>
<init-param>
<param-name>port</param-name>
<param-value>8888</param-value>
</init-param>
<init-param>
<param-name>email</param-name>
<param-value>hsp@sohu.com</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HspFilterConfig</filter-name>
<url-pattern>/abc/*</url-pattern>
</filter-mapping>
/**
* 老师解读: 演示FilterConfig使用
*/
public class HspFilterConfig implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HspFilterConfig init() 被调用..");
//通过filterConfig 获取相关的参数
String filterName = filterConfig.getFilterName();
ip = filterConfig.getInitParameter("ip");
ServletContext servletContext = filterConfig.getServletContext();
//可以获取到该filter所有的配置参数名
Enumeration<String> initParameterNames =
filterConfig.getInitParameterNames();
//遍历枚举
while (initParameterNames.hasMoreElements()) {
System.out.println("名字=" + initParameterNames.nextElement());
}
System.out.println("filterName= " + filterName);
System.out.println("ip= " + ip);
System.out.println("servletContext= " + servletContext);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
● 完成测试, 直接启动就看到相应的输出信息
● 简单应用, 如果访问 ip 是 128.12 网段,就返回登录页面
先思考一把…
代码:
web.xml
<filter>
<filter-name>HspFilterConfig</filter-name>
<filter-class>com.hspedu.filter.HspFilterConfig</filter-class>
<!--这里就是给该filter配置的参数-有程序员根据业务逻辑来设置-->
<init-param>
<param-name>ip</param-name>
<param-value>127.0</param-value>
</init-param>
<init-param>
<param-name>port</param-name>
<param-value>8888</param-value>
</init-param>
<init-param>
<param-name>email</param-name>
<param-value>hsp@sohu.com</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HspFilterConfig</filter-name>
<url-pattern>/abc/*</url-pattern>
</filter-mapping>
/**
* 老师解读: 演示FilterConfig使用
*
*/
public class HspFilterConfig implements Filter {
private String ip; //从配置获取的ip
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("HspFilterConfig init() 被调用..");
//通过filterConfig 获取相关的参数
String filterName = filterConfig.getFilterName();
ip = filterConfig.getInitParameter("ip");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//通过forbidden ip 来进行控制
//先获取到访问ip
String remoteAddr = servletRequest.getRemoteAddr();
if(remoteAddr.contains(ip)) {
System.out.println("封杀该网段..");
servletRequest.getRequestDispatcher("/login.jsp").
forward(servletRequest,servletResponse);
return; //直接返回
}
//继续访问目标资源
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
8. FilterChain 过滤器链
8.1 说明
一句话: FilterChain: 在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链。
8.2 基本原理示意图
● 一图胜千言
8.3 应用实例
- 需求: 演示过滤器链的使用
- 创建 AFilter.java
public class AFilter 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("AFilter---> 线程id=" +
Thread.currentThread().getId());
System.out.println("AFilter doFilter 的前置代码...");
System.out.println("执行 AFilter doFilter()");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("AFilter doFilter 的后置代码...");
}
@Override
public void destroy() {
}
}
- 创建 BFilter.java
public class BFilter 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("BFilter---> 线程id=" +
Thread.currentThread().getId());
System.out.println("BFilter doFilter 的前置代码...");
System.out.println("执行 BFilter doFilter()");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("BFilter doFilter 的后置代码...");
}
@Override
public void destroy() {
}
}
- 创建/使用 …\admin\hi.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>hi</title>
</head>
<body>
<h1>admin 目录下的 hi.jsp</h1>
<h1>后台管理</h1>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
</body>
</html>
- 修改 web.xml, 配置过滤器
<filter>
<filter-name>AFilter</filter-name>
<filter-class>com.hspedu.servlet.AFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>BFilter</filter-name>
<filter-class>com.hspedu.servlet.BFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
- 完成测试
8.4 FilterChain 注意事项和细节
- 多个 filter 和目标资源在一次 http 请求,在同一个线程中
- 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上,就会顺序执行,形成一个 filter 调用链(底层可以使用一个数据结构搞定)
- 多个 filter 共同执行时,因为是一次 http 请求, 使用同一个 request 对象
- 多个 filter 执行顺序,和 web.xml 配置顺序保持一致.
- chain.doFilter(req, resp)方法 将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器,则执行目标资源。
- 小结:
注意执行过滤器链时, 顺序是(用前面的案例分析)
Http请求 -> A 过滤器 dofilter()-> A 过滤器前置代码 -> A 过滤器 chain.doFilter() -> B 过滤器 dofilter() -> B 过滤器前置代码 -> B过滤器 chain.doFilter() -> 目标文件 -> B过滤器后置代码 -> A过滤器后置代码 ->返回给浏览器页面/数据
9. Filter 作业练习
9.1 作业布置
● 需求分析: 使用过滤器, 完成如下要求
- 点击发表评论页面 topic.jsp, 可以在 showTopic.jsp 显示评论内容
- 如果发表的评论内容,有关键字比如 “苹果” “香蕉”, 就返回 topic.jsp, 并提示有禁用词
- 要求发表评论到 showTopic.jsp 时,经过过滤器的处理
- 禁用词, 配置在过滤器, 在启动项目时动态的获取, 注意处理中文
● 思路分析(程序框架图)
9.2 作业评讲
1、创建 topic.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>发表对阿凡达电影评论</h1>
过滤词: 苹果, 香蕉 ${errorInfo}
<form method="post" action="<%=request.getContextPath()%>/topic/showTopic.jsp">
用户: <input type="text" name="username"><br/>
评论: <textarea rows="10" name="content" cols="20"></textarea><br/>
<input type="submit" value="发表评论">
</form>
</body>
</html>
2、创建 showTopic.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>你发表的评论是</h1>
评论内容: <%=request.getParameter("content")%>
</body>
</html>
3、创建 TopicServlet.java
public class TopicFilter implements Filter {
//属性-> 存放禁用词
private String[] forbiddenWords = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//获取禁用词
String forbiddenword = filterConfig.getInitParameter("forbiddenword");
forbiddenWords = forbiddenword.split(",");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//解决从topic.jsp 提交的中文乱码问题
servletRequest.setCharacterEncoding("utf-8");
//判断评论是不是有禁用词
String content = servletRequest.getParameter("content");
//循环遍历一把,看看有没有禁用词
for (String forbiddenWord : forbiddenWords) {//java基础
if (content.contains(forbiddenWord)) {
//返回topic.jsp
servletRequest.setAttribute("errorInfo", "你输入的有禁用词");
servletRequest.getRequestDispatcher("/topic.jsp")
.forward(servletRequest, servletResponse);
return;//返回
}
}
//继续到目标
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
4、配置过滤器
<filter>
<filter-name>TopicFilter</filter-name>
<filter-class>com.hspedu.filter.TopicFilter</filter-class>
<!--配置禁用词-->
<init-param>
<param-name>forbiddenword</param-name>
<param-value>苹果,香蕉</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>TopicFilter</filter-name>
<url-pattern>/topic/*</url-pattern>
</filter-mapping>
5、完成测试