1.Listener
1.1 监听三大作用域创建和销毁的监听
package com.hxuner.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/*
1.Servlet事件监听器步骤
实现web开发中的事件监听功能需要两个步骤:
(1)写一个类实现对应接口。 implements *****Listener
(2)在将写好的监听器类注册到web.xml中。
<listener>
<listener-class>com.hxuner.listener.MyServletContextListener</listener-class>
</listener>
2.监听器的生命周期
当web应用启动后,会加载web.xml中配置的所有监听器的类,并实例化
加载顺序是 listener>filter>servlet(load-on-startup)
3.JavaEE提供监听器的目的
允许开发者参与到这些对象的生命周期中,添加自己的逻辑
Servlet/Filter 开发者在项目中实际使用的是自己写的类,最后实例化的是自己写的类的对象
这种情况下,我们可以通过重写父类方法init/destory来参与到其生命周期中
但是,ServletContext/HttpSession/ServletRequest 使用的并不是开发者自己写的类,这种情况下,开发者没有机会将逻辑添加到init/destory方法中
因此JavaEE设计了对应的监听器,保证在这些组件的生命周期过程中,调用对应的监听器的方法,开发者可以提供对应监听器的实现类,在其中添加需要执行的逻辑,借此参与到对应的生命周期中
*/
/*
ServletContextListener 监听器用于监听 ServletContext 对象的创建和销毁。
生命周期:在web应用启动时创建出来 之后一直驻留在内存中唯一的代表当前web应用 直到web应用移除出容易或服务器关闭时 随着web应用的销毁ServletContext对象跟着被销毁
作用范围:整个web应用范围
主要功能:在整个web应用范围内 整个web应用存活期间 共享数据
*/
public class MyServletContextListener implements ServletContextListener {
//当ServletContext对象被创建时,该方法调用
@Override
public void contextInitialized(ServletContextEvent sce) {
//sce.getServletContext()获取事件中封装的当前ServletContext对象
ServletContext sc=sce.getServletContext();
System.out.println("监听听ServletContext被创建"+sce.getServletContext());//sce.getServletContext()获取事件中封装的当前ServletContext对象
//当前创建,马上向其中添加一个键值对
sc.setAttribute("app", sc.getContextPath());// key=app,value=当前web应用的映射路径
}
//当ServletContext对象被销毁时,该方法调用
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO Auto-generated method stub
System.out.println("监听听ServletContext被销毁"+sce.getServletContext());
}
}
package com.hxuner.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/*
HttpSessionListener监听器用于监听HttpSession对象的创建和销毁。
生命周期:在第一次调用request.getSession()时创建 自杀 - session.invalidate 超时 - 30分钟 意外身亡 - 服务器非正常关闭
作用范围:整个会话
主要功能:在整个会话范围内 整个会话的生命周期期间 共享数据
钝化:在服务器正常关闭时 仍然在存活期间的session 会被序列化后保存在tomcat的work目录下 这个过程称之为session的钝化
活化:在服务器正常启动时 会将钝化的session再恢复到内存中 继续使用 这个过程称之为session的活化
*/
public class MyHttpSessionListener implements HttpSessionListener{
//当session对象被创建时调用
@Override
public void sessionCreated(HttpSessionEvent se) {
// TODO Auto-generated method stub
System.out.println("监听到session对象被创建"+se.getSession());
}
//当session对象被销毁时调用
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// TODO Auto-generated method stub
System.out.println("监听到session对象被销毁"+se.getSession());
}
}
package com.hxuner.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/*
* ServletRequestListener监听器用于监听ServletRequest对象的创建和销毁。
ServletRequestListener
生命周期:请求开始时创建 请求结束时销毁
作用范围:整个请求
主要功能:在请求过程中 在请求范围内 共享数据
*/
public class MyServletRequestListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("监听到request被创建"+sre.getServletRequest());
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
// TODO Auto-generated method stub
System.out.println("监听到request被销毁"+sre.getServletRequest());
}
}
1.2 监听三大作用域中存入值,修改值和删除值的监听
package com.hxuner.listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
public class zMyServletContextAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("监听到request作用域添加了属性");
System.out.println("Addedname"+srae.getName()+",value="+srae.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("监听到request作用域删除了属性");
System.out.println("Removedname"+srae.getName()+",value="+srae.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
System.out.println("监听到request作用域修改了属性");
System.out.println("Replacedname"+srae.getName()+",value="+srae.getValue());
}
}
package com.hxuner.listener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class zMyHttpSessionAttributeListener implements HttpSessionAttributeListener{
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("监听到session作用域添加了属性");
System.out.println("Addedname"+se.getName()+",value="+se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("监听到session作用域移除了属性");
System.out.println("Removedname"+se.getName()+",value="+se.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
// TODO Auto-generated method stub
System.out.println("监听到session作用域修改了属性");
System.out.println("Replacedname"+se.getName()+",value="+se.getValue());
}
}
package com.hxuner.listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
/*
* 监听域三个作用域属性变化的监听器
ServletContextAttributeListener ServletContext作用域的属性
HttpSessionAttributeListener Session作用域的属性
ServletRequestAttributeListener Request作用域的属性
这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,
同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。
*/
public class zMyServletRequestAttributeListener implements ServletRequestAttributeListener{
//attributeAdded 方法
//当向被监听器对象中增加一个属性时,web容器就调用事件监听器的 attributeAdded 方法进行相应,这个方法接受一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保存到域中的属性对象。
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("监听到request作用域添加了属性");
System.out.println("Addedname"+srae.getName()+",value="+srae.getValue());
}
//(attributeRemoved 方法
//当删除被监听对象中的一个属性时,web 容器调用事件监听器的这个方法进行相应。
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("监听到request作用域删除了属性");
System.out.println("Removedname"+srae.getName()+",value="+srae.getValue());
}
//attributeReplaced 方法
//当监听器的域对象中的某个属性被替换时,web容器调用事件监听器的这个方法进行相应。
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
// TODO Auto-generated method stub
System.out.println("监听到request作用域修改了属性");
System.out.println("Replacedname"+srae.getName()+",value="+srae.getValue());
}
}
1.3 监听JavaBean和Session关系的监听
package com.hxuner.JavaBean;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
/*
监听JAVABEAN在Session域中状态变化的监听器
1.Javabean在Session中的状态
保存在 Session 域中的对象可以有多种状态:
(1)绑定到 Session 中。
(2)从Session 域中解除绑定。
(3)随Session 被钝化
(4)随Session被活化
2.
HttpSessionBindingListener - 使javabean自己感知自己在session域中被加入或移除的状态变化的监听器
HttpSessionActivationListener - 使javabean自己感知自己在session域中随着session被钝化 活化 状态变化的监听器,必须实现Serializable序列化接口
这两个监听器比较特殊 不需要单独写类来实现 也不需要在web.xml中进行配置 只需要让javabean自己来实现即可
*/
public class Person implements HttpSessionBindingListener,HttpSessionActivationListener,Serializable{
private static final long serialVersionUID = 1L;
//----------------------HttpSessionBindingListener接口---- 绑定/解绑---------------------------------------------------
//实现了HttpSessionBindingListener接口的 JavaBean 对象可以感知自己被绑定到 Session 中和从 Session 中删除的事件。
//当对象被绑定到 HttpSession 对象中时触发
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("监听到JavaBean被绑定到HttpSession对象中");
}
//当对象从 HttpSession 对象中解除绑定时触发
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("监听到JavaBean从HttpSession对象中解除绑定");
}
//------------------------HttpSessionActivationListener接口 -----钝化/活化-,必须实现Serializable序列化------------------------
//实现了HttpSessionActivationListener接口的 JavaBean 对象可以感知自己被活化和钝化的事件。
//当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被钝化时触发
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("监听到JavaBean随HttpSession对象被钝化");
}
//当绑定到 HttpSession 对象中的对象将要随 HttpSession 对象被活化时触发
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("监听到JavaBean随 HttpSession对象被活化");
}
//-------------------------------------------------------------------------------
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
1.4web.xml配置Listener
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<!-- 配置一个监听器 -->
<!-- 监听器的生效不是基于URL,基于事件,web应用启动后,会按顺序加载所有的监听器并实例化 -->
<!-- web.xml中的加载顺序 listener>filter>servlet(load-on-startup>) -->
<!-- 如果Servlet配置了该标签<load-on-startup>,且值>=0,则该Servlet在web应用启动的时候就加载 -->
<listener>
<listener-class>com.hxuner.listener.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.MyServletRequestListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyServletRequestAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyHttpSessionAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.hxuner.listener.zMyServletContextAttributeListener</listener-class>
</listener>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.hxuner.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
This is my JSP page. <br>
<!-- 输出当前web应用映射路径 -->
page=${pageContext.request.contextPath }<br> <%--默认表示当前web应用映射路径 --%>
page=${app} <%-- EL表达式,简洁,通过ServletContext监听器在ServletContext创建的时候就传过来key=app,value=ServletContext.getPath()--%>
</body>
</html>
2.文件上传
package com.hxuner.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.如果是一个文件上传表单,不能使用传统方法获取请求
//String username=request.getParameter("username");
//System.out.println("username="+username); //输出null
//String addr=request.getParameter("addr");
//System.out.println("addr="+addr); //输出null
//2.获取请求实体中所有内容
/*
ServletInputStream sis=request.getInputStream();
byte[] array=new byte[10];
//使用输入流读取一次内容,读取的内容存入array中,len的值是读取字节的长度
int len=sis.read(array);
while(len!=-1){
String str=new String(array,0,len);
System.out.println(str);
len=sis.read(array);
}
sis.close();
输出-----------------------------265001916915724
Content-Disposition: form-data; name="username"
å¼ ä¸
-----------------------------265001916915724
Content-Disposition: form-data; name="addr"
èå¸
-----------------------------265001916915724
Content-Disposition: form-data; name="file"; filename="176.20.190.64.txt"
Content-Type: text/plain
ÎÒÊÇÒ»¸öºÜ´ÏÃ÷µÄÈË
-----------------------------265001916915724--
*/
//获取ServletContext对象,解决路径难问题
//sc.getRealPath("虚拟路径")->返回该资源的绝对路径
//该方法默认从WebRoot文件夹下开始找
ServletContext sc=this.getServletContext();
//使用传统的方式不能解决文件上传表单的乱码问题
//request.setCharacterEncoding("utf-8");
//------------正确使用commons.fileupload.jar 这个包依靠commons-io进行操作 来接受上传文件--------------------------
//1.构造工厂DiskFileItemFactory(FileItem的工厂)时,指定内存缓冲区大小和临时文件存放位置。
//int sizeThreshold 内存缓冲区的大小
//File repository 临时文件存放位置
//文件上传时需要将请求的实体内容全部读取后才能做处理,此时需要将实体内容缓冲起来 内存缓冲快但是耗费内存 文件缓冲慢,但是可以存放大量数据
//所以此处提供了两个选项 if(数据大小小于内存缓冲区的大小sizeThreshold){使用内存做缓冲 速度快}
// else if(文件大小超过了内存缓冲区的大小){在repository指定的位置下创建临时文件来缓冲数据}
DiskFileItemFactory factory=new DiskFileItemFactory(1024, new File(sc.getRealPath("/temp")));
//2.使用DiskFileItemFactory 对象创建ServletFileUpload对象,进行通用配置。
ServletFileUpload fileUpload=new ServletFileUpload(factory);
//2.1判断当前表单是否是一个文件上传表单 enctype为multipart/form-data类型
//boolean isMultipartContent(HttpServletRequest request)
if(!fileUpload.isMultipartContent(request)){
throw new RuntimeException("请使用正确的文件上传表单");
}
//2.2设置单个文件的最大大小 setFileSizeMax(long fileSizeMax)
fileUpload.setFileSizeMax(1024*1024); //1MB
//2.3设置单次上传的所有文件的总大小设置 setSizeMax(long sizeMax)
fileUpload.setSizeMax(1024*1024*5); //5MB
//2.4---注意3:设置字符集,解决文件名的乱码问题---
fileUpload.setHeaderEncoding("utf-8");
//3.实际读取输入流中的内容,封装成FileItem(对表单中每一个input进行封装)
//List<FileItem> parseRequest(HttpServletRequest request)
try {
List<FileItem> list=fileUpload.parseRequest(request);
//对FileItem集合进行操作,获取表单中的数据
if(list!=null){
for(FileItem fileItem:list){
//判断当前FileItem是不是一个普通字段项 如果返回true表示这是一个普通字段项 返回false表示是一个文件上传项
//boolean isFormField()
if(fileItem.isFormField()){ //是普通的input输入框的内容
//如果是普通字段项
//String getFieldName() //获取字段项的名称
//String getString() //获取字段项的值
//String getString(String encode) //获取字段项的值
//获取input的name
String name=fileItem.getFieldName();
//获取input的value
//String value=fileItem.getString();
String value=fileItem.getString("utf-8"); //---注意1:通知工具类使用utf-8进行解码,解决提交参数乱码问题---
System.out.println("name="+name+",value="+value);
}else{ //是文件上传项
//String getName() //获取文件名
//InputStream getInputStream() //获取文件内容的流
//delete() //删除临时文件
//获取上传的文件的文件名
String fileName=fileItem.getName();
//-----------特别注意1 ie浏览器bug-------------
//ie浏览器的部分版本,在上传文件时,会使用文件的完整路径作为文件名
//a.txt c:users\administra\desktop\a.txt
if(fileName.contains("\\")){ //文件名不允许存在\
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);//文件名从最后一个\+1截取到最后
}
//--------特别注意3 文件名重复-------
//多个上传名称相同时 文件会发生覆盖
//解决方案:应该想办法让文件名 尽量不要重复 - 在文件名的前面拼接UUID来保证文件名绝对不会重复
fileName=UUID.randomUUID()+"_"+fileName; //数据库里保存了该filename与用户的对应关系
//-------特别注意4:上传文件目录下文件过多----------
//一个文件夹下文件过多会造 访问缓慢,甚至有可能无法访问
//解决方案:所以应该想办法将这些文件分目录存储,利用文件名的hascode的16进制表示生成对应的目录。
String hsStr=Integer.toHexString(fileName.hashCode());
//补足8位
while(hsStr.length()<8){
hsStr="0"+hsStr;
}
//生成中间路径
String midPath="/";
for(int i=0;i<hsStr.length();i++){
midPath=midPath+"/"+hsStr.charAt(i)+"/";
}
// 用本变量保存实际存储的路径
// sc.getRealPath方法返回的路径会去掉最后的 /
String savePath=sc.getRealPath("/WEB-INF/upload"+midPath);
// 在服务器上创建对应的文件夹
new File(savePath).mkdirs();
//获取上传的文件的输入流in->read
InputStream is=fileItem.getInputStream();
FileOutputStream fos=null;
try {
//输入流 out ->Write
fos=new FileOutputStream(savePath+"/"+fileName);
/*
* ----------- 特别注意2:文件上传保存位置问题---------------------
上传一个这个index.jsp到upload里,然后访问浏览器/upload/index.jsp就可以通过上传的jsp写的代码显示所有文件内容
所以不让用户通过浏览器直接访问其上传的文件。
文件上传保存的位置一定不能被外界直接访问 防止用户浏览器访问 下载资源 或执行jsp恶意代码
要么保存在WEB-INF下保护起来
要么放在本地磁盘其他位置,保证通过浏览器无法直接访问
*/
byte[] array=new byte[100];
int len=is.read(array);
while(len!=-1){
fos.write(array,0,len);
len=is.read(array);
}
} catch (Exception e) {
// TODO: handle exception
}finally{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
//---注意2:在关流之后,要注意删除临时文件,需要在关流之后调用---
fileItem.delete();
}
}
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%--
提供一个带有文件上传项的表单
文件上传的输入框必须有name属性才能被上传
文件上传的表单必须是post提交
文件上传的表单必须设置enctype=multipart/form-data
--%>
<form action="${app}/UploadServlet" method="post" enctype="multipart/form-data"> <%--文件上传的表单必须是post提交 文件上传的表单必须设置enctype=multipart/form-data --%>
用户名<input type="text" name="username"><br>
地址<input type="text" name="addr"><br>
文件<input type="file" name="file"><br> <%-- 文件上传的输入框必须有name属性才能被上传--%>
<input type="submit" value="提交"><br>
</form>
特别注意2:<br>
上传一个这个index.jsp到upload里,然后访问浏览器/upload/index.jsp就可以通过上传的jsp写的代码显示所有文件内容<br>
所以不让用户通过浏览器直接访问其上传的文件。<br>
文件上传保存的位置一定不能被外界直接访问 防止用户浏览器访问 下载资源 或执行jsp恶意代码<br>
要么保存在WEB-INF下保护起来 <br>
要么放在本地磁盘其他位置,保证通过浏览器无法直接访问<br>
</body>
</html>