问题

Spring,Django,Rails,Express这些框架技术的出现都是为了解决什么问题,现在这些框架都应用在哪些方面?

回答
好,咱们来聊聊这些框架,它们怎么出现的,解决了啥,现在又用在哪儿。咱尽量说得透彻点,少点儿硬邦邦的术语。

你想啊,以前做网站开发,特别是那种要处理用户数据、交互啥的,那真是件挺头疼的事儿。你得自己写一套逻辑来处理用户请求,怎么把数据存进数据库,怎么从数据库里取出来再展示给用户,怎么处理用户提交的表单,怎么保证安全……这就像盖房子,你得从地基开始,一砖一瓦都自己砌。这样一来,效率低不说,还容易出各种奇奇怪怪的问题,维护起来更是个噩梦。

所以,这些框架最根本的目的,就是为了——简化和标准化Web开发。

打个比方,框架就像是盖房子的预制件和施工指南。它给你准备好了很多已经建好的“模块”,比如处理用户请求的“墙壁模块”,连接数据库的“管道模块”,还有一套“施工规范”告诉你怎么把这些模块组装起来,怎么样的结构才最稳固安全。你就不需要从零开始从头搭建,只需要按照指南,把这些预制好的组件拼装起来,再根据自己的需求填些“装饰”进去就行了。这样一来,开发速度大大提升,代码也更规整,更容易被别人看懂和维护。

那它们具体解决了哪些问题呢?

重复性工作自动化: 想想每次写一个能处理用户登录的页面,都需要验证用户密码、查询数据库、生成session……这些都是很常见的,而且写得差不多。框架会把这些通用的功能封装好,你只需要调用一下就行了。
代码组织和结构: 没有框架的话,一个项目可能就是一个大杂烩,文件乱七八糟,不知道哪个文件干啥的。框架通常会规定一套代码组织方式(比如 MVC——模型视图控制器),把不同的功能模块分开,让项目结构更清晰,便于管理和多人协作。
安全性的提升: Web开发中最怕的就是各种安全漏洞,比如SQL注入、跨站脚本攻击(XSS)等等。框架通常会内置一些安全防护机制,帮你处理掉很多常见的安全问题,让你更专注于业务逻辑。
数据库交互的便捷性: 直接写SQL语句其实挺繁琐,而且不同数据库的SQL语法还有点不一样。框架提供了一层“对象关系映射”(ORM)的抽象,你可以用更面向对象的方式来操作数据库,比如创建一个“用户”对象,然后直接调用`.save()`方法就能存进数据库,ORM会帮你处理底层的SQL生成。
路由管理: 怎么把用户访问的URL(比如 `/users/123`)映射到具体处理的代码,框架会提供一套清晰的路由规则来管理。
模板渲染: 怎么把从数据库取到的数据和HTML结合起来生成最终的网页,框架会提供模板引擎来处理。

现在这些框架都应用在哪些方面呢?可以说,只要涉及到Web应用开发,几乎都能看到它们的身影。

我们一个个来说:

Spring (Java)

Spring 是个老牌劲旅了,在Java世界里地位超然。它最初是为了解决Java EE(就是以前做企业级Java应用的那套东西)过于复杂的问题而生的。你想,Java EE虽然功能强大,但配置复杂得要命,有时候光是把项目跑起来就要花好几天时间。

Spring 主要解决的问题:

庞杂的Java EE配置: Spring核心的“控制反转”(IoC)和“依赖注入”(DI)机制,让你可以通过配置来管理对象之间的依赖关系,而不是手动去创建和管理它们。这大大简化了对象的创建和生命周期管理。
模块化和解耦: Spring把它自身拆分成很多模块,比如Spring Core(核心)、Spring MVC(Web开发)、Spring Data(数据库访问)、Spring Security(安全)等等。你可以根据自己的需求选择需要哪些模块,而且这些模块之间耦合度很低,可以灵活组合。
声明式开发: 很多时候,你只需要告诉Spring“我希望这个方法是事务性的”、“我希望这个方法受安全检查保护”,而不需要写具体的事务管理代码或安全检查代码,Spring会帮你“在背后”做好。
Java生态的整合: Spring生态非常庞大,能很好地整合各种Java技术和第三方库,比如Hibernate(ORM框架)、MyBatis(数据库访问)、Quartz(定时任务)等等。

Spring 的应用场景:

几乎所有大型、中型的Java企业级应用都会用到Spring。

