Servlet3 (2) 接调存转

215 阅读13分钟

1. servlet接值

概念: servlet使用 request 对象来接收请求路径和请求头中的数据:

  • void setCharacterEncoding("UTF-8"):设置请求编码。
  • String getParameter(String name):通过name值接收单个请求参数。
  • String[] getParameterValues(String name):通过name值接收多个请求参数。
  • String getRequestURI():获取请求路径,不包括协议,IP和端口。
  • StringBuffer getRequestURL:获取请求全路径,包括协议,IP和端口。
  • String getServerName():获取服务器IP地址,如 localhost
  • Int getServerPort():获取端口号,如 8080
  • String getContextPath():获取项目名称,如 /servlet3
  • String getServletPath():获取servlet路由,如 /api/servlet/request
  • String getQueryString():获取查询串,如 a=1&b=2
  • String getHeader(String name):通过name值获取指定的请求头信息。
  • Cookie[] getCookies():获取请求头中的所有Cookie信息。
  • psm: {{tomcat}}/servlet3/api/request?username=赵四&id=1&id=2

源码: RequestServlet.java

/**
 * @author yap
 */
@WebServlet("/api/request")
public class RequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        System.out.println("param: " + req.getParameter("username"));
        System.out.println("params: " + Arrays.toString(req.getParameterValues("id")));
        System.out.println("uri: " + req.getRequestURI());
        System.out.println("url: " + req.getRequestURL());
        System.out.println("serverName: " + req.getServerName());
        System.out.println("serverPort: " + req.getServerPort());
        System.out.println("contextPath: " + req.getContextPath());
        System.out.println("servletPath: " + req.getServletPath());
        System.out.println("queryString: " + req.getQueryString());
        System.out.println("userAgent: " + req.getHeader("User-Agent"));
        for (Cookie cookie : req.getCookies()) {
            System.out.println(cookie.getName() + "-" + cookie.getValue());
        }
    }

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

2. servlet调用

概念: MVC架构就是对软件进行分层以达到解耦的目的:

  • 视图层 view:只负责搭建前端页面:
    • 通过表单,超链接,AJAX等方式向控制层传递数据。
  • 控制层 controller:只负责与视图层交互数据:
    • 接:接收视图层的请求相关数据。
    • 调:调用业务层方法。
    • 存:存储相关数据。
    • 转:对视图层的请求做出响应。
  • 模型层 model:从控制层分离出来的java类:
    • 实体类 pojo:用于映射数据库对象,存储和传输数据。
    • 业务类 service:只负责对数据进行业务逻辑处理,面向接口开发。
    • 数据类 dao:利用JDBC与数据库交互数据,面向接口开发。
  • MVC架构案例-登陆:
    • 数据库开发:数据库,账户,表,测试数据...
    • JDBC开发:创建maven项目 pro-jdbc,install本地仓库。
    • servlet3项目依赖:引入servlet,lombok和jdbc依赖。
    • pojo开发:对应数据表的实体类。
    • dao开发:开发登陆方法,测试。
    • service开发:开发登陆业务方法,测试。
    • controller开发:开发登陆接口方法。
    • view开发:编写视图层界面。
    • psm: {{tomcat}}/api/account?meta=login&username=admin

MySQL

配置: 数据库名servlet 表名account.sql

CREATE SCHEMA SERVLET DEFAULT CHARSET UTF8MB4;

USE SERVLET;

DROP TABLE IF EXISTS ACCOUNT;
CREATE TABLE ACCOUNT
(
    ID       INT AUTO_INCREMENT COMMENT '主键',
    USERNAME VARCHAR(50) NOT NULL COMMENT '账号' UNIQUE,
    PASSWORD VARCHAR(50) NULL COMMENT '密码',
    PRIMARY KEY (ID)
)
    COMMENT '账号表';

INSERT INTO ACCOUNT (USERNAME, PASSWORD)
VALUES ('admin', ''),
       ('zhaosi', '123'),
       ('liuneng', '456');
COMMIT;

servlet3项目依赖

配置: pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.servlet3</groupId>
    <artifactId>z4-servlet3</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
		<!--自己配置的jdbc maven包-->
        <dependency>
            <groupId>com.yap</groupId>
            <artifactId>pro-jdbc</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <scope>true</scope>
        </dependency>

    </dependencies>


</project>

配置: resources/db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/servlet?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
jdbc.connectionPoolSize=10

