JSP与Servlet比较

我们已经学习了两种Java服务器端技术——JSP和Servlet,比较二者的不同:

  • Servlet在Java代码中通过HttpServletResponse对象动态输出HTML内容
  • JSP在静态HTML内容中嵌入Java代码,Java代码被动态执行后生成HTML内容

两种技术有着不同的特点,在不同的场景下有着各自的优势:

  • Servlet能够很好地组织业务逻辑代码,但是在Java源文件中通过字符串拼接的方式生成动态HTML内容会导致代码维护困难、可读性差
  • JSP虽然规避了Servlet在生成HTML内容方面的劣势,但是在HTML中混入大量、复杂的业务逻辑同样也是不可取的

各司其职——MVC模式

既然JSP和Servlet都有自身的适用环境,那么能否扬长避短,让它们发挥各自的优势呢?答案是肯定的——MVC(Model-View-Controller)模式非常适合解决这一问题。

MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller):

  • Controller——负责转发请求,对请求进行处理
  • View——负责界面显示
  • Model——业务功能编写(例如算法实现)、数据库设计以及数据存取操作实现

在JSP/Servlet开发的软件系统中,这三个部分的描述如下所示:

Clipboard Image.png

  1. Web浏览器发送HTTP请求到服务端,被Controller(Servlet)获取并进行处理(例如参数解析、请求转发)
  2. Controller(Servlet)调用核心业务逻辑——Model部分,获得结果
  3. Controller(Servlet)将逻辑处理结果交给View(JSP),动态输出HTML内容
  4. 动态生成的HTML内容返回到浏览器显示

MVC模式在Web开发中的好处是非常明显,它规避了JSP与Servlet各自的短板,Servlet只负责业务逻辑而不会通过out.append()动态生成HTML代码;JSP中也不会充斥着大量的业务代码。这大大提高了代码的可读性和可维护性。

实现MVC模式

基本原理

所有的HTTP请求都应该由Servlet进行处理,业务逻辑完成后,再调用相应的JSP文件生成HTML内容并返回到浏览器:

/*Bussiness Logic code*/

//Step 1: get request dispatcher
RequestDispatcher dispatcher = request.getRequestDispatcher("view.jsp");

//Step 2: forward req/resp to the specified JSP
dispatcher.forward(request, response);

Servlet API中的RequestDispatcher在这一场景下非常有用,它可以在Servlet中将请求转发(forward)到指定的JSP文件。

从Servlet向JSP中传递数据

通常在Servlet中需要调用业务代码来完成特定的功能并获取结果,同时JSP中动态输出的HTML内容是与这些结果有关的,这就需要将Servlet中的数据传递到JSP中:

request.setAttribute(name, value);

在JSP中可以通过<%= request.getAttribute(name) %>或直接使用EL表达式${name}得到相关的数据。

实例:用户博客列表

用户博客列表页面如下图所示:

Clipboard Image.png

可以看到,上图中动态的内容包括:

  1. 导航栏中的标题Lorem的博客
  2. 文章列表
  3. 文章列表右侧的个人信息——头像、名字、邮箱

Model

用户博客列表页面的业务逻辑就是需要查询某一用户创造的文章列表以及该用户的个人详细信息,我们建立相关的业务对象以及查询方法:

public class Post {

    private long id;
    private String title;
    private String content;
    private User creator;
    private Date createdTime;

    public Post(String title, String content, User creator) {
        this.title = title;
        this.content = content;
        this.creator = creator;
        this.createdTime = new Date();
    }
  //Getter与Setter方法省略
}
public class User {

    private long id;
    private String username;
    private String password;
    private String avatar;
    private String title;
    private String email;

    public User(long id, String username, String password, String avatar, String title, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.avatar = avatar;
        this.title = title;
        this.email = email;
    }
  //Getter与Setter方法省略
}

我们用Data类以及它包含的静态变量来存储用户以及博客列表:

public class Data {

    public static List<User> users = new ArrayList<>();

    public static List<Post> posts = new ArrayList<>();

    static {
        users.add(new User(1L, "user1", "password", "images/default-avatar.jpeg", "title1", "description1"));
        users.add(new User(2L, "user2", "password", "images/default-avatar.jpeg", "title2", "description2"));
        users.add(new User(3L, "user3", "password", "images/default-avatar.jpeg", "title3", "description3"));
    }

    public static User getByUsername(String username) {
        return users.stream()
                .filter(e -> e.getUsername().equals(username))
                .findFirst()
                .orElse(null);
    }

    public static List<Post> getPostByUser(User user) {
        return posts.stream()
                .filter(p -> p.getCreator().getUsername().equals(user.getUsername()))
                .collect(Collectors.toList());
    }

}

这里初始化添加了三个测试用户,用户名分别为user1user2user3,同时包含一个根据用户名查找对应User对象的方法getByUsername,以及一个根据User对象找出它创建的所有Post对象的方法。

Controller

Controller由Servlet实现,它负责获取用户的输入(也就是用户名),并调用业务逻辑方法获取相关数据,然后将请求分发至指定JSP进行处理:

@WebServlet("/userPosts")
public class UserPostController extends HttpServlet {

    private static final long serialVersionUID = -4208401453412759851L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        User user = Data.getByUsername(username);
        List<Post> posts = Data.getPostByUser(user);

        req.setAttribute("posts", posts);
        req.setAttribute("user", user);
        RequestDispatcher dispatcher = req.getRequestDispatcher("/templates/userPost.jsp");
        dispatcher.forward(req, resp);
    }

}

View

在JSP中,就可以通过request.getAttribute("posts")request.getAttribute("user")来访问Servlet中传递来的数据了(这里只包含核心渲染逻辑代码)

<% for (Post post : request.getAttribute("posts")) { %>
  <div class="blog-post">
    <h3 class="blog-post-title">
      <a href="#"><%= post.getTitle() %></a>
    </h3>
    <p class="blog-post-content"><%= post.getContent() %></p>
  </div>
  <hr/>
<% } %>

登录发表评论 注册

反馈意见