后端服务: 这是Spring最核心的战场。各种企业内部管理系统、金融交易系统、电商平台后端、物流系统等等,用的都是Spring(主要是Spring Boot,这是Spring的一个“子项目”,大大简化了Spring应用的配置和部署)。
微服务架构: Spring Cloud是Spring生态下的一个重要项目,专门用于构建微服务应用。它提供了一系列分布式系统开发工具,比如服务注册与发现(Eureka、Consul)、服务调用(Feign、RestTemplate)、熔断降级(Hystrix)、配置中心(Spring Cloud Config)等等。
大数据处理平台: 在很多大数据系统中,也会用到Spring来整合各种组件和管理后台服务。
Web应用和API开发: 通过Spring MVC或Spring WebFlux,可以非常方便地开发RESTful API和传统的Web应用。

Django (Python)

Django 是Python Web开发领域的一把好手。它的设计哲学是“Don't Repeat Yourself (DRY)”——不要重复造轮子,以及“Convention over Configuration”——约定优于配置。这意味着它提供了一整套现成的解决方案,并且鼓励你遵循它的约定俗成的开发模式,这样可以更快地搭建起一个功能完备的网站。

Django 主要解决的问题:

快速原型开发: Django提供了一个非常完整的技术栈,包括ORM、模板引擎、URL路由、表单处理、后台管理系统(admin)等。有了这些,你可以非常快速地搭建起一个具有数据库操作、用户管理和管理后台的网站。
安全性: Django内置了对CSRF(跨站请求伪造)、XSS(跨站脚本)、SQL注入等常见Web攻击的防护。
可维护性和可扩展性: 它强制采用MVC(虽然Django官方更喜欢MVT——ModelViewTemplate的说法,但本质上是类似的关注点分离模式)结构,将数据模型、业务逻辑和界面展示清晰地分开,让代码更容易理解和维护。
内置的强大后台管理: Django的admin站点是其一大亮点。你只需定义好你的数据模型,Django就能自动生成一个功能齐全的后台管理界面,让你能够方便地增删改查数据,这极大地提高了开发效率。

Django 的应用场景:

Django凭借其开发效率和完善的功能,在很多领域都有广泛应用。

内容管理系统 (CMS): 许多博客、新闻网站、企业官网都用Django来构建,因为它们通常需要管理大量的文本内容和用户。
社交网络应用: 像Instagram(最初是用Django开发的)这样的平台,需要处理大量的用户关系、图片上传和消息推送,Django的数据库和文件处理能力很适合这类需求。
电商平台: Django可以很好地处理商品信息、订单管理、用户账户等复杂业务逻辑。
API服务: Django REST Framework 是一个非常流行的库,可以帮助Django快速构建强大的RESTful API服务。
科学研究和数据可视化: 有些需要展示研究成果或数据分析结果的Web应用,也会选择Django。

Rails (Ruby)

Rails(Ruby on Rails)是Ruby语言的代表性Web框架,它的出现可以说是对Web开发的一种革新。Rails的口号是“Developer Happiness”——开发者快乐。它同样遵循“约定优于配置”,并且引入了“敏捷开发”的理念,让开发者能够专注于写业务逻辑,而不是被各种配置细节缠身。

Rails 主要解决的问题:

极高的开发效率: Rails的全家桶非常完整,它提供了强大的ORM(Active Record)、路由(Action Dispatch)、控制器(Action Controller)、视图(Action View)以及各种辅助工具。这使得开发者可以非常快速地构建一个功能完整的Web应用。
“约定优于配置”的极致体现: 只要你按照Rails的约定来命名文件和目录,以及组织代码,很多东西都能自动工作,几乎不需要你写额外的配置文件。比如,如果你有一个叫 `posts` 的模型,Rails会自动知道去数据库里找一个叫 `posts` 的表,并且为它生成增删改查的方法。
代码生成器(Scaffolding): Rails可以自动为你生成模型、视图、控制器等基础代码,让你快速开始一个功能模块。
内置的测试框架: Rails非常重视测试,它自带了测试工具,鼓励开发者编写测试,保证代码质量。
活跃的社区和丰富的Gem(插件): Ruby社区非常活跃,有大量的第三方库(叫做Gem),可以轻松地为你的应用添加各种功能,比如用户认证、文件上传、支付集成等等。

Rails 的应用场景:

Rails在初创公司和需要快速迭代的产品中非常受欢迎。

创业公司产品: 很多知名的科技公司,比如GitHub、Shopify、Airbnb、Basecamp(就是Rails的创造者Basecamp公司)的早期版本,都是用Rails开发的。它的快速迭代能力非常适合创业公司快速验证市场需求。
Web应用和SaaS服务: 任何需要构建Web界面的应用,无论是小型工具还是复杂的在线服务,Rails都能胜任。
原型开发: 如果你想快速做出一个产品原型来演示给投资者或用户看,Rails是非常好的选择。
API开发: 通过与Grape等库结合,Rails也能很好地用于开发API服务。

Express (Node.js)