pojo开发

源码: com.yap.work.pojo.Account.java

/**
 * @Author Yap
 */
@Data
public class Account implements Serializable {
    private Integer id;
    private String username;
    private String password;
}

dao开发

源码: com.yap.work.dao.AccountDao.java

/**
 * @author yap
 */
public interface AccountDao {
/**
     * 查询全部的账号信息
     *
     * @return 返回全部的账号信息
     */
    List<Map<String, Object>> queryForList();

    /**
     * 通过账号查询信息
     *
     * @param username 账号信息
     * @return 对应账号信息的一条结果
     */
    Map<String, Object> queryByUsername(String username);
}

源码: com.yap.work.dao.AccountDaoImpl.java

/**
 * @Author Yap
 */
public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate = new JdbcTemplate();

    @Override
    public List<Map<String, Object>> queryForList() {
        String sql = "SELECT ID, USERNAME, PASSWORD FROM ACCOUNT";
        return jdbcTemplate.queryForList(sql);
    }

    @Override
    public Map<String, Object> queryByUsername(String username) {
        String sql = "SELECT ID, USERNAME, PASSWORD FROM ACCOUNT WHERE USERNAME = ?";
        return jdbcTemplate.queryForMap(sql,username);
    }
}

service开发

源码: com.yap.work.service.AccountServiceMap.java

/**
 * @author yap
 */
public interface AccountServiceMap {
    /**
     *登陆业务 简查登陆是否成功
     *
     * @param username 前端传过来的账号
     * @param password 前端传过来的密码
     * @return 如果登录成功返回true,否则返回false
     */
    boolean login(String username,String password);
}

源码: com.yap.work.service.impl.AccountServiceMapImpl.java


import com.yap.work.dao.AccountDao;
import com.yap.work.dao.impl.AccountDaoImpl;
import com.yap.work.service.AccountServiceMap;
import java.util.Map;

/**
 * @Author Yap
 */
public class AccountServiceMapImpl implements AccountServiceMap {
    private AccountDao accountDao = new AccountDaoImpl();

    @Override
    public boolean login(String username, String password) {
        if (isNull(username) || password == null) {
            return false;
        }
        Map<String, Object> userFromMysql = accountDao.queryByUsername(username);
        if (userFromMysql != null && !userFromMysql.isEmpty()) {
            return userFromMysql.get("password").equals(password);
        }
        return false;
    }

    private boolean isNull(String... strings) {
        for (String str : strings) {
            if (str == null || "".equals(str)) {
                return true;
            }
        }
        return false;
    }
}

controller开发

源码: com.yap.work.servlet.AccountServlet.java

import com.yap.work.service.impl.AccountServiceMapImpl;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author yap
 */
@WebServlet("/api/account")
public class AccountServlet extends HttpServlet {

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

        req.setCharacterEncoding("UTF-8");
        final String LOGIN = "login";

        String meta = req.getParameter("meta");
        if (LOGIN.equals(meta)) {
            login(req, resp);
        }
    }

    private void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        boolean result = new AccountServiceMapImpl().login(username, password);
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println(result ? username + " 登陆成功..." : username + " 登陆失败...");
    }

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

view开发

web: html/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <base href="/servlet3/">
</head>
<body>

<section>
    <form action="api/account" method="post">
        <input type="hidden" name="meta" value="login">

        <label for="username-ipt">账号:</label>
        <input id="username-ipt" name="username">

        <label for="password-ipt">密码:</label>
        <input id="password-ipt" name="password">

        <button type="submit">登录</button>
    </form>
</section>
</body>
</html>

测试

源码: com.yap.dao.AccountDaoTest.java

/**
 * @Author Yap
 */
public class AccountDaoTest {
    @Test
    public void queryForList() {
        AccountDao accountDao = new AccountDaoImpl();
        System.out.println(accountDao.queryForList());
    }
}

源码: com.yap.service.AccountServiceMapImplTest.java

/**
 * @Author Yap
 */
public class AccountServiceMapImplTest {
    private AccountServiceMap accountServiceMap = new AccountServiceMapImpl();

    @Test
    public void login()  {
        String username = null;
        String password = null;
        System.out.println(accountServiceMap.login(username, password));

        username = "";
        password = "";
        System.out.println(accountServiceMap.login(username, password));

        username = "admin";
        password = "";
        System.out.println(accountServiceMap.login(username, password));

        username = "";
        password = "admin";
        System.out.println(accountServiceMap.login(username, password));

        username = "zhaosi";
        password = "123";
        System.out.println(accountServiceMap.login(username, password));

    }
}

