Servlet3 (3) 高级操作

243 阅读8分钟

1. 过滤器

概念: servlet过滤器作用于 service() 前后,与服务器同生共死:

  • 开发过滤器类并实现 javax.servlet.Filter 接口。
  • 重写过滤器类的 init()/destroy()/doFilter():初始化/销毁/过滤器任务方法:
    • doFilter() 中获取 HttpServletRequestHttpServletResponse 对象。
    • doFilter() 中为请求和响应对象统一转码。
    • doFilter() 中使用 chain.doFilter() 以放行请求到 service() 中。
  • 注解方式配置:为过滤器类添加 @WebFilter 并使用 value 属性指定过滤规则。
  • XML方式配置:web.xml 中:
    • 使用 <filter>/<filter-name>/<filter-class> 配置过滤器类。
    • 使用 <filter-mapping>/<filter-name>/<url-pattern> 配置过滤规则。
  • 开发 FilterTestServlet 类:接收中文参数,并写回客户端。
  • psm: /api/encoding?name=赵四

过滤规则中不支持前模糊如 /*/api/*User,但支持后模糊如 /user*/api/*

过滤器布局:

/**
 * @author yap
 **/
@WebFilter("/api/*")
public class EncodingAnnotationFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("EncodingAnnotationFilter init()...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        before(req, resp);
        chain.doFilter(req, resp);
        after();
    }

    private void before(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException {
        System.out.println("EncodingAnnotationFilter: before service()...");
        req.setCharacterEncoding("utf-8");
        resp.setContentType("application/json;charset=utf-8");
    }

    private void after() {
        System.out.println("EncodingAnnotationFilter: after service()...");
    }

    @Override
    public void destroy() {
        System.out.println("EncodingAnnotationFilter destroy()...");
    }
}

servlet测试: EncodingServlet.java

/**
 * @author yap
 */
@WebServlet("/api/encoding")
public class EncodingServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("FilterTestServlet: doGet()...");
        resp.getWriter().print("{\"name\":" + req.getParameter("name") + "}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

1.1 非法请求案例

需求:

  • 已登录状态下,所有 /api/* 路径,都属于正常请求。
  • 未登录状态下,除了 /api/login 路径之外的所有 /api/* 路径,都属于非法请求。
  • psm: /api/encoding?name=赵四,非法请求。
  • psm: /api/login?name=admin,登陆成功。
  • psm: /api/encoding?name=赵四,允许访问。

过滤器布局: IllegalRequestFilter.java

/**
 * @author yap
 */
@WebFilter("/api/*")
public class IllegalRequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("IllegalLoginFilter init()...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        req.setCharacterEncoding("utf-8");
        resp.setContentType("application/json;charset=utf-8");
        
        final String effectivePath = "/api/login";
        if (req.getRequestURI().contains(effectivePath) || isLoggedIn(req, resp)) {
            chain.doFilter(req, resp);
        } else {
            resp.getWriter().println("{\"message\":\"非法登陆!\"}");
        }
    }

    private boolean isLoggedIn(HttpServletRequest req, HttpServletResponse resp) {
        HttpSession session;
        synchronized (session = req.getSession()) {
            return session.getAttribute("name") != null;
        }
    }

    @Override
    public void destroy() {
        System.out.println("IllegalLoginFilter destroy()...");
    }
}

servlet测试: IllegalRequestServlet.java

/**
 * @author yap
 */
@WebServlet("/api/illegal-request")
public class IllegalRequestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // suppose login success

        HttpSession session;
        synchronized (session = req.getSession()) {
            session.setAttribute("name", req.getParameter("name"));
        }

        resp.getWriter().print("{\"message\":\"登陆成功!\"}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

2. 监听器

概念: servlet监听器与服务器同生共死,每种监听器都有不同的使命:

  • 开发监听器类并实现 javax.servlet.ServletContextListener 接口,监视服务器的生死。
  • 重写监听器类的监听方法:
    • contextInitialized():服务器启动时触发。
    • contextDestroyed():服务器关闭时触发。
  • 注解方式配置:为监听器类添加 @WebListener
  • XML方式配置:web.xml 中使用 <listener>/<listener-class> 配置监听器类。

监听器布局: AnnotationListener.java

/**
 * @author yap
 */
@WebListener
public class AnnotationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("AnnotationListener: contextInitialized()...");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("AnnotationListener: contextDestroyed()...");
    }
}

2.1 访客计数案例

需求: 某客户端登录成功时,回写:"当前第x个人登录了您的网站!"

  • psm: /api/visitor-count?meta=login

监听器布局: VisitorCountListener.java

/**
 * @author yap
 */