Express 是Node.js环境下的一个最流行、最核心的Web应用框架。Node.js本身只是一个JavaScript运行时环境,它提供了在服务器端运行JavaScript的能力。Express则是在这个基础上,提供了一套非常轻量级、灵活、无状态的Web应用开发框架。

Express 主要解决的问题:

简化Node.js的HTTP模块: Node.js内置的HTTP模块功能强大,但直接使用会比较底层和繁琐。Express封装了HTTP模块,提供了一个更高级的API来处理请求和响应。
路由处理: Express提供了一个非常强大的路由系统,可以轻松地定义不同URL路径对应的处理函数。
中间件机制: 这是Express的核心亮点之一。中间件就像一系列“插件”,可以按顺序执行,用来处理请求的各个阶段,比如日志记录、身份验证、数据校验、错误处理等等。你可以自由组合这些中间件,非常灵活。
模板引擎支持: Express本身不强制使用特定的模板引擎,但可以轻松集成各种Node.js的模板引擎,如EJS、Pug、Handlebars等。
轻量级和可定制: Express非常“骨感”,它只提供了Web开发最核心的功能,剩下的你可以根据需求自由选择和添加库,这让它非常灵活,适合各种规模的项目。

Express 的应用场景:

Express是Node.js生态的基石,应用非常广泛。

RESTful API 开发: 这是Express最常见的用途。用Express可以非常快速地构建高性能的API服务,为前端应用提供数据支持。
单页应用 (SPA) 的后端: 很多使用React、Vue、Angular等前端框架开发的SPA,其后端API通常就是用Express构建的。
实时应用: 结合Socket.IO等库,Express非常适合构建需要实时通信的应用,比如聊天室、在线协作工具等。
微服务: Express的轻量级和灵活性使其成为构建微服务架构的理想选择。
简单的静态文件服务器: 也可以用Express来快速搭建一个提供静态文件(HTML、CSS、JavaScript、图片等)的服务器。
全栈JavaScript开发: 很多开发者喜欢用JavaScript来写前端和后端,Express是这个过程中必不可少的一环。

总结一下,这些框架的出现,都是为了让开发者能够更高效、更安全、更有条理地构建Web应用。它们通过提供标准化的工具和模式,大大降低了开发的门槛和复杂度。无论是哪种语言的框架,核心思想都是一样的:

封装重复性工作
提供清晰的结构和组织方式
内置安全防护
简化数据交互
提高开发效率和可维护性

它们的存在,极大地推动了Web技术的进步和应用的多样化。

希望这样讲能让你对它们有一个更清晰的认识,也希望能摆脱一些AI写作的痕迹,听起来更像咱们哥俩在聊天。

网友意见

user avatar

归纳题主的问题:

这个世界上有各种各样的框架,设计这些五花八门框架的初衷到底是什么?我们该不该学习框架,该如何学习使用这些框架?


回答题主的问题:

一、首先,到底什么是框架?

想要回答这个问题,我们要慢慢来。



首先从DRY原则开始说起

Don't Repeat Yourself,不要重复你的代码。

DRY原则的重要性怎么提都不过分,很多人说编程是种机械性的工作,而有很多程序员也自嘲为码农,意为编程成了一种没有技术含量的体力性工作。如果不想沦为这个境界,首先需要的就是将DRY原则融入你的血液,在今后的编码工作中加以运用。


1)最初级的DRY:语法级别


       System.out.println(1); System.out.println(2); …… System.out.println(10);      

我想只要学过基础语法,都会采用下面的形式。


       for (int i = 1; i <= 10; i++) {     System.out.println(i); }      

如果发现有任何人采用上面一种形式的编码形式,那么不用怀疑,他对于编程绝对还没有入门。

我们当然会选择省力的做法,这种做法不但省力,还会有利于我们后续修改或扩展这组代码,如:


       for (int i = 1; i <= 10; i++) {     System.out.println(i * 2 + 1); }      

我们进行这样的修改,只需要修改一处,而上面的形式却需要修改10处,当然会更麻烦且更容易出错,所以请记住能不重复就不重复。


2)进阶的DRY原则:方法级别

当我们经常写一些重复性代码时,我们就要注意看能否将其抽取出来成为一个方法,如:


       try {     Thread.sleep(1000); } catch (InterruptedException e) {     e.printStackTrace(); }      

让我们将其抽取到一个方法 threadSleep() 中,这样我们只需要调用 threadSleep() 就可以实现原来的功能,不但所需敲击的代码更少,而且代码看起来更加清楚明白。而为了增加这个方法的复用性,我们还可以将其中固定的数字抽取成为参数,如:


       private static void threadSleep(int millis) {     try {         Thread.sleep(millis);     } catch (InterruptedException e) {         e.printStackTrace();     } }      

这样我们就可以利用这个方法实现不同时间的sleep了。要注意提高代码的复用性也是实践DRY原则的一个重要方法,在后面我们也可以看到框架为了提高所谓的灵活性进行的一些设计,如在适当的位置增加扩展点。