3. servlet存值

概念: servlet中使用 requestsessionapplication 可以进行存取值操作,三者API方法一致,生命周期不同:

  • 通用API方法:
    • setAttribute(K, V):以键值对的方式存值,同名覆盖。
    • getAttribute(K):通过K取出对应的V,不存在返回null。
  • request域:时效为一次请求,请求得到响应后,立即死亡。
  • session域:时效为一次会话,浏览器关闭且在失效时间内无任何请求访问它时死亡:
    • req.getSession():通过请求获取HttpSession对象,若不存在则创建一个新的并返回。
    • req.getSession(true):等效于 getSession()
    • req.getSession(false):通过请求获取session,若不存在返回null。
    • session.isNew():返回session是否是最新创建的。
    • session.getId():返回 sessionId
    • session.setMaxInactiveInterval():设置失效时间间隔-秒,-1表示session永生。
    • session.getMaxInactiveInterval():查看失效时间间隔-秒,默认1800秒。
    • session.invalidate():session立刻失效,等效于 setMaxInactiveInterval(0)
  • application域:时效为一次应用,服务器重启,崩溃,关闭时死亡:
    • req.getServletContext():通过请求获取ServletContext对象。

也可以在 web.xml 中指定 <session-config><session-timeout> 来设置session失效时间间隔-秒。

源码: StoreServlet.java

/**
 * @author yap
 */
@WebServlet("/api/store")
public class StoreServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");

        final String REQUEST = "request";
        final String SESSION = "session";
        final String APPLICATION = "application";

        String meta = req.getParameter("meta");
        if (REQUEST.equals(meta)) {
            request(req, resp);
        } else if (SESSION.equals(meta)) {
            session(req, resp);
        } else if (APPLICATION.equals(meta)) {
            application(req, resp);
        }
    }

    private void request(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setAttribute("req-name", "req-value-1");
        req.setAttribute("req-name", "req-value-2");
        System.out.println(req.getAttribute("req-name"));
        System.out.println(req.getAttribute("abc"));
        resp.getWriter().print("request: " + req.getAttribute("req-name"));
    }

    private void session(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession();
        System.out.println(session.isNew() ? "session is new..." : "session is old...");
        System.out.println("sessionId: " + session.getId());
        System.out.println("inactiveInterval: " + session.getMaxInactiveInterval());
        session.setMaxInactiveInterval(3600);
        System.out.println("inactiveInterval: " + session.getMaxInactiveInterval());
        session.setAttribute("session-name", "session-value-1");
        session.setAttribute("session-name", "session-value-2");
        System.out.println(session.getAttribute("session-name"));
        System.out.println(session.getAttribute("abc"));
        resp.getWriter().print("session: " + session.getAttribute("session-name"));
    }

    private void application(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        ServletContext application = req.getServletContext();
        application.setAttribute("application-name", "application-value-1");
        application.setAttribute("application-name", "application-value-2");
        System.out.println(application.getAttribute("application-name"));
        System.out.println(application.getAttribute("abc"));
        resp.getWriter().print("application: " + application.getAttribute("application-name"));
    }


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

3.1 session长连接原理

概念: HTTP协议是无状态的,每个请求对它来说都是新的,若需要长久保存数据,建议使用session域:

  • 当session被创建时,会生成一个 sessionID,并响应到浏览器的cookie中存储起来。
  • 当前浏览器以后的每一个请求都会携带这个 sessionID 信息。
  • 当web容器看到 sessionID 后,会去寻找对应的session,并将其和本次请求关联起来。
  • 所以不同的电脑,或者同一台电脑中的不同的浏览器都是不能共享session的。

3.2 存值域线程安全问题

