由David发表在天码营
我们已经学习了两种Java服务器端技术——JSP和Servlet,比较二者的不同:
HttpServletResponse
对象动态输出HTML内容两种技术有着不同的特点,在不同的场景下有着各自的优势:
既然JSP和Servlet都有自身的适用环境,那么能否扬长避短,让它们发挥各自的优势呢?答案是肯定的——MVC(Model-View-Controller)模式非常适合解决这一问题。
MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller):
在JSP/Servlet开发的软件系统中,这三个部分的描述如下所示:
MVC模式在Web开发中的好处是非常明显,它规避了JSP与Servlet各自的短板,Servlet只负责业务逻辑而不会通过out.append()
动态生成HTML代码;JSP中也不会充斥着大量的业务代码。这大大提高了代码的可读性和可维护性。
所有的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中动态输出的HTML内容是与这些结果有关的,这就需要将Servlet中的数据传递到JSP中:
request.setAttribute(name, value);
在JSP中可以通过<%= request.getAttribute(name) %>
或直接使用EL表达式${name}
得到相关的数据。
用户博客列表页面如下图所示:
可以看到,上图中动态的内容包括:
Lorem的博客
用户博客列表页面的业务逻辑就是需要查询某一用户创造的文章列表以及该用户的个人详细信息,我们建立相关的业务对象以及查询方法:
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());
}
}
这里初始化添加了三个测试用户,用户名分别为user1
, user2
,user3
,同时包含一个根据用户名查找对应User
对象的方法getByUsername
,以及一个根据User
对象找出它创建的所有Post
对象的方法。
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);
}
}
在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/>
<% } %>