3)继续进阶的DRY原则:类型级别

现在我们看一个类


       public class Person {     private String name;     private int age;     // Setter & Getter ... }      

我们新建一些Person类实例,并进行一些操作:


       Person person = new Person(); person.setName("jack"); person.setAge(18); Person person2 = new Person(); person2.setName("rose"); person2.setAge(17); ..... System.out.printf("Name: %s, Age:%d
", person.getName(), person.getAge()); System.out.printf("Name: %s, Age:%d
", person2.getName(), person2.getAge()); .....      

观察这些代码,其实有很大的DRY改造空间,首先可以添加一个构造方法


       public Person(String name, int age) {     this.name = name;     this.age = age; }      

其次,可以添加一个toString()方法


       public String toString() {     return String.format("Name: %s, Age: %d", name, age); }      

这样的话,上面的代码就可以改成下面的形式。


       Person person = new Person("jack", 18); Person person2 = new Person("rose", 17); ...... System.out.println(person.toString()); System.out.println(person2.toString()); ......      

4)继续继续进阶的DRY原则:多个类组合级别

上面的代码我们其实还是有改善空间,就是利用容器类


       List<Person> list = new ArrayList<>(); list.add(new Person("jack", 18)); list.add(new Person("rose", 17)); ...... list.forEach(p -> System.out.println(p));      

这里利用JDK8的Stream API以及Lambda表达式输出,其实可以进一步简化为


       list.forEach(System.out::println);      

这里我们可以看到,基本上我们写代码只写有变化的代码,而尽量不写机械性重复性的代码,其实后面我们就会知道,这就叫专注于业务逻辑,所谓业务逻辑就是你这个项目中,与别的项目都不一样的地方,必须由你亲自去编写实现的部分。

其实容器类很大程度上也是为了帮助我们编写代码而被设计出来的,首先让我们不必为每一个对象起名字(省去了person,person2,...等变量),然后又为批量操作提供了可能性。像是这样一系列有用的类组合起来可以称之为类库。常用的类库有Commons-Lang包等,为我们提供了一大批实用方法,我之所以提到类库,也是因为框架其实也是一种特殊的类库,但是却与一般的类库有着本质的不同。



设计模式,更高层级的DRY应用

上面我讲到了DRY原则的几个层次,一般情况下大家也早就这样使用了,属于入门之后很容易自己就想到得一些层次。但是设计模式不一样,设计模式是经过长时间编码之后,经过系统性的总结所提出的针对某一类问题的最佳解决方案,又称之为最佳实践。

而在小规模的编码工作中,其实并不需要什么设计模式,只有大型程序才有设计模式发挥的空间,所以我们需要借助一些特定领域有足够规模的问题来了解一下设计模式存在的必要性。


1)连接数据库,进行一些操作,并安全释放数据库连接。


       public static boolean updatePassword(String username, String password, String newpassword) {     Connection conn = null;     PreparedStatement stmt = null;     ResultSet rs = null;     boolean success = false;     try {         conn = beginTransaction();         stmt = conn.prepareStatement("select id, password from user where username = ?");         stmt.setString(1, username);         rs = stmt.executeQuery();         if (rs.next()) {             if (rs.getString("password").equals(password)) {                 PreparedStatement stmt2 = null;                 try {                     stmt2 = conn.prepareStatement("update user set password = ? where id = ?");                     stmt2.setString(1, newpassword);                     stmt2.setLong(2, rs.getLong("id"));                     success = stmt2.executeUpdate() > 0;                 } finally {                     safeClose(stmt2);                 }             }         }         commitTransaction(conn);         return success;     } catch (SQLException e) {         rollbackTransaction(conn);         throw new RuntimeException(e);     } finally {         safeClose(rs);         safeClose(stmt);         safeClose(conn);     } }     

上面是一个简单的数据库事务,虽然只有一个查询和一个更新,但是想要将其继续简化却并不容易,虽然其中有关于业务逻辑的部分只是少量几行代码,但是初始化,异常,提交,回滚操作让我们很难抽取出一个合适的方法来。虽然我们已经抽取出了 begin,commit,rollback,safeClose等方法,但是仍嫌繁琐。

我们发现之所以我们难以抽取方法,主要是因为流程,因为里面牵扯到流程控制,而流程控制一般是由我们程序员来控制的,所以也就必然需要我们手动编码来完成。难道真的就不能继续简化了吗?这就是需要设计模式的时候了。


