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 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
24天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
39 3
|
2月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
218 3
|
8天前
|
并行计算 算法 Java
Java中的Fork/Join框架详解
Fork/Join框架是Java并行计算的强大工具,尤其适用于需要将任务分解为子任务的场景。通过正确使用Fork/Join框架,可以显著提升应用程序的性能和响应速度。在实际应用中,应结合具体需求选择合适的任务拆分策略,以最大化并行计算的效率。
36 23
|
2月前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
138 62
|
24天前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
58 4
|
2月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
53 2
|
2月前
|
中间件 Go API
Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架
本文概述了Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架。
176 1