@WebListener
public class VisitorCountListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
   			 //存值,从0开始
        event.getServletContext().setAttribute("visitorCount", 0);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        event.getServletContext().removeAttribute("visitorCount");
    }
}

servlet测试: VisitorCountServlet.java

/**
 * @author yap
 */
@WebServlet("/api/visitor-count")
public class VisitorCountServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String meta = req.getParameter("meta");

        if (Meta.LOGIN.equals(meta)) {
            login(req, resp);
        }
    }

    interface Meta {
        String LOGIN = "login";
    }

    public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // suppose login success

        ServletContext application;
        int visitorCount;
        //取值 强转之后重新赋值
        synchronized (application = req.getServletContext()) {
            visitorCount = (int) application.getAttribute("visitorCount");
            application.setAttribute("visitorCount", ++visitorCount);
        }
        resp.getWriter().print("{\"message\":\"当前第" + visitorCount + "个人登录了您的网站!\"}");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

2.2 重复登录案例

需求: 某客户端重复登陆同一账号时,回写:"不允许重复登陆!"

  • psm: /api/repeat-login?meta=login
  • psm: /api/exit-login?meta=login

监听器布局: RepeatLoginListener.java

/**
 * @author yap
 */
@WebListener
public class RepeatLoginListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
    //服务器启动的时候先创建一个空数组
        event.getServletContext().setAttribute("onlineUsers", new ArrayList<>());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        event.getServletContext().removeAttribute("onlineUsers");
    }
}

过滤器布局: RepeatLoginFilter.java

/**
 * @author yap
 */
@WebFilter("/api/repeat-login")
public class RepeatLoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("CrossDomainLoginFilter: init()...");
    }

    @SuppressWarnings("all")
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String name = req.getParameter("name");

        ServletContext application;
        synchronized (application = req.getServletContext()) {
       			 // 获取空数组
            List<String> onlineUsers = (List<String>) application.getAttribute("onlineUsers");
          		  //数组是空的或者 没值
            if (onlineUsers.isEmpty() || !onlineUsers.contains(name)) {
            		//放行
                chain.doFilter(req, resp);
            } else {
                resp.getWriter().print(name + " 不允许重复登录!");
            }
        }
    }

    @Override
    public void destroy() {
        System.out.println("CrossDomainLoginFilter: destroy()...");
    }
}

servlet测试: RepeatLoginServlet.java

/**
 * @author yap
 */
@WebServlet("/api/repeat-login")
public class RepeatLoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String meta = req.getParameter("meta");

        if (Meta.LOGIN.equals(meta)) {
            login(req, resp);
        }

    }

    interface Meta {
        String LOGIN = "login";
    }

    @SuppressWarnings("all")
    public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // suppose login success
		// 前端传来的name
        String name = req.getParameter("name");
        ServletContext application;
        synchronized (application = req.getServletContext()) {
            List<String> onlineUsers = (List<String>) application.getAttribute("onlineUsers");
            //把name添加到空数组
            onlineUsers.add(name);
        }
        //过滤器是空的显示登录 不是空的显示以登录
        resp.getWriter().print(name + "登录...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

servlet测试: ExitLoginServlet.java

/**
 * @author yap
 */
@WebServlet("/api/exit-login")
public class ExitLoginServlet extends HttpServlet {

    @SuppressWarnings("all")
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取前端传来的name
        String name = req.getParameter("name");
        ServletContext application;
        synchronized (application = req.getServletContext()){
        // 获取数组里的值
            List<String> onlineUsers = (List<String>) application.getAttribute("onlineUsers");
            //删除 前端传来的name
            onlineUsers.remove(name);
            // 把删除后的数组重新存值,如果不重新存值 数组删除失败
            application.setAttribute("onlineUsers", onlineUsers);
            resp.getWriter().print("{\"message\":\"" + name + "退出登陆!\"}");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

2.3 其他监听器

概念: servlet中除了 ServletContextListener 外,还提供了7种类型的监听:

  • ServletRequestListener:监听请求对象的生死。
  • ServletRequestAttributeListener:监听请求域中属性添加,修改和移除。
  • HttpSessionListener:监听会话对象的生死。
  • HttpSessionAttributeListener:监听会话域中属性添加,修改和移除。
  • HttpSessionBindingListener:监听其实现类绑定/解绑会话。
  • HttpSessionActivationListener:监听会话的迁移(vm-a钝化,vm-b激活),笔记本目前测试不了
  • ServletContextAttributeListener:监听应用域中属性添加,修改和移除。

ServletRequestListener:监听请求对象的生死。

监听器布局: MyRequestListener.java

/**
 * @author yap
 */
@WebListener
public class MyRequestListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent event) {
        HttpServletRequest req = (HttpServletRequest) event.getServletRequest();
        System.out.println("request init: " + req.getRequestURI());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent event) {
        HttpServletRequest req = (HttpServletRequest) event.getServletRequest();
        System.out.println("request destroy: " + req.getRequestURI());
    }
}

servlet测试: MyRequestServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-request")
public class MyRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("request success!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

ServletRequestAttributeListener:监听请求域中属性添加,修改和移除。

监听器布局: MyApplicationAttributeListener.java

/**
 * @author yap
 */
@WebListener
public class MyApplicationAttributeListener implements ServletContextAttributeListener {

    @Override
    public void attributeAdded(ServletContextAttributeEvent event) {
        System.out.println("application add: " + event.getName() + "/" + event.getValue());
    }
    
    @Override
    public void attributeReplaced(ServletContextAttributeEvent event) {
        // old value
        System.out.println("application replace: " + event.getName() + "/" + event.getValue());

    }
    
    @Override
    public void attributeRemoved(ServletContextAttributeEvent event) {
        System.out.println("application remove: " + event.getName() + "/" + event.getValue());

    }
}

servlet测试: MyApplicationAttributeServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-application-attribute")
public class MyApplicationAttributeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        String meta = req.getParameter("meta");

        if (Meta.ADD.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            resp.getWriter().print("add success!");
        } else if (Meta.REPLACE.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            req.getServletContext().setAttribute("name", "joe");
            resp.getWriter().print("replace success!");
        } else if (Meta.REMOVE.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            req.getServletContext().removeAttribute("name");
            resp.getWriter().print("remove success!");
        }
    }

    interface Meta {
        String ADD = "add";
        String REPLACE = "replace";
        String REMOVE = "remove";
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        this.doGet(req, resp);
    }
}

