Java Web ——MVC基础框架讲解及代码演示

Posted 苍山有雪,剑有霜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Web ——MVC基础框架讲解及代码演示相关的知识,希望对你有一定的参考价值。

了解一下类似Spring MVC框架的底层实现

相信看完这个短文,MVC你就懂了,还不懂请艾特我······

带着问题去学习

  • MVC到底是什么?
  • 如何实现MVC?

开始!

MVC是什么?

Web开发

在讲MVC之前,还是简要介绍一下Web开发吧,相信搞程序的或多或少都知道Web编程、Socket编程等等。

其实开发Web应用最原始的方式面向Http连接的底层进行开发,自己编写Web服务,需要了解和考虑的内容包括但不限于:Socket编程、端口号、报文处理、IO多路复用、线程池等等。

举个例子,用Java来编写一个Http服务器:

public class Server 
    public static void main(String[] args) throws IOException 
        ServerSocket ss = new ServerSocket(8080); // 监听指定端口
        System.out.println("server is running...");
        for (;;) 
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        
    


class Handler extends Thread 
    Socket sock;

    public Handler(Socket sock) 
        this.sock = sock;
    

    public void run() 
        try (InputStream input = this.sock.getInputStream()) 
            try (OutputStream output = this.sock.getOutputStream()) 
                handle(input, output);
            
         catch (Exception e) 
            try 
                this.sock.close();
             catch (IOException ioe) 
            
            System.out.println("client disconnected.");
        
    

    private void handle(InputStream input, OutputStream output) throws IOException 
        System.out.println("Process new http request...");
        var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        // 读取HTTP请求:
        boolean requestOk = false;
        String first = reader.readLine();
        if (first.startsWith("GET / HTTP/1.")) 
            requestOk = true;
        
        for (;;) 
            String header = reader.readLine();
            if (header.isEmpty())  // 读取到空行时, HTTP Header读取完毕
                break;
            
            System.out.println(header);
        
        System.out.println(requestOk ? "Response OK" : "Response Error");
        if (!requestOk) 
            // 发送错误响应:
            writer.write("HTTP/1.0 404 Not Found\\r\\n");
            writer.write("Content-Length: 0\\r\\n");
            writer.write("\\r\\n");
            writer.flush();
         else 
            // 发送成功响应:
            String data = "<html><body><h1>Hello, world!</h1></body></html>";
            int length = data.getBytes(StandardCharsets.UTF_8).length;
            writer.write("HTTP/1.0 200 OK\\r\\n");
            writer.write("Connection: close\\r\\n");
            writer.write("Content-Type: text/html\\r\\n");
            writer.write("Content-Length: " + length + "\\r\\n");
            writer.write("\\r\\n"); // 空行标识Header和Body的分隔
            writer.write(data);
            writer.flush();
        
    

一个Http服务器,本质上来说就是TCP服务器,处理流程就是服务器一直监听端口,客户端向指定端口发送请求,服务器接收请求进行相应的逻辑处理,而后将处理结果又返回给客户端,最后在客户端浏览器上进行展示。更详细的玩意儿还是参见《Unix网络编程》那本书的例子,可以模仿C语言写个Java的客户端

但是在实际开发过程中,这些基础、底层的工作,就是重复造轮子,需要耗费大量的时间!

为了方便开发,将这些底层工作封装到一个库中——Servlet中,于是后来的程序员只需要基于Servlet的API来开发Web应用即可,不用再去管这些端口啊、IO多路复用什么的。

基于Servlet开发

基于Servlet开发可以大大减小开发Web服务的复杂程度,例如只需要几行代码,就可以让服务端相应客户端对主页的请求:

//原址https://www.liaoxuefeng.com/wiki/1252599548343744/1304265949708322
//WebServlet注解表示这是一个Servlet,并将url地址为"/"的请求映射到这个类中进行处理
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet 
    protected void doGet(HttpServletRequest req, 
                         HttpServletResponse resp)
        throws ServletException, IOException 
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, world!</h1>");
        // flush强制输出:
        pw.flush();
    

一个Web应用其实就是由一个或多个Servlet组成的,每个Servlet通过注解说明自己负责的路径Url,并通过实现doGet和doPost方法来进行相应的逻辑处理。比如处理GET方法,就覆写doGet()方法:

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        ...
        

