第二阶段(day18)过滤器监听器 servlet3注解

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);
}

继续走下一个过滤器(权限访问控制过滤器):即③.的代码。

然后走④的代码,其他同理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值