概念: web容器为每个请求分配一个线程,这些线程是并发执行的,会产生线程安全问题:

  • 线程安全问题:
    • A线程进来 session.setAttribute("money", 100)
    • A线程挂起...
    • B线程进来 session.setAttribute("money", 200)
    • B线程挂起...
    • A线程苏醒,调用 session.getAttribute("money"),得到的居然是200
  • session域和application域中存储的资源都是线程不安全的,建议同步加锁,以效率换取安全:
    • 不能使用this锁,因为它只能阻止同一个servlet中的不同线程,但不能阻止不同的servlet的线程。
    • 不能在 doGet()doPost() 上加锁,因为非静态方法的锁也是this锁。
    • 建议使用锁类型为session或application对象本身。
  • request域是线程安全的,request中存储的资源在请求时就被销毁了:
    • 请求属性和方法局部变量是servlet中线程最安全的位置。
  • STM设计模式:该设计模式保证一个servlet一次只处理一个请求,效果和同步 doGet()/doPost() 一样,使用方法很简单,只要让servlet实现 SingleThreadModel 接口即可,它有两种策略,一种是让请求排队,一种是维护servlet实例池,并发处理请求,具体使用哪种策略取决于容器开发商,但这两种策略,前者牺牲太多效率,后者违反了servlet的单例规范,所以目前STM已经被ServletAPI废弃掉了,对于STM,它早已退出江湖,而你只需要知道,这名战士,它曾经试图保护过servlet属性的线程安全,就足够了。

源码: SyncServlet.java

/**
 * @author yap
 */
@WebServlet("/api/sync")
public class SyncServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        synchronized (session) {
            session.setAttribute("money", 100);
        }

        ServletContext application = req.getServletContext();
        synchronized (application) {
            application.setAttribute("money", 200);
        }
    }

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

4. servlet转页

概念: 转页方式指的就是响应方式,比如请求转发,重定向,回写JSON等:

  • response常用API方法:
    • addCookie():添加一个Cookie对象,web容器会将其回写到客户端Cookie中。
    • addHeader()/setHeader():添加/修改一个String类型的响应头数据。
    • addIntHeader()/setIntHeader():添加/修改一个int类型的响应头数据。
    • addDateHeader()/setDateHeader():添加/修改一个时间戳类型的响应头数据。
    • setContentLength():设置响应数据的大小,覆盖真实数据大小。
    • setContentType():设置响应的MIME数据类型和编码。
    • setStatus():设置成功时的响应码状态,建议2345开头三位数字。
    • getOutputStream():获取响应字节流 ServletOutputStream 实例。
    • getWriter():获取响应字节流 PrintWriter 实例。
    • sendRedirect():用于请求重定向,等效于响应头中的 location 字段。
    • containsHeader():判断是否含有指定的响应头数据。
  • 请求转发与重定向:
    • 请求转发: 一次请求,URL无变化,效率高,只需从 webapp 下开始设置后半段路径。
    • 重定向: 二次请求,URL有变化,效率低,必须从项目发布名开始设置路径。
    • psm: {{tomcat}}/servlet3/api/response 源码: ResponseServlet.java
/**
 * @author yap
 */
@WebServlet("/api/response")
public class ResponseServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Cookie cookie = new Cookie("cookie-name", "cookie-value");
        cookie.setMaxAge(60 * 5);
        resp.addCookie(cookie);
        System.out.println(resp.containsHeader("string-header"));
        resp.addHeader("string-header", "string-header");
        System.out.println(resp.containsHeader("string-header"));
        resp.addIntHeader("int-header", 9527);
        resp.addDateHeader("date-header", System.currentTimeMillis());
        resp.setContentLength(1);
        resp.setStatus(200);
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().print("response-data");
    }

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

4.1 请求转发

概念: 请求转发就是servlet将响应HTML页面的工作转发给另一个servlet或HTML,并支付 requestresponse 对象,最终返回静态HTML数据:

  • 获取请求转发器 RequestDispatcher 实例:req.getRequestDispatcher()
    • 形参传入转发的目标地址,即接手人的路径,约定以 / 开头。
  • 请求转发器转发的时候需要接手 requestresponse 参数:
    • forward():表示永久将请求转交,此后均为死代码,抛出 IllegalStateException 异常。
    • include():表示暂时将请求转交,它们工作结束后再接管回来,由自己完成请求和响应的最后处理,如再次使用另一个 include()forward() 等,实际开发中一般不使用。
  • psm: {{tomcat}}/servlet3/api/forward 源码: ForwardServlet.java
/**
 * @author yap
 */
@WebServlet("/api/forward")
public class ForwardServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        System.out.println("转发给index.html");
        req.getRequestDispatcher("/index.html").forward(req, resp);
    }

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

4.2 重定向

