firewalld
防火墙 - Firewalld
firewalld 服务命令
1 | systemctl enable firewalld.service # 开启服务(开机自动启动服务) |
firewall-cmd 命令
firewall-cmd
命令用于配置防火墙。
1 | firewall-cmd --version # 查看版本 |
1 | systemctl enable firewalld.service # 开启服务(开机自动启动服务) |
firewall-cmd
命令用于配置防火墙。
1 | firewall-cmd --version # 查看版本 |
环境:CentOS
通过 crontab
命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script 脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。
Linux 通过 crond 服务来支持 crontab。
crond
服务使用 systemctl list-unit-files
命令确认 crond
服务是否已安装。
1 | $ systemctl list-unit-files | grep crond |
如果为 enabled,表示服务正运行。
开机自动启动 crond 服务:chkconfig crond on
或者,按以下命令手动启动:
1 | systemctl enable crond.service # 开启服务(开机自动启动服务) |
crontab 命令格式如下:
1 | crontab [-u user] file crontab [-u user] [ -e | -l | -r ] |
说明:
-u user
:用来设定某个用户的 crontab 服务;file
:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。-e
:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。-l
:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。-r
:从/var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。-i
:在删除用户的 crontab 文件时给确认提示。有两种方法写入定时任务:
crontab -e
然后添加相应的任务,存盘退出。/etc/crontab
文件,即 vi /etc/crontab
,添加相应的任务。crontab 要执行的定时任务都被保存在 /etc/crontab
文件中。
crontab 的文件格式如下:
逗号用于分隔列表。例如,在第 5 个字段(星期几)中使用 MON,WED,FRI
表示周一、周三和周五。
连字符定义范围。例如,2000-2010
表示 2000 年至 2010 年期间的每年,包括 2000 年和 2010 年。
除非用反斜杠()转义,否则命令中的**百分号(%)**会被替换成换行符,第一个百分号后面的所有数据都会作为标准输入发送给命令。
字段 | 是否必填 | 允许值 | 允许特殊字符 |
---|---|---|---|
Minutes | 是 | 0–59 | * ,- |
Hours | 是 | 0–23 | * ,- |
Day of month | 是 | 1–31 | * ,- |
Month | 是 | 1–12 or JAN–DEC | * ,- |
Day of week | 是 | 0–6 or SUN–SAT | * ,- |
/etc/crontab
文件示例:
1 | SHELL=/bin/bash |
1 | * * * * * myCommand |
1 | 3,15 * * * * myCommand |
1 | 3,15 8-11 * * * myCommand |
1 | 3,15 8-11 */2 * * myCommand |
1 | 3,15 8-11 * * 1 myCommand |
1 | 30 21 * * * /etc/init.d/smb restart |
1 | 45 4 1,10,22 * * /etc/init.d/smb restart |
1 | 10 1 * * 6,0 /etc/init.d/smb restart |
1 | 0,30 18-23 * * * /etc/init.d/smb restart |
1 | 0 23 * * 6 /etc/init.d/smb restart |
1 | 0 * * * * /etc/init.d/smb restart |
1 | 0 23-7 * * * /etc/init.d/smb restart |
在智力水平相当的前提下,常常会发现:有些人做事,事倍功半;有些人做事,事半功倍。
做任何事,如果有了清晰的思路,正确的指导方针,肯定是比毫无头绪要高效很多。所以,现实中,常常会看到这样一种现象,优秀的人,往往全面优秀,干什么都出彩;而平庸的人,做什么都出不了成绩。
大多数人不是天才,想要变得优秀,唯一的途径就是:按照正确的习惯(方式方法),坚持不懈的努力进步(自律)。
我们日复一日做的事情,决定了我们是怎样的人。因此所谓卓越,并非指行为,而是习惯。
We are what we repeatedly do. Excellence, then, is not an act, but a habit.
——莎士比亚
名称 | 图示 | 方式 | 优点 | 缺点 |
---|---|---|---|---|
时间法 | 时间顺序 | 清晰明确 操作性强 |
时间分配不合理或 突出情况容易打乱计划 |
|
清单法 | 重要程度 | 要事优先 事无遗漏 |
未完成容易形成压力; 容易造成形式主义,为打卡而完成 |
|
三段法 | 完成状态 | 态度明了 有条不紊 |
灵活性差,只能电子版或软件实现编辑, 纸质版事项会重复 |
|
OKR 法 | 目标分解 | 目标导向 高效成事 |
适合复杂事情或大项目的分解执行跟进 | |
分类法 | 八个方面 | 事事周全 面面俱到 |
越是想顾周全,越难周全, 面面俱到,也会事事难完成 |
|
四象限法 | ![]() |
轻重缓急 | 要事优先 忽略次要 |
被要事牵着走,忽略了 人生应该适度娱乐的重要性 |
甘特图法 | 日期进度 | 进度直观 易于理解 |
进度条只能反映时间进度, 无法反映事项具体完成情况的进度 |
|
PDCA 法 | 流程顺序 | 流程推进 循环解决 |
流程化容易形成思维惯性, 并且缺乏压力难以形成创造性 |
5W2H 分析法是一种思考问题的启发式思维方式。5W2H 分析法用五个以 W
开头的英语单词和两个以 H
开头的英语单词进行设问,得到关键性问题的答案,最后总结归纳出问题的目标、解决思路、处理方法等,这就叫做 5W2H 法。
5W2H 分析法又叫七问分析法,是二战中美国陆军兵器修理部首创。这种分析法广泛用于企业管理和技术活动,对于决策和执行性的活动措施也非常有帮助,也有助于弥补考虑问题的疏漏。
5W2H 分析法的意义在于:避免遇到一个问题后,不知从何入手。通过设问方式,由点成线,由线成面,把问题的关键点串联起来,整理出问题的解决思路。
四象限原则是一种时间管理方式。
有首歌唱出了大多数职场人的心声:时间都去哪儿了?
事情、任务太多,时间太少,分身乏术。
时间管理四象限法则是美国的管理学家科维提出的一个时间管理的理论,按处理顺序划分为:紧急又重要、重要不紧急、紧急不重要、不紧急不重要。
第一象限(重要而紧急)
第二象限(重要但不紧急)
第三象限(紧急但不重要)
第四象限(不紧急也不重要)
在介绍 HBase 之前,我们不妨先了解一下为什么需要 HBase,或者说 HBase 是为了达到什么目的而产生。
在 HBase 诞生之前,Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。
Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式访问数据。这意味着即使是最简单的工作,也必须搜索整个数据集,即:Hadoop 无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来同时解决海量数据存储和随机访问的问题,HBase 就是其中之一 (HBase,Cassandra,CouchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。
注:数据结构分类:
- 结构化数据:即以关系型数据库表形式管理的数据;
- 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等;
- 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。
HBase 是一个构建在 HDFS(Hadoop 文件系统)之上的列式数据库。
HBase 是一种类似于 Google’s Big Table
的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。
HBase 的核心特性如下:
HBase 的其他特性
根据上一节对于 HBase 特性的介绍,我们可以梳理出 HBase 适用、不适用的场景:
HBase 不适用场景:
HBase 适用场景:
一言以蔽之——HBase 适用的场景是:实时地随机访问超大数据集。
HBase 的典型应用场景
前面已经提及,HBase 是一个列式数据库,其数据模型和关系型数据库有所不同。其数据模型的关键术语如下:
RDBMS | HBase |
---|---|
RDBMS 有它的模式,描述表的整体结构的约束 | HBase 无模式,它不具有固定列模式的概念;仅定义列族 |
支持的文件系统有 FAT、NTFS 和 EXT | 支持的文件系统只有 HDFS |
使用提交日志来存储日志 | 使用预写日志 (WAL) 来存储日志 |
使用特定的协调系统来协调集群 | 使用 ZooKeeper 来协调集群 |
存储的都是中小规模的数据表 | 存储的是超大规模的数据表,并且适合存储宽表 |
通常支持复杂的事务 | 仅支持行级事务 |
适用于结构化数据 | 适用于半结构化、结构化数据 |
使用主键 | 使用 row key |
HDFS | HBase |
---|---|
HDFS 提供了一个用于分布式存储的文件系统。 | HBase 提供面向表格列的数据存储。 |
HDFS 为大文件提供优化存储。 | HBase 为表格数据提供了优化。 |
HDFS 使用块文件。 | HBase 使用键值对数据。 |
HDFS 数据模型不灵活。 | HBase 提供了一个灵活的数据模型。 |
HDFS 使用文件系统和处理框架。 | HBase 使用带有内置 Hadoop MapReduce 支持的表格存储。 |
HDFS 主要针对一次写入多次读取进行了优化。 | HBase 针对读/写许多进行了优化。 |
行式数据库 | 列式数据库 |
---|---|
对于添加/修改操作更高效 | 对于读取操作更高效 |
读取整行数据 | 仅读取必要的列数据 |
最适合在线事务处理系统(OLTP) | 不适合在线事务处理系统(OLTP) |
将行数据存储在连续的页内存中 | 将列数据存储在非连续的页内存中 |
列式数据库的优点:
列式数据库的缺点:
HBase 安装可以参考以下文档:
(1)连接 HBase
在 HBase 安装目录的 /bin
目录下执行 hbase shell
命令进入 HBase 控制台。
1 | $ ./bin/hbase shell |
(2)输入 help
可以查看 HBase Shell 命令。
(3)创建表
可以使用 create
命令创建一张新表。必须要指定表名和 Column Family。
1 | hbase(main):001:0> create 'test', 'cf' |
(4)列出表信息
使用 list
命令来确认新建的表已存在。
1 | hbase(main):002:0> list 'test' |
可以使用 describe
命令可以查看表的细节信息,包括配置信息
1 | hbase(main):003:0> describe 'test' |
(5)向表中写数据
可以使用 put
命令向 HBase 表中写数据。
1 | hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1' |
(6)一次性扫描表的所有数据
使用 scan
命令来扫描表数据。
1 | hbase(main):006:0> scan 'test' |
(7)查看一行数据
使用 get
命令可以查看一行表数据。
1 | hbase(main):007:0> get 'test', 'row1' |
(8)禁用表
如果想要删除表或修改表设置,必须先使用 disable
命令禁用表。如果想再次启用表,可以使用 enable
命令。
1 | hbase(main):008:0> disable 'test' |
(9)删除表
使用 drop
命令可以删除表。
1 | hbase(main):011:0> drop 'test' |
(10)退出 HBase Shell
使用 quit
命令,就能退出 HBase Shell 控制台。
传统的关系型数据库存在以下缺点:
LIKE
查询的匹配会非常慢,即使在有索引的情况下。况且关系型数据库也不应该对文本字段进行索引。随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。
NoSQL,泛指非关系型的数据库,可以理解为 SQL 的一个有力补充。
在 NoSQL 许多方面性能大大优于非关系型数据库的同时,往往也伴随一些特性的缺失,比较常见的,是事务库事务功能的缺失。 数据库事务正确执行的四个基本要素:ACID 如下:
名称 | 描述 | |
---|---|---|
A | Atomicity (原子性) | 一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。 事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。 |
C | Consistency 一致性 | 在事务开始之前和事务结束以后,数据的数据的一致性约束没有被破坏。 |
I | Isolation 隔离性 | 数据库允许多个并发事务同时对数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。 |
D | Durability 持久性 | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 |
下面介绍 5 大类 NoSQL 数据针对传统关系型数据库的缺点提供的解决方案:
列式数据库是以列相关存储架构进行数据存储的数据库,主要适合于批量数据处理和即时查询。
相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合于小批量的数据处理,常用于联机事务型数据处理。
基于列式数据库的列列存储特性,可以解决某些特定场景下关系型数据库 I/O 较高的问题。
传统关系型数据库是按照行来存储数据库,称为“行式数据库”,而列式数据库是按照列来存储数据。
将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释:
HBase
HBase 是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的 BigTable 建模,实现的编程语言为 Java。它是 Apache 软件基金会的 Hadoop 项目的一部分,运行于 HDFS 文件系统之上,为 Hadoop 提供类似于 BigTable 规模的服务。因此,它可以容错地存储海量稀疏的数据。
BigTable
BigTable 是一种压缩的、高性能的、高可扩展性的,基于 Google 文件系统(Google File System,GFS)的数据存储系统,用于存储大规模结构化数据,适用于云端计算。
优点如下:
列式数据库由于其针对不同列的数据特征而发明的不同算法,使其往往有比行式数据库高的多的压缩率,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize)
读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点
1 | 执行步骤如下: |
缺点如下:
以 HBase 为例说明:
K-V 数据库指的是使用键值 (key-value) 存储的数据库,其数据按照键值对的形式进行组织、索引和存储。
KV 存储非常适合存储不涉及过多数据关系业务关系的数据,同时能有效减少读写磁盘的次数,比 SQL 数据库存储拥有更好的读写性能,能够解决关系型数据库无法存储数据结构的问题。
Redis
Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。
Cassandra
Apache Cassandra(社区内一般简称为 C*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。
LevelDB
LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。
以 Redis 为例:
优点如下:
缺点如下: 针对 ACID,Redis 事务不能支持原子性和持久性 (A 和 D),只支持隔离性和一致性 (I 和 C) 特别说明一下,这里所说的无法保证原子性,是针对 Redis 的事务操作,因为事务是不支持回滚(roll back),而因为 Redis 的单线程模型,Redis 的普通操作是原子性的。
大部分业务不需要严格遵循 ACID 原则,例如游戏实时排行榜,粉丝关注等场景,即使部分数据持久化失败,其实业务影响也非常小。因此在设计方案时,需要根据业务特征和要求来做选择
适用场景 - 储存用户信息(比如会话)、配置文件、参数、购物车等等。这些信息一般都和 ID(键)挂钩。
不适用场景
文档数据库(也称为文档型数据库)是旨在将半结构化数据存储为文档的一种数据库,它可以解决关系型数据库表结构 schema 扩展不方便的问题。文档数据库通常以 JSON 或 XML 格式存储数据。
由于文档数据库的 no-schema 特性,可以存储和读取任意数据。由于使用的数据格式是 JSON 或者 XML,无需在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。
MongoDB
**MongoDB **是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。
CouchDB
Apache CouchDB 是一个开源数据库,专注于易用性和成为”完全拥抱 web 的数据库“。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。
以 MongoDB 为例进行说明
优点如下:
缺点如下:
MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持久性)
虽然官方宣布 MongoDB 将在 4.0 版本中正式推出多文档 ACID 事务支持,最后落地情况还有待见证。
适用场景:
不适用场景:
传统关系型数据库主要通过索引来达到快速查询的目的,在全文搜索的业务下,索引也无能为力,主要体现在:
LIKE
查询,而 LIKE
查询是整表扫描,效率非常低而全文搜索引擎的出现,正是解决关系型数据库全文搜索功能较弱的问题。
全文搜索引擎的技术原理称为 **倒排索引(inverted index)
**,是一种索引方法,其基本原理是建立单词到文档的索引。与之相对是,是“正排索引”,其基本原理是建立文档到单词的索引。
现在有如下文档集合:
正排索引得到索引如下:
可见,正排索引适用于根据文档名称查询文档内容
简单的倒排索引如下:
带有单词频率信息的倒排索引如下:
可见,倒排索引适用于根据关键词来查询文档内容
Elasticsearch
Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式,多租户 -能够全文搜索与发动机 HTTP Web 界面和无架构 JSON 文件。Elasticsearch 是用 Java 开发的,并根据 Apache License 的条款作为开源发布。根据 DB-Engines 排名,Elasticsearch 是最受欢迎的企业搜索引擎,后面是基于 Lucene 的 Apache Solr。
Solr
Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制
以 Elasticsearch 为例: 优点如下:
缺点如下:
适用场景如下:
不适用场景如下:
图形数据库应用图论存储实体之间的关系信息。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。
Neo4j
Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。
ArangoDB
ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。
Titan
Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。
以 Neo4j 为例:
Neo4j 使用数据结构中图(graph)的概念来进行建模。 Neo4j 中两个最基本的概念是节点和边。节点表示实体,边则表示实体之间的关系。节点和边都可以有自己的属性。不同实体通过各种不同的关系关联起来,形成复杂的对象图。
针对关系数据,2 种 2 数据库的存储结构不同:
Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点都有指向其邻居节点的指针,可以让我们在 O(1) 的时间内找到邻居节点。另外,按照官方的说法,在 Neo4j 中边是最重要的,是”first-class entities”,所以单独存储,这有利于在图遍历的时候提高速度,也可以很方便地以任何方向进行遍历
如下优点:
缺点如下:
适用场景如下:
不适用场景如下:
关系型数据库和 NoSQL 数据库的选型,往往需要考虑几个指标:
常见软件系统数据库选型参考如下:
设计实践中,要基于需求、业务驱动架构,无论选用 RDB/NoSQL/DRDB, 一定是以需求为导向,最终数据存储方案必然是各种权衡的综合性设计
JSP
全称Java Server Pages
,是一种动态网页开发技术。
它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以 <%
开头以 %>
结束。
JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。
JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。
JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。
JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。
JSP 程序与 CGI 程序有着相似的功能,但和 CGI 程序相比,JSP 程序有如下优势:
最后,JSP 是 Java EE 不可或缺的一部分,是一个完整的企业级应用平台。这意味着 JSP 可以用最简单的方式来实现最复杂的应用。
以下列出了使用 JSP 带来的其他好处:
JSP 是一种 Servlet,但工作方式和 Servlet 有所差别。
Servlet 是先将源代码编译为 class 文件后部署到服务器下的,先编译后部署。
Jsp 是先将源代码部署到服务器再编译,先部署后编译。
Jsp 会在客户端第一次请求 Jsp 文件时被编译为 HttpJspPage 类(Servlet 的一个子类)。该类会被服务器临时存放在服务器工作目录里。所以,第一次请求 Jsp 后,访问速度会变快就是这个道理。
网络服务器需要一个 JSP 引擎,也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。
JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于 JSP 网页的特殊元素。
下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。
以下步骤表明了 Web 服务器是如何使用 JSP 来创建网页的:
以上提及到的步骤可以用下图来表示:
一般情况下,JSP 引擎会检查 JSP 文件对应的 servlet 是否已经存在,并且检查 JSP 文件的修改日期是否早于 servlet。如果 JSP 文件的修改日期早于对应的 servlet,那么容器就可以确定 JSP 文件没有被修改过并且 servlet 有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。
理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。
JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 servlet。
以下是 JSP 生命周期中所走过的几个阶段:
很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示:
当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。
编译的过程包括三个步骤:
容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样:
1 | public void jspInit(){ |
一般来讲程序只初始化一次,servlet 也是如此。通常情况下您可以在 jspInit()方法中初始化数据库连接、打开文件和创建查询表。
这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。
当 JSP 网页完成初始化后,JSP 引擎将会调用 _jspService()
方法。
_jspService()
方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样:
1 | void _jspService(HttpServletRequest request, |
_jspService()
方法在每个 request 中被调用一次并且负责产生与之相对应的 response,并且它还负责产生所有 7 个 HTTP 方法的回应,比如 GET、POST、DELETE 等等。
JSP 生命周期的销毁阶段描述了当一个 JSP 网页从容器中被移除时所发生的一切。
jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要执行任何清理工作时复写 jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。
jspDestroy()方法的格式如下:
1 | public void jspDestroy() { |
脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。
脚本程序的语法格式:
1 | <% 代码片段 %> |
或者,您也可以编写与其等价的 XML 语句,就像下面这样:
1 | <jsp:scriptlet> |
任何文本、HTML 标签、JSP 元素必须写在脚本程序的外面。
下面给出一个示例,同时也是本教程的第一个 JSP 示例:
1 | <html> |
注意:请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。
将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入 http://localhost:8080/hello.jsp
。运行后得到以下结果:
如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:<>
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
接下来我们将以上程序修改为:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
这样中文就可以正常显示了。
一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。在 JSP 文件中,您必须先声明这些变量和方法然后才能使用它们。
JSP 声明的语法格式:
1 | <%! declaration; [ declaration; ]+ ... %> |
或者,您也可以编写与其等价的 XML 语句,就像下面这样:
1 | <jsp:declaration> |
程序示例:
1 | <%! int i = 0; %> <%! int a, b, c; %> <%! Circle a = new Circle(2.0); %> |
一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。
由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。
表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。
JSP 表达式的语法格式:
1 | <%= 表达式 %> |
同样,您也可以编写与之等价的 XML 语句:
1 | <jsp:expression> |
程序示例:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
运行后得到以下结果:
1 | 今天的日期是: 2016-6-25 13:40:07 |
JSP 注释主要有两个作用:为代码作注释以及将某段代码注释掉。
JSP 注释的语法格式:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
运行后得到以下结果:
1 | 今天的日期是: 2016-6-25 13:41:26 |
不同情况下使用注释的语法规则:
语法 | 描述 |
---|---|
<%-- 注释 --%> |
JSP 注释,注释内容不会被发送至浏览器甚至不会被编译 |
<!-- 注释 --> |
HTML 注释,通过浏览器查看网页源代码时可以看见注释内容 |
<% |
代表静态 <% 常量 |
%> |
代表静态 %> 常量 |
' |
在属性中使用的单引号 |
" |
在属性中使用的双引号 |
JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java API 甚至建立 Java 代码块,包括判断语句和循环语句等等。
If…else
块,请看下面这个例子:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
运行后得到以下结果:
1 | IF...ELSE 实例 |
现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
浏览器访问,运行后得出以下结果:
1 | SWITCH...CASE 实例 |
在 JSP 程序中可以使用 Java 的三个基本循环类型:for,while,和 do…while。
让我们来看看 for 循环的例子,以下输出的不同字体大小的”菜鸟教程”:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
运行后得到以下结果:
将上例改用 while 循环来写:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行):
JSP 运算符
JSP 支持所有 Java 逻辑和算术运算符。
下表罗列出了 JSP 常见运算符,优先级从高到底:
类别 | 操作符 | 结合性 |
---|---|---|
后缀 | () [] . (点运算符) |
左到右 |
一元 | ++ - - ! ~ |
右到左 |
可乘性 | * / % |
左到右 |
可加性 | + - |
左到右 |
移位 | >> >>> << |
左到右 |
关系 | > >= < <= |
左到右 |
相等/不等 | == != |
左到右 |
位与 | & |
左到右 |
位异或 | ^ |
左到右 |
位或 | ` | ` |
逻辑与 | && |
左到右 |
逻辑或 | ` | |
条件判断 | ?: |
右到左 |
赋值 | `= += -= *= /= %= >>= <<= &= ^= | =` |
逗号 | , |
左到右 |
JSP 语言定义了以下几个字面量:
JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。
JSP 指令以开<%@
开始,以%>
结束。
JSP 指令语法格式如下:
1 | <%@ directive attribute="value" %> |
指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。
JSP 中的三种指令标签:
指令 | 描述 |
---|---|
<%@ page ... %> |
定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等 |
<%@ include ... %> |
包含其他文件 |
<%@ taglib ... %> |
引入标签库的定义,可以是自定义标签 |
Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以包含多个page
指令。
Page 指令的语法格式:
1 | <%@ page attribute="value" %> |
等价的 XML 格式:
1 | <jsp:directive.page attribute="value" /> |
例:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
下表列出与 Page 指令相关的属性:
属性 | 描述 |
---|---|
buffer | 指定 out 对象使用缓冲区的大小 |
autoFlush | 控制 out 对象的 缓存区 |
contentType | 指定当前 JSP 页面的 MIME 类型和字符编码 |
errorPage | 指定当 JSP 页面发生异常时需要转向的错误处理页面 |
isErrorPage | 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面 |
extends | 指定 servlet 从哪一个类继承 |
import | 导入要使用的 Java 类 |
info | 定义 JSP 页面的描述信息 |
isThreadSafe | 指定对 JSP 页面的访问是否为线程安全 |
language | 定义 JSP 页面所用的脚本语言,默认是 Java |
session | 指定 JSP 页面是否使用 session |
isELIgnored | 指定是否执行 EL 表达式 |
isScriptingEnabled | 确定脚本元素能否被使用 |
JSP 可以通过include
指令来包含其他文件。
被包含的文件可以是 JSP 文件、HTML 文件或文本文件。包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。
Include 指令的语法格式如下:
1 | <%@ include file="文件相对 url 地址" %> |
include 指令中的文件名实际上是一个相对的 URL 地址。
如果您没有给文件关联一个路径,JSP 编译器默认在当前路径下寻找。
等价的 XML 语法:
1 | <jsp:directive.include file="文件相对 url 地址" /> |
JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合。
taglib
指令引入一个自定义标签集合的定义,包括库路径、自定义标签。
taglib
指令的语法:
1 | <%@ taglib uri="uri" prefix="prefixOfTag" %> |
uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。
等价的 XML 语法:
1 | <jsp:directive.taglib uri="uri" prefix="prefixOfTag" /> |
JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记代码就能使用 JSP 提供的丰富功能。JSP 动作元素是对常用的 JSP 功能的抽象与封装,包括两种,自定义 JSP 动作元素与标准 JSP 动作元素。
与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用。JSP 动作元素是用 XML 语法写成的。
利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。
动作元素只有一种语法,它符合 XML 标准:
1 | <jsp:action_name attribute="value" /> |
动作元素基本上都是预定义的函数,JSP 规范定义了一系列的标准动作,它用 JSP 作为前缀,可用的标准动作元素如下:
语法 | 描述 |
---|---|
jsp:include | 在页面被请求的时候引入一个文件。 |
jsp:useBean | 寻找或者实例化一个 JavaBean。 |
jsp:setProperty | 设置 JavaBean 的属性。 |
jsp:getProperty | 输出某个 JavaBean 的属性。 |
jsp:forward | 把请求转到一个新的页面。 |
jsp:plugin | 根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。 |
jsp:element | 定义动态 XML 元素 |
jsp:attribute | 设置动态定义的 XML 元素属性。 |
jsp:body | 设置动态定义的 XML 元素内容。 |
jsp:text | 在 JSP 页面和文档中使用写入文本的模板 |
所有的动作要素都有两个属性:id 属性和 scope 属性。
<jsp:include>
<jsp:include>
用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。
如果被包含的文件为 JSP 程序,则会先执行 JSP 程序,再将执行结果包含进来。
语法格式如下:
1 | <jsp:include page="相对 URL 地址" flush="true" /> |
前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。
以下是 include 动作相关的属性列表。
属性 | 描述 |
---|---|
page | 包含在页面中的相对 URL 地址。 |
flush | 布尔属性,定义在包含资源前是否刷新缓存区。 |
示例:
以下我们定义了两个文件 date.jsp 和 main.jsp,代码如下所示:
date.jsp 文件代码:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
main.jsp 文件代码:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下:
1 | include 动作实例 |
<jsp:useBean>
jsp:useBean 动作用来加载一个将在 JSP 页面中使用的 JavaBean。
这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。
jsp:useBean 动作最简单的语法为:
1 | <jsp:useBean id="name" class="package.class" /> |
在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索 bean 的属性。
以下是 useBean 动作相关的属性列表。
属性 | 描述 |
---|---|
class | 指定 Bean 的完整包名。 |
type | 指定将引用该对象变量的类型。 |
beanName | 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。 |
在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素:
<jsp:setProperty>
jsp:setProperty 用来设置已经实例化的 Bean 对象的属性,有两种用法。首先,你可以在 jsp:useBean 元素的外面(后面)使用 jsp:setProperty,如下所示:
1 | <jsp:useBean id="myName" ... /> |
此时,不管 jsp:useBean 是找到了一个现有的 Bean,还是新创建了一个 Bean 实例,jsp:setProperty 都会执行。第二种用法是把 jsp:setProperty 放入 jsp:useBean 元素的内部,如下所示:
1 | <jsp:useBean id="myName" ... > |
此时,jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。
jsp:setProperty 动作有下面四个属性,如下表:
属性 | 描述 |
---|---|
name | name 属性是必需的。它表示要设置属性的是哪个 Bean。 |
property | property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是”*“,表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。 |
value | value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如”true”)通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如”42”)通过 Integer.valueOf 转换。 value 和 param 不能同时使用,但可以使用其中任意一个。 |
param | param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 |
<jsp:getProperty>
jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。语法格式如下:
1 | <jsp:useBean id="myName" ... /> |
下表是与 getProperty 相关联的属性:
属性 | 描述 |
---|---|
name | 要检索的 Bean 属性名称。Bean 必须已定义。 |
property | 表示要提取 Bean 属性的值 |
实例
以下实例我们使用了 Bean:
1 | package com.runoob.main; |
编译以上实例文件 TestBean.java :
1 | $ javac TestBean.java |
编译完成后会在当前目录下生成一个 TestBean.class 文件, 将该文件拷贝至当前 JSP 项目的 WebContent/WEB-INF/classes/com/runoob/main 下( com/runoob/main 包路径,没有需要手动创建)。
下面是一个 Eclipse 中目录结构图:
下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。
现在让我们在 main.jsp 文件中调用该 Bean:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
浏览器访问,执行以上文件,输出如下所示:
<jsp:forward>
jsp:forward 动作把请求转到另外的页面。jsp:forward 标记只有一个属性 page。语法格式如下所示:
1 | <jsp:forward page="相对 URL 地址" /> |
以下是 forward 相关联的属性:
属性 | 描述 |
---|---|
page | page 属性包含的是一个相对 URL。page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet. |
实例
以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。
date.jsp 文件代码如下:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
main.jsp 文件代码:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下:
1 | 今天的日期是: 2016-6-25 14:37:25 |
<jsp:plugin>
jsp:plugin 动作用来根据浏览器的类型,插入通过 Java 插件 运行 Java Applet 所必需的 OBJECT 或 EMBED 元素。
如果需要的插件不存在,它会下载插件,然后执行 Java 组件。 Java 组件可以是一个 applet 或一个 JavaBean。
plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。param 元素可用于向 Applet 或 Bean 传递参数。
以下是使用 plugin 动作元素的典型实例:
1 | <jsp:plugin type="applet" codebase="dirname" code="MyApplet.class" |
如果你有兴趣可以尝试使用 applet 来测试 jsp:plugin
动作元素,<fallback>
元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。
<jsp:element>
、 <jsp:attribute>
、<jsp:body>
<jsp:element>
、 <jsp:attribute>
、<jsp:body>
动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。
以下实例动态定义了 XML 元素:
1 | <%@ page language="java" contentType="text/html; charset=UTF-8" |
浏览器访问以下页面,输出结果如下所示:
<jsp:text>
jsp:text动作元素允许在 JSP 页面和文档中使用写入文本的模板,语法格式如下:
1 | <jsp:text>模板数据</jsp:text> |
以上文本模板不能包含其他元素,只能只能包含文本和 EL 表达式(注:EL 表达式将在后续章节中介绍)。请注意,在 XML 文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个 CDATA 部分的值。
1 | <jsp:text><![CDATA[<br>]]></jsp:text> |
如果你需要在 XHTML 中声明 DOCTYPE,必须使用到 <jsp:text>
动作元素,实例如下:
1 | <jsp:text><![CDATA[<!DOCTYPE html |
你可以对以上实例尝试使用jsp:text及不使用该动作元素执行结果的区别。
JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。JSP 隐式对象也被称为预定义变量。
JSP 所支持的九大隐式对象:
对象 | 描述 |
---|---|
request | HttpServletRequest类的实例 |
response | HttpServletResponse类的实例 |
out | PrintWriter类的实例,用于把结果输出至网页上 |
session | HttpSession类的实例 |
application | ServletContext类的实例,与应用上下文有关 |
config | ServletConfig类的实例 |
pageContext | PageContext类的实例,提供对 JSP 页面所有对象以及命名空间的访问 |
page | 类似于 Java 类中的 this 关键字 |
Exception | Exception类的对象,代表发生错误的 JSP 页面中对应的异常对象 |
request
对象是javax.servlet.http.HttpServletRequest
类的实例。
每当客户端请求一个 JSP 页面时,JSP 引擎就会制造一个新的request
对象来代表这个请求。
request
对象提供了一系列方法来获取 HTTP 头信息,cookies,HTTP 方法等等。
response
对象是javax.servlet.http.HttpServletResponse
类的实例。
当服务器创建request
对象时会同时创建用于响应这个客户端的response
对象。
response
对象也定义了处理 HTTP 头模块的接口。通过这个对象,开发者们可以添加新的 cookies,时间戳,HTTP 状态码等等。
out
对象是javax.servlet.jsp.JspWriter
类的实例,用来在response
对象中写入内容。
最初的JspWriter
类对象根据页面是否有缓存来进行不同的实例化操作。可以在page
指令中使用buffered='false'
属性来轻松关闭缓存。
JspWriter
类包含了大部分java.io.PrintWriter
类中的方法。不过,JspWriter
新增了一些专为处理缓存而设计的方法。还有就是,JspWriter
类会抛出IOExceptions
异常,而PrintWriter
不会。
下表列出了我们将会用来输出boolean
,char
,int
,double
,String
,object
等类型数据的重要方法:
方法 | 描述 |
---|---|
out.print(dataType dt) | 输出 Type 类型的值 |
out.println(dataType dt) | 输出 Type 类型的值然后换行 |
out.flush() | 刷新输出流 |
session
对象是javax.servlet.http.HttpSession
类的实例。和 Java Servlets 中的session
对象有一样的行为。
session
对象用来跟踪在各个客户端请求间的会话。
application
对象直接包装了 servlet 的ServletContext
类的对象,是javax.servlet.ServletContext
类的实例。
这个对象在 JSP 页面的整个生命周期中都代表着这个 JSP 页面。这个对象在 JSP 页面初始化时被创建,随着jspDestroy()
方法的调用而被移除。
通过向application
中添加属性,则所有组成您 web 应用的 JSP 文件都能访问到这些属性。
config
对象是javax.servlet.ServletConfig
类的实例,直接包装了 servlet 的ServletConfig
类的对象。
这个对象允许开发者访问 Servlet 或者 JSP 引擎的初始化参数,比如文件路径等。
以下是 config 对象的使用方法,不是很重要,所以不常用:
1 | config.getServletName(); |
它返回包含在<servlet-name>
元素中的 servlet 名字,注意,<servlet-name>
元素在WEB-INF\web.xml
文件中定义。
pageContext
对象是javax.servlet.jsp.PageContext
类的实例,用来代表整个 JSP 页面。
这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。
这个对象存储了request
对象和response
对象的引用。application
对象,config
对象,session
对象,out
对象可以通过访问这个对象的属性来导出。
pageContext
对象也包含了传给 JSP 页面的指令信息,包括缓存信息,ErrorPage URL,页面 scope 等。
PageContext
类定义了一些字段,包括 PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了 40 余种方法,有一半继承自javax.servlet.jsp.JspContext
类。
其中一个重要的方法就是removeArribute()
,它可接受一个或两个参数。比如,pageContext.removeArribute(“attrName”)移除四个 scope 中相关属性,但是下面这种方法只移除特定 scope 中的相关属性:
1 | pageContext.removeAttribute("attrName", PAGE_SCOPE); |
这个对象就是页面实例的引用。它可以被看做是整个 JSP 页面的代表。
page
对象就是this
对象的同义词。
exception
对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。
EL 表达式是用${}
括起来的脚本,用来更方便地读取对象。EL 表达式写在 JSP 的 HTML 代码中,而不能写在<%
与%>
引起的 JSP 脚本中。
JSP 表达式语言(EL)使得访问存储在 JavaBean 中的数据变得非常简单。JSP EL 既可以用来创建算术表达式也可以用来创建逻辑表达式。在 JSP EL 表达式内可以使用整型数,浮点数,字符串,常量 true、false,还有 null。
典型的,当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可:
1 | <jsp:setProperty name="box" property="perimeter" value="100" /> |
JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下:
1 | ${expr} |
其中,expr 指的是表达式。在 JSP EL 中通用的操作符是”.”和”[]”。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。
举例来说,上面的 <jsp:setProperty>
标签可以使用表达式语言改写成如下形式:
1 | <jsp:setProperty |
当 JSP 编译器在属性中见到”${}”格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。
您也可以在标签的模板文本中使用表达式语言。比如 <jsp:text>
标签简单地将其主体中的文本插入到 JSP 输出中:
1 | <jsp:text> |
现在,在jsp:text标签主体中使用表达式,就像这样:
1 | <jsp:text> |
在 EL 表达式中可以使用圆括号来组织子表达式。比如 ${(1 + 2) _ 3}
等于 9,但是 ${1 + (2 _ 3)}
等于 7。
想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true:
1 | <%@ page isELIgnored ="true|false" %> |
这样,EL 表达式就会被忽略。若设为 false,则容器将会计算 EL 表达式。
EL 表达式支持大部分 Java 所提供的算术和逻辑操作符:
操作符 | 描述 |
---|---|
. | 访问一个 Bean 属性或者一个映射条目 |
[] | 访问一个数组或者链表的元素 |
( ) | 组织一个子表达式以改变优先级 |
+ | 加 |
- | 减或负 |
* | 乘 |
/ or div | 除 |
% or mod | 取模 |
== or eq | 测试是否相等 |
!= or ne | 测试是否不等 |
< or lt | 测试是否小于 |
> or gt | 测试是否大于 |
<= or le | 测试是否小于等于 |
>= or ge | 测试是否大于等于 |
&& or and | 测试逻辑与 |
|| or or | 测试逻辑或 |
! or not | 测试取反 |
empty | 测试是否空值 |
JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下:
1 | ${ns:func(param1, param2, ...)} |
ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度:
1 | ${fn:length("Get my length")} |
要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用 <taglib>
标签在 JSP 文件中包含这些库。
JSP EL 支持下表列出的隐含对象:
隐含对象 | 描述 |
---|---|
pageScope | page 作用域 |
requestScope | request 作用域 |
sessionScope | session 作用域 |
applicationScope | application 作用域 |
param | Request 对象的参数,字符串 |
paramValues | Request 对象的参数,字符串集合 |
header | HTTP 信息头,字符串 |
headerValues | HTTP 信息头,字符串集合 |
initParam | 上下文初始化参数 |
cookie | Cookie 值 |
pageContext | 当前页面的 pageContext |
您可以在表达式中使用这些对象,就像使用变量一样。接下来会给出几个例子来更好的理解这个概念。
pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样:
1 | ${pageContext.request.queryString} |
pageScope,requestScope,sessionScope,applicationScope 变量用来访问存储在各个作用域层次的变量。
举例来说,如果您需要显式访问在 applicationScope 层的 box 变量,可以这样来访问:applicationScope.box。
param 和 paramValues 对象用来访问参数值,通过使用 request.getParameter 方法和 request.getParameterValues 方法。
举例来说,访问一个名为 order 的参数,可以这样使用表达式:${param.order}
,或者${param["order"]}
。
接下来的例子表明了如何访问 request 中的 username 参数:
1 | <%@ page import="java.io.*,java.util.*" %> <% String title = "Accessing Request |
param 对象返回单一的字符串,而 paramValues 对象则返回一个字符串数组。
header 和 headerValues 对象用来访问信息头,通过使用 request.getHeader 方法和 request.getHeaders 方法。
举例来说,要访问一个名为 user-agent 的信息头,可以这样使用表达式:${header.user-agent}
,或者 ${header["user-agent"]}
。
接下来的例子表明了如何访问 user-agent 信息头:
1 | <%@ page import="java.io.*,java.util.*" %> <% String title = "User Agent |
运行结果如下:
header 对象返回单一值,而 headerValues 则返回一个字符串数组。
JSP 标准标签库(JSTL)是一个 JSP 标签集合,它封装了 JSP 应用的通用核心功能。
JSTL 支持通用的、结构化的任务,比如迭代,条件判断,XML 文档操作,国际化标签,SQL 标签。 除了这些,它还提供了一个框架来使用集成 JSTL 的自定义标签。
根据 JSTL 标签所提供的功能,可以将其分为 5 个类别。
Apache Tomcat 安装 JSTL 库步骤如下:
从 Apache 的标准标签库中下载的二进包(jakarta-taglibs-standard-current.zip)。
下载 jakarta-taglibs-standard-1.1.2.zip 包并解压,将 jakarta-taglibs-standard-1.1.2/lib/ 下的两个 jar 文件:standard.jar 和 jstl.jar 文件拷贝到 /WEB-INF/lib/ 下。
将 tld 下的需要引入的 tld 文件复制到 WEB-INF 目录下。
接下来我们在 web.xml 文件中添加以下配置:
1 | <?xml version="1.0" encoding="UTF-8"?> |
使用任何库,你必须在每个 JSP 文件中的头部包含 <taglib>
标签。
核心标签是最常用的 JSTL 标签。引用核心标签库的语法如下:
1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> |
标签 | 描述 |
---|---|
<c:out> |
用于在 JSP 中显示数据,就像<%= … > |
<c:set> |
用于保存数据 |
<c:remove> |
用于删除数据 |
<c:catch> |
用来处理产生错误的异常状况,并且将错误信息储存起来 |
<c:if> |
与我们在一般程序中用的 if 一样 |
<c:choose> |
本身只当做 <c:when> 和 <c:otherwise> 的父标签 |
<c:when> |
<c:choose> 的子标签,用来判断条件是否成立 |
<c:otherwise> |
<c:choose> 的子标签,接在 <c:when> 标签后,当 <c:when> 标签判断为 false 时被执行 |
<c:import> |
检索一个绝对或相对 URL,然后将其内容暴露给页面 |
<c:forEach> |
基础迭代标签,接受多种集合类型 |
<c:forTokens> |
根据指定的分隔符来分隔内容并迭代输出 |
<c:param> |
用来给包含或重定向的页面传递参数 |
<c:redirect> |
重定向至一个新的 URL. |
<c:url> |
使用可选的查询参数来创造一个 URL |
JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下:
1 | <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> |
标签 | 描述 |
---|---|
<fmt:formatNumber> |
使用指定的格式或精度格式化数字 |
<fmt:parseNumber> |
解析一个代表着数字,货币或百分比的字符串 |
<fmt:formatDate> |
使用指定的风格或模式格式化日期和时间 |
<fmt:parseDate> |
解析一个代表着日期或时间的字符串 |
<fmt:bundle> |
绑定资源 |
<fmt:setLocale> |
指定地区 |
<fmt:setBundle> |
绑定资源 |
<fmt:timeZone> |
指定时区 |
<fmt:setTimeZone> |
指定时区 |
<fmt:message> |
显示资源配置文件信息 |
<fmt:requestEncoding> |
设置 request 的字符编码 |
JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下:
1 | <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> |
标签 | 描述 |
---|---|
<sql:setDataSource> |
指定数据源 |
<sql:query> |
运行 SQL 查询语句 |
<sql:update> |
运行 SQL 更新语句 |
<sql:param> |
将 SQL 语句中的参数设为指定值 |
<sql:dateParam> |
将 SQL 语句中的日期参数设为指定的 java.util.Date 对象值 |
<sql:transaction> |
在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行 |
JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下:
1 | <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %> |
在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的 <Tomcat 安装目录>\lib
下:
XercesImpl.jar
xalan.jar
标签 | 描述 |
---|---|
<x:out> |
与 <%= ... > ,类似,不过只用于 XPath 表达式 |
<x:parse> |
解析 XML 数据 |
<x:set> |
设置 XPath 表达式 |
<x:if> |
判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体 |
<x:forEach> |
迭代 XML 文档中的节点 |
<x:choose> |
<x:when> 和 <x:otherwise> 的父标签 |
<x:when> |
<x:choose> 的子标签,用来进行条件判断 |
<x:otherwise> |
<x:choose> 的子标签,当 <x:when> 判断为 false 时被执行 |
<x:transform> |
将 XSL 转换应用在 XML 文档中 |
<x:param> |
与 <x:transform> 共同使用,用于设置 XSL 样式表 |
JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下:
1 | <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> |
函数 | 描述 |
---|---|
fn:contains() | 测试输入的字符串是否包含指定的子串 |
fn:containsIgnoreCase() | 测试输入的字符串是否包含指定的子串,大小写不敏感 |
fn:endsWith() | 测试输入的字符串是否以指定的后缀结尾 |
fn:escapeXml() | 跳过可以作为 XML 标记的字符 |
fn:indexOf() | 返回指定字符串在输入字符串中出现的位置 |
fn:join() | 将数组中的元素合成一个字符串然后输出 |
fn:length() | 返回字符串长度 |
fn:replace() | 将输入字符串中指定的位置替换为指定的字符串然后返回 |
fn:split() | 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回 |
fn:startsWith() | 测试输入字符串是否以指定的前缀开始 |
fn:substring() | 返回字符串的子集 |
fn:substringAfter() | 返回字符串在指定子串之后的子集 |
fn:substringBefore() | 返回字符串在指定子串之前的子集 |
fn:toLowerCase() | 将字符串中的字符转为小写 |
fn:toUpperCase() | 将字符串中的字符转为大写 |
fn:trim() | 移除首尾的空白符 |
自定义标签是用户定义的 JSP 语言元素。当 JSP 页面包含一个自定义标签时将被转化为 servlet,标签转化为对被 称为 tag handler 的对象的操作,即当 servlet 执行时 Web container 调用那些操作。
JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 JSP 页面。 JSP 2.0 规范中引入 Simple Tag Handlers 来编写这些自定义标记。
你可以继承 SimpleTagSupport 类并重写的 doTag()方法来开发一个最简单的自定义标签。
接下来,我们想创建一个自定义标签叫作ex:Hello,标签格式为:
1 | <ex:Hello /> |
要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示:
1 | package com.runoob; import javax.servlet.jsp.tagext.*; import |
以下代码重写了 doTag()方法,方法中使用了 getJspContext()方法来获取当前的 JspContext 对象,并将”Hello Custom Tag!”传递给 JspWriter 对象。
编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:<Tomcat安装目录>webapps\ROOT\WEB-INF\custom.tld
。
1 | <taglib> |
接下来,我们就可以在 JSP 文件中使用 Hello 标签:
1 | <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> |
以上程序输出结果为:
1 | Hello Custom Tag! |
你可以像标准标签库一样在标签中包含消息内容。如我们要在我们自定义的 Hello 中包含内容,格式如下:
1 | <ex:Hello> |
我们可以修改标签处理类文件,代码如下:
1 | package com.runoob; |
接下来我们需要修改 TLD 文件,如下所示:
1 | <taglib> |
现在我们可以在 JSP 使用修改后的标签,如下所示:
1 | <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> |
以上程序输出结果如下所示:
1 | This is message body |
你可以在自定义标准中设置各种属性,要接收属性,值自定义标签类必须实现 setter 方法, JavaBean 中的 setter 方法如下所示:
1 | package com.runoob; |
属性的名称是”message”,所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的 <attribute>
元素添加此属性:
1 | <taglib> |
现在我们就可以在 JSP 文件中使用 message 属性了,如下所示:
1 | <%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%> |
以上实例数据输出结果为:
1 | This is custom tag |
你还可以包含以下属性:
属性 | 描述 |
---|---|
name | 定义属性的名称。每个标签的是属性名称必须是唯一的。 |
required | 指定属性是否是必须的或者可选的,如果设置为 false 为可选。 |
rtexprvalue | 声明在运行表达式时,标签属性是否有效。 |
type | 定义该属性的 Java 类类型 。默认指定为 String |
description | 描述信息 |
fragment | 如果声明了该属性,属性值将被视为一个 JspFragment。 |
以下是指定相关的属性实例:
1 | ..... |
如果你使用了两个属性,修改 TLD 文件,如下所示:
1 | ..... |
Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。
Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。
Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下:
版本 | 日期 | JAVA EE/JDK 版本 | 特性 |
---|---|---|---|
Servlet 4.0 | 2017 年 10 月 | JavaEE 8 | HTTP2 |
Servlet 3.1 | 2013 年 5 月 | JavaEE 7 | 非阻塞 I/O,HTTP 协议升级机制 |
Servlet 3.0 | 2009 年 12 月 | JavaEE 6, JavaSE 6 | 可插拔性,易于开发,异步 Servlet,安全性,文件上传 |
Servlet 2.5 | 2005 年 10 月 | JavaEE 5, JavaSE 5 | 依赖 JavaSE 5,支持注解 |
Servlet 2.4 | 2003 年 11 月 | J2EE 1.4, J2SE 1.3 | web.xml 使用 XML Schema |
Servlet 2.3 | 2001 年 8 月 | J2EE 1.3, J2SE 1.2 | Filter |
Servlet 2.2 | 1999 年 8 月 | J2EE 1.2, J2SE 1.2 | 成为 J2EE 标准 |
Servlet 2.1 | 1998 年 11 月 | 未指定 | First official specification, added RequestDispatcher, ServletContext |
Servlet 2.0 | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | |
Servlet 1.0 | 1997 年 6 月 |
Servlet 生命周期如下:
1 | <%@ page isThreadSafe="false" %> |
由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的 ID,下一次用户在请求中包含此 ID,服务器根据此判断到底是哪一个用户。
1**
:信息性状态码2**
:成功状态码3**
:重定向状态码4**
:客户端错误状态码5**
:服务器错误状态码(1)XML 文档有两种约束方式:
(2)XML 文档区别:
1 DTD 不符合 XML 的语法结构,schema 符合 XML 的语法结构;
2 DTD 的约束扩展性比较差,XML 文档只能引入一个 DTD 的文件。schema 可以引入多个文件;
3 DTD 不支持名称空间(理解包结构),schema 支持名称空间;
4 DTD 支持数据比较少,schema 支持更多的数据类型;
(3)解析方式主要有三种:
SkyWalking 是一个基于 Java 开发的分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。
SkyWalking 是观察性分析平台和应用性能管理系统。
提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。
从逻辑上讲,SkyWalking 分为四个部分:探针(Probes),平台后端,存储和 UI。
进入 Apache SkyWalking 官方下载页面,选择安装版本,下载解压到本地。
安装分为三个部分:
Arthas
是 Alibaba 开源的 Java 诊断工具 。
Arthas
可以解决的问题:
Arthas
支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
arthas-boot
(推荐)下载arthas-boot.jar
,然后用java -jar
的方式启动:
1 | wget https://alibaba.github.io/arthas/arthas-boot.jar |
打印帮助信息:
1 | java -jar arthas-boot.jar -h |
如果下载速度比较慢,可以使用 aliyun 的镜像:
1 | java -jar arthas-boot.jar --repo-mirror aliyun --use-http |
如果从 github 下载有问题,可以使用 gitee 镜像
1 | wget https://arthas.gitee.io/arthas-boot.jar |
as.sh
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车
执行即可:
1 | curl -L https://alibaba.github.io/arthas/install.sh | sh |
上述命令会下载启动脚本文件 as.sh
到当前目录,你可以放在任何地方或将其加入到 $PATH
中。
直接在 shell 下面执行./as.sh
,就会进入交互界面。
也可以执行./as.sh -h
来获取更多参数信息。
如果从 github 下载有问题,可以使用 gitee 镜像
1 | curl -L https://arthas.gitee.io/install.sh | sh |
最新版本,点击下载:下载地址
解压后,在文件夹里有arthas-boot.jar
,直接用java -jar
的方式启动:
1 | java -jar arthas-boot.jar |
打印帮助信息:
1 | java -jar arthas-boot.jar -h |
1 | wget https://alibaba.github.io/arthas/arthas-demo.jar |
arthas-demo
是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。
arthas-demo
源代码:查看
在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):
1 | wget https://alibaba.github.io/arthas/arthas-boot.jar |
admin
用户来执行:sudo su admin && java -jar arthas-boot.jar
或 sudo -u admin -EH java -jar arthas-boot.jar
。~/logs/arthas/
目录下的日志。java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar -h
打印更多参数信息。选择应用 java 进程:
1 | $ $ java -jar arthas-boot.jar |
Demo 进程是第 2 个,则输入 2,再输入回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
1 | [INFO] Try to attach process 71560 |
输入dashboard,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
1 | $ dashboard |
arthas-demo
进程的 Main Classthread 1
会打印线程 ID 1 的栈,通常是 main 函数的线程。
1 | $ thread 1 | grep 'main(' |
1 | $ jad demo.MathGame |
通过watch命令来查看demo.MathGame#primeFactors
函数的返回值:
1 | $ watch demo.MathGame primeFactors returnObj |
更多的功能可以查看进阶使用。
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行shutdown
命令。
.java
文件为.class
文件.class
文件,redefine 到 JVM 里请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行
shutdown
或将增强过的类执行reset
命令。
Arthas 支持使用管道对上述命令的结果进行进一步的处理,如sm java.lang.String * | grep 'index'
当线上出现偶发的问题,比如需要 watch 某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了,详情请参考这里
通过 websocket 连接 Arthas。
在3.1.4
版本后,增加了用户数据回报功能,方便统一做安全或者历史数据统计。
在启动时,指定stat-url
,就会回报执行的每一行命令,比如: ./as.sh --stat-url 'http://192.168.10.11:8080/api/stat'
在 tunnel server 里有一个示例的回报代码,用户可以自己在服务器上实现。
Maven 是一个项目管理工具。它负责管理项目开发过程中的几乎所有的东西。
maven 把项目的构建划分为不同的生命周期(lifecycle)。粗略一点的话,它这个过程(phase)包括:编译、测试、打包、集成测试、验证、部署。maven 中所有的执行动作(goal)都需要指明自己在这个过程中的执行位置,然后 maven 执行的时候,就依照过程的发展依次调用这些 goal 进行各种处理。
这个也是 maven 的一个基本调度机制。一般来说,位置稍后的过程都会依赖于之前的过程。当然,maven 同样提供了配置文件,可以依照用户要求,跳过某些阶段。
Maven 的标准工程结构如下:
1 | |-- pom.xml(maven的核心配置文件) |
所谓的”约定优于配置”,在 maven 中并不是完全不可以修改的,他们只是一些配置的默认值而已。但是除非必要,并不需要去修改那些约定内容。maven 默认的文件存放结构如下:
每一个阶段的任务都知道怎么正确完成自己的工作,比如 compile 任务就知道从 src/main/java 下编译所有的 java 文件,并把它的输出 class 文件存放到 target/classes 中。
对 maven 来说,采用”约定优于配置”的策略可以减少修改配置的工作量,也可以降低学习成本,更重要的是,给项目引入了统一的规范。
maven 使用如下几个要素来唯一定位某一个输出物:
例如:想在 maven 工程中引入 4.12 版本的 junit 包,添加如下依赖即可。
1 | <dependency> |
maven 有自己的版本规范,一般是如下定义 <major version>
、<minor version>
、<incremental version>-<qualifier>
,比如 1.2.3-beta-01。要说明的是,maven 自己判断版本的算法是 major,minor,incremental 部分用数字比 较,qualifier 部分用字符串比较,所以要小心 alpha-2 和 alpha-15 的比较关系,最好用 alpha-02 的格式。
maven 在版本管理时候可以使用几个特殊的字符串 SNAPSHOT,LATEST,RELEASE。比如”1.0-SNAPSHOT”。各个部分的含义和处理逻辑如下说明:
Linux 环境安装可以使用我写一键安装脚本:https://github.com/dunwu/linux-tutorial/tree/master/codes/linux/ops/service/maven
Maven 依赖于 Java,所以本地必须安装 JDK。
打开控制台,执行 java -version
确认本地已安装 JDK。
1 | $ java -version |
进入 **官网下载地址**,选择合适版本,下载并解压到本地。解压命令如下:
1 | # 以下解压命令分别针对 zip 包和 tar 包 |
添加环境变量 MAVEN_HOME
,值为 Maven 的安装路径。
输入 vi /etc/profile
,添加环境变量如下:
1 | # MAVEN 的根路径 |
执行 source /etc/profile
,立即生效。
右键 “计算机”,选择 “属性”,之后点击 “高级系统设置”,点击”环境变量”,来设置环境变量,有以下系统变量需要配置:
检验是否安装成功,执行 mvn -v
命令,如果输出类似下面的 maven 版本信息,说明配置成功。
1 | $ mvn -v |
setting.xml
文件是 Maven 的默认配置文件,其默认路径为:<Maven 安装目录>/conf/settings.xml
。
如果需要修改 Maven 配置,直接修改 setting.xml
并保持即可。
例如:想要修改本地仓库位置可以按如下配置,这样,所有通过 Maven 下载打包的 jar 包都会存储在 D:\maven\repo
路径下。
1 | <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
执行指令:
1 | mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false |
会在当前路径新建一个名为 my-app
的 Maven 工程,其目录结构如下:
1 | my-app |
其中, src/main/java
目录包含 java 源码, src/test/java
目录包含 java 测试源码,而 pom.xml
文件是 maven 工程的配置文件。
pom.xml 是 maven 工程的配置文件,它描述了 maven 工程的构建方式,其配置信息是很复杂的,这里给一个最简单的配置示例:
1 | project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
执行以下命令,即可构建项目:
1 | mvn clean package -Dmaven.test.skip=true -B -U |
构建成功后,会输出类似下面的信息:
1 | ... |
这时,在当前路径下会产生一个 target
目录,其中包含了构建的输出物,如:jar 包、class 文件等。
这时,我们可以执行以下命令启动 jar 包:
1 | java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App |
(1)创建 Maven 工程
依次点击 File -> New -> Project 打开创建工程对话框,选择 Maven 工程。
(2)输入项目信息
(3)点击 Intellij 侧边栏中的 Maven 工具界面,有几个可以直接使用的 maven 命令,可以帮助你进行构建。
(1)Maven 插件
在 Eclipse 中创建 Maven 工程,需要安装 Maven 插件。
一般较新版本的 Eclipse 都会带有 Maven 插件,如果你的 Eclipse 中已经有 Maven 插件,可以跳过这一步骤。
点击 Help -> Eclipse Marketplace,搜索 maven 关键字,选择安装红框对应的 Maven 插件。
(2)Maven 环境配置
点击 Window -> Preferences
如下图所示,配置 settings.xml 文件的位置
(3)创建 Maven 工程
File -> New -> Maven Project -> Next,在接下来的窗口中会看到一大堆的项目模板,选择合适的模板。
接下来设置项目的参数,如下:
groupId是项目组织唯一的标识符,实际对应 JAVA 的包的结构,是 main 目录里 java 的目录结构。
artifactId就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称。
点击 Finish,Eclipse 会创建一个 Maven 工程。
(4)使用 Maven 进行构建
Eclipse 中构建方式:
在 Elipse 项目上右击 -> Run As 就能看到很多 Maven 操作。这些操作和 maven 命令是等效的。例如 Maven clean,等同于 mvn clean 命令。
你也可以点击 Maven build,输入组合命令,并保存下来。如下图:
Maven 命令构建方式:
当然,你也可以直接使用 maven 命令进行构建。
进入工程所在目录,输入 maven 命令就可以了。
在 Maven 工程中添加依赖 jar 包,很简单,只要在 POM 文件中引入对应的<dependency>
标签即可。
参考下例:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
<dependency>
标签最常用的四个属性标签:
<groupId>
- 项目组织唯一的标识符,实际对应 JAVA 的包的结构。<artifactId>
- 项目唯一的标识符,实际对应项目的名称,就是项目根目录的名称。<version>
- jar 包的版本号。可以直接填版本数字,也可以在 properties 标签中设置属性值。<scope>
- jar 包的作用范围。可以填写 compile、runtime、test、system 和 provided。用来在编译、测试等场景下选择对应的 classpath。可以在 http://mvnrepository.com/ 站点搜寻你想要的 jar 包版本
例如,想要使用 log4j,可以找到需要的版本号,然后拷贝对应的 maven 标签信息,将其添加到 pom .xml 文件中。
要添加 Maven 插件,可以在 pom.xml 文件中添加 <plugin>
标签。
1 | <build> |
<configuration>
标签用来配置插件的一些使用参数。
假设要创建一个父 maven 工程,它有两个子工程:my-app 和 my-webapp:
1 | +- pom.xml |
app 工程的 pom.xml 如下,重点在于在 modules 中引入两个子 module:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
选择编译 XXX 时,会依次对它的所有 Module 执行相同操作。
maven-antrun-plugin 能让用户在 Maven 项目中运行 Ant 任务。用户可以直接在该插件的配置以 Ant 的方式编写 Target, 然后交给该插件的 run 目标去执行。在一些由 Ant 往 Maven 迁移的项目中,该插件尤其有用。此外当你发现需要编写一些自定义程度很高的任务,同时又觉 得 Maven 不够灵活时,也可以以 Ant 的方式实现之。maven-antrun-plugin 的 run 目标通常与生命周期绑定运行。
Archtype 指项目的骨架,Maven 初学者最开始执行的 Maven 命令可能就是mvn archetype:generate,这实际上就是让 maven-archetype-plugin 生成一个很简单的项目骨架,帮助开发者快速上手。可能也有人看到一些文档写了mvn archetype:create, 但实际上 create 目标已经被弃用了,取而代之的是 generate 目标,该目标使用交互式的方式提示用户输入必要的信息以创建项目,体验更好。 maven-archetype-plugin 还有一些其他目标帮助用户自己定义项目原型,例如你由一个产品需要交付给很多客户进行二次开发,你就可以为 他们提供一个 Archtype,帮助他们快速上手。
maven-assembly-plugin 的用途是将项目打包,该包可能包含了项目的可执行文件、源代码、readme、平台脚本等等。 maven-assembly-plugin 支持各种主流的格式如 zip、tar.gz、jar 和 war 等,具体打包哪些文件是高度可控的,例如用户可以 按文件级别的粒度、文件集级别的粒度、模块级别的粒度、以及依赖级别的粒度控制打包,此外,包含和排除配置也是支持的。maven-assembly- plugin 要求用户使用一个名为assembly.xml
的元数据文件来表述打包,它的 single 目标可以直接在命令行调用,也可以被绑定至生命周期。
maven-dependency-plugin 最大的用途是帮助分析项目依赖,dependency:list能够列出项目最终解析到的依赖列表,dependency:tree能进一步的描绘项目依赖树,dependency:analyze可以告诉你项目依赖潜在的问题,如果你有直接使用到的却未声明的依赖,该目标就会发出警告。maven-dependency-plugin 还有很多目标帮助你操作依赖文件,例如dependency:copy-dependencies能将项目依赖从本地 Maven 仓库复制到某个特定的文件夹下面。
在一个稍大一点的组织或团队中,你无法保证所有成员都熟悉 Maven,那他们做一些比较愚蠢的事情就会变得很正常,例如给项目引入了外部的 SNAPSHOT 依赖而导致构建不稳定,使用了一个与大家不一致的 Maven 版本而经常抱怨构建出现诡异问题。maven-enforcer- plugin 能够帮助你避免之类问题,它允许你创建一系列规则强制大家遵守,包括设定 Java 版本、设定 Maven 版本、禁止某些依赖、禁止 SNAPSHOT 依赖。只要在一个父 POM 配置规则,然后让大家继承,当规则遭到破坏的时候,Maven 就会报错。除了标准的规则之外,你还可以扩展该插 件,编写自己的规则。maven-enforcer-plugin 的 enforce 目标负责检查规则,它默认绑定到生命周期的 validate 阶段。
maven-help-plugin 是一个小巧的辅助工具,最简单的help:system可以打印所有可用的环境变量和 Java 系统属性。help:effective-pom和help:effective-settings最 为有用,它们分别打印项目的有效 POM 和有效 settings,有效 POM 是指合并了所有父 POM(包括 Super POM)后的 XML,当你不确定 POM 的某些信息从何而来时,就可以查看有效 POM。有效 settings 同理,特别是当你发现自己配置的 settings.xml 没有生效时,就可以用help:effective-settings来验证。此外,maven-help-plugin 的 describe 目标可以帮助你描述任何一个 Maven 插件的信息,还有 all-profiles 目标和 active-profiles 目标帮助查看项目的 Profile。
maven-release-plugin 的用途是帮助自动化项目版本发布,它依赖于 POM 中的 SCM 信息。release:prepare用来准备版本发布,具体的工作包括检查是否有未提交代码、检查是否有 SNAPSHOT 依赖、升级项目的 SNAPSHOT 版本至 RELEASE 版本、为项目打标签等等。release:perform则 是签出标签中的 RELEASE 源码,构建并发布。版本发布是非常琐碎的工作,它涉及了各种检查,而且由于该工作仅仅是偶尔需要,因此手动操作很容易遗漏一 些细节,maven-release-plugin 让该工作变得非常快速简便,不易出错。maven-release-plugin 的各种目标通常直接在 命令行调用,因为版本发布显然不是日常构建生命周期的一部分。
为了使项目结构更为清晰,Maven 区别对待 Java 代码文件和资源文件,maven-compiler-plugin 用来编译 Java 代码,maven-resources-plugin 则用来处理资源文件。默认的主资源文件目录是src/main/resources
,很多用户会需要添加额外的资源文件目录,这个时候就可以通过配置 maven-resources-plugin 来实现。此外,资源文件过滤也是 Maven 的一大特性,你可以在资源文件中使用*${propertyName}*形式的 Maven 属性,然后配置 maven-resources-plugin 开启对资源文件的过滤,之后就可以针对不同环境通过命令行或者 Profile 传入属性的值,以实现更为灵活的构建。
可能是由于历史的原因,Maven 2.3 中用于执行测试的插件不是 maven-test-plugin,而是 maven-surefire-plugin。其实大部分时间内,只要你的测试 类遵循通用的命令约定(以 Test 结尾、以 TestCase 结尾、或者以 Test 开头),就几乎不用知晓该插件的存在。然而在当你想要跳过测试、排除某些 测试类、或者使用一些 TestNG 特性的时候,了解 maven-surefire-plugin 的一些配置选项就很有用了。例如 mvn test -Dtest=FooTest 这样一条命令的效果是仅运行 FooTest 测试类,这是通过控制 maven-surefire-plugin 的 test 参数实现的。
Maven 默认只允许指定一个主 Java 代码目录和一个测试 Java 代码目录,虽然这其实是个应当尽量遵守的约定,但偶尔你还是会希望能够指定多个 源码目录(例如为了应对遗留项目),build-helper-maven-plugin 的 add-source 目标就是服务于这个目的,通常它被绑定到 默认生命周期的 generate-sources 阶段以添加额外的源码目录。需要强调的是,这种做法还是不推荐的,因为它破坏了 Maven 的约定,而且可能会遇到其他严格遵守约定的插件工具无法正确识别额外的源码目录。
build-helper-maven-plugin 的另一个非常有用的目标是 attach-artifact,使用该目标你可以以 classifier 的形式选取部分项目文件生成附属构件,并同时 install 到本地仓库,也可以 deploy 到远程仓库。
exec-maven-plugin 很好理解,顾名思义,它能让你运行任何本地的系统程序,在某些特定情况下,运行一个 Maven 外部的程序可能就是最简单的问题解决方案,这就是exec:exec的 用途,当然,该插件还允许你配置相关的程序运行参数。除了 exec 目标之外,exec-maven-plugin 还提供了一个 java 目标,该目标要求你 提供一个 mainClass 参数,然后它能够利用当前项目的依赖作为 classpath,在同一个 JVM 中运行该 mainClass。有时候,为了简单的 演示一个命令行 Java 程序,你可以在 POM 中配置好 exec-maven-plugin 的相关运行参数,然后直接在命令运行mvn exec:java 以查看运行效果。
在进行 Web 开发的时候,打开浏览器对应用进行手动的测试几乎是无法避免的,这种测试方法通常就是将项目打包成 war 文件,然后部署到 Web 容器 中,再启动容器进行验证,这显然十分耗时。为了帮助开发者节省时间,jetty-maven-plugin 应运而生,它完全兼容 Maven 项目的目录结构,能够周期性地检查源文件,一旦发现变更后自动更新到内置的 Jetty Web 容器中。做一些基本配置后(例如 Web 应用的 contextPath 和自动扫描变更的时间间隔),你只要执行 mvn jetty:run ,然后在 IDE 中修改代码,代码经 IDE 自动编译后产生变更,再由 jetty-maven-plugin 侦测到后更新至 Jetty 容器,这时你就可以直接 测试 Web 页面了。需要注意的是,jetty-maven-plugin 并不是宿主于 Apache 或 Codehaus 的官方插件,因此使用的时候需要额外 的配置settings.xml
的 pluginGroups 元素,将 org.mortbay.jetty 这个 pluginGroup 加入。
很多 Maven 用户遇到过这样一个问题,当项目包含大量模块的时候,为他们集体更新版本就变成一件烦人的事情,到底有没有自动化工具能帮助完成这件 事情呢?(当然你可以使用 sed 之类的文本操作工具,不过不在本文讨论范围)答案是肯定的,versions-maven- plugin 提供了很多目标帮助你管理 Maven 项目的各种版本信息。例如最常用的,命令 mvn versions:set -DnewVersion=1.1-SNAPSHOT 就能帮助你把所有模块的版本更新到 1.1-SNAPSHOT。该插件还提供了其他一些很有用的目标,display-dependency- updates 能告诉你项目依赖有哪些可用的更新;类似的 display-plugin-updates 能告诉你可用的插件更新;然后 use- latest-versions 能自动帮你将所有依赖升级到最新版本。最后,如果你对所做的更改满意,则可以使用 mvn versions:commit 提交,不满意的话也可以使用 mvn versions:revert 进行撤销。
常用 maven 命令清单:
生命周期 | 阶段描述 |
---|---|
mvn validate | 验证项目是否正确,以及所有为了完整构建必要的信息是否可用 |
mvn generate-sources | 生成所有需要包含在编译过程中的源代码 |
mvn process-sources | 处理源代码,比如过滤一些值 |
mvn generate-resources | 生成所有需要包含在打包过程中的资源文件 |
mvn process-resources | 复制并处理资源文件至目标目录,准备打包 |
mvn compile | 编译项目的源代码 |
mvn process-classes | 后处理编译生成的文件,例如对 Java 类进行字节码增强(bytecode enhancement) |
mvn generate-test-sources | 生成所有包含在测试编译过程中的测试源码 |
mvn process-test-sources | 处理测试源码,比如过滤一些值 |
mvn generate-test-resources | 生成测试需要的资源文件 |
mvn process-test-resources | 复制并处理测试资源文件至测试目标目录 |
mvn test-compile | 编译测试源码至测试目标目录 |
mvn test | 使用合适的单元测试框架运行测试。这些测试应该不需要代码被打包或发布 |
mvn prepare-package | 在真正的打包之前,执行一些准备打包必要的操作。这通常会产生一个包的展开的处理过的版本(将会在 Maven 2.1+中实现) |
mvn package | 将编译好的代码打包成可分发的格式,如 JAR,WAR,或者 EAR |
mvn pre-integration-test | 执行一些在集成测试运行之前需要的动作。如建立集成测试需要的环境 |
mvn integration-test | 如果有必要的话,处理包并发布至集成测试可以运行的环境 |
mvn post-integration-test | 执行一些在集成测试运行之后需要的动作。如清理集成测试环境。 |
mvn verify | 执行所有检查,验证包是有效的,符合质量规范 |
mvn install | 安装包至本地仓库,以备本地的其它项目作为依赖使用 |
mvn deploy | 复制最终的包至远程仓库,共享给其它开发人员和项目(通常和一次正式的发布相关) |
示例:最常用的 maven 构建命令
1 | mvn clean install -Dmaven.test.skip=true -B -U |
清理本地输出物,并构建 maven 项目,最后将输出物归档在本地仓库。
:bulb: 想了解更多 maven 命令行细节可以参考官方文档: