1过滤器
1.1. 什么是过滤器
1.过滤器是向 Web 应用程序的请求和响应处理添加功能的Web 服务组件
2.Filter是一个可以加在servlet前执行的组件,可以通过这个机制制作一些通用性的功能,跟生活中用的水的过滤器的机制很相似
过滤器是官方提供的一个javaEE标准开发组件,用法和servlet非常相似。
示例如下:
创建项目day8_filter_listener
src包下创建com.javasm.controller.ServletDemo
一个servlet如下:(配置在第一行)
@WebServlet("/demo1") public class ServletDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet执行了"); } }
在javasm下创建filter包,包下创建FilterDemo1,实现Filter接口(在javax.servlet,针对网络请求的过滤器),其配置和servlet的一样。
@WebFilter("/demo1") public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //servlet里是HttpServletRequest,它继承ServletRequest System.out.println("请求进入了filter1"); } @Override public void destroy() { } }
运行项目,访问http://localhost:8080/day8/demo1,结果:控制台打印出请求进入了filter1。
如果两个servlet配置了相同的请求地址,服务器不会启动。这里,过滤器可以和servlet配置相同的请求地址,且请求先进入过滤器。如何servlet也生效?
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("请求进入了filter1"); filterChain.doFilter(servletRequest,servletResponse); }
运行项目,访问http://localhost:8080/day8/demo1,结果:请求进入了filter1 servlet执行了
servlet之间相互调用需要用转发,语法上需要写清楚转发的对象地址。过滤器不需要写地址,其配置地址是谁,就用filterChain.doFilter()方法调用谁。
过滤器配置的请求地址除了用注解,也可使用配置文件去配置,也是在web.WEB-INF.web.xml里:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name></filter-name> <filter-class></filter-class> </filter> <filter-mapping> <filter-name></filter-name> <url-pattern></url-pattern> </filter-mapping> </web-app>
和servlet基本一致,名字换了而已。
1.2. 过滤器生命周期
Filter跟servlet生命周期类似
实例对象也是由服务器自己创建,创建是在服务器启动时就创建,而不是访问时创建(不同于servlet)。
生命周期也是实例化-->初始化-->过滤(执行功能代码)-->销毁
示例:
@WebFilter("/demo1") public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter实例被创建了 init方法被触发了"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("请求进入了filter1"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("filter实例准备要销毁了 destory方法被触发了"); } }
运行(服务器启动),控制台就会输出:filter实例被创建了 init方法被触发了
浏览器访问http://localhost:8080/day8/demo1,控制台会输出:请求进入了filter1 servlet执行了
在不断地访问过程中,init方法不再执行,只会不断执行doFilter方法。
关闭服务器,控制台会输出:filter实例准备要销毁了 destory方法被触发了
总结:filter请求地址可和servlet配置的一样,且在servlet前执行,故可写一些公共的功能。
1.3其他
servlet间不能配置相同的请求地址,但filter可以。
在filter包下创建FilterDemo2,请求地址也配置成demo1。
@WebFilter("/demo1") public class FilterDemo2 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter实例被创建了 init方法被触发了"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("请求进入了filter2"); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { System.out.println("filter实例准备要销毁了 destory方法被触发了"); } }
启动服务器,控制台就会输出:filter实例被创建了 init方法被触发了
filter实例被创建了 init方法被触发了
创建了两个实例对象,并且分别触发了init方法。
然后浏览器访问:http://localhost:8080/day8/demo1,控制台会输出:
请求进入了filter1 servlet执行了
请求进入了filter2 servlet执行了
servlet执行了
过滤器间使用同一个请求地址(多层过滤),公共功能可以分成多个过滤器去写。多个过滤器排队执行,若用配置文件,是谁先加载就先执行谁。若使用注解,则根据类名去执行(如上,同样的英文名后加了不同的数字)。
有关配置地址问题的第一种情况,相同功能的servlet经过同一个过滤器:
公共功能肯定不是针对某一个servlet。故filter的配置上会使用扩大请求进入范围的方式,即通配符*。
将ServletDemo的配置地址改为:
@WebServlet("/order/*")
若所有进order的请求都进过滤器,将过滤器配置地址也改为:
@WebServlet("/order/*")
另一种情况,不同功能的servlet经过同一个过滤器:
controller包下创建ServletDemo2:
@WebServlet("/user/*") public class ServletDemo2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("servlet2执行了"); } }
若想让过滤器能同时进入这两个servlet,在ServletDemo,将配置地址改为:
@WebServlet("/serv/order/*")
在ServletDemo2将配置地址改为:
@WebServlet("/serv/user/*")
在FilterDemo1将配置地址改为:
@WebFilter("/serv/*")
启动服务器,然后浏览器输入:http://localhost:8080/day8/serv/user,结果是控制台输出:
请求进入了filter2 servlet执行了
浏览器输入:http://localhost:8080/day8/serv/order
输出:请求进入了filter1 servlet执行了
第三种情况,所有的请求都要走过滤器。
将FilterDemo1配置地址改为:
@WebFilter("/*")
结果就是发给服务器的请求,不管是访问servlet,页面,图片,css文件,js文件等等,都会进入过滤器,控制台都会输出:请求进入了filter1
比如:创建web.mypage.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 测试页面 </body> </html>
重新部署或者重启服务器,浏览器输入:http://localhost:8080/day8/web.mypage.html
控制台会输出:请求进入了filter1
如果此时过滤器没有filterChain.doFilter()方法
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("请求进入了filter2"); }
结果就是页面显示不出:测试页面。因为请求卡在过滤器里了。
1.4过滤器典型场景
适合做通用的功能(公共性质的功能),常见的:
1.字符编码过滤器
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { servletRequest.setCharacterEncoding("utf-8"); System.out.println("请求进入了filter1"); filterChain.doFilter(servletRequest,servletResponse); }
2.权限控制(用户能否访问该功能)
①.controller包下:
新建LoginServlet
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //接收用户名密码 去数据库查询 HttpSession session = req.getSession(); //登录成功的用户 session.setAttribute("loginUser","小明"); //当前用户的权限 List<String> userRoleList = new ArrayList<String>(); userRoleList.add("/user"); userRoleList.add("/order"); //把当前登录成功的用户的权限列表放入session session.setAttribute("userRole",userRoleList); system.out.println("用户登录成功"); } }
在ServletDemo:
@WebServlet("/user") public class ServletDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("user用户功能执行了"); } }
在ServletDemo2:
@WebServlet("/order") public class ServletDemo2 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("order订单执行了"); } }
创建ServletDemo3:
@WebServlet("/prod") public class ServletDemo3 extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("prod商品功能执行了"); } }
②.filter包下:
FilterDemo1:
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //在过滤器中,如果方法不够,需要强转成真实类型 HttpServletRequest myreq = (HttpServletRequest) servletRequest; HttpSession session = myreq.getSession(); //拿到用户 List<String> userRole = (List<String>)session.getAttribute("userRole"); //权限列表 String requestURI = myreq.getRequestURI(); //获取当次请求的完整地址 String servletPath = myreq.getServletPath(); //获取servlet的配置地址(会忽略通配符) //判断用户当次请求路径是否包含在用户允许的权限列表中 if (userRole.contains(servletPath)){ System.out.println("有权限"); filterChain.doFilter(servletRequest,servletResponse); }else { servletResponse.setContentType("application/json;charset=utf-8"); PrintWriter writer = servletResponse.getWriter(); writer.print("{\"retCode\":50000,\"retMsg\":\"没有权限\"}"); writer.flush(); writer.close(); System.out.println("没有权限"); } }
访问权限之前应该判断有没有登录,即权限和登录搭配起来用
1.登录权限控制 已登录/未登录
权限控制如上,登录控制类似。先走登录,看用户有没有登录,有登录继续往后执行,没有登录,返回服务接口,告知没有登录。
FilterDemo2:
@WebFilter("/serv/*") public class FilterDemo1 implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("filter实例被创建了 init方法被触发了"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //在过滤器中,如果方法不够,需要强转成真实类型 HttpServletRequest myreq = (HttpServletRequest) servletRequest; HttpSession session = myreq.getSession(); //拿到用户 List<String> userRole = (List<String>)session.getAttribute("userRole"); //权限列表 String requestURI = myreq.getRequestURI(); //获取当次请求的完整地址 String servletPath = myreq.getServletPath(); //获取servlet的配置地址(会忽略通配符) //判断用户当次请求路径是否包含在用户允许的权限列表中 if (userRole!=null){ if (userRole.contains(servletPath)){ System.out.println("有权限"); filterChain.doFilter(servletRequest,servletResponse); }else { servletResponse.setContentType("application/json;charset=utf-8"); PrintWriter writer = servletResponse.getWriter(); writer.print("{\"retCode\":60000,\"retMsg\":\"没有登录\"}"); writer.flush(); writer.close(); System.out.println("没有登录"); } } } @Override public void destroy() { System.out.println("filter实例准备要销毁了 destory方法被触发了"); } }
登录权限控制是有顺序的,应该先看有没有登录,登录过之后,再考虑权限的问题。故两个过滤器的类名互换。
FilterDemo1是登录的过滤器,FilterDemo2是权限的过滤器。
重新部署,访问:http://localhost:8080/day8/login
后台会输出没有登录。
过滤器配置的请求地址是/*,登录请求也会进入,会先找loginUser是否为空,此时还未登录成功,故会输出没有登录。
登录的请求应该让他直接走。
在FilterDemo1中:
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest myreq = (HttpServletRequest) servletRequest; HttpSession session = myreq.getSession(); String loginUser = (String)session.getAttribute("loginUser"); String servletPath = myreq.getServletPath(); if("/login".equals(servletPath)){ //如果是登录需要直接放行 filterChain.doFilter(myreq,servletResponse); }else { if (loginUser!=null){ filterChain.doFilter(myreq,servletResponse); }else { servletResponse.setContentType("application/json;charset=utf-8"); PrintWriter writer = servletResponse.getWriter(); writer.print("{\"retCode\":60000,\"retMsg\":\"没有登录\"}"); writer.flush(); writer.close(); System.out.println("没有登录"); } } }
其实除了放行相应的服务,静态资源也需要放行(一般就这两类需要放行)。比如登录页面,图片,等等。
可以放行的一般称为白名单,对应的是黑名单。
故:
if("/login".equals(servletPath)||servletPath.endsWith(".html")||servletPath.endsWith(".png")||servletPath.endsWith(".js")||servletPath.endsWith(".css")){ //如果是登录需要直接放行 filterChain.doFilter(myreq,servletResponse); }
同样的在FilterDemo2中也加入该白名单。
接下来看更细的粒度,按钮功能权限控制,数据也是从数据库出。
如果数据量大,直接用中间表,此时是三个表的设计。如果数据量小,就用精简的设计,此时是两个表。
在大型系统里会换成另一种模型,用户和权限间还会加一个角色,不同角色的权限不同,此时是五个表。
粒度 1.登录权限控制 已登录/未登录
2.模块功能权限控制 基于用户 配置模块
3.按钮功能权限控制 基于用户 配置按钮功能 用户的权限数据更多
2监听器
2.1. 什么是监听器
监听器是javaweb组件中提供的另外一种机制,用来监控域对象的创建、销毁过程(在发生创建、销毁时,可以执行自己的代码)以及域对象中属性的创建、替换、销毁。
(这些对象并不是由我们创建,在他们的创建和销毁中,可以加入自己的代码。和事件很类似)
1.监听web对象创建与销毁的监听器
ServletContextListener
HttpSessionListener
ServletRequestListener
(针对三个域对象)
2.监听web对象属性变化
ServletContextAttributeListener
HttpSessionAttributeListener
ServletRequestAttributeListener
(域对象里的属性)
示例如下:
在javasm包下创建listener包,包下新建MyListener,需要实现接口,不同的接口决定使用哪一种监听器。
监听器也要配置给服务器,这里用注解,不存在请求的地址。
@WebListener public class MyListener implements HttpSessionListener { //当session对象被创建或销毁时,该方法的代码会执行 @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { //HttpSessionEvent是事件对象 HttpSession session = httpSessionEvent.getSession(); //获取当前创建出的session对象 System.out.println("在监听器中 获取到被创建出的session对象:"+session.getId()); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { HttpSession session = httpSessionEvent.getSession(); System.out.println(session.getId()+"此时session对象将被回收"); } }
注:java里对象的回收看java虚拟机的心情
在controller包下新建MyTestServlet,用来测试:
@WebServlet("/test") public class MyTestServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); session.setMaxInactiveInterval(5); //设置有效期 System.out.println(session.getId()); } }
重新部署,访问:http://localhost:8080/day8/test
2.2监听器典型场景
1.在线用户列表
标记当前哪些用户已经连接进来。登录时把用户相关信息放在session对象,如果session对象过期,相当于该对象不在线。
监听器:
@WebListener public class MyListener implements HttpSessionListener { Map<String,HttpSession> onlineUser = new HashMap<String,HttpSession>(); @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { HttpSession session = httpSessionEvent.getSession(); onlineUser.put(session.getId(),session); System.out.println(onlineUser); System.out.println("在监听器中 获取到被创建出的session对象:"+session.getId()); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { HttpSession session = httpSessionEvent.getSession(); System.out.println(session.getId()+"此时session对象将被回收"); onlineUser.remove(session.getId()); System.out.println(onlineUser); } }
MyTestServlet:
@WebServlet("/test") public class MyTestServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); session.setMaxInactiveInterval(5); //不写这个,默认半个小时 session.setAttribute("loginUser","xxxx"); System.out.println(session.getId()); } }
3.数据库连接池
资源池,作用是复用资源。比如线程池,放的线程对象;数据库连接池放的数据库连接对象;servlet池,等等。
数据库连接对象为何要复用?
数据库是一个应用程序,java代码也是应用程序。两段应用程序连接是IP寻址,端口找应用,再配上资源地址,故两个应用程序走的tcp/ip,自己定了一个应用层协议:jdbc:mysql。和浏览器连接web服务器没有区别。
复用数据库连接对象,就能节省tcp/ip连接方式的创建连接和断开连接的消耗。tcp/ip方式握手是连接3次,断开4次。连接和断开消耗的是计算机cpu的时间。
做一个数据库连接池,最核心的内容是把资源装起来,并且把资源做标记,可用还是不可用,比如用布尔值标记是否可用。要用资源创建一个方法,用完资源归还再创建一个方法。
用的比较多的数据库连接池是国内的德鲁伊Druid,在第一阶段全讲过。
3.1概念
1.连接池
连接池是在内存中预设好一定数量的连接对象,以备用户在进行数据库操作时直接使用
2.性能
数据库连接的建立、断开均由管理池统一管理
3.连接池技术与传统数据库连接的比较
数据库操作性能得到提升
通过连接池管理数据库的连接与释放、提高了系统资源的使用效率。
3.2 Druid
1.Druid是阿里巴巴开源平台上一个数据库连接池实现
2.Druid是Java语言中最好的数据库连接池
3.Druid能够提供强大的监控和扩展功能
4.Druid可以由不同类型的节点,组成一个集群
3.2.1Druid下载
1.druid-x.x.x.jar 项目中导入Jar包
Jar包下载地址:
http://repo1.maven.org/maven2/com/alibaba/druid/1.1.10/druid-1.1.10.jar
源码下载地址:
http://repo1.maven.org/maven2/com/alibaba/druid/1.1.10/druid-1.1.10-sources.jar
3.2.2.Druid对象和设置
示例:
之前的分页查询为例,先看之前的代码:
在jdbc.properties配置文件中:
jdbc.user=root jdbc.pass=root jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=true&characterEncoding=utf-8 jdbc.driver=com.mysql.jdbc.Driver
在src.com.javasm.util.DBHelper:
public class DBHelper { static String username; static String pwd; static String url; static String drivername; static { Properties p = new Properties(); try { //程序运行时 不一定在你的工程目录的编译目录下 p.load(DBHelper.class.getResourceAsStream("/jdbc.properties")); username = p.getProperty("jdbc.user"); pwd = p.getProperty("jdbc.pass"); url = p.getProperty("jdbc.url"); drivername = p.getProperty("jdbc.driver"); Class.forName(drivername); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConn() { Connection con = null; try { con = DriverManager.getConnection(url, username, pwd); } catch (SQLException e) { e.printStackTrace(); } return con; } public static void CloseConn(Connection conn,Statement stat,PreparedStatement psta,ResultSet rs){ try { if(stat!=null)stat.close(); if(psta!=null)psta.close(); if(rs!=null)rs.close(); if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
用德鲁伊去写:
①.将druid-1.1.23.jar放在web.WEB-INF.lib包下
配置给该项目
在util包下创建DBHelper2:
public class DBHelper2 { static String username; static String pwd; static String url; static String drivername; static DruidDataSource dds = new DruidDataSource(); static { Properties p = new Properties(); try { p.load(DBHelper.class.getResourceAsStream("/jdbc.properties")); username = p.getProperty("jdbc.user"); pwd = p.getProperty("jdbc.pass"); url = p.getProperty("jdbc.url"); drivername = p.getProperty("jdbc.driver"); dds.setUsername(username); dds.setPassword(pwd); dds.setUrl(url); dds.setDriverClassName(drivername); dds.init(); //数据库连接池初始化。可不调用该方法,等到调用数据库连接池对象时,会自动初始化。 } catch (Exception e) { e.printStackTrace(); } } public static Connection getConn() { Connection con = null; try { con = dds.getConnection(); //druid提供的连接方法 } catch (SQLException e) { e.printStackTrace(); } return con; } public static void CloseConn(Connection conn, Statement stat, PreparedStatement psta, ResultSet rs){ //这里的close方法也是druid提供的,并不是关闭连接,而是把连接的状态回复到原始状态 try { if(stat!=null)stat.close(); if(psta!=null)psta.close(); if(rs!=null)rs.close(); if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
②.在dao.impl包下:
将DBHelper全部换成DBHelper2。
一个问题:
数据库连接池在真正使用时会把数据库连接池初始化,并且创建出来。程序执行过程中,第一次会有代码去执行创建数据库连接池,故第一次访问会比后续访问慢一些。尽管影响大不,但创建连接池的过程,不应该放在用户请求过程,而是服务器启动时。
服务器启动时创建数据库,可用监听器。ServletContext就是服务器对象。
新建javasm.listener.DBCreater
@WebListener public class DBCreater implements ServletContextListener { static DruidDataSource dds = new DruidDataSource(); @Override public void contextInitialized(ServletContextEvent servletContextEvent) { Properties p = new Properties(); try { p.load(DBHelper.class.getResourceAsStream("/jdbc.properties")); dds.setUsername(p.getProperty("jdbc.user")); dds.setPassword(p.getProperty("jdbc.pass")); dds.setUrl(p.getProperty("jdbc.url")); dds.setDriverClassName(p.getProperty("jdbc.driver")); dds.init(); } catch (Exception e) { e.printStackTrace(); } } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { dds.close(); //关闭数据库连接池 } public static Connection getConn() { Connection con = null; try { con = dds.getConnection(); //druid提供的连接方法 } catch (SQLException e) { e.printStackTrace(); } return con; } public static void CloseConn(Connection conn, Statement stat, PreparedStatement psta, ResultSet rs){ //这里的close方法也是druid提供的,并不是关闭连接,而是把连接的状态回复到原始状态 try { if(stat!=null)stat.close(); if(psta!=null)psta.close(); if(rs!=null)rs.close(); if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
将DBHelper2全部换成DBCreater。
4 servlet3
servlet也有版本,现在使用的都是3.0以后的版本。
3.0默认使用nio(非阻塞型io)处理,效率相对提高。
(发送数据到服务器,服务器解析数据,并且返回响应的过程中处理效率更高,不是排队执行,而是通过状态判断执行。)
nio由web服务器使用,和写代码没关系,tomcat里已经写过了。tomcat8.5及以上版本默认使用nio处理。这也是使用8.5以上版本的原因。
servlet3.0之后也提供了注解的方式提供配置。注解是通过反射读取并加载进来的。反射可获得类型对象,然后获得类型对象里的方法,属性,成员变量等等。注解实际和他们类似,也是类上面的一种特殊描述。(配置文件的配置是单独开io读取)
注解的完整格式并不比配置文件简单多少:
@WebServlet(urlPatterns = {"/demo"}, asyncSupported = true, loadOnStartup = -1, name = "DemoServlet", displayName = "DemoServlet ", initParams = { @WebInitParam(name = "username", value = "etc") } )
不过它有简化格式(类和请求地址的对应关系),就是之前写的。
记住简化格式即可:
简化格式 WebServlet("/xxx") WebFilter("/xxx") WebListener
接下来看简化的上传文件的写法:
①.在项目day7_upload的controller包下新建AjaxUploadServlet2:
先看看以前的AjaxUploadServlet:
@WebServlet("/ajaxupload") public class AjaxUploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String realPath = req.getServletContext().getRealPath("/"); System.out.println(realPath); String myFolderPath = "myupload/"; String retPath = ""; DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(dff); try { List<FileItem> list = servletFileUpload.parseRequest(req); System.out.println(list); for (FileItem item:list){ if (!item.isFormField()){ File file = new File(realPath + myFolderPath + item.getName()); item.write(file); //要返回给页面的路径 存入数据库 retPath = myFolderPath + item.getName(); } } } catch (Exception e) { e.printStackTrace(); } resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("{\"imgsrc\":\""+retPath+"\"}"); writer.flush(); writer.close(); } }
简化后,AjaxUploadServlet2:
@WebServlet("/ajaxupload") @MultipartConfig public class AjaxUploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String realPath = req.getServletContext().getRealPath("/"); System.out.println(realPath); String myFolderPath = "myupload/"; String retPath = ""; Part myajaxfile = req.getPart("myajaxfile"); //文件对象,参数key根据上传时放的键值对确定 myajaxfile.write(realPath+myFolderPath+myajaxfile.getSubmittedFileName()); resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("{\"imgsrc\":\""+retPath+"\"}"); writer.flush(); writer.close(); } }
5.其他:
1.Tomcat文档
在tomcat文档的最上面有example,对javaEE的核心组件如何用都有例子。
WebSocket Examples和javaEE官方组件没什么关系,是第三方的组件,可通过js向服务器发送长链接。典型场景:在线客服,大型多人在线游戏。
xhtml是语法比较严格的html,在比较官方的文档才会出现。
2.tomcat常见问题
1.tomcat老是运行之前被我删除的web项目,导致报错
如图:
删除了一个day10项目,再启动服务器,启动不起来,一直报错。
解决的办法并不需要删除什么workspace和tomcat里的缓存之类的。
如下:
把占用的day10项目删除(右边的-),再添加要运行的项目(右边的+)。
不用管day10项目,它的缓存存在与否并不影响你的运行。
2.运行完一个项目,又创建了一个项目,启动tomcat服务器时报错。
首先还是如上图一样,运行哪个项目,就部署(deployment)哪个项目。如果还是有问题,基本就是端口占用问题,按下面流程走一遍:
(1)win+R,打开命令提示符框,输入cmd,进入命令提示符
(2)输入netstat -aon | findstr 1099,找到占用1099端口的进程ID:PID
(3)输入taskkill -f -pid PID
(4)重启Tomcat(在tomcat安装目录下的bin目录里,关闭shutdown.bat,再开启startup.bat)
3.其他问题
试试Build-->Build Artifacts-->选中项目,build(重新打包)。
这个是Tomcat本身的问题,有时候在E:\workspace\two\javaEE\out\artifacts里没有创建day11项目。
控制台报错:$ is not defined
这基本就是没引对js文件或者,js文件引的不对导致。
Failed to load resource: the server responded with a status of 404 ()
输入的地址不对,导致页面不存在。或者引得js文件有问题。
6.存在过滤器的服务器到底是怎么走的?
先放一张流程图:
用户登录之后才能获取用户的权限列表,进行其他操作。
①.浏览器(用户)发送请求:http://localhost:8080/day8/login (含/login)
下面这个代码目前不执行。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行登录服务");
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
HttpSession session = req.getSession();
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
if("jack".equals(username)&&"abc123".equals(userpwd)){
System.out.println("登录成功");
//登录的标记
session.setAttribute("loginUser",username);
//用户权限列表
//模拟去数据库查出用户权限列表
List<String> userAuth = new ArrayList<String>();
userAuth.add("/user/add");
userAuth.add("/user/query");
userAuth.add("/order/query");
session.setAttribute("userAuth",userAuth);
//登录成功
re.setRetCode(ReturnCode.LOGIN_OK.getCode());
re.setRetMsg(ReturnCode.LOGIN_OK.getMsg());
}else{
System.out.println("登录失败");
//登录失败
re.setRetCode(ReturnCode.LOGIN_FAILED.getCode());
re.setRetMsg(ReturnCode.LOGIN_FAILED.getMsg());
}
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
}
②.服务器接收到请求,先走过滤器,这里是先走登录访问控制的过滤器。
先走下面这个代码:
@WebFilter("/*")
public class AuthFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//把对象转成子类型 可以调用子类型的方法
//HttpServletRequest ServletRequest
//HttpServletResponse ServletResponse
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
HttpSession session = req.getSession();
//登录成功时 才会有loginUser
String loginUser =(String) session.getAttribute("loginUser");
String servletPath = req.getServletPath();//servlet配置的路径 会忽略*后边的部分
String requestURI = req.getRequestURI();//当次请求的路径
System.out.println(servletPath);
System.out.println(requestURI);
if("/login".equals(servletPath)||"/logout".equals(servletPath)||servletPath.endsWith(".html")||servletPath.endsWith(".png")){
filterChain.doFilter(req,servletResponse);
}else{
if(loginUser!=null){
//请求是否向后执行 filterChain.doFilter(req,servletResponse);
//用户登录过 继续执行
filterChain.doFilter(req,servletResponse);
}else{
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
re.setRetCode(ReturnCode.NO_LOGIN.getCode());
re.setRetMsg(ReturnCode.NO_LOGIN.getMsg());
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
}
}
@Override
public void destroy() {
}
}
发现login在该过滤器的白名单里,会走③.的代码。
filterChain.doFilter(req,servletResponse);可看成最上面流程图的箭头,用来连接每个节点。
③.执行下一个过滤器(权限访问控制过滤器):
@WebFilter("/*")
public class AuthFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
HttpSession session = req.getSession();
//登录成功时 才会有loginUser
List<String> userAuthList =(List<String>) session.getAttribute("userAuth");
String servletPath = req.getServletPath();//servlet配置的路径 会忽略*后边的部分
String requestURI = req.getRequestURI();//当次请求的路径
System.out.println(servletPath);
System.out.println(requestURI);
String currentPath = requestURI.split("/day20")[1];
System.out.println(currentPath);
System.out.println(userAuthList);
//filterChain.doFilter(req,resp);
//检查当次请求路径 是否包含在用户权限列表中 如果包含 则直接向后执行 不包含 返回无权限json
if("/login".equals(servletPath)||"/logout".equals(servletPath)||servletPath.endsWith(".html")||servletPath.endsWith(".png")){
filterChain.doFilter(req,resp);
}else{
if(userAuthList.contains(currentPath)){
filterChain.doFilter(req,resp);
}else{
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
re.setRetCode(ReturnCode.NO_POM.getCode());
re.setRetMsg(ReturnCode.NO_POM.getMsg());
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
}
/*
* 权限访问控制
*
* 权限表 访问路径
* 1 商品功能 /prod
* 2 订单功能 /order
* 3 用户功能 /user
*
*
* 1.查出当前用户的权限列表
* (1商品 /prod,3订单 /order)
* 2.使用用户当次访问的路径 与用户在数据库中存储的允许访问的路径做对比
* 通过请求对象 读取到用户当次访问的路径
* 如果当次访问路径 在用户的权限列表中
* true 直接放行
* false 返回没有权限json
*
*
* */
}
@Override
public void destroy() {
}
}
会发现login在该过滤器的白名单里,又碰到filterChain.doFilter(req,servletResponse);
此时才会执行①.的代码,即走登录服务接口。
假设用户登录成功了,对应的也获取了该用户相应的权限列表。
假如获得订单查询的权限。
④. 浏览器(用户)发送请求:http://localhost:8080/day8/order/query (含order)
该代码目前不执行。
@WebServlet("/order/*")
public class OrderServlet extends BaseServlet {
public void query(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行订单查询服务");
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
re.setRetCode(ReturnCode.REQ_SUCCESS.getCode());
re.setRetMsg(ReturnCode.REQ_SUCCESS.getMsg());
re.setRetData("订单列表");
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
public void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行订单添加服务");
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
re.setRetCode(ReturnCode.REQ_SUCCESS.getCode());
re.setRetMsg(ReturnCode.REQ_SUCCESS.getMsg());
re.setRetData("订单添加成功");
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
public void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行订单删除");
//根据处理结果 返回不同的json数据
ReturnEntity re = new ReturnEntity();
re.setRetCode(ReturnCode.REQ_SUCCESS.getCode());
re.setRetMsg(ReturnCode.REQ_SUCCESS.getMsg());
re.setRetData("订单删除成功");
String returnStr = "";
//把响应的数据 转成json格式字符串
returnStr = JSON.toJSONString(re);
resp.setContentType("application/json;charset=utf-8");
PrintWriter writer = resp.getWriter();
//通过输出流 返回
writer.print(returnStr);
writer.flush();
writer.close();
}
}
⑤.还是先走登录访问控制的过滤器。
即②.的代码。
走到:
if(loginUser!=null){
//请求是否向后执行
//用户登录过 继续执行
filterChain.doFilter(req,servletResponse);
}
继续走下一个过滤器(权限访问控制过滤器):即③.的代码。
然后走④的代码,其他同理。