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

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

Java Web ——MVC基础框架讲解及代码演示(上):https://developer.aliyun.com/article/1508609

实现一个简单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();
        }
    }

    // 处理post请求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            process(req, resp, this.postMappings);
        } catch (PebbleException e) {
            e.printStackTrace();
        }
    }

    // 主要逻辑是根据getMappings和postMappings获取指定的method,并通过反射调用
    private void process(HttpServletRequest req, HttpServletResponse resp,
                         Map<String, ? extends AbstractDispatcher> dispatcherMap) throws ServletException, IOException, PebbleException {
         ......
    }
}

上面所提到的注解,实现起来很简单:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GetMapping {
    String value();
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PostMapping {
    String value();
}

之前提到的ModelAndView,其实主要有两个作用,一是指明模板html的地址,二是获取要展示的内容:

public class ModelAndView {
    Map<String, Object> model;
    String view;

    public ModelAndView(String view) {
        this.view = view;
        this.model = new HashMap<>();
    }

    public ModelAndView(String view, String name, Object value) {
        this.view = view;
        this.model = new HashMap<>();
        this.model.put(name, value);
    }

    public ModelAndView(String view, Map<String, Object> model) {
        this.view = view;
        this.model = new HashMap<>(model);
    }
}

上述代码中,string view简单来说就是某一个html文件的路径,一般是通过模板引擎生成的,有关模板引擎可以参考:模板引擎.

另一个变量model,就是之前定义的bean,以获取用户信息为例:

@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);
    }

最后还需要一个View对内容进行前端渲染,简单来说就是将需要展示的内容写入html中:

public class ViewEngine {
    private final PebbleEngine engine;

    public ViewEngine(ServletContext servletContext) {
        // 定义一个ServletLoader用于加载模板:
        ServletLoader loader = new ServletLoader(servletContext);
        // 模板编码:
        loader.setCharset("UTF-8");
        // 模板前缀,这里默认模板必须放在`/WEB-INF/templates`目录:
        loader.setPrefix("/WEB-INF/templates");
        // 模板后缀:
        loader.setSuffix("");
        // 创建Pebble实例:
        this.engine = new PebbleEngine.Builder()
                .autoEscaping(true) // 默认打开HTML字符转义,防止XSS攻击
                .cacheActive(false) // 禁用缓存使得每次修改模板可以立刻看到效果
                .loader(loader).build();
    }

    public void render(ModelAndView mv, Writer writer) throws IOException, PebbleException {
        // 查找模板:
        PebbleTemplate template = this.engine.getTemplate(mv.view);
        // 渲染:
        template.evaluate(writer, mv.model);
    }
}

对以上内容做一个总结:

补充资料

重定向与转发

Redirect(重定向):服务器给浏览器回复一个302码同时附带新的url,浏览器接收到302响应后就会向新的url发送请求报文(浏览器发起了两次请求),如下:

Foward(转发):在Servlet内部进行转发,而不会发送到浏览器在对新的url进行请求,如下:

Session与Cookie

在Web应用程序中,经常需要保持用户的登录状态(不用频繁的输入账号密码),即我们需要有一个方式可以让Web应用能够识别用户的身份。


Servlet提供了一个基于Cookie的识别机制——Session。


Cookie是服务器给浏览器分配的唯一ID,并以Cookie的形式发送给浏览器,在后续的访问中,浏览器在请求报文中附带此Cookie,从而服务器可以识别其用户身份。


Session有什么缺点?


由于Session需要将用户数据保存在内存中,一方面,需要耗费一定的服务器资源;另一方面,当采用服务器集群时,必须保证同一个用户映射到固定的服务器上,这需要反向代理服务器的支持,比如采取Hash等方式将其转发至固定的服务器。


总之,由于Session的存在会使得服务器集群扩展较为艰难。


PS:Cookie只是请求报文中附加的信息而已,除了Cookie,还可以通过隐藏表单、URL末尾附加ID来追踪Session(只要有保证唯一性的标识即可)。


PS:所以平时的登录操作,可以看作是服务器给浏览器分配了一个Session ID;登出或者较长时间不操作,则从浏览器中移除这个ID,下次登录的时候就需要再登录了。

参考资料

廖老师的网站