HttpSessionListener:监听会话对象的生死。

监听器布局: MySessionListener.java

/**
 * @author yap
 */
@WebListener
public class MySessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("session init: " + se.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session destroy: " + se.getSession().getId());
    }

}

servlet测试: MySessionServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-session")
public class MySessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String meta = req.getParameter("meta");
        HttpSession session;
        if (Meta.CREATE.equals(meta)) {
            session = req.getSession();
            resp.getWriter().print("session create success: " + session.getId());
        } else if (Meta.DESTROY.equals(meta)) {
            session = req.getSession();
            session.invalidate();
            resp.getWriter().print("session destroy success: " + session.getId());
        }
    }

    interface Meta {
        String CREATE = "create";
        String DESTROY = "destroy";
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

HttpSessionAttributeListener:监听会话域中属性添加,修改和移除。

监听器布局: MySessionAttributeListener.java

/**
 * @author yap
 */
@WebListener
public class MySessionAttributeListener implements HttpSessionAttributeListener {

    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        System.out.println("session add: " + event.getName() + "/" + event.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        System.out.println("session remove: " + event.getName() + "/" + event.getValue());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        // value is old...
        System.out.println("session replace: " + event.getName() + "/" + event.getValue());
    }
}

servlet测试: MySessionAttributeServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-session-attribute")
public class MySessionAttributeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        String meta = req.getParameter("meta");

        if (Meta.ADD.equals(meta)) {
            req.getSession().setAttribute("name", "admin");
            resp.getWriter().print("add success!");
        } else if (Meta.REPLACE.equals(meta)) {
            req.getSession().setAttribute("name", "admin");
            req.getSession().setAttribute("name", "joe");
            resp.getWriter().print("replace success!");
        } else if (Meta.REMOVE.equals(meta)) {
            req.getSession().setAttribute("name", "admin");
            req.getSession().removeAttribute("name");
            resp.getWriter().print("remove success!");
        }
    }

    interface Meta {
        String ADD = "add";
        String REPLACE = "replace";
        String REMOVE = "remove";
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        this.doGet(req, resp);
    }
}

HttpSessionBindingListener:监听其实现类绑定/解绑会话。

监听器布局: MyUser.java

/**
 * @author yap
 */
public class MyUser implements HttpSessionBindingListener {

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("MyUser bound to session: " + event.getSession().getId());
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("MyUser unbound from session: " + event.getSession().getId());
    }
}

