Java技术体系和tomcat初识

拖了一个学期结果到最后还是要来学习一下的东西…现在看来Java还是绕不过去的一个技术点啊.

Write Once, Run Anywhere

来看看Java

如果说我们把编程语言分成两类: 系统级别和应用级别开发的话, 归属到系统级别的有C, C++, go, erlang等等. 而另外应用级别开发的, 我们可以把C#, Java, Python, Ruby, Perl这些放进去. 对于Ruby, Perl这些, 他们有自己的解释器, 而对于Java和python而言, 这一层面上有一点细微的不同, 他们的解释器或者说虚拟机, 更加底层. 这些虚拟机为这些语言提供了库级别的一些虚拟化.

说道Java, 想到的第一点关键字可能就是动态网站. 这里我们所说的动态网站有两种, 一种是客户端动态, 也就是下载源码到本地, 接着借由用户所安装的编译器或者是执行组件来在本地进行加载, 另外一种就是我们的服务端动态网站. 这一种涉及到了一个协议叫做CGI 也就是通用网关接口. 并且同时, 他还引入了webapp server的概念, 例如, 能够运行jsp的容器有: tomcat, jboss, jetty 运行PHP的php-fpm等等.

Java也就是一种一个应用型语言, 最初是由SUN公司开发的. 并且Java语言诞生的缘由其实是为了开发机顶盒. 在上世纪90年代, web服务器技术正在兴起, 而HTML这种静态内容就显得不是这么满意, 于是这个环境下, 就希望能够获得一些动态的展示效果, 而Java一次编译到处运行的特点就显得十分合适, 只要安装了Java虚拟机, 就可以运行Java程序.

但是, 这种情况下, 如果有人开发了一些恶意的程序试图劫持客户机的话, 是十分容易就会成功的, 因此这种Applet技术就很快的没有了. 由客户端运行不行, 但是由服务端就行了啊. 所以后来这种技术又重新被使用了起来.

在1995年, 发布了Java的第一个版本Java1.0. 一年后发布了第一个版本的Java开发包, 即JDK. 并且包含了一个JVM, 但是这里的JVM性能低下启动速度缓慢, 此时包中包含了除了上面的JVM, 还包括Applet和AWT.

Sun把Java分成了三个方向, 这个时候Java是第二版所以就是Java 2. 而这三个就是我们熟知的: J2EE, J2SE, J2ME. 这其中最失败就是最后一个, 也就是移动端. 事实上, 我们现在使用的Java虚拟机, 并不是上面出现的那个JVM, 我们把上面的那个虚拟机命名为Sun Classic VM. 我们现在使用的虚拟机主要是由HotSpot所研发的.

到了2006年, Sun开源了Java技术, 遵循GPL规范, 并且建立了OpenJDK组织来管理这些代码. 到JDK1.7两者基本没有实质性的变化. 我们在Linux所下载的JDK版本其实就是OpenJDK版本的.

Java体系结构

对于我们今天要说的Java体系结构, 主要有这些:

  • Java编程语言
  • Java Class文件格式
  • Java API
  • Java VM

对于Java代码而言, 我们为了能够运行, 需要一个编译器的帮助, 将我们所编写的源代码编译成字节码文件, 也叫做Java类文件, 这些我们自己编写源代码生成的类文件可能也会需要引入java一些内置的类, 这些类也同样都是class文件. 接着我们的jvm同样引入这些字节码文件(其实就是api), 一起放在执行引擎里运行, 最后还是需要调用C库和系统调用来实现各项功能.

考虑到不同操作系统的运行库不一样, 并且还有很多系统调用层面上的差异, 我们的Java API也是需要调用不同的本地方法的. 这样看来, Java的API就更像是一个翻译官.

接下来我们就来看一下JVM的一些核心组成部分, 我们知道作为一个加载运行类文件肯定是要有一个boot loader和执行引擎的. 如果我们运行多个Java程序, 他们所使用的Java虚拟机都是一样的, 这些程序运行所需要的内存空间都是由JVM申请和管理的, 这也就意味着, 我们在写Java程序的时候是不需要考虑到内存空间的组织的, 但是对于Java程序而言, 由于这些内存空间的操作是由JVM来进行的, 也就是说程序员不要进行内存的管理. JVM有自己的垃圾回收机制, 主要面向堆空间, 因为我们的对象创建都在堆内存中.

那么这个虚拟机运行时主要分成两种模式: 守护线程和非守护线程两种, 所有用户创建的都是作为非守护线程存在的, 只有那些类似垃圾回收机制的服务才是守护线程. 刚刚我们说了JVM的堆内存, 除了堆区域, JVM运行时区域还有别的:

  • 方法区: 线程共享, 用于存储被加载的类信息. 常量, 静态变量等等
  • Java栈: 这些就是线程私有的, 存放线程自己的局部变量等信息
  • PC寄存器: 线程独占的内存空间
  • 本地方法栈