其他的如POST等请求方法也是类似的。

可以看到,当有了Servlet之后,开发Web应用就变得简洁了起来,只需要编写处理对应url的逻辑处理即可。

但是,你们有没有发现一个问题——在Java代码里出现了跟前端有关的代码。在Servlet程序中,输出HTML的方式如下:

PrintWriter pw = resp.getWriter();
pw.write("<html>");
pw.write("<body>");
pw.write("<h1>Welcome, " + name + "!</h1>");
pw.write("</body>");
pw.write("</html>");
pw.flush();

上述这种输出HTML的步骤较为繁琐,而且容易出错,并且也不像直接编写HTML网页那么直观(比如JS、CSS语言),虽然全栈工程师前后端都可以care,不过在项目开发中还是前端和后端的东西各自分开最好,才能使两者的开发效率最高。

为了解决这个问题,后面出现了JSP(Java Server Pages)语言。

JSP代码类似于html语言,并且其中还可以嵌入Java代码,进行变量插入、变量输出等操作:

//原址:https://www.liaoxuefeng.com/wiki/1252599548343744/1266262958498784
<html>
<head>
<title>Hello World - JSP</title>
</head>
<body>
<%-- JSP Comment --%>
<h1>Hello World!</h1>
<p>
<%
out.println("Your IP address is ");
%>
    <span style="color:red">
    <%= request.getRemoteAddr() %>
    </span>
    </p>
    </body>
</html>

不过这种方式(JSP)虽然可以很方便的输出HTML,并在其中实现一定的动态内容,运行简单的代码,但是写起Java来也忒不适应了吧,总感觉怪怪的。

另外,Servlet简化了Web应用的开发,可以基于Java语言实现复杂的业务逻辑,但是不太适合输出HTML。

那么,有没有一种方式可以结合两者,从而是两者相辅相成呢?

有的!那就是MVC!

MVC设计模式

先给出MVC设计模式的简单定义——Model-View-Controller:

  • Model(模型)是前后端定义的数据传输单元,通常前端只是需要后端提供一些数据来展示而已,比如用户名、处理结果来,需要HTML输出的东西;
  • View(视图)的工作就是接收后端发过来的Model,取出数据,然后写入Html中;
  • Controller(控制器)其实就是业务逻辑,类似于之前的Servlet开发,区别在于现在将处理结果都放在Model里,然后发给View;

来个例子吧,实现用户登录,获取信息

首先,我们编写了两个JavaBean(JavaBean简单来说就是一个可以放入数据和取出数据的类),也就是之前说的Model

//源码:https://www.liaoxuefeng.com/wiki/1252599548343744/1266264917931808
public class User 
    public long id;
    public String name;
    public School school;

public class School 
    public String name;
    public String address;

接着,来实现Controller,在UserServlet中实现从数据库读取User、School等信息,并放入Request中,再发送给user.jsp,也就是View进行渲染处理:

@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet 
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        // 假装从数据库读取:
        School school = new School("No.1 Middle School", "101 South Street");
        User user = new User(123, "Bob", school);
        // 放入Request中:
        req.setAttribute("user", user);
        // forward给user.jsp:
        req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
    

View——user.jsp负责将接收到的user信息进行渲染展示,即写入Html中:

<%@ page import="com.itranswarp.learnjava.bean.*"%>
<%
User user = (User) request.getAttribute("user");
%>
    <html>
    <head>
    <title>Hello World - JSP</title>
    </head>
    <body>
    <h1>Hello <%= user.name %>!</h1>
    <p>School Name:
<span style="color:red">
<%= user.school.name %>
    </span>
    </p>
    <p>School Address:
<span style="color:red">
<%= user.school.address %>
    </span>
    </p>
    </body>
</html>

以上,就完成了一个简单的web应用,在浏览器访问http://localhost:8080/user,结果如下:

捋一捋,用一个结构图来描述上述代码:

简单来说,就是将整个Web开发分成了三个部分:逻辑处理、前端展示、数据传输,分别代表Controller、View、Model。

这种方式有什么好处?分工明确、开发简单。

Controller就好好关注逻辑处理,比如访问数据库啦、进行复杂的逻辑交互等等;View就关注如何让界面好看就完了,比如用模板引擎写出好看的界面,留几个空白给Model进行展示就ok了!

