Java Web 入门导论 (未完待续...)
适合新手入门Java Web技术栈,减少茫然和弯路。知其然,更知其所以然....
持续更新 j-days
简言之:2条线,不迷茫
- 线索1 -- 沿着数据的流向,思考每个环节存在的需求和问题,以及java如何解决,如java实现http协议接受客户端的请求是java web服务的前提
- 线索2 -- 终极目标:业务至上:【解放web开发者(从低级、重复劳动),专注于业务】,即旧技术/方案存在的问题,以及新技术/方案如何解决和优化,不断将开发者从繁琐、重复的底层劳动中解放出来,从而更多的关注业务 -- Spring及Boot成为java事实规范的主要原因
以下按照数据流的方向、各主要环节概论
1、【起点(服务的前提)】 -- 问题:如何建立通信链路,使用哪种"官方语言",即网络协议
- IP协议蔟(先 服务定位):根据IP+port确定网络中唯一的服务端(服务器+监听端口的服务程序);
- TCP协议族(再 建立连接):通过握手建立服务端和客户端间稳定的单工/双工连接;
- 核心目标 - 通信:
- Client-Server 丰富通信 - HTTP协议:C-S架构关注丰富的表现层(json、xml、甚至是文件)(超文本),构建多样的WWW网络服务和站点;
- Server-Server 高效通信 - RPC协议:服务端之间通信往往是业务数据的交互,特征是:频率高、数据小,更关注载荷(体/头 比例)效率和性能,不关注丰富度,典型如dubbo、thrift
2、【I/O(服务进程(用户态) <---> 系统(硬件)级 间数据流转)】
客户端通过上述链路、协议将数据输送到服务端的网卡缓冲区以后,关注操作系统(OS)如何将网卡接收到的请求数据(读缓冲区)(内核态)传递给服务进程的内存空间(用户(即进程)态)【请求读取】,反之【响应输出】
核心:关注操作系统IO机制的升级路线(语言层(无论是Java、Python、NodeJS还是Go...)只是封装了系统调用)
-
BIO阻塞IO(❌Web服务基本废弃):
-
read/write
系统调用(用户态 <-> 内核态) 同步阻塞,每个线程指定处理一个连接(1000连接=1000线程,线程栈(1M左右)*1000,内存直接溢出OOM,且线程切换(上下文的保存和恢复)的成本成为主要矛盾⚠️)
-
InputStream/OutputStream
java sdk的输入/输出抽象接口,可以看做对read/write的包装和IO的抽象,提供对象化的IO Api
-
-
NIO/2 非阻塞(主流✅):
-
selector/epoll
多路复用,代替旧版阻塞式read/write等待,单线程(Acceptor线程)可以监听万级连接上的IO事件,并分发给处理(工作者)线程池。(对应于【1主-多从】Reactor响应式线程模型,可参阅Netty线程模型相关)
-
Buffer+Channel
代替直接InputStream/OutputStream,提升性能,增强功能
- 通过Buffer将随机读写转化为批量顺序读写 【提升性能】
- 通过Channel提供读写双工的增强API,读写只发生在Channel+Buffer上,不再直接系统调用(read/write),不阻塞(不用内核态 <-> 用户态状态)【增强功能】
-
-
AIO异步IO(非主流 ⚠️ 系统支持不成熟):
依赖于操作系统的async_io机制
3、服务(核心): 根据请求执行一系列业务逻辑后返回处理结果(响应输出),
关注2大责任链
-
1、servlet规范与容器(Tomcat为例)【传统⚠️】:
servlet规范是Java对于服务的抽象(Java接口编程的典型),制定了标准服务端程序的服务流程、功能组件,和容器规范(如果没有规范,便无法成为行业标准,也得不到大公司/大牛的加入和支持,因此接口思想是Java最重要的编程思想,重抽象(接口/规范设计),多实现(择优而胜))
- 2大组件:
- Filter - 组成前置和后置的责任链,用于执行服务前置的预处理(如权限验证)和服务后置的后处理(如服务耗时统计)
- Servlet - 真正的服务组件,核心方法service用于扩展、根据不同的请求,分发执行不同的业务逻辑
-
容器(常见Tomcat):
Servlet容器规范的实现,负责Servlet的实例化和生命周期管理,并提供连接管理、协议解析(如http)等基础支持,让开发者只关注扩展Servlet、实现自己的业务,并不需重复的底层劳动
-
问题: Servlet#service 太过底层和原始,参数的解析、验证、路由的分发以及响应的包装等需要开发者手动处理,繁重而冗余⚠️
- 2大组件:
-
2、Spring MVC责任链【主流✅】:
鉴于传统Servlet机制的问题,以核心DispathServlet全权代理所有请求,解决了参数解析、绑定、验证和自动路由、响应封装等大部分底层、重复劳动,并通过注解(声明式编程)和动态代理机制(动态编程)让任何类和方法都具备"服务Service"的能力,而不再局限于继承重量级的类Servle,开发者真正实现了【业务至上】
- 2大组件:
- Interceptor:类似于Servlet中Filter,前置/后置责任链
- Controller:服务的集合,内部的每个方法都可以声明为服务(HandlerMethod,类似于Servlet#service),但只关心当前的请求参数和业务逻辑
-
分层隔离原则:
即虽然Servlet规范中的Filter/Servlet和Spring MVC中Interceptor/Controller具备相似的能力,但业务层不应轻易跨过Spring MVC去使用Servlet机制,会打破分层隔离的原则,引起代码逻辑的混乱,且没有额外的好处。
- 2大组件:
4、Spring/Boot(事实规范 -- 助力Java Web起飞)
提升Java开发的效率和逼格,Java Web编程的事实规范
-
Spring(实例管理工厂/容器)
改变传统的手动创建游离实例、管理生命周期、依赖层层传递(尤其是嵌套较深)等问题,将组件实例的创建、管理、相互依赖委托给Spring工厂(即IoC,控制/依赖反转),降低了手动管理的重复劳动,更基于容器提供了AOP等高级编程的支持(集中的好处是可以管理,并提供高级功能),以此进一步提高自动化水平(如boot的自动化配置,事务的自动化代理)
-
反转(也可以理解为委托(给Spring))
可以理解为根据依赖关系(先)Spring自动创建被依赖的实例、并建立依赖关系,而常规(正常、手动)情况下,是先创建被依赖的实例,再手动设置依赖关系(后)
-
产生原因 -- 2个基本事实:
- 1、功能性组件多是无状态的单例模式(无状态服务原则),无需手动冗余创建、管理(需要自动化、透明化);
- 2、组件间需要相互组合才能实现更复杂的业务功能(依赖,组合模式);
-
-
Spring Boot(开发脚手架/启动器):
基于【约定大于配置】的思想,在Spring容器支持条件化配置的基础上,对常用的web组件进行约定的配置(各种starter包,如spring mvc starter,spring data starter...so on)
- 基本事实:在使用某个组件时(如Tomcat容器),大部分的场景下(90%)都会使用默认或通用的配置,每次都进行重复的配置或拷贝是不明智的,因此需要进一步解放出来 --> 【专注业务,别做低级劳动】
-
总论:
通过Sping以及Boot,让Java开发具备了脚本语言开发(脚手架)开箱即用的敏捷开发能力,解决了大部分的低级劳动,进一步解放开发者,如只要引入starter-web依赖,tomcat、mvc等无需任何额外配置,开箱即跑,6到飞起(做过传统java web开发的应该对web.xml重复、繁重的配置深有体会)
5、Java并发编程(前后端开发的主要区别)
上千/万用户的并发请求反应到服务器便是并发的处理请求(串行处理是不可取的,IO很慢,第50-1000等待的用户就跑了),就必须保证并发的安全性,因此并发安全和编程是Java web开发的重要(首要)命题。
- 并发手段:多线程与线程池(主流,但非唯一)
- 并发安全
- 并发安全的手段:synchronized,lock,cas、volatile等;
- 并发安全的对象/字段:AtomicInteger原子系列
- 并发安全的集合(容器):ConcurrentHashMap系列
6、业务模型和存储
业务的本质是业务模型和服务设计,并最终持久化业务,以形成业务数据。3大存储,不同侧重
-
MySQL:
事务性性存储是大部分业务(尤其是核心业务)必不可少的,关注(业务)模型间的关联和事务
-
基本事实:
一个业务要么成功,要么失败,不应该处于半成功、半失败的错误中间态 ==> 事务(业务)一致性
备注: 如果db层没有提供这个机制,就要业务侧去额外实现和保证(无疑是重复、冗余而低级的劳动)
-
-
Redis
K-V键值存储,关注查询瓶颈的热数据,提升服务体验(非必须,但更友好)
-
ES
侧重关键词的检索,弥补MySQL文本检索的劣势,通过倒排索引(以关键字搜索文档,而非(遍历、逐个)文档搜索是否包含关键字),加速文本搜索
..................... 未完,待续 ...........................................
最后
- 项目实践 * 时间,不断深化Java及各技术栈,将理论转化为能力