servlet测试: MyUserBindingServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-session-binding")
public class MyUserBindingServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String meta = req.getParameter("meta");
        MyUser myUser = new MyUser();

        if (Meta.BOUND.equals(meta)) {
            req.getSession().setAttribute("myUser", myUser);
            resp.getWriter().print("myUser bound success!");
        } else if (Meta.UNBOUND.equals(meta)) {
            req.getSession().removeAttribute("myUser");
            resp.getWriter().print("myUser unbound success!");
        }
    }

    interface Meta {
        String BOUND = "bound";
        String UNBOUND = "unbound";
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

ServletContextAttributeListener:监听应用域中属性添加,修改和移除。

监听器布局: MyApplicationAttributeListener.java

/**
 * @author yap
 */
@WebListener
public class MyApplicationAttributeListener implements ServletContextAttributeListener {

    @Override
    public void attributeAdded(ServletContextAttributeEvent event) {
        System.out.println("application add: " + event.getName() + "/" + event.getValue());
    }
    
    @Override
    public void attributeReplaced(ServletContextAttributeEvent event) {
        // old value
        System.out.println("application replace: " + event.getName() + "/" + event.getValue());

    }
    
    @Override
    public void attributeRemoved(ServletContextAttributeEvent event) {
        System.out.println("application remove: " + event.getName() + "/" + event.getValue());

    }
}

servlet测试: MyApplicationAttributeServlet.java

/**
 * @author yap
 */
@WebServlet("/api/my-application-attribute")
public class MyApplicationAttributeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        String meta = req.getParameter("meta");

        if (Meta.ADD.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            resp.getWriter().print("add success!");
        } else if (Meta.REPLACE.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            req.getServletContext().setAttribute("name", "joe");
            resp.getWriter().print("replace success!");
        } else if (Meta.REMOVE.equals(meta)) {
            req.getServletContext().setAttribute("name", "admin");
            req.getServletContext().removeAttribute("name");
            resp.getWriter().print("remove success!");
        }
    }

    interface Meta {
        String ADD = "add";
        String REPLACE = "replace";
        String REMOVE = "remove";
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        this.doGet(req, resp);
    }
}

3. 报表打印

概念: 报表打印就是将数据生成到一个Excel文件中:

  • 引入依赖:jxls-core
  • 准备一个Excel模板,模板中支持插值符号 ${}<jx:> 标签。
  • 开发 UserService.listAndPrintExcel()
    • 准备一个Map类型的用户数据。
    • 获取转换器对象 XLSTransformer
    • 调用 transformXLS() 指定模板位置,数据和输出路径(带文件名)。
  • 开发 ExcelServlet:调用业务层 listAndPrintExcel()
  • 如果想下载下来,可以直接重定向这个文件的位置。

依赖

        <!--jxls-core-->
        <dependency>
            <groupId>net.sf.jxls</groupId>
            <artifactId>jxls-core</artifactId>
            <version>1.0-RC-2</version>
        </dependency>	

user-template.xls

布局: ExcelService.java

/**
 * @author yap
 */
public interface ExcelService {

    /**
     * 获取所有的用户信息并打印报表
     *
     * @param templatePath    模板真实物理路径
     * @param outputDirectory Excel文件输出位置
     */
    void listAndPrintExcel(String templatePath, String outputDirectory);
}

布局: ExcelServiceImpl.java

/**
 * @author yap
 */
public class ExcelServiceImpl implements ExcelService {

    @Override
    public void listAndPrintExcel(String templatePath, String outputDirectory) {
        File directory = new File(outputDirectory);
        if (!directory.exists()) {
            System.out.println(directory.mkdirs());
        }
        XLSTransformer transformer = new XLSTransformer();
        try {
            transformer.transformXLS(templatePath, getExcelData(), outputDirectory + "/user.xls");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Map<String, Object> getExcelData() {
        Map<String, Object> map = new HashMap<>(2);
        List<Map<String, Object>> users = new ArrayList<>();

        Map<String, Object> user;
        int amountOfUsers = 100;
        for (int i = 1; i <= amountOfUsers; i++) {
            user = new HashMap<>(3);
            user.put("id", i);
            user.put("username", "admin-" + i);
            user.put("password", "123" + i);
            users.add(user);
        }

        map.put("title", "用户信息表");
        map.put("users", users);
        return map;
    }
}

布局: ExcelServlet.java

/**
 * @author yap
 */
@WebServlet("/api/excel")
public class ExcelServlet extends HttpServlet {

    @SneakyThrows
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String templatePath = req.getServletContext().getRealPath("/template/user-template.xls");
        String outputDirectory = req.getServletContext().getRealPath("/excel");
        ExcelService userService = new ExcelServiceImpl();
        userService.listAndPrintExcel(templatePath, outputDirectory);

        // 直接重定向访问要打印的文件可以下载文件,但这种方式无法识别中文,如果是中文,使用IO流下载
        resp.sendRedirect(req.getContextPath() + "/excel/user.xls");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        this.doGet(request, response);
    }
}