Filter过滤器
Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能,统一字符集编码。
它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
如何借助Filter实现拦截功能?
Servlet API提供了一个Filter接口,编写的过滤器必须实现该接口。Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,Web服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到过滤的目的.
Filter开发两步走
①编写java类实现java.servlet.Filter接口,并实现其doFilter方法。
public class DemoFilter implements Filter{
@Override
public void destroy() {
System.out.println("destroy");
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
System.out.println("doFilter");
}
@Override
public void init(FilterConfig config) throws ServletException {
Sysout.out.println(config.getInitParameter("username"));
System.out.println("init");
}
}
②在web.xml文件中对编写的filter类进行注册,并设置它所能拦截的资源。
</welcome-file-list>
<!-- 配置Servlet,但现在我不在这里配,可以用注解的方式在控制类中配 -->
<!-- 配置过滤器, -->
<filter>
<filter-name>DemoFilter</filter-name>
<filter-class>cn.xsw.mvcproject.filter.DemoFilter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>xiongshaowen</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DemoFilter</filter-name>
<url-pattern>/*</url-pattern> <!--拦截项目的所有资源-->
<dispatcher>REQUEST</dispatcher> <!-- 指定拦截的请求,默认request,还有- FORWARD
- INCLUDE
- ASYNC
- ERROR -->
</filter-mapping>
</web-app>
测试:因为过滤(拦截)了所有资源,所以我启动服务器,打开登录页面(或其它页面)都显示空白,说明拦截了
每刷新一下网页,或打开另一个网页,就会输出一次doFilter,说明该方法会执行n多次,拦截功能是由doFilter()方法来做。
放行拦截的所有资源
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(req, resp);
}
自定义实现Filter接口的父类
有时候我们不用去每次去实现三个方法,这样显得太麻烦了,如destory根本不用我们去重写。我们全都会用到doFilter()方法,我们只要重写该方法即可。request,response都改为HttpServletRequest,HttpServletResponse对象,这样方便,省得我们到时强制转换。
一个实现Filter接口的通用类GenericFilter.java
public class GenericFilter implements Filter{
private FilterConfig filterConfig; //保证重写方法时会在父类中初始化该对象,因为我们继承该类时,就不用写那么多代码
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest)req, (HttpServletResponse)resp, chain);
}
//重写doFilter,以便子类覆盖,如果有子类,那么也是先执行父类的doFilter()方法,我们在父类中调用了该方法,所以保证了子类重写也一定会执行doFilter()
public void doFilter(HttpServletRequest req,HttpServletResponse resp,FilterChain chain) {
}
@Override
public void init(FilterConfig config) throws ServletException {
this.filterConfig=config;
init();
}
/**
* 这个init方法,也是提供给子类覆盖的方法,当然了,可以重写有参的init()方法,但这有可能丢掉
* filterConfig的初始化,因为java jvm会执行子类重写的同名同参方法
* 为什么说有可能,因为人一般不会考虑到那么周到,
* 如果周到的话,子类重写有参的init方法中,一定要写上:
* super.init(config);
*/
protected void init() {
}
}
通用类的应用,我们一般只要重写doFilter(HttpServletRequest req,H… resp,… chian)方法即可
当然了,可以重写有参的init()方法,但这有可能丢掉filterConfig的初始化实例,获取config对象时,会报空指针异常,因为java jvm会执行子类重写的同名同参方法,为什么说有可能,因为人一般不会考虑到那么周到,如果周到的话,子类重写有参的init方法中,一定要写上:
super.init(config);
public class DemoFilter extends GenericFilter{
@Override
public void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) {
System.out.println("我是DemoFilter,我继承了GenericFilter类,我的");
try {
chain.doFilter(req, resp); //拦截后,放行这里要处理异常
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void init() {
System.out.println("init");
System.out.println(getFilterConfig().getInitParameter("username"));
}
}
运行:结果
Filter禁用浏览器缓存
什么是浏览器缓存?
浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这就是浏览器缓存。
为什么使用浏览器缓存?
对于浏览器缓存,相信大家都不会陌生,因为我们经常使用它,但是我们为什么使用浏览器缓存呢?作为用户,使用缓存可以使我们更快的打开一个已经访问过的页面。作为web站点的管理者,浏览器缓存可以在一定程度上减少服务器开销,同时避免相同内容的重复传输带来的带宽浪费。
浏览器缓存是如何产生的?
浏览器请求某个URL,服务端会根据请求返回相应内容,此时浏览器会把这些内容存储起来,这样就产生了浏览器缓存。浏览器缓存的产生同时依赖于浏览器和web服务器,它们之间必然存在沟通,这种沟通机制我们称之为“缓存协商”。
Filter禁用缓存:
public class DemoFilter2 extends GenericFilter{
@Override
public void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) {
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
resp.setDateHeader("Expires", -1); //设置过期时间 -1表示马上清除
try {
chain.doFilter(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Flter改造mvcproject案例的自动登录
前面开发中,我在login.jsp中用js代码实现了登录成功后,再访问login.jsp会自动登录到main.jsp页面。
<title>登陆页面</title>
<script type="text/javascript">
function getCook(c_name){
//document.cookie,得到的是一个字符串式,每个cookie以健值对形式存再用;号隔开,最后一个键值对没有分号
if(document.cookie.length>0){
var start = document.cookie.indexOf(c_name+"=");
start = start+c_name.length+1;
var end = document.cookie.indexOf(";",start);
if(end==-1)
end=document.cookie.length; //如果没找到;,则表示;取最后个键值对
var cval=document.cookie.substring(start,end);
return unescape(cval);
}
}
window.onload=function(){
var form = document.getElementById("loginform");
var username= document.getElementById("username");
if(getCook("ssid")!=null && getCook("ssid")!="" && getCook("userKey")!=null && getCook("userKey")!=""){
username.value=getCook("userKey");
form.submit();
}
}
现在我们改造一下,用过滤器实现拦截处理,只拦截login.jsp页面。
public class AutoLoginFilter extends GenericFilter {
@SuppressWarnings("unused")
@Override
public void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws IOException, ServletException {
//基本思想:判断请求中有没有cookie,有的话,userKey,ssid,是不是相等的
//实现自动登录,跳转到main.jsp页面,同时把userKey值放到session,实现了自动登录效果
//如果没有cookie,没有关键cookie,转发它的请求(放行login.jsp),让页面正常执行登录
Cookie[] cookies=req.getCookies();
if(cookies!=null && cookies.length>0) {
String username=null;
String ssid= null;
for(Cookie c:cookies) {
if(c.getName().equals("userKey")) {
username=c.getValue();
}
if(c.getName().equals("ssid")) {
ssid=c.getValue();
}
}
if(username!=null && ssid!=null && ssid.equals(CookieUtils.md5Encrypt(username))) {
HttpSession session = req.getSession();
session.setAttribute("usernamesession", username);//这个会话域的对象保存了用户名,在main.jsp页面会获取它,用来判断是否登录过,如为空,则跳转到登录而
resp.sendRedirect(req.getContextPath()+"/main.jsp");
}else {
chain.doFilter(req, resp); //拦截处理了后,记住,放行这代码,要先写,免得忘记
}
}else{ //cookies都没有,放行,让login.jsp正常显示
chain.doFilter(req, resp); //拦截处理了后,记住,放行这代码,要先写,免得忘记
}
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>mvcproject</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>AutoLoginFilter</filter-name>
<filter-class>cn.xsw.mvcproject.filter.AutoLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AutoLoginFilter</filter-name>
<url-pattern>/login.jsp</url-pattern>
</filter-mapping>
</web-app>
FIlter判断是否登录改造mvcproject案例
当没有登录成功时,不能登其它要有权限的页面(包括控制层的映射的视图–如删除功能,没有页面,只是一个逻辑)。本工程案例中,只有login.jsp页面可以在没有登录的情况下打开。
基本思路:
如果登录成功,会在上面的自动登录过滤器中,保存了usernamesession(session.setAttribute(“usernamesession”,username);)还有login.udo中也保存了usernamesession,这里可以不必。
web.xml中配过滤器时,配了两个参数,一个参数表示不要权限可以访问的资源,一个是要有登录权限才可访问的资源。
IsLoginFilter.java过滤拦截,再放行满足条件的资源。
IsLoginFilter.java
public class IsLoginFilter extends GenericFilter {
@Override
public void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws IOException, ServletException {
String path=req.getServletPath().substring(1); //我打开了那个页面,就会显示对应的路径名,如main.jsp,打印 ‘/main.jsp'.
//System.out.println("当前页面:"+path);
String autho = getFilterConfig().getInitParameter("authority"); //拿到web.xml中配置的参数,内容如:main.jsp,update.jsp,add.jsp,add.udo,query.udo,delete.udo
String noautho = getFilterConfig().getInitParameter("noautho");
String[] strArr = autho.split(",");
String[] strArrno = noautho.split(",");
//System.out.println("noautho::::"+noautho);
//不需要权限的直接放行 :login.jsp,login.udo,logout.udo,index.html,index.jsp,error.jsp
for(String str:strArrno) {
if(path.equals(str)) {
chain.doFilter(req, resp);
}
}
//需要登录权限的,要登录成功才能放行
HttpSession session = req.getSession();
for(String str:strArr) {
if(str.equals(path)) {
String username =(String)session.getAttribute("usernamesession"); //自动登录过滤器类,login控制类中都有保存
if(username!=null) {
chain.doFilter(req, resp);
}else {
resp.sendRedirect(req.getContextPath()+"/login.jsp");
}
}
}
}
}
web.xml
<!-- 过滤器权限控制,authority,要权限的参数,noautho不要权限的参数 -->
<filter>
<filter-name>IsLoginFilter</filter-name>
<filter-class>cn.xsw.mvcproject.filter.IsLoginFilter</filter-class>
<init-param>
<param-name>authority</param-name>
<param-value>main.jsp,update.jsp,add.jsp,add.udo,query.udo,delete.udo</param-value>
</init-param>
<init-param>
<param-name>noautho</param-name>
<param-value>login.jsp,login.udo,logout.udo,index.html,index.jsp,error.jsp,drawCode.udo</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>IsLoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
监听器
Listener的介绍和基本用法
监听器用于监听web应用中某些对象、信息的创建、销毁、增加,修改,删除等动作的发生时,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等。
使用Listener步骤
①通过实现具体接口创建实现类(可实现多个监听器接口)
②配置实现类成为监听器,或者将注册监听器,有两种配置方式:
通过web.xml方式配置,代码如下:
<listener>
<listener-class>com.zrgk.listener.MyListener</lisener-class>
</listener>
直接用@WebListener注解修饰实现类
常用的Web事件的监听接口如下:
ServletContextListener:用于监听Web的启动及关闭
ServletContextAttributeListener:用于监听ServletContext范围内属性的改变
ServletRequestListener:用于监听用户请求
ServletRequestAttributeListener:用于监听ServletRequest范围属性的改变
HttpSessionListener:用于监听用户session的开始及结束
HttpSessionAttributeListener:用于监听HttpSession范围内的属性改变
1.ServletContextListener
①该接口用于监听Web应用的启动与关闭
②该接口的两个方法:
contextInitialized(ServletContextEvent event); // 启动web应用时调用
contextDestroyed(ServletContextEvent event); // 关闭web应用时调用
③如何获得application对象:
ServletContext application = event.getServletContext();
例:
ServletContextListenerImpl.java
public class ServletContextListenerImpl implements ServletContextListener{
/**
* 该方法会在web应用服务器程序关闭时触发
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
System.out.println("服务器关闭了!");
}
/**
* 该方法会在web应用服务器程序启动时触发
*/
@Override
public void contextInitialized(ServletContextEvent event) {
System.out.println("服务器启动了!");
ServletContext application=event.getServletContext();
System.out.println("拿到了代表整个web应用程序的对象的引用:"+application.getServletContextName());
}
}
web.xml或注解,来注册监听器
<!-- 注册监听器 -->
<listener>
<listener-class>cn.xsw.mvcproject.listener.ServletContextListenerImpl</listener-class>
</listener>
效果,当启动tomcat后,控制台会显示
信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
服务器启动了!
拿到了代表整个web应用程序的对象的引用:mvcproject
五月 08, 2025 10:27:59 上午 org.apache.coyote.AbstractProtocol start
2.ServletContextAttributeListener
①该接口 用于监听ServletContext范围(application)内属性的改变。
②该接口的三个方法:
attributeAdded(ServletContextAttributeEvent event); // 当把一个属性存进application时触发
attributeRemoved(ServletContextAttributeEvent event); // 当把一个属性从application删除时触发
attributeReplaced(ServletContextAttributeEvent event); // 当替换application内的某个属性值时触发
③如何获得application对象:
ServletContext application = event.getServletContext();
例:一打开index.jsp,就到控制台中看到相应的键值对的键与值字符串
index.jsp
<body>
<%
application.setAttribute("name","xiongshaowen");
%>
</body>
ServletContextAttributeListenerImpl.java
public class ServletContextAttributeListenerImpl implements ServletContextAttributeListener{
/**
* 在ServletContext(application)范围里,添加属性的时刻,触发
*/
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
System.out.println(event.getName()); //拿到新添加属性的键
System.out.println(event.getValue()); //拿到新添加进来属性的值
}
/**
* 在ServletContext(application)范围里,移除属性的时刻,触发
*/
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
System.out.println(event.getName()); //拿到新移除属性的键
System.out.println(event.getValue()); //拿到新移除属性的值
}
/**
* 在ServletContext(application)范围里,替换属性的时刻,触发
*/
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
System.out.println(event.getName()); //拿到被替换属性的键
System.out.println(event.getValue()); //...值
}
}
3.ServletRequestListener与ServletRequestAttributeListener
ServletRequestListener用于监听用户请求。
ServletRequestAttributeListener用于监听request范围内属性的变化。
ServletRequestListener两个需要实现的方法
提交表单和转发。
requestInitialized(ServletRequestEvent event); //用户请求到达、被初始化时触发
requestDestroyed(ServletRequestEvent event); // 用户请求结束、被销毁时触发
ServletRequestAttributeListener三个需要实现的方法
attributeAdded(ServletRequestAttributeEvent event); // 向request范围内添加属性时触发
attributeRemoved(ServletRequestAttributeEvent event); // 从request范围内删除某个属性时触发
attributeReplaced(ServletRequestAttributeEvent event); // 替换request范围内某个属性值时触发
获取reqeust对象
HttpServletRequest req = (HttpServletRequest)event.getServletRequest();
4. HttpSessionListener与HttpSessionAttributeListener
HttpSessionListener监听用户session的开始与结束。
HttpSessionAttributeListener监听HttpSession范围(session)内的属性的改变。
HttpSessionListener要实现的方法:
sessionCreated(HttpSessionEvent event); // 用户与服务器的会话开始、创建时触发
sessionDestroyed(HttpSessionEvent event); // 用户与服务器的会话结束时触发
HttpSessionAttributeListener要实现的方法:
attributeAdded(HttpSessionBindingEvent event) ; // 向session范围内添加属性时触发
attributeRemoved(HttpSessionBindingEvent event); // 删除session范围内某个属性时触发
attributeReplaced(HttpSessionBindingEvent event); // 替换session范围内某个属性值时触发
如何得到session对象
HttpSession session = event.getSession();
例:同时执行两个接口,实现5个方法,用注解,注册监听器
@WebListener
public class HttpSessionListenerImpl implements HttpSessionListener,HttpSessionAttributeListener{
@Override
public void sessionCreated(HttpSessionEvent event) {
System.out.println("会话ID:"+event.getSession().getId());
System.err.println("会话建立了了!");
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("会话结束了了!");
}
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
System.out.println("会话新添加属性:"+event.getName()+",属性值:"+event.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
System.out.println("会话移除属性:"+event.getName()+","+event.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
System.out.println("会话替换属性:"+event.getName()+","+event.getValue());
}
}
测试效果:当启动服务器,打开login.jsp页面,登录成功后,我用session存储了usernamesession
会话ID:8AB0112390029B9CCC55C5FB4E3ECE79
会话新添加属性:usernamesession, 属性值:xiongshaowen
会话建立了了!
测试效果2:设置会话有效期为5秒,看看关闭或刷新浏览器,会在控制台显示会话关闭,不然的话依默认有效期20分钟,太难等了。
在login.jsp中用java代码设置会话有效期为1秒,这里只为了测试,不可能在生产环境中这么干。
<%
session.setMaxInactiveInterval(5);
%>
案例:在线显示所有已登录用户名
1.不精确离开的情况,没有数据库帮助的情况下,又会话默认存在20分钟有效期,没办法记录用户离开的时间
我利用会话监听器与及属性改变监听器来做监听,当属性(login.udo中,当用户登录成功,session添加了用户名(usernamesession属生))添加时,监听器自动监听到了,获取它,同时又获取session.ID,两个值保存在Map中,以键值对的形式存储,ID—USERNAME.
OnlineListener.java
@WebListener
public class OnlineListener implements HttpSessionListener,HttpSessionAttributeListener{
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
ServletContext application = session.getServletContext(); //保存计数器
@SuppressWarnings("unchecked")
Map<String,String> online=(Map<String, String>)application.getAttribute("online");
if(online==null) {
online=new HashMap<String, String>();
}
online.put(session.getId(), session.getAttribute("usernamesession").toString());
application.setAttribute("online", online);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
}
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
ServletContext application = session.getServletContext();
@SuppressWarnings("unchecked")
Map<String,String> online = (Map<String, String>)application.getAttribute("online");
if(online==null) {
online = new HashMap<>();
}
String username= (String)session.getAttribute("usernamesession");
username=username==null?"游客":username;
online.put(session.getId(), username);
application.setAttribute("online", online);
}
/**
* 会话关闭,把登陆状态删除(online集合中)
*/
@Override
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
ServletContext application = session.getServletContext();
@SuppressWarnings("unchecked")
Map<String,String> online = (Map<String, String>)application.getAttribute("online");
if(online!=null) {
online.remove(session.getId());
application.setAttribute("online", online);
}
}
}
main.jsp ----在body的最下方建表格
<table border="1" cellpadding="0" cellspacing="0" style="width:80%;margin:0 auto;">
<tr>
<td>在线用户的会话id</td>
<td>在级用户的用户名</td>
</tr>
<%
Map<String,String> online = (Map<String,String>)application.getAttribute("online");
for(String str:online.keySet()){
%>
<tr>
<td><%=str %></td>
<td><%=online.get(str) %></td>
</tr>
<%
}
%>
</table>
2.精确离开的情况,利用数据库操作,精确记录时间字段可以精准的记录会话的关闭时间
- ServletRequestListener,ServletRequestAttributeListener
1.当请求进来的时候(绝大多数是request请求),记录访问者(包括游客)信息到数据库。如果数据库中有,更新它的访问时间,没有的话,插入
2.离开的用户状态信息记录,10分内,用户没有操作,标记用户离线,数据库表中在线信息记录,删除
3.访问者登录成功后(login.udo), 将Online数据表里的username,从游客改为真正的用户名
OnlineRequestListener.java-----监听器,在当前request请求时,就把会话中ip,登录时间,访问的网页。。。都保存到数据库中,如果数据库中有该用户,则更新一下时间和页面,以不要超时(10分钟)删除记录。
@WebListener
public class OnlineRequestListener implements ServletRequestListener,ServletRequestAttributeListener{
OnlineService onlineService=FactoryService.getOnlineService();
public void attributeAdded(ServletRequestAttributeEvent event) {
}
public void attributeRemoved(ServletRequestAttributeEvent event) {
}
public void attributeReplaced(ServletRequestAttributeEvent event) {
}
public void requestDestroyed(ServletRequestEvent event) {
}
/*1.通过请求拿到访问者的基本信息(注:session对象的获取)
* 2.OnlineService,查询数据库有没有用户登录状态信息,有的话,更新为当前时间和当前访的页面,没有的话,插入到库中
*/
public void requestInitialized(ServletRequestEvent event) {
HttpServletRequest req=(HttpServletRequest)event.getServletRequest();
HttpSession session = req.getSession();
String ssid = session.getId();
String ip=req.getRemoteAddr();
//拿到用户正在访问的页面
String page=req.getRequestURI();
String username=(String)session.getAttribute("usernamesession"); //登录成功时保存到session域空间的
username = username==null?"游客":username;
//当前用户正在请求的时候,获取它的Online信息,
Online ol=new Online();
ol.setSsid(ssid);
ol.setPage(page);
ol.setUsername(username);
ol.setIp(ip);
ol.setTime(new Date());
Online online=onlineService.getOnlineBySsid(ssid);
if(online!=null) {
online.setTime(new Date());
online.setPage(page);
onlineService.updateOnline(online);
}else {
onlineService.insertOnline(ol);
}
}
}
UserController.java中,login登录成功或已登录过没超时,我们要把用户名设置到online表中,以便把‘游客’改为真正的用户名,因为上面的监听器,是监听所有访问该项目的,包括没有登录成功的
online.udo 是供main.jsp显示在线用户的
public void login(xxxxxxx req,xxxxx resp) throws.......{
..........
break;
}
//到这已登录成功,准许客户,进入main.jsp
req.getSession().setAttribute("usernamesession",user.getUsername() );
Online ol=onlineService.getOnlineBySsid(session.getId());
if(ol!=null) {
ol.setUsername(username);
onlineService.updateOnline(ol);
}
//req.getRequestDispatcher("/main.jsp").forward(req, resp);
resp.sendRedirect(req.getContextPath()+"/main.jsp");
}else {
req.setAttribute("note", "用户名或密码或验证码是错误 的!");
//req.getRequestDispatcher("/login.jsp").forward(req, resp);
resp.sendRedirect(req.getContextPath()+"/login.jsp");
}
}else { //已经登录过,并在有效期,可直接进入main.jsp页
req.getSession().setAttribute("usernamesession",username);
Online ol=onlineService.getOnlineBySsid(session.getId());
if(ol!=null) {
ol.setUsername(username);
onlineService.updateOnline(ol);
}
//req.getRequestDispatcher("/main.jsp").forward(req, resp);
resp.sendRedirect(req.getContextPath()+"/main.jsp");
}
}else {
System.out.println("重复提交了表单!!");
req.getRequestDispatcher("/main.jsp").forward(req, resp);
}
}
/**
* 显示的有在线用户的信息
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
public void online(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
List<Online> list=onlineService.getAllOnLine();
req.setAttribute("online", list);
req.getRequestDispatcher("/online.jsp").forward(req, resp);
}
- ServletContextListener,web应用启动时,要做每5秒钟检查–javax.swing.Timer定时器做,过期用户,并执行删除
@WebListener
public class OnlineServletContextListener implements ServletContextListener{
public final int MAX_MILLTIS=10*60*1000; //常数,表示10分钟,表示用户请求后10钟内没有操作,设置离线
OnlineService onlineService=FactoryService.getOnlineService();
@Override
public void contextDestroyed(ServletContextEvent event) {
}
@Override
public void contextInitialized(ServletContextEvent event) {
//服务器启动的时候,触发
List<Online> expiresUserList = new ArrayList<Online>();//存放超过10分钟没有操作的用户登录状态信息记录对应的对象的集合
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//定时器,监听器,javax.swing.Timer; 每隔5秒检查
new Timer(5 * 1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
List<Online> list = onlineService.getAllOnLine();
if (list != null && list.size() > 0) {
Date exDate = null;
for (Online ol : list) {
exDate = ol.getTime();
simpleDateFormat.format(exDate);
Long exMilles;
try {
exMilles = simpleDateFormat.parse(exDate.toString()).getTime();
if ((System.currentTimeMillis() - exMilles) > MAX_MILLTIS) {
expiresUserList.add(ol);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
// 从数据库中删除掉过时的访问者信息
onlineService.deleteExpiresOnline(expiresUserList);
//数据库中删除过时的访问者信息
expiresUserList.clear();
}
}).start();
}
}
其它Online.java,OnlineDao.java,OnlineDaoImpl.java,OnlineService.java,OnlineServiceImpl.java
//1.Online.java
public class Online {
private String ssid;
private String username;
private String page;
private String ip;
private Date time;
//get/set方法,toString方法,无参构造方法,有参构造方法
}
//2.OnlineDao.java
public interface OnlineDao {
/**
* 取出所有在线访问者信息(包括游客)
* @return
*/
public List<Online> getAllOnLine();
/**
* 插入一条Online信息
* @param online
*/
public void insertOnline(Online online);
/**
* 更新保存的online信息
* @param online
*/
public void updateOnline(Online online);
/**
* 根据ssid删除online信息
* @param ssid
*/
public void deleteExpiresOnline(String ssid);
/**
* 根据ssid获取一条在线记录信息
* @param ssid
* @return
*/
public Online getOnlineBySsid(String ssid);
}
//3.OnlineDaoImpl.java
public class OnlineDaoImpl extends BaseDao<Online> implements OnlineDao{
@Override
public List<Online> getAllOnLine() {
String sql = "SELECT `ssid`,`username`,`page`,`ip`,`time` FROM `online`";
return super.getAll(sql);
}
@Override
public void insertOnline(Online online) { //父类BaseDao中的update方法是insert,update,delete的通用方法,由QueryRunner类决定的
String sql="INSERT INTO `online` (`ssid`,`username`,`page`,`ip`,`time`) VALUES (?,?,?,?,?);";
super.update(sql, online.getSsid(),online.getUsername(),online.getPage(),online.getIp(),online.getTime());
}
@Override
public void updateOnline(Online online) {
String sql="UPDATE `online` SET `username`=?,`page`=?,`ip`=?,`time`=? WHERE `ssid`=?;";
super.update(sql,online.getUsername(),online.getPage(),online.getIp(),online.getTime(),online.getSsid());
}
@Override
public void deleteExpiresOnline(String ssid) {
String sql="DELETE FROM `online` WHERE `ssid`=?;";
super.update(sql, ssid);
}
@Override
public Online getOnlineBySsid(String ssid) {
String sql="SELECT `ssid`,`username`,`page`,`ip`,`time` FROM `online` WHERE `ssid`=?;";
return super.get(sql,ssid);
}
}
//4.OnlineService.java
/**
* 服务层,这里我们不是大项目,没有过多的业务,只有删除时候我改一下,我们服务层,要删除所有已过期的登录者信息,
* 而DAO层因为直接与数据库打交道,只删除一条,所以服务层这里稍微加一点业务,其它不变
* @author Administrator
*
*/
public interface OnlineService {
/**
* 取出所有在线访问者信息(包括游客)
* @return
*/
public List<Online> getAllOnLine();
/**
* 插入一条Online信息
* @param online
*/
public void insertOnline(Online online);
/**
* 更新保存的online信息
* @param online
*/
public void updateOnline(Online online);
/**
* 根据ssidArr数组删除多个online信息
* @param ssid
*/
public void deleteExpiresOnline(List<Online> expiresUserList);
/**
* 根据ssid获取一条在线记录信息
* @param ssid
* @return
*/
public Online getOnlineBySsid(String ssid);
}
//5.OnlineServiceImpl.java
public class OnlineServiceImpl implements OnlineService{
OnlineDao onlineDao=FactoryDao.getOnlineDao();
@Override
public List<Online> getAllOnLine() {
return onlineDao.getAllOnLine();
}
@Override
public void insertOnline(Online online) {
onlineDao.insertOnline(online);
}
@Override
public void updateOnline(Online online) {
onlineDao.updateOnline(online);
}
@Override
public void deleteExpiresOnline(List<Online> list) {
//遍历这个list集合
if(list!=null && list.size()>0) {
for(Online ol:list) {
onlineDao.deleteExpiresOnline(ol.getSsid());
}
}
}
@Override
public Online getOnlineBySsid(String ssid) {
return onlineDao.getOnlineBySsid(ssid);
}
}
文件的上传与下载
上传文件的表单注意项:
①请求方式必须是post, 因为get请求传的只是文件名字,没有请求体内容过不去。
②使用file的表单域
③使用multipart/form-data的请求编码方式
4.所需要的包,都已下载,拷呗到lib下,再右击各包,点Build path–add to … 。
index.jsp,请求(post方式,enctype=“multipart/form-data”
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件的上下与下载</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/uploadServlet.fdo" enctype="multipart/form-data" method="post">
<input type="file" name="filename"/><br><br>
<input type="text" name="desc"/><br><br>
<input type="submit" value="上传"/>
</form>
</body>
</html>
1.什么是multipart/form-data?
首先我们需要明白在html中的enctype属性,
enctype:规定了form表单在发送到服务器时候编码方式。他有如下的三个值。
①application/x-www-form-urlencoded。默认的编码方式。但是在用文本的传输,大型文件的时候,使用这种编码就显得 效率低下。
②multipart/form-data 。 指定传输数据为二进制类型,比如图片、mp3、文件。
③text/plain。纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。
2.明确在enctype参数为application/x-www-form-urlencoded的时候post和get请求参数和请求体是什么形式的
get请求请求头:
GET/www.xxx.com?name=%22hello+world%22&**file=temp.png**&submit=submit HTTP/1.1
因为get请求没有请求体,所有他的所有参数都是在url的后边添加。type=file的表单项只能获取到文件的名字,并不能获取文件的内容。
post请求请求头:
POST /www.baidu.com HTTP/1.1
请求体:
name=%22hello+world%22&file=temp.png&submit=submit
(1)我们可以发现不管是post请求和get请求,他们的参数存在的形式都是不变的,通过key=value的形式存在。
(2)表单项type=file只能获取获取文件的名字不能获取文件的内容。
3. 明确在enctype参数为multipart/form-data的时候post和get请求参数和请求体是什么形式的
GET/www.xxx.com?name=%22hello+world%22&file=temp.png&submit=submit HTTP/1.1
get请求和multipart/form-data结合无效,因为文件上传需要请求体。
post请求请求头:
POST /www.xxx.com HTTP/1.1
请求体:在火狐上看看请求体!
通过观察发现这个的请求体就发生了变化。这种请求体被称之为多部件请求体。
什么是多部件请求体:就是把每一个表单项分割为一个部件。
因为表单项分为普通表单项和文件表单项,所以说部件也有区别。
普通表单项:
一个请求头:Content-Disposition: form-data; name=”name”
一个请求体:里面就是我们表单的值”hello world”
文件表单项两个请求头:
Content-Disposition: form-data; name="file"; filename="temp.png"
Content-Type: image/png
一个请求体:
在multipart/form-data的post请求下serlvet里取一下参数值!下面代码是在UploadFileController.java中写的
@WebServlet(urlPatterns= {"*.fdo"})
public class UploadFileController extends HttpServlet{
private static final long serialVersionUID = 1L;
@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 {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
System.out.println("这里是post请示的处理方法,根路径:"+req.getContextPath());
String mn=req.getServletPath(); //返回提交请求的地址名 如: /query.udo
mn=mn.substring(1); // 去掉反斜杠
mn=mn.substring(0,mn.length()-4); //去掉 .udo
try {
Method method=this.getClass().getDeclaredMethod(mn,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this, req,resp);
} catch (Exception e) { //捕获总异常即可,后面的捕获异常可去掉
e.printStackTrace();
}
}
private void uploadServlet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getParameter("filename")); //null
System.out.println(req.getParameter("desc")); //null
}
}
----------------------------------------------------------------------------------------------
这里是post请示的处理方法,根路径:/mvcuploadfile
null
null
总结:参数获取不到主要是因为在使用multipart/form-data属性之后请求体发生了变化。不是key=value的形式出现所以说获取不到。
解决办法:
(1)我们可以通过js代码来些修改,把我们的参数追加在url的后边,了解一下即可,我们不用这种处理方法。
<form id="upload" name="upload" action="fileftp.jsp" method="post" ENCTYPE="multipart/form-data">
<input type="hidden" name="otherName" id="otherName" value="abcdefg"/>
<td nowrap>
<input type="file" id="file1" name="file1" value="" size="40" class="sbttn"/>
<input type="submit" value="上传" class="sbttn"/>
</td>
</form>
<script language="javascript">
function formSubmit(){
var action="fileftp.jsp";
action+="?otherName="+document.upload.otherName.value;
document.upload.action=action;
document.upload.submit();
}
</script>
(2)通过修改服务器端代码。前提是利用jar包,下面的二级标题
以下的内容都属于这个要讲的东西,只是为了让我以后好找到它。
文件上传的fileupload 组件
两个jar包,已经下载好了,commons-fileupload-1.2.2.jar和commons-io-1.4.jar
下面是一个讲述组件处理上传或下载请求的过程,但不能运行
@WebServlet(urlPatterns= {"*.fdo"})
public class UploadFileController extends HttpServlet{
private static final long serialVersionUID = 1L;
@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 {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
System.out.println("这里是post请示的处理方法"+req.getContextPath());
String mn=req.getServletPath(); //返回提交请求的地址名 如: /query.udo
mn=mn.substring(1); // 去掉反斜杠
mn=mn.substring(0,mn.length()-4); //去掉 .udo
try {
Method method=this.getClass().getDeclaredMethod(mn,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this, req,resp);
} catch (Exception e) { //捕获总异常即可,后面的捕获异常可去掉
e.printStackTrace();
}
}
@SuppressWarnings("unused")
private void uploadServlet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println(req.getParameter("filename"));
// System.out.println(req.getParameter("desc"));
//1.创建一个工厂类,创建的对象,可以设置很多对上传文件的限制
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.拿到到request请求的解析器,解析器可以设置单个文件的大小,也可设置多个文件的总大小
ServletFileUpload sfu = new ServletFileUpload();
//sfu.setFileSizeMax(fileSizeMax);
//sfu.setSizeMax(sizeMax);
//3.解析器来解析request请求解析器,要捕获异常信息
//FileItem:就是封装有的一个一个的form提交过来的表单域项,表单项分两大类:普通域,文件域
try {
List<FileItem> list=sfu.parseRequest(req);
for (FileItem fileItem : list) {
//判断表单项是普通表单项,否则是文件表单项
if(fileItem.isFormField()) {
String name=fileItem.getFieldName();
String value=fileItem.getString(); //拿到表单标签name属性的值
}else { //否则是文件域表单项
fileItem.getName(); //拿到文件的名字
fileItem.getContentType(); //文件的类型
fileItem.getSize(); //文件的大小
//FileInputStream in=(FileInputStream)fileItem.getInputStream();
InputStream in = fileItem.getInputStream(); //接口类型,不用强转
OutputStream out = new FileOutputStream("c:\\xiong\\xxx.jpg");//这样就可以把文件写到(下载)到本地磁盘上了
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
DiskFileItemFactory工厂类的常用方法:
构造方法两个,其它方法两个
-
public DiskFileItemFactory()
-
public DiskFileItemFactory(int sizeThreshold,File repository)
-
void setSizeThreshold(int sizeThreshold)
Apache文件上传组件在解析上传数据中的每个字段内容时,需要临时保存解析出的数据,以便在后面进行数据的进一步处理(保存在磁盘特定位置或插入数据库)。因为Java虚拟机默认可以使用的内存空间是有限的,超出限制时将会抛出“java.lang.OutOfMemoryError”错误。如果上传的文件很大,例如800M的文件,在内存中将无法临时保存该文件内容,Apache文件上传组件转而采用临时文件来保存这些数据;但如果上传的文件很小,例如600个字节的文件,显然将其直接保存在内存中性能会更加好些。
setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值(以字节为单位的int值),如果从没有调用该方法设置此临界值,将会采用系统默认值10KB。对应的getSizeThreshold() 方法用来获取此临界值。 -
void setRepository(File repository)
setRepositoryPath方法用于设置当上传文件尺寸大于setSizeThreshold方法设置的临界值时,将文件以临时文件形式保存在磁盘上的存放目录。有一个对应的获得临时文件夹的 File getRespository() 方法。 -
File getRespository() 方法
获得临时文件夹的方法。
注意: 当从没有调用此方法设置临时文件存储目录时,默认采用系统默认的临时文件路径,可以通过系统属性 java.io.tmpdir 获取。如下代码:
System.getProperty("java.io.tmpdir");
Tomcat系统默认临时目录为“<tomcat安装目录>/temp/”。
FileItem类的常用方法:
-
boolean isFormField()
isFormField方法用于判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,如果是普通表单字段则返回true,否则返回false。因此,可以使用该方法判断是否为普通表单域,还是文件上传表单域。 -
String getName()
getName方法用于获得文件上传字段中的文件名。
注意IE或FireFox中获取的文件名是不一样的,IE中是绝对路径,FireFox中只是文件名。这种差异性,我们要处理好,让统一。 -
String getFieldName()
getFieldName方法用于返回表单标签name属性的值。如上例中<input type="text" name="column" />
中name属性的value。 -
void write(File file)
write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。如果FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。 -
String getString()
getString方法用于将FileItem对象中保存的数据流内容以一个字符串返回,它有两个重载的定义形式:
public java.lang.String getString()
public java.lang.String getString(java.lang.String encoding)
throws java.io.UnsupportedEncodingException
前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。如果在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码(如:utf-8)名称。
-
String getContentType()
getContentType 方法用于获得上传文件的类型,即表单字段元素描述头属性“Content-Type”的值,如“image/jpeg”。如果FileItem类对象对应的是普通表单字段,该方法将返回null。 -
boolean isInMemory()
isInMemory方法用来判断FileItem对象封装的数据内容是存储在内存中,还是存储在临时文件中,如果存储在内存中则返回true,否则返回false。 -
void delete()
delete方法用来清空FileItem类对象中存放的主体内容,如果主体内容被保存在临时文件中,delete方法将删除该临时文件。
尽管当FileItem对象被垃圾收集器收集时会自动清除临时文件,但及时调用delete方法可以更早的清除临时文件,释放系统存储资源。另外,当系统出现异常时,仍有可能造成有的临时文件被永久保存在了硬盘中。 -
InputStream getInputStream()
以输入流的形式返回上传文件的数据内容,这个非常重要,下载时我们要通过它获取流(文件内容)。 -
long getSize()
返回该上传文件的大小(以字节为单位)。
上传
上传文件前应该想到是注意项:
/**
- 1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
- 2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
- 3、为防止一个目录下面出现太多文件,要使用hash算法打散存储。
- 4、要限制上传文件的最大值。
- 5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
*/
基本思路:
①获取和创建保存文件的最终目录和临时目录
②创建一个DiskFileItemFactory工厂
③创建一个文件上传解析器
④判断提交上来的数据是否是上传表单的数据,是不是Multipart编码方式ServletFileUpload.isMultipartContent(request)
⑤使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>
集合
⑥判断普通域还是文件域
⑦将普通表单域的键值对显示出来
⑧验证后缀的合法性
⑨将文件流写入保存的目录中(生成新的文件名,避免一个目录中文件太多而生成新的存储目录)
index.jsp----上传请求页
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件的上下与下载</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/uploadServlet.fdo" enctype="multipart/form-data" method="post">
<input type="file" name="filename"/><br><br>
<input type="text" name="desc"/><br><br>
<input type="submit" value="上传"/>
</form>
</body>
</html>
UploadFileController.java
@WebServlet(urlPatterns= {"*.fdo"})
public class UploadFileController extends HttpServlet{
private static final long serialVersionUID = 1L;
@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 {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
System.out.println("这里是post请示的处理方法"+req.getContextPath());
String mn=req.getServletPath(); //返回提交请求的地址名 如: /query.udo
mn=mn.substring(1); // 去掉反斜杠
mn=mn.substring(0,mn.length()-4); //去掉 .udo
try {
Method method=this.getClass().getDeclaredMethod(mn,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this, req,resp);
} catch (Exception e) { //捕获总异常即可,后面的捕获异常可去掉
e.printStackTrace();
}
}
/**
* 表单提交以post方式,ENCTYPE="multipart/form-data"内容编码,提交的表单内容,我们在这里用getParamter("xxx")是拿不到的
* 本例用利用jar包commons-fileupload-1.2.2.jar和commons-io-1.4.jar,来帮肋拿到提交的文件内容
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@SuppressWarnings("unused")
private void uploadServlet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println(req.getParameter("filename")); //这样直接拿表单提交的数据是null值
// System.out.println(req.getParameter("desc")); //这样直接拿表单提交的数据是null值
//①获取和创建保存文件的最终目录和临时目录
String savePath = req.getSession().getServletContext().getRealPath("/WEB-INF/upload"); //保存在upload中的绝对路径,tomcat服务器中
String tempPath = req.getSession().getServletContext().getRealPath("/WEB-INF/temp"); //临时目录,接收文件时用到(如果设置了一个临界值,)分块上传时
File tempFile=new File(tempPath); //找目录
if(!tempFile.exists()) {
tempFile.mkdirs(); //创建临时目录
}
//②创建一个DiskFileItemFactory工厂 可以设置很多对上传文件的限制
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(100*1024); //小于100KB,放在内存中,上传的文件大于100KB时,放到tempPath中
factory.setRepository(tempFile); //设置上传文件的临时目录
//③创建一个文件上传解析器.拿到到request请求的解析器,解析器可以设置单个文件的大小,也可设置多个文件的总大小
ServletFileUpload sfu = new ServletFileUpload(factory);
sfu.setFileSizeMax(600*1024*1024); //限制上传单个文件的大小在600m内
sfu.setHeaderEncoding("UTF-8"); //防止中文乱码
sfu.setSizeMax(600*1024*1024); //批量上传时,限制所有文件的总大小在600m内
sfu.setProgressListener(new ProgressListener() {
@Override
public void update(long yUploadFileSize, long uploadFileSize, int item) {
System.out.println("上传文件总大小为:"+uploadFileSize+",已经上传文件的大小:"+yUploadFileSize);
}
});
//④判断提交上来的数据是否是上传表单的数据,是不是Multipart编码方式
if(!ServletFileUpload.isMultipartContent(req)) {
return; //如果不是Multipart方式(二进制),直接不做事了。
}
//⑤使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个```List<FileItem>```集合
//解析器来解析request请求解析器,要捕获异常信息
//FileItem:就是封装有的一个一个的form提交过来的表单域项,表单项分两大类:普通域,文件域
OutputStream out =null;
InputStream in =null;
try {
List<FileItem> list=sfu.parseRequest(req);
if(list!=null && list.size()>0) {
for (FileItem fileItem : list) {
//判断表单项是普通表单项,否则是文件表单项
if(fileItem.isFormField()) {
//将普通表单域的键值对显示出来
String filename=fileItem.getFieldName();
String value=fileItem.getString(); //拿到表单标签name属性的值
System.out.println("普通的表单项,name: "+filename+",value为: "+value);
}else { //否则是文件域表单项
String filename=fileItem.getName(); //拿到文件的名字 如: xxx.doc xxx.txt
if(filename== null || filename.trim().equals("")) {
continue; //结束本次,接着执行下一次我循环
}
filename = filename.substring(filename.lastIndexOf("\\")+1); //不同浏览器,获取的文件路径不一样,要处理一下,保证是真正的文件名
String fileType=fileItem.getContentType(); //文件的类型
long fileSize=fileItem.getSize(); //文件的大小
String fileEX=filename.substring(filename.lastIndexOf(".")+1); //拿到文件的扩展名
// 难证扩展名的合法性,即不在规定的文件扩展列表中
if(fileEX.equals("rar")||fileEX.equals("zip")) {
throw new RuntimeException("禁止上传压缩文件!");
}
// 将文件流写入保存的目录中(生成新的文件名,避免一个目录中文件太多而生成新的存放目录
String saveFilename = makeFileName(filename); //制作文件名,保证文件名不重复
String realSavePath = makePath(saveFilename,savePath); //创建保存的目录绝(对路径)
//先创建一个输出流
in=fileItem.getInputStream();
out = new FileOutputStream(realSavePath+"\\"+saveFilename);
//建立缓冲区,相当于吃饭的勺子
byte[] buffer = new byte[1024];
int len=0;
while((len=in.read(buffer))!=-1) {
out.write(buffer,0,len);
}
in.close();
out.close();
}
}
}
} catch (FileUploadBase.FileSizeLimitExceededException e) {
System.out.println("单个文件大小超出限制!");
}catch(FileUploadBase.SizeLimitExceededException e) {
System.out.println("总的文件大小超出限制");
}catch(Exception e) {
System.out.println("文件上传失败!");
}finally {
if(in!=null) in.close();
if(out!=null) out.close();
}
}
private String makePath(String saveFilename, String savePath) {
//
int hashCode = saveFilename.hashCode();
int dir1 = hashCode&0xf; //hashCode与1111作与运算,拿到0到15之间的数,即最多有16个子目录
int dir2 = hashCode&0xf >>4; //得到的结果还是在0-15之间
String dir = savePath+"\\"+dir1+"\\"+dir2;
File file=new File(dir);
if(!file.exists()) {
file.mkdirs();
}
System.out.println("保存的路径:"+dir);
return dir;
}
//保证保存的文件名不会重复
private String makeFileName(String filename) {
return UUID.randomUUID().toString()+"_"+filename;
}
}
测试:localhost:8081/mvcuploadfile/index.jsp----选择一个不为压缩的文件,点上传,控制台上没有打印异常信息,就表明上传成功:
上传到的目录绝对路径为:C:\Users\Administrator\Desktop\STSWORKSPACE.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\mvcuploadfile\WEB-INF\upload\12\0\xxxx.xxx
增加批量上传与限制类型上传
index.jsp中我们要用到jQuery框架,uploadfile.properties属性文件。
批量上传:index.jsp视图页面,通过jQuery产生多个上传文件的表单,都包含在一个提交的范围 之内。请求链接到控制层,控制类,调用服务层的方法,具体上传,request存储上传过程中产生的异常信息,再转发,注入到jsp页面获取用。
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件的上下与下载</title>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.11.0.min.js"></script>
<script type="text/javascript">
var i = 2;
$(function(){
$("#addFile").click(function(){
//alert("点了我一下")
$(this).parent().parent().before( //在‘点击新添加一个附件’按钮的父点的父点的前方,添加下面的两个表格行
"<tr class=\"file\">" // \"file\" 中的 \ 是转义字符,也可写成 'file'
+ "<td>请选择上传的第"+i+"个文件:</td>"
+ "<td><input type=\"file\" name=\"file"+i+"\" /></td></tr>"
+ "<tr class=\"desc\">"
+ "<td>请第"+i+"个上传的文件的描述:</td>"
+ "<td><input type=\"text\" name=\"desc"+i+"\" />"
+ "<button type=\"button\" id=\"delete" + i + "\">删除</button>"
+ "</td></tr>"
);
i++;
//删除新增的表单项
$("#delete"+(i-1)).click(function(){
var $tr = $(this).parent().parent();
$tr.prev("tr").remove();
$tr.remove();
i--;
//删除了中间的tr节点,我们要对所有的tr的节点,重新的排序,name值file1,file2....连续的
$(".file") .each( function(index) { //index是系统的,默认为0
var count = index + 1;
$(this).find( "td:first").text( "请选择上传的第 " + count + " 个文件:");
$(this).find( "td:last input").attr( "name", "file" + count);
});
$(".desc").each(function(index) {
var count = index + 1;
$(this).find("td:first").text("请第" +count +"个上传的文件的描述:");
$(this).find("td:last input").attr("name","desc" +count);
});
});
});
});
</script>
<style>
tr {
height: 45px;
line-height: 45px;
}
table {
margin-left: 30px;
}
</style>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload.up" method="post" enctype="multipart/form-data">
<table>
<c:if test="${not empty errorMsg}">
<tr>
<td colspan="2" style="color: red; font-weight: bolder;" >${errorMsg}</td>
</tr>
</c:if>
<tr class="file">
<td>请选择上传的第 1 个文件:</td>
<td><input type="file" name="file1" /></td>
</tr>
<tr class="desc">
<td>请第1个上传的文件的描述:</td>
<td><input type="text" name="desc1" /></td>
</tr>
<tr>
<td><input style="float: right;" type="submit" value="点击上传文件" /></td>
<td><button type="button" id="addFile">点击新增加一个附件</button></td>
</tr>
</table>
</form>
<!--下面是显示上传文件的表格具体看后面的下载|删除代码-->
</body>
</html>
UploadFileController.java,----控制层我在这改进了,不处理具体业务,只做调用转发用,具体业务调用服务层的方法
UploadFileService uploadfileservice = FactoryService.getUploadFileService();
protected void upload(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
uploadfileservice.saveFile(req, resp);
resp.sendRedirect(req.getContextPath()+"/index.up");
} catch (Exception e) {
req.setAttribute("errorMsg", e.getMessage());
req.getRequestDispatcher("/index.up").forward(req, resp);
}
}
uploadfile.properties,限制保存文件的位置,与上传文件的类型,除此之外的文件不合法
savePath=/WEB-INF/upload
tempPath=/WEB-INF/temp
sizeThreshold=102400
fileSizeMax=607152000
sizeMax=619430400
fileEx=zip,rar,doc,docx,ppt,pptx,txt,jpg,png,mp4,java
UploadFilePropertiesUtil .java工具类是单例饿汉模式,只提供两个方法,存放键值对,取出键值对的值,在服务层中调用到,用于获取properties文件中的键值对属性值
/**
* 这个工具类,是单例模式,有两个方法
* addProperty(),添加键值对形式,存取从(监听器中)获取的uploadfile.properties中声明的键值对(如保存的位置,文件名,可上传的文件类型等)到map中,
* getProperty()获取键值对的值
* 一个成员Map map
*/
public class UploadFilePropertiesUtil {
private Map<String, String> map = new HashMap<>();
private UploadFilePropertiesUtil() {}
private static UploadFilePropertiesUtil instance = null;
//单例模式,懒汉模式
public static UploadFilePropertiesUtil getInstance() {
if(instance==null) {
instance = new UploadFilePropertiesUtil();
}
return instance;
}
public void addProperty(String key,String value) {
map.put(key, value);
}
public String getProperty(String key) {
return map.get(key);
}
}
FileUploadPropertisInitListener .java,整个web应用启动时触发的,这里的功能:服务器一启动,获取uploadfile.properties文件内容,转为输入流,可以用工具类获取内容,保存到一个Map集合中
/**
监听器,从uploadfile.properties中读取键值对形式的数据,
初始化整个web项目的数据
uploadfile.properties中的内容:指定保存文件的路径,
临时目录(当文件大于100KB时,上传要分块上传,
不能一下把文件放到内存中,因内存是稀缺资源)。。。。
*
*/
@WebListener
public class FileUploadPropertisInitListener implements ServletContextListener{
@Override
public void contextDestroyed(ServletContextEvent arg0) {}
@Override
public void contextInitialized(ServletContextEvent event) {
//web服务器启动的时间执行
InputStream in = getClass().getClassLoader()
.getResourceAsStream("uploadfile.properties");
Properties properties = new Properties();
try {
properties.load(in);
for(Object o:properties.keySet()) {
String key = (String)o;
String value = properties.getProperty(key);
//System.out.println("key:" + key + ", value:" + value );
UploadFilePropertiesUtil.getInstance().addProperty(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
UploadFileServiceImpl.java----服务层中,保存要上传的文件到指定的目录中,同时保存上传文件的信息对象(UploadFile.java)到数据库表中uploadfiles
UploadFile.java
public class UploadFile {
private int id;
private String oldFileName;
private String fileType;
private String fileSize;
private String savePath;
private Date saveTime;
private String desc;
private String saveName;
//set/get,无参构造方法toString()。。。
}
UploadFileServiceImpl.java 上传方法
public class UploadFileServiceImpl implements UploadFileService {
UploadfileDao uploadFileDao = FactoryDao.getUploadfileDao(); //Dao层的类的对象
private String saveDir = UploadFilePropertiesUtil.getInstance().getProperty("savePath");
private String tempDir = UploadFilePropertiesUtil.getInstance().getProperty("tempPath");
private String sizeThreshold = UploadFilePropertiesUtil.getInstance().getProperty("sizeThreshold");
private String sizeMax = UploadFilePropertiesUtil.getInstance().getProperty("sizeMax");
private String fileSizeMax = UploadFilePropertiesUtil.getInstance().getProperty("fileSizeMax");
private String fileEx = UploadFilePropertiesUtil.getInstance().getProperty("fileEx");
@Override
public void saveFile(HttpServletRequest req, HttpServletResponse resp) {
// 先把文件保存下来,到服务器指定的目录
// ①获取和创建保存文件的最终目录和临时目录
String savePath = req.getSession().getServletContext().getRealPath(this.saveDir); // 保存文件的服务器上的绝对路径
String tempPath = req.getSession().getServletContext().getRealPath(this.tempDir); // 临时目录
File tempFile = new File(tempPath);
if (!tempFile.exists()) {
tempFile.mkdirs(); // 如果临时目录不存在的话,我用代码的方式,创建一个新目录
}
// ②创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(Integer.parseInt(this.sizeThreshold)); // 100KB, 上传的文件,<100KB,放到内存当中, >100KB,放tempPath
factory.setRepository(tempFile); // 设置上传文件的临时目录
// ③创建一个文件上传解析器
ServletFileUpload servletFileUpload = new ServletFileUpload(factory); // 得到了文件的解析器
servletFileUpload.setFileSizeMax(Integer.parseInt(this.fileSizeMax)); // 限制上传单个文件的大小在20MB以内
servletFileUpload.setHeaderEncoding("UTF-8"); // 防止上传的中文信息是乱码
servletFileUpload.setSizeMax(Integer.parseInt(this.sizeMax)); // 限制多个文件同时上传的时候,总大小最大值
// ④判断提交上来的数据是否是上传表单的数据,是不是Multipart编码方式ServletFileUpload.isMultipartContent(request)
if (!ServletFileUpload.isMultipartContent(req)) {
throw new RuntimeException("上传文件的Form的编码方式不正确!");
}
// ⑤使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合
String desc = "";
String fileName = "";
String fileType = "";
long fileSize = 0;
String fileEx1 = "";
String saveFileName = "";
String realSavePath = "";
OutputStream out = null;
InputStream in = null;
try {
List<FileItem> fileList = servletFileUpload.parseRequest(req);
if (fileList != null && fileList.size() > 0) {
for (FileItem fileItem : fileList) {
if (fileItem.isFormField()) {
// ⑦将普通表单域的键值对显示出来
desc = fileItem.getString("UTF-8");
//每一次为desc 赋值 : 代表着一个文件已经上来 , 意味着完成里一次文件的上传操作
//在这里把上传上来的文件的信息,写入数据库里
if(!"".equals(fileName)) {
UploadFile uf = new UploadFile();
uf.setDesc(desc);
uf.setFileSize(fileSize + "");
uf.setFileType(fileType);
uf.setOldFileName(fileName);
uf.setSavePath(realSavePath);
uf.setSaveName(saveFileName);
uf.setSaveTime(new Date());
//addFileInfo
addFileInfo(uf);
}
} else {
// ⑥判断普通域还是文件域
fileName = fileItem.getName(); // 拿到文件名字 xxx.doc xxx.txt
fileType = fileItem.getContentType(); // 拿到文件的类型 image/jpg
// 注意事项: ie拿到的fileName是带有绝对路径, D:\abc\xxx.doc ; 火狐浏览器拿到的 xxxx.doc
fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
fileSize = fileItem.getSize(); // 拿到文件的总大小
fileEx1 = fileName.substring(fileName.lastIndexOf(".") + 1);
// ⑧验证后缀的合法性
if (this.fileEx.indexOf(fileEx1) == -1) {
throw new RuntimeException("禁止上传该文件类型,文件后缀:" + fileEx1);
}
// ⑨将文件流写入保存的目录中(生成新的文件名,避免一个目录中文件太多而生成新的存储目录)
saveFileName = makeFileName(fileName);
realSavePath = makePath(saveFileName, savePath);
// 先创建一个输出流
out = new FileOutputStream(realSavePath + "\\" + saveFileName);
in = fileItem.getInputStream();
// 建立缓存区,做一个搬运文件数据流的勺子
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
}
}
}
//删除临时目录下临时文件
File tempd = new File(tempPath);
for(File file:tempd.listFiles()) {
file.delete();
}
} catch (FileUploadBase.SizeLimitExceededException e) {
throw new RuntimeException("上传文件总大小超出了限制:" + Integer.parseInt(this.sizeMax) / (1024 * 1024) + "MB!");
} catch (FileUploadBase.FileSizeLimitExceededException e) {
throw new RuntimeException("上传单个文件的大小超出了限制:" + Integer.parseInt(this.fileSizeMax) / (1024 * 1024) + "MB!");
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UploadFileService.java 接口
public interface UploadFileService {
//保存文件的方法,这里我们是通过request请求,和response响应等保存文件(主要是流的作用)
public void saveFile(HttpServletRequest request,HttpServletResponse response);
}
UploadFileDaoImpl .java----dao层类,具体与数据库打交道,插入一个UploadFile对象信息到表(uploadfiles)中。
public class UploadFileDaoImpl extends BaseDao<UploadFile> implements UploadfileDao{
@Override
public void addFileInfo(UploadFile uploadFile) {
String sql = "INSERT INTO `uplaodfiles`(`old_file_name`,`file_type`,`file_szie`,`save_path`,"
+ "`save_time`,`desc`,`save_name`)values(?,?,?,?,?,?,?)";
System.out.println(sql);
super.update(sql, uploadFile.getOldFileName(),uploadFile.getFileType(),
uploadFile.getFileSize(),uploadFile.getSavePath(),uploadFile.getSaveTime(),uploadFile.getDesc()
,uploadFile.getSaveName());
}
}
mysql,表uploadfiles 结构
下载与删除
下载
UploadFileController.java
@WebServlet(urlPatterns = { "*.up" })
public class UploadFileController extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final ServletResponse resp = null;
UploadFileService uploadfileservice = FactoryService.getUploadFileService();
@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 {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String mn = req.getServletPath();
mn = mn.substring(1);
mn = mn.substring(0, mn.length() - 3);
try {
Method method = this.getClass().getDeclaredMethod(mn, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
//转发到视图index.jsp
private void index(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<UploadFile> list = uploadfileservice.getUploadFiles();
req.setAttribute("upfiles", list);
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
//上传,这里我们把上传具体操作放到服务层里做了,参考mvcuploadfile项目
protected void upload(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
.....
}
//删除上传的不要的文件,数据库记录也要删除之,不是临时文件,临时文件在上传的时候产生的,上传好,也立即写代码删除
private void deleteFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
.....
}
//下载,根据视图传来的id,下载相关的文件
private void downloadFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取要下载的文件的绝对路径
int id = Integer.parseInt(req.getParameter("id"));
UploadFile uf = uploadfileservice.getUploadFileById(id);
String filePath = uf.getSavePath()+"\\"+uf.getSaveName();
String userAgent = req.getHeader("User-Agent");
//2.获取要下载的文件名
String fileName = uf.getOldFileName();
//针对IE或者以IE为内核的浏览器:主要是解决,下载的时候,文件名是中文乱码
if (userAgent.contains("MSIE")||userAgent.contains("Trident")) {
fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
} else {
//非IE浏览器的处理:
fileName = new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
}
//3.设置content-disposition响应头控制浏览器以下载的方式打开文件
resp.setHeader("content-disposition","attachment;filename="+fileName);
//4.获取要下载的文件输入流
InputStream in = new FileInputStream(filePath);
int len = 0;
//5.创建书缓冲区
byte[] buffer = new byte[1024];
//6.通过response对象获取OutputStream输出流对象
OutputStream os = resp.getOutputStream();
//7.将FileInputStream流对象写入到buffer缓冲区
while((len=in.read(buffer))>0){
os.write(buffer,0,len);
}
//8.关闭流
in.close();
os.close();
}
}
index.jsp
链接到控制层,uf是由控制层的index.up,获取的req.setAttribute(“upfiles”,list);保存的集合对象。
<br> 已经上传的文件:<br><br>
<table border="1" cellpadding="0" cellspacing="0">
<tr>
<td>id</td>
<td>老名字</td>
<td>大小</td>
<td>描述</td>
<td>上传日期</td>
<td>删除/下载</td>
</tr>
<c:forEach var="uf" items="${upfiles}">
<tr>
<td>${uf.id }</td>
<td>${uf.oldFileName }</td>
<td>${uf.fileSize }</td>
<td>${uf.desc }</td>
<td>${uf.saveTime }</td>
<td><a href="${pageContext.request.contextPath}/deleteFile.up?id=${uf.id}">删除</a> | <a href="${pageContext.request.contextPath}/downloadFile.up?id=${uf.id}">下载</a>
</tr>
</c:forEach>
</table>
删除
1.删除上传过程中,因文件超过100KB ,其剩下的存放在临时目录中,不放在内存中,因为内存是很珍贵的。
这个代码写在上传实现的代码中,
......
// 建立缓存区,做一个搬运文件数据流的勺子
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
}
}
}
//删除临时目录下临时文件
File tempd = new File(tempPath);
for(File file:tempd.listFiles()) {
file.delete();
}
} catch (FileUploadBase.SizeLimitExceededException e) {
throw new RuntimeException("上传文件总大小超出了限制:" + Integer.parseInt(this.sizeMax) / (1024 * 1024) + "MB!");
......
2.删除上传的文件,这个即要删除对应的数据记录,也要删除磁盘上的文件
我们点南视图中的删除
,进入到控制层的deleteFile()方法中,根据id,删除数据库中对应的记录。
private void deleteFile(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int id = Integer.parseInt(req.getParameter("id"));
String filePath = req.getParameter("fp");
System.out.println(filePath);
uploadfileservice.deletUploadFile(id); //调用服务层的删除文件方法,去删除数库的记录
resp.sendRedirect(req.getContextPath()+"/index.up");
}
服务层的UploadFileServiceImpl.java
@Override
public void deletUploadFile(int id) {
UploadFile uFile = uploadFileDao.get(id);
// 先把数据库里信息删除
uploadFileDao.deletUploadFile(id); //dao层的具体与数据库直接打交道的删除记录
// 还得把服务器磁盘上的文件删除
deleteFile(uFile.getSavePath()+"\\" + uFile.getSaveName());
}
//删除服务器上的上传文件
@Override
public void deleteFile(String savePath) {
System.out.println(savePath);
File file = new File(savePath);
if(file.isFile()) {
file.delete();
}
}
dao层的UploadFileDaoImpl.java具体实现删除记录
@Override
public void deletUploadFile(int id) {
String sql = "delete from uplaodfiles where id=? ";
super.update(sql,id);
}
javaweb打包部暑
经常我们要把工程(应用程序)放到其它服务器中部暑运行,如果把项目放进去是不得行的。所以要先打包,再把包放进去,启动相应的服务器可运行。
打包有三种
- tomcat服务器打包,我们不配置服务器的话,默认打包到一个很复杂的地方,我们可以删除原有的务服器,再来新建服务器,把项目添加服务中,这样就可以配打包到tomcat安装目录的wtpwebapps中,也可通过Deploy path:可设置包部署在在任何地方(例:
Y:\
),再不通过开发工具启动服务器(通过开发工具启动服务器,看不出效果,因为它每次启动都会自动编译项目打包),就可运行项目了。
下图中mvcuploadfile是tomcat编译好的项目 字节码文件
- 通过Export输出成web的war包,把该 war包,放到服务器的运行目录(如上面的
y:\
),再外部启动tomcat,会自动解压包为 ’1‘ 一样的字节码文件。经过多次测试,该包要放到C:\Users\Administrator\Desktop\apache-tomcat-8.5.16\webapps下—即tomcat安装目录下的webapps下,会自动解压,上面设置的y:\
目录,只有解压了war包后,放进去才行。 - 自已通过解压软件,解压文件,再放到运行目录中。。。。。