2)应用设计模式「模板方法模式」


       public static boolean updatePassword(String username, String password, String newpassword) {     return connection(conn -> statement(conn, "select id, password from user where username = ?", stmt -> {         stmt.setString(1, username);         return resultSet(stmt, rs -> {             if (rs.next()) {                 if (rs.getString("password").equals(password)) {                     long id = rs.getLong("id");                     return statement(conn, "update user set password = ? where id = ?", stmt2 -> {                         stmt2.setString(1, newpassword);                         stmt2.setLong(2, id);                         return stmt2.executeUpdate() == 1;                     });                 }             }             return false;         });     })); }      

可以看到,所有的conn,stmt,rs的开启和关闭,事务的提交和回滚都不用自己手动编写代码进行操作了,之所以可以达到这个效果,就是因为使用了模板方法设计模式,核心就是通过回调方法传递想对资源进行的操作,然后将控制权交给另一个方法,让这个方法掌握流程控制,然后适当的时候回调我们的代码(也就是我们自己写的业务逻辑相关的代码)。

这是需要额外写的几个方法


       public interface ConnectionCallback<T> {     T doConnection(Connection conn) throws SQLException; } public interface StatementCallback<T> {     T doStatement(PreparedStatement stmt) throws SQLException; } public interface ResultSetCallback<T> {     T doResultSet(ResultSet rs) throws SQLException; } public static <T> T connection(ConnectionCallback<T> callback) {     Connection conn = null;     T result = null;     try {         conn = beginTransaction();         result = callback.doConnection(conn);         commitTransaction(conn);     } catch (SQLException e) {         rollbackTransaction(conn);         throw new RuntimeException(e);     } finally {         safeClose(conn);     }     return result; } public static <T> T statement(Connection conn, String sql, StatementCallback<T> callback) throws SQLException {     PreparedStatement stmt = null;     T result = null;     try {         stmt = conn.prepareStatement(sql);         result = callback.doStatement(stmt);     } finally {         safeClose(stmt);     }     return result; } public static <T> T resultSet(PreparedStatement stmt, ResultSetCallback<T> callback) throws SQLException {     ResultSet rs = null;     T result = null;     try {         rs = stmt.executeQuery();         result = callback.doResultSet(rs);     } finally {         safeClose(rs);     }     return result; }      

你们可能会疑惑,这些代码加上我们写的业务逻辑的代码,比原来的代码还要长,有什么必要使用这个设计模式。这正是我前面已经指出的一个问题,那就是要你的程序规模足够大才有必要应用设计模式,试想如果你有上百个乃至上千个数据库操作方法需要写,那么是不是写这几个额外的方法,就不算什么了呢。

其实这正是DRY原则在更高层次上的应用,即结合设计模式来达到更高层次的代码复用效果,进而应用DRY原则。而想要在这个层次继续向上攀升,那就必须是结合众多设计模式以及一些高层架构设计,能够帮助我们实现这一目的的就是框架。


3)框架,是设计模式的集大成者,是DRY原则的最高应用

先让我们来看一下,使用框架会是什么样的一种体验?

这里以Hibernate + Spring声明式事务为例


       @Transactional public boolean updatePassword(String username, String password, String newpassword) {     User user = (User) session().createQuery("from User where username = :username")             .setString("username", username)             .uniqueResult();     if (user != null && user.getPassword().equals(password)) {         user.setPassword(newpassword);         return true;     }     return false; }      

可以发现令人惊讶的简洁,而且代码逻辑异常清晰,完全不需要考虑conn,stmt,rs等资源的释放,以及事务的提交和回滚,但是这些事情其实框架已经默默的帮我们做到了。这才叫真正的专注于业务逻辑,尽最大可能的只写与业务逻辑有关的代码。

当然这些框架的效果虽然神奇,其实只要细细探究其内部原理,是完全可以理解并掌握的。


二、那么问题就来了,框架到底是什么?要不要学,怎么学?

上面我说过了,框架其实就是一个或一组特殊的类库,特殊在什么地方?特殊在控制权转移!

框架与一般类库不同的地方是,我们调用类库,而框架调用我们。也就是说框架掌握整个程序的控制权,我们必须一定程度上把程序流程的控制权交给框架,这样框架才能更好的帮助我们。

下面以JavaWeb开发为例再进行一些说明,并顺便简单介绍一下JavaWeb的一些脉络。



静态网页时代

本来网站都是一个个静态HTML组成的,或许这些网页还是用Dreamweaver写的,但是这样的静态页面显然不能满足我们,很快我们就迎来了动态网页的时代。



Servlet时代

如果熟悉HTTP协议的话,我们就知道其实访问网页的过程不过是一次TCP连接罢了。浏览器发起TCP连接到服务器,服务器接受请求,然后返回HTML代码作为响应。那么我们完全可以等到接受到请求之后,再动态生成HTML代码返回给客户端。