不过扯了这么多, 其实最后是想说的是对于Java服务端应用的实现, 是通过将html标签硬编码到应用程序中, 也就是我们熟悉的JSP. 而服务端想要实现这种, 就一定需要Web Container的帮助.

JSP的运行过程

首先来看这么一个过程图吧:

jsp_processing

首先客户发出请求, 请求达到我们的包含JSP容器的Web服务器的时候, 会去从磁盘上取得请求的源文件, 接着通过翻译将我们的jsp代码转换成为java源文件, 最后通过编译会得到字节码文件, 继而将字节码文件回到JSP容器中执行得到结果返回给客户端.

现在说来, 对于JSP容器的开源实现主要有三个, Tomcat, Resinjetty. 其中就tomcat7.0,Jetty7和Resin3.1的比较, 相对来说Resin是性能最高的一个, 还支持负载均衡. 不过仅仅可用来做开源和学习目的. 而Jetty是相对来说性能最不好的那一个.

接下来我们就来进入今天的主题吧, 来了解下Tomcat.

Tomcat初识

Tomcat包含了三个组件, 分别是迎来提供servlet容器的Catalina, 用来做HTTP协议的连接器的Coyote, 以及一个JSP引擎Jasper. 而Tomcat就是使用Java语言进行的开发.

接下来我们来看一下Tomcat的组成部分, 首先最外层的肯定就是Server组件, 接着内层是一个服务提供组件, 再向内层就是engine组件, 而每一个engine又可以包含多个Host组件, 每一个Host中就是各种上下文环境的配置了, 而之前说的连接器则可以连接Server, Service和Engine, 接受解析用户的请求, 接着映射到内部组件中.

由于Tomcat是使用Java语言进行开发的, 所以面向对象的思想会体现在Tomcat中. 例如, 我们上面说的Server就相当于是一个Tomcat实例, 每启动一个Tomcat就相当于是一个新的实例被生成.

Tomcat最核心的组件就是engine组件, 他是用来执行jsp或者servlet代码的, 但是由于engine无法去接受和处理用户的请求, 所以就需要一个连接器来实现, 但是这个连接器接受并且解析到了用户的请求但是确保没有办法把这个传达给Engine, 所以这个时候就有需要一个辅助的组件, 也就是Service. Service负责将连接器处理的结果传达给engine组件. 一个engine组件可以拥有多个连接器, 但是一个连接器只能跟一个engine建立关系. 正因为此, 每一个Service里面只能有一个engine组件. 最后还差一个Host组件, 这个东西可以近乎用httpd中的虚拟主机来理解. 至于Context就是类似httpd中的alias.

以上就是各个组件之间的关系. 每一个组件都使用来实现.

使用大概XML格式的配置文件来表示的话就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<server>
<service>
<connector />
<connector />
...
<engine>
<host>
<context />
...
</host>
...
</engine>
</service>
</server>

这也就是配置文件的组织结构框架.

由于Tomcat是存在web server的, 所以支持standalone模式运行, 只不过说到底, tomcat需要维护内部的JSP代码运行, 如果还要再消耗资源来控制web服务器的并发的话, 是一件非常累的事情. ( 不过,tomcat的http server是使用Java内置的线程模型开发的, 所以并发效果还是可以的. 不过我们还是要专人专事的原则. ) 所以一般情况下, 我们使用反向代理的方式来运行. 例如在前端加上一个Nginx或者httpd. 更进阶的, 我们进行独立网络配置, 分开主机来进行, 只不过在这种时候, 我们就需要考虑之前说的关于session保持的问题了.

事实上, 上面说的三种运行模式, 就是tomcat支持的三种运行模式:

  • standalone: 通过内置的web server来处理用户请求
  • proxy: 由专门的web server服务客户端的http请求
    • in-process: 同一主机
    • network: 不同主机

Tomcat安装和使用

如果直接使用yum进行安装的话, 则使用的tomcat版本是7.0. 据说这个版本是目前比较稳定的一个. 如果嫌这个版本过于旧, 则可以到官方站点去下载二进制包直接解压使用.

需要说明的是, 由于tomcat是使用Java开发的, 因此我们还需要下载Java的开发工具包, 不过这都是很简单的了, 我们直接跳过吧. 但是给个小提醒, Linux下当然是可以进行多版本的Java共存的:

1
2
3
4
5
6
7
[root@VM-node0 ~]# cd /usr/java/
[root@VM-node0 java]# ls -lh
total 0
lrwxrwxrwx 1 root root 16 Sep 15 16:18 default -> /usr/java/latest
drwxr-xr-x 8 root root 258 Sep 15 16:18 jdk1.8.0_181-amd64
lrwxrwxrwx 1 root root 28 Sep 15 16:18 latest -> /usr/java/jdk1.8.0_181-amd64

这里有两个链接, 可以看到, 默认使用的就是最新版本的java, 而这个最新的指向就是我们安装的1.8版本. 另外, 和Windows一样, 你也需要配置环境变量, 我们在/etc/profile.d下面创建一个新的脚本文件:

1
2
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH

这里我们使用的是默认版本的Java, 你也可以指向最新的或者是指定版本的.

使用-version选项, 可以查看当前的Java版本和对应使用的虚拟机的版本. 例如我的:

1
2
3
4
5
[root@VM-node0 ~]# java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

可以看到使用的是混合模式的HotSpot虚拟机.

除此之外, 我们还需要配置一下Catalina的环境变量, 也就是CATALINA_HOME. 配置结束之后, 我们重读一下刚刚创建的两个新shell脚本, 之后就可以使用tomcat的一些内置脚本了, 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@VM-node0 ~]# version.sh 
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/java/default
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Server version: Apache Tomcat/8.5.34
Server built: Sep 4 2018 22:28:22 UTC
Server number: 8.5.34.0
OS Name: Linux
OS Version: 3.10.0-862.11.6.el7.x86_64
Architecture: amd64
JVM Version: 1.8.0_181-b13
JVM Vendor: Oracle Corporation

这会显示当前tomcat的一些环境变量, 系统信息和使用的Java虚拟机版本.

除此之外, 我们可以使用一个统一的脚本通过传递参数来实现不同的功能, 这个脚本就是catalina.sh.

1
2
3
4
5
[root@VM-node0 tomcat]# ls bin/
bootstrap.jar catalina-tasks.xml configtest.bat digest.bat setclasspath.sh startup.bat tomcat-native.tar.gz version.bat
catalina.bat commons-daemon.jar configtest.sh digest.sh shutdown.bat startup.sh tool-wrapper.bat version.sh
catalina.sh commons-daemon-native.tar.gz daemon.sh setclasspath.bat shutdown.sh tomcat-juli.jar tool-wrapper.sh

也可以通过传递一个help参数来看看支持哪些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@VM-node0 tomcat]# catalina.sh --help
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/java/default
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
debug Start Catalina in a debugger
debug -security Debug Catalina with a security manager
jpda start Start Catalina under JPDA debugger
run Start Catalina in the current window
run -security Start in the current window with security manager
start Start Catalina in a separate window
start -security Start in a separate window with security manager
stop Stop Catalina, waiting up to 5 seconds for the process to end
stop n Stop Catalina, waiting up to n seconds for the process to end
stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running
configtest Run a basic syntax check on server.xml - check exit code for result
version What version of tomcat are you running?
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined

之后我们来看看Tomcat的目录结构吧, 大体上都是见名知意的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@VM-node0 tomcat]# tree . -L 1
.
├── bin
├── BUILDING.txt
├── conf
├── CONTRIBUTING.md
├── lib
├── LICENSE
├── logs
├── NOTICE
├── README.md
├── RELEASE-NOTES
├── RUNNING.txt
├── temp
├── webapps
└── work

7 directories, 7 files

其中, bin就是我们使用的脚本所在和启动时候需要使用到的类, lib就是他的类库,. 而conf则是配置文件存放目录, logs就是日志文件, webapps就是应用程序的默认部署目录, work则是将原始代码转化之后的存放目录, 每一次的源代码的变化就会导致这个目录内容的变化, 最后的temp就是临时文件目录.

直接通过catalina.sh start就可以启动tomcat, 默认的监听端口是8080, 所以你要保证别出现冲突了.

现在我们稍微来说一下关于Tomcat的配置文件, 至于更详细的配置我们下次再来说.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@VM-node0 tomcat]# tree conf/
conf/
├── Catalina
│   └── localhost
├── catalina.policy # 这个就是再执行安全管理的情况下加载的安全运行策略
├── catalina.properties # 用于设定Java相关的参数和对于JVM的调优相关
├── context.xml # 每一个webapp的默认配置, 如果在app自己的`WEB-INF`目录下没有自己的配置, 就会使用这个
├── jaspic-providers.xml
├── jaspic-providers.xsd
├── logging.properties # 配置日志记录等
├── server.xml # 主配置文件
├── tomcat-users.xml # 对于用户认证的账号和密码配置
├── tomcat-users.xsd
└── web.xml # 为每个WebApp提供部署配置

2 directories, 10 files