概念: 重定向主要使用场景是避免请求重复转发导致错误数据:

  • 重定向防刷新原理:
    • 请求转发时:我向班长发送 想吃一个面包 请求,班长会响应给我一个面包,但我因口吃说了十遍(页面刷新),于是得到十个面包,数据错误。
    • 重定向时:我向班长发送 想吃一个面包 请求,班长会响应给我面包店地址,但我因口吃说了十遍(页面刷新),于是得到十次地址,然后我按照地址自己去买,最终只得到一个面包,数据正常。
  • 通过响应发送重定向:resp.sendRedirect()
    • 需要指定完整的目标路径,建议使用 req.getContextPath() 动态拼接项目发布名。
  • psm: {{tomcat}}/servlet3/api/redirect 源码: RedirectServlet.java
/**
 * @author yap
 */
@WebServlet("/api/redirect")
public class RedirectServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("重定向到/index.html");
        resp.sendRedirect(req.getContextPath() + "/index.html");
    }

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

4.3 AJAX响应

概念: AJAX请求因异步而无法使用请求转发和重定向,仅支持响应流回写数据:

  • AJAX请求的请求头额外携带 x-requested-with=XMLHttpRequest 信息。
  • 对应AJAX请求响应的最佳数据格式是JSON字符串,注意KEY值必须添加双引号。
  • 响应JSON数据时建议设置响应MIME类型为 application/json,此时AJAX可以直接得到一个JSON格式的数据,而省去解析JSON字符串的过程。
  • psm: {{tomcat}}/servlet3/api/ajax
    • 请求头添加 x-requested-with = XMLHttpRequest 模拟AJAX请求
    • GET方式:直接使用查询串传递 meta=ajax_type
    • POST方式:在 Body/x-www-form-urlencoded 传递 meta=ajax_type 源码: com.yap.work.servlet.AjaxServlet.java
/**
 * @author yap
 */
@WebServlet("/api/ajax")
public class AjaxServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        req.setCharacterEncoding("UTF-8");

        String meta = req.getParameter("meta");
        if (Meta.AJAX_TYPE.equals(meta)) {
            ajaxType(req, resp);
        }
    }

    interface Meta {
        String AJAX_TYPE = "ajax_type";
        String XML_HTTP_REQUEST = "XMLHttpRequest";
    }

    private void ajaxType(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        String xRequestedWith = req.getHeader("x-requested-with");
        if (xRequestedWith != null && xRequestedWith.equals(Meta.XML_HTTP_REQUEST)) {
            System.out.println("本次请求是AJAX请求,回写JSON数据...");
            resp.setContentType("application/json;charset=UTF-8");
            String jsonStr = "{\"username\":\"admin\",\"password\":\"123\"}";
            resp.getWriter().print(jsonStr);
        } else {
            System.out.println("本次请求不是AJAX请求,转发到index.html...");
            req.getRequestDispatcher("/index.html").forward(req, resp);
        }
    }

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

5. JSON工具

概念: JSON工具可以将List,Map,Set或者一个单独的Pojo类转换成一个格式准确的JSON字符串。

5.1 jackson

概念: jackson是用来序列化和反序列化JSON的java开源框架,SpringMVC的默认JSON解析器,简单易用,速度快,占用内存低,性能较好,可以扩展和定制:

  • 依赖:jackson-core/jackson-annotations/jackson-databind
  • ObjectMapper():构造 ObjectMapper 核心对象。
  • setSerializationInclusion(JsonInclude.Include incl):设置序列化过程的参与者:
    • param1:JsonInclude.Include.NON_NULL:null值不参与序列化。
    • return:返回配置后的 ObjectMapper 对象。
  • configure(SerializationFeature f, boolean state):设置序列化某些配置的开启或禁止:
    • param1:SerializationFeature.WRITE_DATES_AS_TIMESTAMPS:是否将Date序列化成时间戳。
    • param2:true表示开启配置,false表示禁止配置。
    • return:返回配置后的 ObjectMapper 对象。
  • setDateFormat(DateFormat format):设置Date类型数据的序列化格式。
    • param1:DataFormat 日期模板对象。
    • return:返回配置后的 ObjectMapper 对象。
  • writeValueAsString(Object obj):将数据序列化为JSON字符串并返回。
  • writeValue(*, Object obj):将第二个参数序列化并通过第一个参数直接完成回写过程:
    • param1若为 File 对象,表示将param2序列化后保存到指定的文件中。
    • param1若为 PrintWriter 对象,表示将param2序列化后回写到字符输出流中。
    • param1若为 ServletOutputStream 对象,表示将param2序列化后回写到字节输出流中。
  • psm: {{tomcat}}/servlet3/api/json?meta=jackson 源码: com.yap.work.servlet.JsonServlet