Servlet就是这么做的,其主要代码不过是利用out.write()一点一点的输出HTML代码罢了。当然我们可以在其中掺杂一点动态的东西,如返回当前的时间。


       out.write("<!DOCTYPE html>
"); out.write("<html>
"); out.write("<head>
"); out.write("<title>Index Page</title>
"); out.write("</head>
"); out.write("<body>
"); out.write("Hello, " + new Date() + "
"); out.write("</body>
"); out.write("</html>
");      

③ JSP包打天下的时代

纯粹的Servlet很是丑陋,给前端程序员理解和修改这样的代码带来了很多困难。因此JSP技术被发明了出来,原理也不复杂,就是不直接写Servlet,而是先写好JSP文件,再由服务器将JSP文件编译成Servlet。而JSP中是以常见的HTML标签为主,这样前端程序员就能方便的修改这些代码了。


       <!DOCTYPE html> <html> <head> <title>Index Page</title> </head> <body> Hello, <%=new Date()%> </body> </html>      

PS:由只使用 Servlet到使用JSP,虽然是一个简单的变化,但这迎合了前后端专业分工的大趋势,让前段人员只需要懂得HTML/CSS/JavaScrip代码就可以开始工作,而不需要学习Servlet那枯燥无味的用法,因此借着JSP技术的东风,JavaWeb技术迅速的扩展开来了。


④ Servlet + JSP 时代

随着JSP技术的发展,用它写成的网站也越来越大,业务逻辑也越来越复杂。开发人员渐渐发现整个网站渐渐的再次变成了一团乱麻,不仅仅是JSP中夹杂了大量的Java代码,页面之间的耦合关系也越来越紧密。

即便是要修改一个简单的按钮文本,或者是引入一段静态的内容,也需要打开越来越庞大的JSP页面,艰难到找到需要修改的部分,有时还不仅仅是一处,这种修改是有很大的风险的,完全有可能引入新的错误。

这时候开发者渐渐意识到,仅仅使用JSP是不行的,JSP承担了太多的责任。这时人们又想起了Servlet,Servlet中主要使用Java代码,处理业务逻辑非常轻松。如果JSP只使用HTML代码,而将业务逻辑的代码转移到Servlet中,就可以大大的减轻JSP的负担,并且让前后端分工更加明确。



MVC模式时代


Servlet + JSP模式的基础上,Java阵营进一步发展出了一种适合JavaWeb应用的设计模式,MVC设计模式,即将程序分为显示层(Viewer),控制层(Controller),模型层(Model)。如下图所示:

一次典型的访问是这样的流程:

1. 用户输入网址或点击链接或提交表单,浏览器发起请求

2. --> 通过互联网,通过HTTP协议 -->

3. Tomcat接受到HTTP请求,生成HttpServletRequest对象,根据Web.xml的配置,调用开发者编写的HttpServlet,HttpServlet根据请求内容,调用JavaBean获取数据,JavaBean从数据库获取数据,返回HttpServlet,HttpServlet将数据转发给JSP,JSP负责将数据渲染为HTML,由Tomcat负责将HTML转化为HTTP响应,返回客户端。

4. --> 通过互联网,通过HTTP协议 -->

5. 客户端浏览器接收到HTTP响应,浏览器将HTML渲染为页面,并运行其中可能存在的JavaScript进一步调整界面。


整个流程必须由开发者精确设计才能运作流畅,其中客户端HTML和JavaScript属于前端设计,服务器运行的其他内容属于后端设计。虽然符合J2EE规范的Tomcat等应用服务器已经帮我们实现了最复杂的一块,即HTTP协议部分,还给我们提供了JSP这个模板引擎,以及自定义标签等手段。但是在控制层,在模型层,J2EE能给我们的帮助少之甚少。


就拿用户提交一个表单为例,而我们在Servlet中获取参数为例,虽然不用我们解析HTTP报文,应该已经是要谢天谢地了,但是我们要做的事情仍然很多,分析一下:


1. 客户端传过来的数据全是文本,而我们需要的是Java对象。

2. 凡是文本就有编码问题,而这需要前后端配合解决。

3. 客户端的输入是不可信的,我们必须校验参数的合法性。

4. 我们还必须将校验结果反馈给客户,并且最好不要让客户全部重新输入。

5. 我们往往不是只有一个参数需要,而是有几个甚至更多参数,要妥善的处理各种情况组合。


这些事情几乎全部都需要我们手动编码来完成,几乎每一个 Servlet 都充斥着这样的代码,设置编码,获取参数,校验参数,校验通不过返回错误信息,校验通过则进行业务处理。而更重要的是,获取参数仅仅是整个流程中的一小步,我们的Servlet中存在着大量的重复性,机械性代码,而处理业务逻辑的代码可能只有一两行。



JavaWeb框架

既然存在着大量的重复,我们当然不能忍,必须请出DRY大法。显然JavaWeb应用是一个规模庞大,流程复杂的应用,我们正需要JavaWeb框架的帮助。以Struts2框架为例,他能给我们什么帮助呢?


1. 在控制层,由Struts2的核心控制器接管控制权,将本来在Web.xml进行配置的一些工作,转移到自定义的struts.xml文件中,这个文件的配置形式更友好。

2. Struts2封装了Serlvet Api,使用POJO对象作为控制器(Action),大量使用反射,不要求继承特定类,有利于复用及单元测试。提供ActionSupport类,结合struts2标签,能很方面实现的校验信息的收集及反馈。

3. 提供国际化支持,在显示层有国际化相关的标签,在控制层由国际化相关的API。提供基于配置的校验及JS生成技术。智能化的参数类型转换,支持自定义转换器。提供Action拦截器,方便实现AOP模式。

4. 提供了基于OGNL表达式的数据共享模式,前后端数据交流更简单,提供了Struts2标签库,简单好用,支持多种模板,如FreeMarker,支持各种插件,如JSON,支持整合多种框架,如Spring。总之一句话,能在各方各面给我们强大的帮助。



所以当然要学框架,要用框架,那么要怎么学?

1. 用框架要知其然,还要知其所以然,要大体明白框架实现一个功能特性的原理,不能只是会用,只是觉得很神奇就可以了。就拿前面的Hibernate + Spring声明式事务为例,要弄明白框架这部分是怎么实现的。

2. 首先要夯实你的语言基础,如JavaSE基础,语法掌握,用法掌握,有些同学语法还不熟练就开始学框架,等于地基没打就起高楼,你可能会快一步,但是迟早要遇到瓶颈,甚至摔跟头。

3. 那么何时开始学习框架?我不建议新手一开始就直接使用框架。

就好像一开始学习编程语言,大家都不推荐直接使用IDE,一定要用命令行自己编译运行几个文件之后,了解清楚了之后才可以使用IDE,要不然对于底层原理不了解,遇到问题没法自己手动排查。

4. 使用框架也是一样,如果不是自己写多了重复性的代码,就很难理解框架为什么要这么设计。如果不尝试几种不同的实现,就很难理解框架为了灵活性而做出的设计和扩展点。如果不写几十个权限检查语句,就很难理解AOP到底有什么好处。

5. 框架这么好,我该全部使用框架吗?首先只有在规模以上的程序中,才有应用框架的必要,一个简单的程序没必要使用框架,当然如果你很熟练,使用也无所谓。

6. 要学习一下框架的核心源代码,要为扩展框架做好准备,因为虽然框架基本上还算灵活,但是面对错综复杂的业务需求,永远不可能面面俱到,而你不了解框架的话,可能会给你实现业务需求造成麻烦。这也是有些人坚持使用Servlet+JSP原生开发,而不是用框架的理由。

7. 只要程序大了,归根究底还是要使用框架的,不是用别人写好的,就是自己写一套。这里我不建议自己写,不要重复造轮子,总有专业造轮子的。你草草写就的往往不如别人已经千锤百炼的代码。除非你是为了学习与研究的目的,自己写,那就是一件很好的事情。

类似的话题

  • 回答
    好,咱们来聊聊这些框架,它们怎么出现的,解决了啥,现在又用在哪儿。咱尽量说得透彻点,少点儿硬邦邦的术语。你想啊,以前做网站开发,特别是那种要处理用户数据、交互啥的,那真是件挺头疼的事儿。你得自己写一套逻辑来处理用户请求,怎么把数据存进数据库,怎么从数据库里取出来再展示给用户,怎么处理用户提交的表单,.............
  • 回答
    Spring Boot 用起来是否“难”?这个问题其实挺微妙的。在我看来,Spring Boot 本身一点也不难用,甚至可以说非常友好、易上手。它的难点,更多时候在于我们过去接触过的开发模式,或者对它“应该如何工作”的固有认知,以及它所处的生态系统中可能存在的其他复杂因素。咱们掰开了揉碎了聊聊,为啥.............
  • 回答
    Spring:Java技术的巅峰,抑或只是一个阶段?在Java技术领域,Spring框架的地位举足轻重,几乎成为了现代Java开发的代名词。它以其强大的依赖注入、面向切面编程、事务管理等特性,极大地简化了Java EE的开发,让开发者能够更专注于业务逻辑的实现,而非繁琐的配置和基础设施的搭建。那么,.............
  • 回答
    美国地名里带“泉”(Spring)的,确实不少,这背后可不是什么偶然,而是深深烙印着这片土地早期开发和居民生活方式的历史。咱们一块儿掰开了揉碎了聊聊,看看这“泉”字到底承载了啥。首先,咱们得回到美国这片土地是怎么被开发的。早期的欧洲移民,尤其是英国人,他们来到北美大陆,面对的首先是陌生的自然环境。他.............
  • 回答
    好的,咱们不整那些花里胡哨的条条框框,就来聊聊C .NET Core和Java Spring这两大阵营,到底哪个更适合你,怎么选。这事儿得掰开了揉碎了说。首先,你得明白,这俩都不是什么新晋的小鲜肉了,都是经历过市场锤炼的老牌选手,都有各自的坚实用户群体和成熟的生态。选择哪个,很大程度上取决于你当前的.............
  • 回答
    .......
  • 回答
    .......
  • 回答
    中国的程序员并非“开发不出来”像Spring那样优秀的框架,而是我们所处的开发环境、行业生态、技术积累以及发展路径与Spring诞生的土壤存在显著差异。要深入探讨这一点,我们需要从多个维度来审视。首先,历史沉淀与技术积累的深度是绕不开的议题。Spring的诞生并非一蹴而就,它是在Java企业级开发经.............
  • 回答
    奶粉还是鲜牛奶?这是一个很多家长在面对孩子口粮时,都会纠结的问题。尤其是当市面上出现了像Spring Sheep绵羊奶粉这样比较小众但口碑不错的选择时,这种纠结就更加普遍了。咱们今天就来好好聊聊,到底选奶粉还是鲜奶,以及这个Spring Sheep绵羊奶粉,值不值得咱们掏钱。奶粉 vs. 鲜牛奶:各.............
  • 回答
    您提到的“String landscape的10^500种结果”是一个非常吸引人的概念,尤其是在理论物理和宇宙学领域。不过,我们得先澄清一下,这个说法本身是存在一些误解或者说是不够精确的,它并非指“String landscape”本身有10^500种“结果”,而是与弦理论(String Theor.............
  • 回答
    将字符串用作枚举(Enum)通常来说会带来一些性能上的损失,尤其是在需要频繁进行比较、查找或转换的场景下。下面我将从几个方面详细说明这个问题,尽量用更贴近实际开发经验的语言来阐述,避免 AI 的生硬感。首先,咱们得明白什么叫“把 string 当 enum 用”。通常情况下,枚举(Enum)是一种特.............
  • 回答
    为什么很多程序员对String的执行效率耿耿于怀? 深度解析程序员对 String 的执行效率之所以“耿耿于怀”,并非空穴来风,而是源于 String 在很多编程语言中,特别是 Java、C 等面向对象语言中,其 不可变性(Immutability) 以及由此带来的一系列设计和实现上的考量。这种“耿.............
  • 回答
    在 C++ 中,将 `std::string` 类型转换为 `int` 类型有几种常见且强大的方法。理解它们的原理和适用场景对于编写健壮的代码至关重要。下面我将详细介绍几种常用的方法,并分析它们的优缺点: 方法一:使用 `std::stoi` (C++11 及以后版本)这是 最推荐 的方法,因为它提.............
  • 回答
    在 C++ 标准库的 `std::string` 类设计之初,确实没有提供一个直接的 `split` 函数。这与其他一些高级语言(如 Python、Java)中普遍存在的 `split` 方法有所不同。要理解为什么会这样,我们需要深入探究 C++ 的设计哲学、标准库的演进过程以及当时的开发环境和需求.............
  • 回答
    在设计 API 请求参数时,是选择字符串(String)还是数字(Number)作为参数类型,这确实是一个值得深入探讨的问题。两者各有优劣,并没有绝对的“更好”,关键在于理解它们的特性以及在不同场景下的适用性。下面我们就来详细分析一下。 字符串(String)作为请求参数当我们谈论字符串作为请求参数.............
  • 回答
    JDK 9 对 `String` 底层实现的修改,从 `char[]` 转向 `byte[]`,这背后是一个复杂的技术演进和优化考量,旨在提升 Java 在处理文本数据时的效率和内存占用。这不是一个突兀的改变,而是基于对字符串本质的重新认识以及对现代应用场景的适应。为什么会有这个改变? 根源在于 ".............
  • 回答
    Java 中 `String` 的设计,特别是关于 `==` 和 `.equals()` 的区别,是初学者常常会遇到的一个“坑”,也是 Java 语言设计者们深思熟虑的结果。要理解为什么不能直接用 `==` 比较 `String` 的值,我们需要深入探讨 Java 中对象的内存模型以及 `Strin.............
  • 回答
    在 C++ 中,`std::string` 声明在循环内部还是外部,这并非一个简单的“总是这样做”的问题,而是涉及到效率、内存管理、以及代码意图的考量。这就像是在问,你是在路边买了个三明治边走边吃,还是回家坐下来慢慢享用。两者都有各自的场景和理由。让我们深入剖析一下这两种做法: 声明在循环外部当我们.............
  • 回答
    .......
  • 回答
    .......

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有