有关模板引擎的具体内容,可以了解:https://blog.csdn.net/qq_42266891/article/details/108265478

实现一个简单MVC框架

上文虽然结合JSP和Servlet可以发挥两者的优点,但是仍存在一些问题:

  • Servlet提供的接口仍然偏向底层,我想用纯粹的Java语言咋办;
  • JSP实现的页面比较简单,难以满足前端的各种骚操作,我想前端好看一点!

其实,廖大实现了一个简单的MVC框架(可以更好地理解Spring MVC的底层知识),可以直接阅读:

https://www.liaoxuefeng.com/wiki/1252599548343744/1337408645759009。

下面做一个简单介绍

首先,项目框架是这样的:

其中,bean其实就是后续Model将要用到的东西,根据具体的业务来定,User的具体实现如下:

public class User 
    public String email;
    public String password;

    public String name;
    public String description;

    public User() 
    

    public User(String email, String password, String name, String description) 
        this.email = email;
        this.password = password;
        this.name = name;
        this.description = description;
    

controller其实就是日常开发中的业务处理逻辑,UserController负责用户登录、注销、用户展示:

public class UserController 
    private Map<String, User> userDatabase = new HashMap<String, User>() 
        
            List<User> users = Arrays.asList( //
                    new User("bob@example.com", "" +
                            "", "Bob", "This is bob."),
                    new User("tom@example.com", "tomcat", "Tom", "This is tom."));
            users.forEach(user -> 
                put(user.email, user);
            );
        
    ;

    @GetMapping("/signin")
    public ModelAndView signin() 
        return new ModelAndView("/signin.html");
    

    @PostMapping("/signin")
    public ModelAndView doSignin(SignInBean bean, HttpServletResponse response, HttpSession session)
            throws IOException 
        User user = userDatabase.get(bean.email);
        if (user == null || !user.password.equals(bean.password)) 
            response.setContentType("application/json");
            PrintWriter pw = response.getWriter();
            pw.write("\\"error\\":\\"Bad email or password\\"");
            pw.flush();
         else 
            session.setAttribute("user", user);
            response.setContentType("application/json");
            PrintWriter pw = response.getWriter();
            pw.write("\\"result\\":true");
            pw.flush();
        
        return null;
    

    @GetMapping("/signout")
    public ModelAndView signout(HttpSession session) 
        session.removeAttribute("user");
        return new ModelAndView("redirect:/");
    

    @GetMapping("/user/profile")
    public ModelAndView profile(HttpSession session) 
        User user = (User) session.getAttribute("user");
        if (user == null) 
            return new ModelAndView("redirect:/signin");
        
        return new ModelAndView("/profile.html", "user", user);
    

可以看到,编写业务处理逻辑只需要通过Get、Post注解来指明映射url地址即可,极为方便!返回的ModelAndView是给前端进行展示的东西,此处按下不表。

最后的framework就是MVC框架的核心内容,完全可以封装成一个包用作自己代码的后续开发。

首先,对于一个MVC框架来说,它需要一个分发器来接收所有的url,而后再根据请求内容将请求内容转发给对应的controller进行逻辑处理,分发器的实现

//源码地址:https://www.liaoxuefeng.com/wiki/1252599548343744/1337408645759009
@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet 
    private final Map<String, GetDispatcher> getMappings = new HashMap<>();

    private final Map<String, PostDispatcher> postMappings = new HashMap<>();

    // 指定package并自动扫描:
    private final List<Class<?>> controllers = Arrays.asList(IndexController.class, UserController.class);
    private ViewEngine viewEngine;
    //扫描指定package中所有的Get和Post注解,获取对应的controller处理方法
    @Override
    public void init() throws ServletException 
        // 利用反射,来获取所有Get和Post注解对应的method
        ......
        // 创建ViewEngine:
        this.viewEngine = new ViewEngine(getServletContext());
    

    // 处理get请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        try 
            process(req, resp, this.getMappings);
         catch (PebbleException e) 
            e.printStackTrace()以上是关于Java Web ——MVC基础框架讲解及代码演示的主要内容,如果未能解决你的问题,请参考以下文章

Java Web ——MVC基础框架讲解及代码演示

Python Web框架Tornado的异步处理代码演示样例

Struts入门 配置文件的讲解

Spring MVC 入门示例讲解

Spring MVC入门讲解

Spring MVC XML 的配置