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,下次登录的时候就需要再登录了。

参考资料

廖老师的网站

相关文章
|
2月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
365 6
|
2月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
258 115
|
2月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
184 98
|
2月前
|
Java 编译器 API
java最新版和java8的区别,用代码展示
java最新版和java8的区别,用代码展示
272 43
|
2月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
250 4
|
6月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
6月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。
|
6月前
|
JSON 开发框架 自然语言处理
【HarmonyOS Next之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(三)
本文主要介绍了应用开发中的三大核心内容:生命周期管理、资源限定与访问以及多语言支持。在生命周期部分,详细说明了应用和页面的生命周期函数及其触发时机,帮助开发者更好地掌控应用状态变化。资源限定与访问章节,则聚焦于资源限定词的定义、命名规则及匹配逻辑,并阐述了如何通过 `$r` 引用 JS 模块内的资源。最后,多语言支持部分讲解了如何通过 JSON 文件定义多语言资源,使用 `$t` 和 `$tc` 方法实现简单格式化与单复数格式化,为全球化应用提供便利。
260 104
|
6月前
|
JavaScript 前端开发 API
【HarmonyOS Next之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(二)
本文介绍了HarmonyOS应用开发中的HML、CSS和JS语法。HML作为标记语言,支持数据绑定、事件处理、列表渲染等功能;CSS用于样式定义,涵盖尺寸单位、样式导入、选择器及伪类等特性;JS实现业务逻辑,包括ES6语法支持、对象属性、数据方法及事件处理。通过具体代码示例,详细解析了页面构建与交互的实现方式,为开发者提供全面的技术指导。
278 104