相关文章
|
21小时前
|
Java 开发者
别再写错代码了!Java抽象类与接口的正确使用姿势!
【6月更文挑战第17天】在Java中,抽象类与接口助力构建灵活代码结构,提升效率。抽象类用于定义公共行为和属性,适合有层次的对象集合;接口包含抽象方法,实现多态,适合不相关对象集合。通过示例展示了如何创建抽象类和实现接口,强调理解其核心价值和使用场景的重要性,以提升代码质量和设计。正确使用抽象类与接口,让代码从平凡走向专业。
|
21小时前
|
算法 Java 开发者
震惊!你的Java代码还可以这样写:深入抽象类与接口的世界!
【6月更文挑战第17天】在Java编程中,抽象类与接口如双子星座般引领开发。通过抽象类`Shape`实现形状的共性,如颜色和抽象方法`getArea()`,展示多态性;接口`PerimeterCalculator`则在不修改`Shape`的基础上,允许`Circle`和`Rectangle`添加计算周长功能,体现扩展性。两者结合,助力构建优雅、灵活的程序结构。
|
1天前
|
算法 Java 程序员
Java多态:不只是代码,更是思想的碰撞!
【6月更文挑战第17天】Java的多态性展示了编程的哲学,通过抽象基类(如`AudioFile`、`Shape`、`Product`)和重写方法实现。案例中,音乐播放器利用多态统一处理不同音频格式,绘图软件优雅地绘制各种形状,电商系统灵活管理商品信息。多态简化代码,增强可扩展性,连接技术与设计,体现代码的灵活性和艺术性。
|
2天前
|
安全 Java
Java 面向对象之旅:封装——让代码更加“接地气”的秘诀。
【6月更文挑战第16天】**Java面向对象的封装秘籍:**将数据和操作打包成类,如`Student`和`Car`,隐藏内部详情,只通过`get/set`方法交互。封装提升代码清晰度,便于管理和保护安全性,就像整理工具箱,让每个功能一目了然,操作自如。
|
2天前
|
安全 Java 数据安全/隐私保护
一探 Java 封装究竟:为何它让代码更加“高大上”?
【6月更文挑战第16天】Java中的封装如城堡般迷人,它确保数据安全(如`UserInfo`类的私有属性),增强代码结构(如`Order`类的操作封装),并提升复用性与扩展性(如`Shape`类的抽象设计)。封装是打造高质量、易维护代码的关键,让代码既安全又高效。
|
3天前
|
前端开发 JavaScript Java
计算机Java项目|基于Web的足球青训俱乐部管理后台系统的设计与开发
计算机Java项目|基于Web的足球青训俱乐部管理后台系统的设计与开发
|
5天前
|
传感器 小程序 搜索推荐
(源码)java开发的一套(智慧校园系统源码、电子班牌、原生小程序开发)多端展示:web端、saas端、家长端、教师端
通过电子班牌设备和智慧校园数据平台的统一管理,在电子班牌上,班牌展示、学生上课刷卡考勤、考勤状况汇总展示,课表展示,考场管理,请假管理,成绩查询,考试优秀标兵展示、校园通知展示,班级文化各片展示等多种化展示。
29 0
(源码)java开发的一套(智慧校园系统源码、电子班牌、原生小程序开发)多端展示:web端、saas端、家长端、教师端
|
13天前
|
关系型数据库 MySQL PHP
PHP的生命周期:从诞生到现代Web开发
本文将探索PHP的发展历程,从其最初的设计目标到成为现代Web开发的核心语言。我们将深入了解PHP如何适应不断变化的技术环境,并保持其在开发者社区中的受欢迎程度。
|
4天前
|
Dart 前端开发 JavaScript
Flutter for Web:跨平台移动与Web开发的新篇章
Flutter for Web是Google的开源UI工具包Flutter的延伸,用于构建高性能、高保真的跨平台应用,包括Web。它基于Dart语言和Flutter的核心框架,利用Skia渲染引擎通过WebAssembly在Web上运行。开发流程包括安装SDK、创建项目、编写Dart代码和部署。性能优化涉及减少渲染开销、代码压缩等。与传统Web框架相比,Flutter for Web在开发效率和性能上有优势,但兼容性和生态系统尚待完善。
11 0
|
6天前
|
JavaScript 前端开发 UED
在Web开发中,拖放(Drag and Drop)和动画效果是提升用户体验的重要工具
【6月更文挑战第12天】本文介绍了如何使用JavaScript实现拖放功能和基本动画效果。拖放功能通过监听mousedown、mousemove和mouseup事件,计算并更新元素位置实现。动画效果则利用requestAnimationFrame函数创建平滑移动的视觉效果。示例代码包括HTML结构和对应的JavaScript实现。
22 1