今天班主任带来了一个好消息,传智播客组织出去玩,缓解一下学习的压力,我们选择了去香山玩,好愉快啊!
这是我今天学到的知识,希望分享给大家,想学的就来一起学习吧!!!
Servlet技术规范,描述三种技术 : Servlet(服务器小程序) 、Filter(过滤器) 、Listener(监听器)
Filter是运行在服务器端,对服务器端web资源的访问 进行拦截,起到过滤的作用
Servlet的API中定义了一个接口 Filter,用户只需要编写程序实现Filter接口,完成过滤器编写
filter:过滤器控制是否允许访问目标web资源,拦截访问资源,实际上是拦截url
过滤器拦截的是rquest和response,操作request和response数据
要想使用过滤器需要在xml文件中配置filter
filter<javaee API>:
filter 在是在tomcat服务器启动时创建对象,调运init()只执行一次
在服务器内部调运资源,默认filter是不执行拦截的如果需要就配置<dispatcher>REQUEST/RESPONSE</dispatcher>
Filter生命周期 :
init(FilterConfig)
Filter对象在tomcat服务器启动时创建,调用init方法(只会创建一个对象,init方法执行一次)
doFilter(request,response,filterChain)
doFilter 每次拦截目标资源时,执行
destroy()
destroy 服务器关闭时执行
filter的开发步骤:
1、编写类 实现 Filter接口
2、在服务器端注册 Filter (配置拦截哪个web资源) ----- web.xml
XML配置:
<!-- 注册过滤器 -->
<filter>
<filter-name>为filter起一个变量名</filter-name>
<filter-class>filter地址</filter-class>
配置初始化参数,通过Filter的init方法中的FilterConfig获取到参数,用来在Filter初始化阶段,将参数传递给过滤器
在doFilter方法中通过getInitParameter(String name) 获得过滤器初始化参数
<init-param>
<param-name>设置key</param-name>
<param-value>设置value</param-value>
</init-param>
</filter>
<!-- 配置过滤器去拦截哪个资源 -->
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>要拦截的资源路径/hello.jsp</url-pattern>
</filter-mapping>
介绍<filter-mapping> 过滤器拦截的配置:
1.如果连接的目标资源是一个Servlet,可以选择url和servlet名称两种配置方式
<!-- 拦截/hello是Servlet 路径 -->
<url-pattern>/hello</url-pattern>
<url-pattern> 和 Servlet中路径写法一样,有三种 : 完全匹配、目录匹配、扩展名匹配
<!-- 拦截Servlet 还可以通过Servlet类名称进行拦截 -->
<servlet-name>HelloServlet</servlet-name>
2.<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式
容器调用服务器端资源 有四种方式:REQUEST、FORWARD、INCLUDE、ERROR
3、客户端访问被拦截目标资源之前,服务器调用Filter的doFilter方法执行过滤
4、Filter的doFilter方法中传入 FilterChain对象,
在FilterChain中也有一个doFilter方法,
如果调用FilterChain的doFilter方法就会执行xml文件中的目标资源,否则目标资源不会执行
/**chain.doFilter(request, response);
FilterChain:
在客户端访问服务器web资源时,服务器端为一个web资源,配置多个过滤器拦截 ,这多个过滤器,就会组成过滤器链 FilterChain,
调用FilterChain的doFilter 表示要执行过滤器链的下一个资源,如果当前过滤器已经是链上最后一个过滤器,就会执行目标资源
注意:web服务器根据Filter在web.xml文件中的<mapping>注册顺序,决定先调用哪个Filter
通过 ServletContext getServletContext() 获得ServletContext对象
FilterConfig 提供参数,是Filter类私有参数,别的Filter的初始化参数,不能在当前Filter中进行获取
在xml中配置全局参数,<context-param> 进行配置,通过ServletContext获得,getServletContext()可以获得ServletContext对象
filter应用:
1.统一全站字符编码过滤器
经常会使用,而过滤器可以在目标资源之前执行,将很多程序中处理乱码公共代码,提取到过滤器中 ,以后程序中不需要处理编码问题了
在doFilte方法中可以:
处理请求post乱码代码
request.setCharacterEncoding("utf-8");
设置响应编码集代码
response.setContentType("text/html;charset=utf-8");
参考代码:
public class EncodingFilter implements Filter {
private String encoding;
@Override
public void destroy() {
}
@Override
// 将编码集 提取配置文件中,通过初始化参数传递进来
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 处理请求 post乱码
request.setCharacterEncoding(encoding);
// 设置响应编码
response.setContentType("text/html;charset=" + encoding);
//放行过滤器
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
encoding = filterConfig.getInitParameter("encoding");
}
}
2.禁止浏览器缓存动态页面的过滤器
因为动态页面数据,是由程序生成的,所以如果有缓存,就会发生,客户端查看数据不是最新数据情况
,对于动态程序生成页面,设置浏览器端禁止缓存页面内容
由于Filter的request和response不是基于Http协议的所以先强转成带有Http协议的request和response
HttpServlerRequest request = (HttpServletRequest)request
HttpServlerResponse response = (HttpServletResponse)response
response.setDateHeader("Expires",-1);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");
将禁用缓存代码,提取到过滤器中,通过url配置,禁用所有JSP页面的缓存 ,这样在xml文件中就可以对不需要拿缓冲的资源进行过滤
参考代码:
public class NoCacheFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// doFilter的参数与HTTP协议无关request response对象,如果操作与HTTP协议相关方法,需要强制类型转换
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setDateHeader("Expires", -1);
httpServletResponse.setHeader("Cache-Control", "no-cache");
httpServletResponse.setHeader("Pragma", "no-cache");
chain.doFilter(request, httpServletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
3.控制浏览器缓存静态web资源
应用三:控制浏览器缓存静态web资源
Tomcat缓存策略:第一次访问服务器是,服务器返回资源,
并同时返回一个头信息:last-Modified:最后修改时间,
在浏览器端保存最后修改时间,当浏览器再次访问服务器时会带着If-Modifed-Since最后修改时间去访问,
这时服务器会判断浏览器带来的最后修改时间是否与资源的最后修改时间一直,如果一直就返回一个304状态码
通知浏览器拿缓冲.
对于服务器端经常不变化文件,设置客户端缓存时间,在客户端资源缓存时间到期之前,就不会去访问服务器获取该资源
在过滤器中控制浏览器缓存静态web资源
比tomcat内置缓存策略更优手段,减少服务器请求次数,提升性能
设置静态资源缓存时间,需要设置 Expires 过期时间 ,在客户端资源没有过期之前,不会产生对该资源的请求的
设置Expires 通常使用 response.setDateHeader(int) 进行设置 设置毫秒值
参考代码:
public class ExpiresFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 设置过期时间,需要设置Expires
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 过期时间 = 当前时间 + 还有多久过期
httpServletResponse.setDateHeader("Expires", System.currentTimeMillis()
+ 1000L * 60 * 60 * 24 * 30);
chain.doFilter(request, httpServletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
4.自动登录过滤器
自动登陆过滤器
在访问一个站点时,登陆时如果勾选自动登陆(可以设置最近多久不用,手动登陆)在规定时间内再次访问该站点时,直接进行登陆后状态
在用户完成登陆后,勾选自动登陆复选框,服务器端将用户名和密码 以Cookie形式,保存在客户端。
当用户下次访问该站点,过滤器从Cookie中获取用户名和密码信息完成自动登陆
在Filter中要做的三步:
1)判断用户是否已经登陆,如果已经登陆,没有自动登陆的必要
2)判断Cookie中是否含有自动登陆信息 ,如果没有,无法完成自动登陆
3)使用cookie用户名和密码 完成自动登陆
参考代码:
public class Demo4Servlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得请求中用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 根据用户名和密码查询数据库
QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from user where username = ? and password= ?";
try {
User existUser = queryRunner.query(sql, new BeanHandler<User>(
User.class), username, MD5Utils.md5(password));
if (existUser == null) {
// 登陆失败
request.setAttribute("msg", "用户名或者密码错误!");
request.getRequestDispatcher("/demo4/login.jsp").forward(
request, response);
return;
} else {
// 登陆成功
// 将用户信息 保存到Session
request.getSession().setAttribute("existUser", existUser);
// 判断是否勾选自动登陆
if ("true".equals(request.getParameter("autologin"))) {
// 勾选了,在cookie中保存用户名和密码
Cookie cookie = new Cookie("autologin", existUser
.getUsername()
+ "#itcast#" + existUser.getPassword());
cookie.setPath("/");
cookie.setMaxAge(60 * 60 * 24 * 90);
response.addCookie(cookie);
}
// 跳转到index.jsp
response.sendRedirect("/day17/demo4/index.jsp");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
小介:
如果将用户密码保存在cookie文件中,非常不安全的 ,通常情况下密码需要加密后才能保存到客户端
*介绍一个使用md5算法对密码进行加密
* md5加密算法是一个单向加密算法 ,支持明文---密文 不支持密文解密
MySQL数据库中提供md5函数,可以完成md5的加密
Java中提供类 MessageDigest 完成MD5加密
**将数据表中所有密码 变为密文 update user set password = md5(password);
5.过滤器实现URL级别权限认证
系统中存在很多资源,将需要进行权限控制的资源,放入特殊路径中,
编写过滤器管理访问特殊路径的请求,如果没有相应身份和权限,控制无法访问
参考代码:
public class PrivilegeFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void destroy()
{}
private Map<String, String> map = new HashMap<String, String>();
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 读取所有参数
Enumeration<String> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = filterConfig.getInitParameter(name);
map.put(value, name);
}
// 获得来访者 资源路径
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String path = httpServletRequest.getRequestURI().substring(
httpServletRequest.getContextPath().length());
System.out.println(path);
// 判断该路径需要哪个权限
for (String needPrivilegePath : map.keySet()) {
if (path.startsWith(needPrivilegePath)) {
// 需要什么角色
String needRole = map.get(needPrivilegePath);
// 判断当前用户权限是否满足
User existUser = (User) httpServletRequest.getSession()
.getAttribute("existUser");
if (existUser == null) {
// 还没有登录
request.getRequestDispatcher("/demo4/login.jsp").forward(
httpServletRequest, response);
return;
} else {
// 当前用户角色
String currentUserRole = existUser.getRole();
if (needRole.equals(currentUserRole)) {
// 权限符合
chain.doFilter(httpServletRequest, response);
return;
} else {
// 权限不足
throw new RuntimeException("权限不足,无法访问!");
}
}
}
}
chain.doFilter(httpServletRequest, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
}
Filter高级应用:
Decorator模式
1)包装类需要和被包装对象 实现相同接口,或者继承相同父类
2)包装类需要持有 被包装对象的引用
在包装类中定义成员变量,通过包装类构造方法,传入被包装对象
3)在包装类中,可以控制原来那些方法需要加强
不需要加强 ,调用被包装对象的方法
需要加强,编写增强代码逻辑
ServletRequestWrapper 和 HttpServletRequestWrapper
提供对request对象进行包装的方法,但是默认情况下每个方法都是调用原来request对象的方法,
也就是说包装类并没有对request进行增强
如果要增强就可以在这两个包装类基础上,继承HttpServletRequestWrapper 和 HttpServletRequestWrapper 覆盖需要增强的方法即可
6.完全解决get和post乱码的过滤器
在Filter中,对request对象进行包装,增强获得参数的方法
getParameter
getParameterValues
getParameterMap
参考代码:
public class GenericEncodingFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
// 自定义request对象
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
private boolean hasEncode;
public MyRequest(HttpServletRequest request) {
super(request);// super必须写
this.request = request;
}
// 对需要增强方法 进行覆盖
@Override
public Map getParameterMap() {
// 先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
// get请求
Map<String, String[]> parameterMap = request.getParameterMap();
if (!hasEncode) { // 确保get手动编码逻辑只运行一次
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码
values[i] = new String(values[i]
.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0]; // 取回参数的第一个值
}
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
7.增强Response对象,对响应数据进行压缩
先说一下在Tomcat服务器内,提供对响应压缩 配置实现
在conf/server.xml 中
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"/> 添加 compressableMimeType="text/html,text/xml,text/plain" compression="on"
参考代码:
public class GzipFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 自定义缓冲区,重写response的getWriter和getOutputStream
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 字节缓存区
// 将数据写入内存数组中
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
HttpServletResponse myresponse = new HttpServletResponseWrapper(
httpServletResponse) {
private PrintWriter out;
@Override
// 重写getWriter 流获得是对 getOutputStream 编码获得
public PrintWriter getWriter() throws IOException {
System.out.println("getWriter...");
if (out == null) {
// 确保PrintWriter只有一个对象,flushbuffer中输出缓冲区内容
out = new PrintWriter(new OutputStreamWriter(
byteArrayOutputStream, getCharacterEncoding()));
}
return out;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
System.out.println("getOutputStream...");
return new ServletOutputStream() {
@Override
// 将数据写到哪
public void write(int b) throws IOException {
// 将响应数据 写入自定义缓存区
byteArrayOutputStream.write(b);
}
};
}
@Override
public void flushBuffer() throws IOException {
getOutputStream().flush();
getWriter().flush();
}
};
// 目标资源执行,只有目标资源执行后,才有响应数据
chain.doFilter(request, myresponse);
myresponse.flushBuffer();
// 目标资源已经执行过,数据已经在 byteArrayOutputStream 缓存区
byte[] data = byteArrayOutputStream.toByteArray(); // data是未压缩数据
System.out.println("未压缩数据长度:" + data.length);
// 读data数据进行压缩
byte[] gzipData = gzip(data);// gzipData是压缩后数据
System.out.println("压缩后数据长度:" + gzipData.length);
// 原来response 目的地是客户端浏览器
httpServletResponse.setHeader("Content-Encoding", "gzip");
httpServletResponse.setContentLength(gzipData.length);
httpServletResponse.getOutputStream().write(gzipData);
httpServletResponse.getOutputStream().flush();
}
// 对data数据进行gzip压缩
public byte[] gzip(byte[] data) {
// 定义字节缓存区,用gzip方式向缓存区写数据
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
try {
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(
arrayOutputStream);
gzipOutputStream.write(data);// 将原数据 压缩gzip格式写入新的缓存区\
gzipOutputStream.close();
arrayOutputStream.flush();
return arrayOutputStream.toByteArray();// 返回压缩后的内容
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("压缩失败!");
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}