/**
 * @author yap
 */
@WebServlet("/api/json")
public class JsonServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        req.setCharacterEncoding("UTF-8");

        String meta = req.getParameter("meta");
        if (Meta.JACKSON.equals(meta)) {
            jackson(req, resp);
        } else if (Meta.GSON.equals(meta)) {
            gson(req, resp);
        }
    }

    interface Meta {
        String JACKSON = "jackson";
        String GSON = "gson";
    }

    private Map<String, Object> getMap(int num) {
        Map<String, Object> map01 = new HashMap<>(2);
        map01.put("map-key-1", "map-val-1");
        map01.put("map-null-1", null);
        map01.put("map-empty-1", "");
        map01.put("map-date-1", new Date());
        map01.put("map-gender-1", "1");

        Map<String, Object> map02 = new HashMap<>(2);
        map02.put("map-key-2", "map-val-2");
        map02.put("map-null-2", null);
        map02.put("map-empty-2", "");
        map02.put("map-date-2", new Date());
        map02.put("map-gender-2", "1");
        return num == 1 ? map01 : map02;
    }

    private Account getAccount(int num) {
        Account account01 = new Account(9527, "user-1", "pass-1");
        Account account02 = new Account(9528, "user-2", "pass-2");
        return num == 1 ? account01 : account02;
    }

    private void jackson(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        // new objectMapper
        ObjectMapper objectMapper = new ObjectMapper();

        // writeValueAsString(): map to json-str
        System.out.println(objectMapper.writeValueAsString(this.getMap(1)));

        // writeValueAsString(): pojo to json-str
        System.out.println(objectMapper.writeValueAsString(this.getAccount(1)));

        // writeValueAsString(): list<pojo> to json-str
        List<Account> accounts = new ArrayList<>();
        accounts.add(this.getAccount(1));
        accounts.add(this.getAccount(2));
        System.out.println(objectMapper.writeValueAsString(accounts));

        // new and config objectMapper
        objectMapper = new ObjectMapper()
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
                .setDateFormat(new SimpleDateFormat("yyyy/MM/dd hh:mm:ss"));

        // writeValue(): list<map> to json-str and response
        List<Map<String, Object>> list = new ArrayList<>();
        list.add(this.getMap(1));
        list.add(this.getMap(2));
        resp.setContentType("application/json;charset=utf-8");
        objectMapper.writeValue(resp.getWriter(), list);
    }

    private void gson(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        Gson gson = new Gson();

        // toJson(): map to json-str
        System.out.println(gson.toJson(this.getMap(2)));

        // toJson(): pojo to json-str
        System.out.println(gson.toJson(this.getAccount(2)));

        // toJson(): list<pojo> to json-str
        List<Account> accounts = new ArrayList<>();
        accounts.add(this.getAccount(1));
        accounts.add(this.getAccount(2));
        System.out.println(gson.toJson(accounts));

        // new gson by GsonBuilder
        gson = new GsonBuilder()
                .serializeNulls()
                .setDateFormat("yyyy-MM-dd")
                .create();

        // toJson(): list<map> to json-str and response
        List<Map<String, Object>> list = new ArrayList<>();
        list.add(this.getMap(1));
        list.add(this.getMap(2));
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print(gson.toJson(list));
    }

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

5.2 GSON

概念: Gson是Google公司发布的序列化和反序列化JSON的java开源框架:

  • 依赖:gson
  • Gson():构造 Gson 核心对象。
  • GsonBuilder():构造 GsonBuilder 构造器对象。
  • serializeNulls():设置序列化数据中的所有null值:
    • return:返回配置后的 GsonBuilder 对象。
  • setDateFormat(String format):设置Date数据格式:
    • param1:日期格式字符串。
    • return:返回配置后的 GsonBuilder 对象。
  • create():通过 GsonBuilder 构造 Gson 核心对象。
  • toJson(Object obj):将数据序列化为JSON字符串并返回。
  • psm: {{tomcat}}/servlet3/api/json?meta=gson 源码:
		 <!--jackson-core-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.7</version>
        </dependency>

        <!--jackson-annotations-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.7</version>
        </dependency>

        <!--jackson-databind-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.7</version>
        </dependency>

        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>