Java Web 开发详解

Posted wespten

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Web 开发详解相关的知识,希望对你有一定的参考价值。

一、Web基础

1、Web 概述

Web 在英文中的含义是网状物、网络。在计算机领域,它通常指的是后者,即网络。

像 WWW 是由 3 个单词组成的,即World Wide Web,中文含义是万维网。

他们的出现都是为了让我们在网络的世界中获取资源,这些资源的存放之处,我们称之为网站。我们通过输入网站的地址(即网址),就可以访问网站中提供的资源。

在网上我们能访问到的内容全是资源(不区分局域网还是广域网)。只不过,不同类型的资源展示的效果不一样。资源可以分为静态资源和动态资源:

  • 静态资源指的是,网站中提供给人们展示的资源是一成不变的,也就是说不同人或者在不同时间,看到的内容都是一样的。例如:我们看到的新闻,网站的使用手册,网站功能说明文档等等。而作为开发者,我们编写的 html、css、js、图片、多媒体等,都可以称为静态资源。

  • 动态资源指的是,网站中提供给人们展示的资源是由程序产生的,在不同的时间或者用不同的人员由于身份的不同,所看到的内容是不一样的。例如:我们在12306上购买火车票,火车票的余票数由于时间的变化,会逐渐的减少,直到最后没有余票。还有,我们在 CSDN 上下载资料,只有登录成功后,且积分足够时才能下载。否则就不能下载,这就是访客身份和会员身份的区别。作为开发人员,我们编写的 JSP、servlet、php、ASP 等都是动态资源。

关于广域网和局域网的划分,广域网指的就是万维网,也就是我们说的互联网;局域网是指的是在一定范围之内可以访问的网络,出了这个范围,就不能再使用的网络。

2、系统结构

  • 根据基础结构划分:C/S 结构,B/S 结构两类。
  • 根据技术选型划分:Model1 模型,Model2 模型,MVC 模型、三层架构 + MVC 模型。
  • 根据部署方式划分:一体化架构,垂直拆分架构,分布式架构,流动计算架构,微服务架构。

1. C/S 结构

它指的是客户端——服务器的方式,其中 C 代表着 Client,S 代表着服务器。

C/S 结构的系统设计图如下:

2. B/S 结构

它指的是浏览器——服务器的方式,其中 B 代表着 Browser,S 代表着服务器。

B/S 结构的系统设计图如下:

3. 两种结构的区别及优略

两种结构的区别:

  1. 硬件环境不同:C/S 通常是建立在专用的网络或小范围的网络环境上(即局域网),且必须要安装客户端;而 B/S 是建立在广域网上的,适应范围强,通常有操作系统和浏览器就行。

  2. C/S 结构比 B/S 结构更安全,因为用户群相对固定,对信息的保护更强。

  3. B/S 结构维护升级比较简单,而 C/S 结构维护升级相对困难。

优势:

  1. C/S:是能充分发挥客户端PC的处理能力,很多工作可以在客户端处理后再提交给服务器。对应的优点就是客户端响应速度快。

  2. B/S:总体拥有成本低、维护方便、分布性强、开发简单,可以不用安装任何专门的软件就能 实现在任何地方进行操作,客户端零维护,系统的扩展非常容易,只要有一台能上网的电脑就能使用。

3、HTTP协议

1. HTTP协议简介

在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,所以:

  • HTML是一种用来定义网页的文本,会HTML,就可以编写网页;

  • HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

HTTP协议是一个基于TCP协议之上的请求-响应协议,它非常简单,我们先使用Chrome浏览器查看新浪首页,然后选择View - Developer - Inspect Elements就可以看到HTML:

切换到Network,重新加载页面,可以看到浏览器发出的每一个请求和响应: 

使用Chrome浏览器可以方便地调试Web应用程序。

对于Browser来说,请求页面的流程如下:

  1. 与服务器建立TCP连接;
  2. 发送HTTP请求;
  3. 收取HTTP响应,然后把网页在浏览器中显示出来。

浏览器发送的HTTP请求如下:

GET / HTTP/1.1
Host: www.sina.com.cn
User-Agent: Mozilla/5.0 xxx
Accept: */*
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8

其中,第一行表示使用GET请求获取路径为/的资源,并使用HTTP/1.1协议,从第二行开始,每行都是以Header: Value形式表示的HTTP头,比较常用的HTTP Header包括:

  • Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
  • User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似Mozilla/5.0 ... Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko
  • Accept:表示浏览器能接收的资源类型,如text/*image/*或者*/*表示所有;
  • Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
  • Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate, br

服务器的响应如下:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 21932
Content-Encoding: gzip
Cache-Control: max-age=300

<html>...网页数据...

服务器响应的第一行总是版本号+空格+数字+空格+文本,数字表示响应代码,其中2xx表示成功,3xx表示重定向,4xx表示客户端引发的错误,5xx表示服务器端引发的错误。数字是给程序识别,文本则是给开发者调试使用的。

常见的响应代码有:

  • 200 OK:表示成功;
  • 301 Moved Permanently:表示该URL已经永久重定向;
  • 302 Found:表示该URL需要临时重定向;
  • 304 Not Modified:表示该资源没有修改,客户端可以使用本地缓存的版本;
  • 400 Bad Request:表示客户端发送了一个错误的请求,例如参数无效;
  • 401 Unauthorized:表示客户端因为身份未验证而不允许访问该URL;
  • 403 Forbidden:表示服务器因为权限问题拒绝了客户端的请求;
  • 404 Not Found:表示客户端请求了一个不存在的资源;
  • 500 Internal Server Error:表示服务器处理时内部出错,例如因为无法连接数据库;
  • 503 Service Unavailable:表示服务器此刻暂时无法处理请求。

从第二行开始,服务器每一行均返回一个HTTP头。服务器经常返回的HTTP Header包括:

  • Content-Type:表示该响应内容的类型,例如text/htmlimage/jpeg
  • Content-Length:表示该响应内容的长度(字节数);
  • Content-Encoding:表示该响应压缩算法,例如gzip
  • Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒。

HTTP请求和响应都由HTTP Header和HTTP Body构成,其中HTTP Header每行都以\\r\\n结束。如果遇到两个连续的\\r\\n,那么后面就是HTTP Body。浏览器读取HTTP Body,并根据Header信息中指示的Content-Type、Content-Encoding等解压后显示网页、图像或其他内容。

通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了javascript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。

关于HTTP协议的详细内容,请参考HTTP权威指南一书,或者Mozilla开发者网站

这里我们以服务器的身份响应客户端请求,编写服务器程序来处理客户端请求通常就称之为Web开发。

4、编写HTTP Server

我们来看一下如何编写HTTP Server。一个HTTP Server本质上是一个TCP服务器,我们先用TCP编程的多线程实现的服务器端框架:

public class Server 
    public static void main(String[] args) throws IOException 
        ServerSocket ss = new ServerSocket(8080); // 监听指定端口
        System.out.println("server is running...");
        for (;;) 
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        
    


class Handler extends Thread 
    Socket sock;

    public Handler(Socket sock) 
        this.sock = sock;
    

    public void run() 
        try (InputStream input = this.sock.getInputStream()) 
            try (OutputStream output = this.sock.getOutputStream()) 
                handle(input, output);
            
         catch (Exception e) 
            try 
                this.sock.close();
             catch (IOException ioe) 
            
            System.out.println("client disconnected.");
        
    

    private void handle(InputStream input, OutputStream output) throws IOException 
        var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        // TODO: 处理HTTP请求
    

只需要在handle()方法中,用Reader读取HTTP请求,用Writer发送HTTP响应,即可实现一个最简单的HTTP服务器。编写代码如下:

private void handle(InputStream input, OutputStream output) throws IOException 
    System.out.println("Process new http request...");
    var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
    var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
    // 读取HTTP请求:
    boolean requestOk = false;
    String first = reader.readLine();
    if (first.startsWith("GET / HTTP/1.")) 
        requestOk = true;
    
    for (;;) 
        String header = reader.readLine();
        if (header.isEmpty())  // 读取到空行时, HTTP Header读取完毕
            break;
        
        System.out.println(header);
    
    System.out.println(requestOk ? "Response OK" : "Response Error");
    if (!requestOk) 
        // 发送错误响应:
        writer.write("HTTP/1.0 404 Not Found\\r\\n");
        writer.write("Content-Length: 0\\r\\n");
        writer.write("\\r\\n");
        writer.flush();
     else 
        // 发送成功响应:
        String data = "<html><body><h1>Hello, world!</h1></body></html>";
        int length = data.getBytes(StandardCharsets.UTF_8).length;
        writer.write("HTTP/1.0 200 OK\\r\\n");
        writer.write("Connection: close\\r\\n");
        writer.write("Content-Type: text/html\\r\\n");
        writer.write("Content-Length: " + length + "\\r\\n");
        writer.write("\\r\\n"); // 空行标识Header和Body的分隔
        writer.write(data);
        writer.flush();
    

这里的核心代码是,先读取HTTP请求,这里我们只处理GET /的请求。当读取到空行时,表示已读到连续两个\\r\\n,说明请求结束,可以发送响应。发送响应的时候,首先发送响应代码HTTP/1.0 200 OK表示一个成功的200响应,使用HTTP/1.0协议,然后,依次发送Header,发送完Header后,再发送一个空行标识Header结束,紧接着发送HTTP Body,在浏览器输入http://localhost.com:8080/就可以看到响应页面:

HTTP目前有多个版本,1.0是早期版本,浏览器每次建立TCP连接后,只发送一个HTTP请求并接收一个HTTP响应,然后就关闭TCP连接。由于创建TCP连接本身就需要消耗一定的时间,因此,HTTP 1.1允许浏览器和服务器在同一个TCP连接上反复发送、接收多个HTTP请求和响应,这样就大大提高了传输效率。

我们注意到HTTP协议是一个请求-响应协议,它总是发送一个请求,然后接收一个响应。能不能一次性发送多个请求,然后再接收多个响应呢?HTTP 2.0可以支持浏览器同时发出多个请求,但每个请求需要唯一标识,服务器可以不按请求的顺序返回多个响应,由浏览器自己把收到的响应和请求对应起来。可见,HTTP 2.0进一步提高了传输效率,因为浏览器发出一个请求后,不必等待响应,就可以继续发下一个请求。

HTTP 3.0为了进一步提高速度,将抛弃TCP协议,改为使用无需创建连接的UDP协议,目前HTTP 3.0仍然处于实验阶段。

总结:

使用B/S架构时,总是通过HTTP协议实现通信,Web开发通常是指开发服务器端的Web应用程序。

二、JavaEE和Tomcat

1、JavaEE 规范

JavaEE规范是J2EE规范的新名称,早期被称为 J2EE 规范,其全称是 Java 2 Platform Enterprise Edition,是由 SUN 公司领导、各厂家共同制定并得到广泛认可的工业标准(JCP 组织成员)。

其中,JCP 组织(官网)的全称是 Java Community Process,是一个开放的国际组织,主要由 Java 开发者以及被授权者组成,职能是发展和更新,成立于 1998 年。

JavaEE 规范是众多 Java 开发技术的总称。这些技术规范都是沿用自 J2EE 的,一共包括了 13 个技术规范,如jsp/servlet、jndi、jaxp、jdbc、jni、jaxb、jmf、jta、jpa、EJB 等。

JavaEE 的版本是延续了 J2EE 的版本,但是没有继续采用其命名规则。J2EE 的版本从 1.0 开始到 1.4 结束,而 JavaEE 版本是从 JavaEE 5 版本开始的,详情请参考:JavaEE8 规范概览

2、Tomcat

1. Tomcat 简介

服务器的概念非常的广泛,它可以指代一台特殊的计算机(相比普通计算机运行更快、负载更高、价格更贵),也可以指代用于部署网站的应用。

以下说的服务器,其实是 Web 服务器,或者应用服务器,它本质就是一个软件,一个应用。作用就是发布我们的应用(工程),让用户可以通过浏览器访问我们的应用。

常见的应用服务器:

服务器名称说明
weblogic实现了 javaEE 规范,重量级服务器,又称为 javaEE 容器
websphereAS实现了 javaEE 规范,重量级服务器
JBOSSAS实现了 JavaEE 规范,重量级服务器,免费
Tomcat实现了 jsp/servlet 规范,是一个轻量级服务器,开源免费

2. Tomcat下载与安装

Tomcat 官网下载地址

Tomcat 各版本所需支持:

Tomcat 目录结构:

3. Tomcat 基础使用

Tomcat实际上也是一个Java程序,我们看看Tomcat的启动流程:

  1. 启动JVM并执行Tomcat的main()方法;
  2. 加载war并初始化Servlet;
  3. 正常服务。

启动Tomcat无非就是设置好classpath并执行Tomcat某个jar包的main()方法,我们完全可以把Tomcat的jar包全部引入进来,然后自己编写一个main()方法,先启动Tomcat,然后让它加载我们的webapp就行。

我们新建一个web-servlet-embedded工程,编写pom.xml如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>web-servlet-embedded</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
        <tomcat.version>10.1.1</tomcat.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>$tomcat.version</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>$tomcat.version</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

其中,<packaging>类型仍然为war,引入依赖tomcat-embed-core和tomcat-embed-jasper,引入的Tomcat版本<tomcat.version>为10.1.1。

不必引入Servlet API,因为引入Tomcat依赖后自动引入了Servlet API。因此,我们可以正常编写Servlet如下:

@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet 
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        resp.setContentType("text/html");
        String name = req.getParameter("name");
        if (name == null) 
            name = "world";
        
        PrintWriter pw = resp.getWriter();
        pw.write("<h1>Hello, " + name + "!</h1>");
        pw.flush();
    

然后,我们编写一个main()方法,启动Tomcat服务器:

public class Main 
    public static void main(String[] args) throws Exception 
        // 启动Tomcat:
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(Integer.getInteger("port", 8080));
        tomcat.getConnector();
        // 创建webapp:
        Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
        WebResourceRoot resources = new StandardRoot(ctx);
        resources.addPreResources(
                new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
        ctx.setResources(resources);
        tomcat.start();
        tomcat.getServer().await();
    

这样,我们直接运行main()方法,即可启动嵌入式Tomcat服务器,然后,通过预设的tomcat.addWebapp("", new File("src/main/webapp"),Tomcat会自动加载当前工程作为根webapp,可直接在浏览器访问http://localhost:8080/:

通过main()方法启动Tomcat服务器并加载我们自己的webapp有如下好处:

  1. 启动简单,无需下载Tomcat或安装任何IDE插件;
  2. 调试方便,可在IDE中使用断点调试;
  3. 使用Maven创建war包后,也可以正常部署到独立的Tomcat服务器中。

对SpringBoot有所了解的童鞋可能知道,SpringBoot也支持在main()方法中一行代码直接启动Tomcat,并且还能方便地更换成Jetty等其他服务器。

推荐使用main()方法启动嵌入式Tomcat服务器并加载当前工程的webapp,便于开发调试,且不影响打包部署,能极大地提升开发效率。

Tomcat启动报错解决:

问题 1:启动一闪而过。

  • 原因:没有配置环境变量。

  • 解决办法:配置上 JAVA_HOME 环境变量。

问题 2:报错信息 Address already in use : JVM_Bind。

  • 原因:端口被占用。

  • 解决办法:找到占用该端口的应用。

    • 已占的进程不重要:使用 cmd 命令:netstat -a -o 查看 pid,在任务管理器中结束占用端口的进程。
    • 已占的进程很重要:修改 Tomcat 自己的端口号。在 Tomcat 目录下\\conf\\server.xml中修改配置:

问题 3:启动时很多异常,但能正常启动。

  • 原因:Tomcat 中部署着很多项目,每次启动这些项目都会启动。而这些项目中有启动报异常的。

  • 解决办法:

    • 能找到报异常的项目,就把它从发布目录中移除。
    • 不能确定报异常的项目,就重新部署一个新的 Tomcat。

其它问题:

  • 例如启动产生异常,但是不能正常启动。此时就需要部署一个新的 Tomcat 启动,来确定是系统问题,还是 Tomcat 的问题。

  • 此时就需要具体问题,具体分析,然后再对症解决。

4. IDEA 集成 Tomcat

5. Tomcat 配置虚拟目录

虚拟目录的配置,支持两种方式:第一种是通过在主配置文件中添加标签实现;第二种是通过写一个独立配置文件实现。

方式一:在server.xml的<Host>元素中加一个<Context path="" docBase=""/>元素。

  • path:访问资源 URI。URI 名称可以随便起,但是必须在前面加上一个“/”。
  • docBase:资源所在的磁盘物理地址。

方式二:是写一个独立的xml文件,该文件名可以随便起,但在文件内写一个<Context/>元素。

  • 该文件要放在 Tomcat 目录中的conf\\Catalina\\localhost\\目录下。
  • 需要注意的是,在使用了独立的配置文件之后,访问资源 URI 就变成了/+文件的名称,而Contextpath属性就失效了。

6. Tomcat 配置虚拟主机(域名)

在<Engine>元素中添加一个<Host name="" appBase="" unparkWARs="" autoDeploy="" />,其中:

  • name:指定主机的名称。
  • appBase:当前主机的应用发布目录。
  • unparkWARs:启动时是否自动解压 war 包。
  • autoDeploy:是否自动发布。

配置示例如下:

<Host name="www.itcast.cn" appBase="D:\\itcastapps" unpackWARs="true" autoDeploy="true"/>

<Host name="www.itheima.com" appBase="D:\\itheimaapps" unpackWARs="true" autoDeploy="true"/>

7. Tomcat 默认项配置

1)配置默认端口

Tomcat 服务器的主配置文件中配置着访问端口,它在配置文件中写的值是:8080。配置方式如下:

<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

2)配置默认应用

有两种方式可以配置默认应用。

方式一:把要作为默认应用的应用,名称改为ROOT,放到webapps目录中。

方式二:写一个独立的配置文件,文件名称为ROOT.xml

  • 注意:ROOT必须大写。当使用了独立的ROOT.xml文件时,webappsROOT应用就不再是默认应用。

3)配置默认主页

首先要明确的是,配置默认主页是针对应用所说的,是应用的默认主页。

在应用的 web.xml 中配置:

<welcome-file-list>
    <welcome-file>默认主页</welcome-file>
</welcome-file-list>

例如:

<welcome-file-list>
    <!-- 有多个默认页时,先找到的就显示,不再往下找 -->
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

3、Java Web 应用

1. Java Web 工程概述

JavaWeb应用是一个全新的应用种类,这类应用程序指供浏览器访问的程序,通常也简称为 Web 应用。

一个 Web 应用由多个静态 Web 资源和动态 Web 资源组成,例如有 html、css、js 文件,jsp 文件、java 程序、支持的 jar 包、工程配置文件、图片、音视频等等。

Web 应用开发好后,若想供外界访问,需要把 Web 应用的所在目录,交给 Web 服务器管理(Tomcat 就是 Web 服务器之一),这个过程称之为虚似目录的映射。

2. Java Web 应用目录结构

myapp -------- 应用名称
    demo.html
    css/demo.css
    js/demo.js
	WEB-INF -------- 如果有 web.xml 或者 .class 文件时,该目录必须存在,且严格区分大小写。
	        -------- 该目录下的资源,客户端是无法直接访问的。
                -------- 目录中内容如下:
        classes 目录 -------- web 应用的 class 文件(加载顺序:我们的 class,lib 目录中的 jar 包,tomcat 的 lib 目录中的 jar 包。其优先级依次降低)
        lib 目录 -------- web 应用所需的 jar 包(tomcat 的 lib 目录下 jar 为所有应用共享)
        web.xml -------- web 应用的主配置文件

3. IDEA 创建 Java Web 应用工程

4. Java Web 应用部署

1)IDEA 部署

2)war 包发布

步骤一:使用命令jar -cvf war [需要打包的目录路径]打包 war 包。

步骤二:把打好的 war 包拷贝到 tomcat 的 webapps 目录中。 

步骤三:Tomcat 在启动时,会自动解压 war 包。 

三、Servlet 控制器

1、Java Web 设计模式

上面的例子,我们看到,编写HTTP服务器其实是非常简单的,只需要先编写基于多线程的TCP服务,然后在一个TCP连接中读取HTTP请求,发送HTTP响应即可。 

但是,要编写一个完善的HTTP服务器,以HTTP/1.1为例,需要考虑的包括:

  • 识别正确和错误的HTTP请求;
  • 识别正确和错误的HTTP头;
  • 复用TCP连接;
  • 复用线程;
  • IO异常处理;
  • ...

这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的HTML页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。

因此,在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能:

一个完整的Web应用程序的开发流程如下:

  1. 编写Servlet;
  2. 打包为war文件;
  3. 复制到Tomcat的webapps目录下;
  4. 启动Tomcat。

我们来实现一个最简单的Servlet:

// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet 
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException 
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, world!</h1>");
        // 最后不要忘记flush强制输出:
        pw.flush();
    

一个Servlet总是继承自HttpServlet,然后覆写doGet()或doPost()方法。注意到doGet()方法传入了HttpServletRequest和HttpServletResponse两个对象,分别代表HTTP请求和响应。我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequest和HttpServletResponse就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。

现在问题来了:Servlet API是谁提供?

Servlet API是一个jar包,我们需要通过Maven来引入它,才能正常编译。编写pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>web-servlet-hello</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>hello</finalName>
    </build>
</project>

注意到这个pom.xml与前面我们讲到的普通Java程序有个区别,打包类型不是jar,而是war,表示Java Web Application Archive:

<packaging>war</packaging>

引入的Servlet API如下:

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <scope>provided</scope>
</dependency>

注意到<scope>指定为provided,表示编译时使用,但不会打包到.war文件中,因为运行期Web服务器本身已经提供了Servlet API相关的jar包。

整个工程结构如下:

目录webapp目前为空,如果我们需要存放一些资源文件,则需要放入该目录。有的同学可能会问,webapp目录下是否需要一个/WEB-INF/web.xml配置文件?这个配置文件是低版本Servlet必须的,但是高版本Servlet已不再需要,所以无需该配置文件。

运行Maven命令mvn clean package,在target目录下得到一个hello.war文件,这个文件就是我们编译打包后的Web应用程序。

如果执行package命令遇到Execution default-war of goal org.apache.maven.plugins:maven-war-plugin:2.2:war failed错误时,可手动指定maven-war-plugin最新版本3.3.2,参考练习工程的pom.xml。

现在问题又来了:我们应该如何运行这个war文件?

普通的Java程序是通过启动JVM,然后执行main()方法开始运行。但是Web应用程序有所不同,我们无法直接运行war文件,必须先启动Web服务器,再由Web服务器加载我们编写的HelloServlet,这样就可以让HelloServlet处理浏览器发送的请求。

因此,我们首先要找一个支持Servlet API的Web服务器。常用的服务器有:

  • Tomcat:由Apache开发的开源免费服务器;
  • Jetty:由Eclipse开发的开源免费服务器;
  • GlassFish:一个开源的全功能JavaEE服务器。

还有一些收费的商用服务器,如Oracle的WebLogic,IBM的WebSphere

无论使用哪个服务器,只要它支持Servlet API 5.0(因为我们引入的Servlet版本是5.0),我们的war包都可以在上面运行。这里我们选择使用最广泛的开源免费的Tomcat服务器。

要运行我们的hello.war,首先要下载Tomcat服务器,解压后,把hello.war复制到Tomcat的webapps目录下,然后切换到bin目录,执行startup.sh或startup.bat启动Tomcat服务器:

$ ./startup.sh 
Using CATALINA_BASE:   .../apache-tomcat-10.1.x
Using CATALINA_HOME:   .../apache-tomcat-10.1.x
Using CATALINA_TMPDIR: .../apache-tomcat-10.1.x/temp
Using JRE_HOME:        .../jdk-17.jdk/Contents/Home
Using CLASSPATH:       .../apache-tomcat-10.1.x/bin/bootstrap.jar:...
Tomcat started.

在浏览器输入http://localhost:8080/hello/即可看到HelloServlet的输出:

细心的童鞋可能会问,为啥路径是/hello/而不是/?因为一个Web服务器允许同时运行多个Web App,而我们的Web App叫hello,因此,第一级目录/hello表示Web App的名字,后面的/才是我们在HelloServlet中映射的路径。

那能不能直接使用/而不是/hello/?毕竟/比较简洁。

答案是肯定的。先关闭Tomcat(执行shutdown.sh或shutdown.bat),然后删除Tomcat的webapps目录下的所有文件夹和文件,最后把我们的hello.war复制过来,改名为ROOT.war,文件名为ROOT的应用程序将作为默认应用,启动后直接访问http://localhost:8080/即可。

实际上,类似Tomcat这样的服务器也是Java编写的,启动Tomcat服务器实际上是启动Java虚拟机,执行Tomcat的main()方法,然后由Tomcat负责加载我们的.war文件,并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。如果Tomcat服务器收到的请求路径是/(假定部署文件为ROOT.war),就转发到HelloServlet并传入HttpServletRequest和HttpServletResponse两个对象。

因为我们编写的Servlet并不是直接运行,而是由Web服务器加载后创建实例运行,所以,类似Tomcat这样的Web服务器也称为Servlet容器。

在Servlet容器中运行的Servlet具有如下特点:

  • 无法在代码中直接通过new创建Servlet实例,必须由Servlet容器自动创建Servlet实例;
  • Servlet容器只会给每个Servlet类创建唯一实例;
  • Servlet容器会使用多线程执行doGet()或doPost()方法;
  • 在Servlet中定义的实例变量会被多个线程同时访问,要注意线程安全;
  • HttpServletRequest和HttpServletResponse实例是由Servlet容器传入的局部变量,它们只能被当前线程访问,不存在多个线程访问的问题;
  • 在doGet()或doPost()方法中,如果使用了ThreadLocal,但没有清理,那么它的状态很可能会影响到下次的某个请求,因为Servlet容器很可能用线程池实现线程复用;

总结:

  • 编写Web应用程序就是编写Servlet处理HTTP请求;
  • Servlet API提供了HttpServletRequest和HttpServletResponse两个高级接口来封装HTTP请求和响应;
  • Web应用程序必须按固定结构组织并打包为.war文件;
  • 需要启动Web服务器来加载我们的war包来运行Servlet。

2、Servlet 简介

Servlet 是 SUN 公司提供的一套规范,名称就叫 Servlet 规范,它也是 JavaEE 规范之一。我们可以通过访问官方 API 学习和查阅里面的内容。

打开官方 API 网址,在左上部分找到 javax.servlet 包,在左下部分找到 Servlet,如下图显示:

通过阅读 API,我们可以得到如下信息:

  1. Servlet 是一个运行在 Web 服务端的 Java 小程序。
  2. 它可以用于接收和响应客户端的请求,主要功能在于交互式地浏览和修改数据,生成动态的 Web 内容。
  3. 要想实现 Servlet 功能,可以实现 Servlet 接口、继承 GenericServlet 或者 HttpServlet。
  4. 每次请求都会执行 service 方法。
  5. Servlet 还支持配置。

Servlet 主要执行以下工作:

使用 Servlet,可以收集来自网页表单的用户输入,可以呈现来自数据库或者其他来源的记录,还可以动态地创建网页(JSP)。

  1. 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 Applet 或自定义的 HTTP 客户端程序的表单。
  2. 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 Cookies、媒体类型和浏览器能理解的压缩格式等。
  3. 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,认用 Web 服务,或者直接计算得出对应的响应。
  4. 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样自包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
  5. 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返国的文档类型(例如 HTML)、设置 Cookie 和缓存参数,以及其他类似的任务。

Java Servlet 通常情况下与使用 CGI(common gateway interface,公共网关接口)实现的程序可以达到异曲同工的效果。

但是相比于 CGI,Servlet 有以下几点优势:

  1. 性能明显更好。
  2. 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  3. Servlet 是独立于平台的,因为它们是用 Java 编写的。
  4. 服务器上的 Java 安全管理器具有一定的限制,以保护服务器计算机上的资源。因此 Servlet 是可信的。
  5. Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 Socket 和 RMI 机制与 Applet、数据库或其他软件进行交互。

3、Servlet 基础使用

1. Servlet 编写步骤

1)编码

  1. 前期准备-创建 Java Web 工程;

  2. 编写一个普通类继承 GenericServlet 并重写 service 方法;

  3. 在 web.xml 配置 Servlet;

2)测试

  1. 在 Tomcat 中部署项目;

  2. 在浏览器访问 Servlet。

2. Servlet 执行过程 

  1. 浏览器使用 Socket(IP+端口)与服务器建立连接。
  2. 浏览器将请求数据按照 HTTP 打成一个数据包(请求数据包)发送给服务器。
  3. 服务器解析请求数据包并创建请求对象(request)和响应对象(response)。
    • 请求对象是 HttpServletRquest 接口的一个实现。
    • 响应对象是 HttpServletResponse 接口的一个实现,响应对象用于存放 Servlet 处理自的结果。
  4. 服务器将解析之后的数据存放到请求对象(request)里面。
  5. 服务器依据请求资源路径找到相应的 Servlet 配置,通过反射创建 Servlet 实例。
  6. 服务器调用其 service() 方法,在在调用 serviceO方法时,会将事先创建好的请求对象(request)和响应对象(response)作为参数进行传递。
  7. 在 Servlet 内部,可以通过 reques st 获得请求数据,或者通过 response 设置响应数据
  8. 服务器从 response 中获取数据 按照 HTTP 打成一个数据包(响应数据包),发送给浏览器。
  9. 浏览器解析响应数据包,取出相应的数据,生成相应的界面。

3. Servlet 类视图

  • 在 Servlet 的 API 介绍中,除了继承 GenericServlet 外还可以继承 HttpServlet。
  • 通过查阅 servlet 的类视图,我们看到 GenericServlet 还有一个子类 HttpServlet。
  • 同时,在 service 方法中还有参数 ServletRequest 和 ServletResponse。

它们的关系如下图所示:

4. Servlet 编写方式

我们在实现 Servlet 功能时,可以选择以下三种方式:

第一种:实现 Servlet 接口,接口中的方法必须全部实现。

  • 使用此种方式,表示接口中的所有方法在需求方面都有重写的必要。此种方式支持最大程度的自定义。

第二种:继承 GenericServlet,service 方法必须重写,其他方可根据需求,选择性重写。

  • 使用此种方式,表示只在接收和响应客户端请求这方面有重写的需求,而其他方法可根据实际需求选择性重写,使我们的开发 Servlet 变得简单。但是,此种方式是和 HTTP 协议无关的。

第三种:继承 HttpServlet。

  • 它是 javax.servlet.http 包下的一个抽象类,是 GenericServlet 的子类。
  • 如果我们选择继承 HttpServlet 时,只需要重写 doGet 和 doPost 方法,不需要覆盖 service 方法。
  • 使用此种方式,表示我们的请求和响应需要和 HTTP 协议相关。也就是说,我们是通过 HTTP 协议来访问的。那么每次请求和响应都符合 HTTP 协议的规范。请求的方式就是 HTTP 协议所支持的方式(HTTP 协议支持 7 种请求方式:GET、POST、PUT、DELETE、TRACE、OPTIONS、HEAD)。
  • 为了实现代码的可重用性,通常我们只需要在 doGet 或者 doPost 方法任意一个中提供具体功能即可,而另外的那个方法只需要调用提供了功能的方法。

5. Servlet 生命周期

对象的生命周期,就是对象从生到死的过程,即:出生——活着——死亡。用更偏向于开发的官方说法,就是对象从被创建到销毁的过程。

Servlet 的生命周期主要有初始化阶段、处理客户端请求阶段和终止阶段:

  1. 初始化阶段

    • Servlet 容器加载 Servlet,加载完成后,Servlet 容器会创建一个 Servlet 实例并调用 init() 方法,init() 方法只会调用一次。
    • Servlet 容器会在以下几种情况加载 Servlet:
      1. Servlet 容器启动时自动加载某些 Servlet,这样需要在 web.xml 文件中添加。
      2. 在 Servlet 容器启动后,客户首次向 Servlet 发送请求。
      3. Servlet 类文件被更新后,重新加载。
  2. 处理客户端请求阶段

    • 每收到一个客户端请求,服务器就会产生一个新的线程去处理。对于用户的 Servlet 请求,Servlet 容器会创建一个特定于请求的 ServletRequest 和 ServletResponse。
    • 对于 Tomcat 来说,它会将传递来的参数放入一个哈希表中,这是一个 String->String[]的键值映射。
  3. 终止阶段

    • 当 Web 应用被终止,或者 Servlet 容器终止运行,又或者 Servlet 重新加载 Servlet 新实例时,Servlet 容器会调用 Servlet 的 destroy() 方法。

通过分析 Servlet 的生命周期可以发现,它的实例化和初始化只会在请求第一次到达 Servlet 时执行,而销毁只会在 Tomcat 服务器停止时执行。

由此我们得出一个结论,Servlet 对象只会创建一次,销毁一次。所以,每一个 Servlet 只有一个实例对象。如果一个对象实例在应用中是唯一的存在,那么我们就说它是单实例的,即运用了单例模式。

如下是一个典型的 Servlet 生命周期方案: 

  1. 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。
  2. Servlet 容器在调用 service() 方法之前加载 Servlet。
  3. Servlet 容器处理由多个线程产生的多个请求,每个线程执行一个单一的 Servlet 实例的 service() 方法。

6. Servlet 执行时一般要实现的方法

Servlet 类要继承的 GenericServlet 与 HttpServlet 类说明:

  1. GenericServlet 类是一个实现了 Servlet 的基本特征和功能的基类,其完整名称为 javax.Servlet.GenericServlet,它实现了 Servlet 和 ServletConfig 接口。
  2. HtpServlet 类是 GenericServlet 的子类,其完整名称为javax.Servlet.HttpServlet,它提供了处理 HTTP 的基本构架。如果一个 Servlet 类要充分使用 HTTP 的功能,就应该继 HttpServlet。在 HttpServlet 类及其子类中,除可以调用 HttpServlet 类内部新定义的方法外,可以调用包括 Servlet、ServletConfig 接口和 GenericServlet 类中的一些方法。

Servlet 若继承上述类,执行时一般要实现的方法:

publle void init(servletconfig config)
public void service(servletRequest request, servletResponse response) public void destroy()
public Servletconfig getservletConfig() 
publle string getservletInfo()
  1. init() 方法在 Servlet 的生命周期中仅执行一次,在 Servlet 引擎创建 Servlet 对象后执行。 Servlet 在调用 init() 方法时,会传递一个包含 Servlet 的配置和运行环境信息的 ServletConfig 对象。如果初始化代码中要使用到 ServletConfig 对象,则初始化代码就只能在 Servlet 的 init() 方法中编写,而不能在构造方法中编写。默认的 init(方法通常是符合要求的,不过也可以根据需要进行覆盖,比如管理服务器端资源、初始化数据库连接等,默认的 inti()方法设置了 Servlet的初始化参数,并用它的 ServeltConfig 对象参数来启动配置,所以覆盖 init() 方法时,应调用 super.init() 以确保仍然执行这些任务。

  2. service() 方法是 Servlet 的核心,用于响应对 Servlet 的访问请求。对于 HttpServlet,每当客户请求一个 HttpServlet 对象时,该对象的 serviceO方法就要被调用,HttpServlet 默认的 serviceo方法的服务功能就是调用与 HTTP 请求的方法相应的 do 功能:doPostO和 doGet0,所以对于 HttpServlet,一般都是重写 doPostO和 doGet()方法。

  3. destroy() 方法在 Servlet 的生命周期中也仅执行一次,即在服务器停止卸载 Servlet 之前被调用,把 Servlet 作为服务器进程的一部分关闭。默认的 destroy() 方法通常是符合要求的,但也可以覆盖,来完成与 init() 方法相反的功能。比如在卸载 Servlet 时将统计数字保存在文件中,或是关闭数据库连接或 I/O 流。

  4. getServletConfig() 方法返回一个 ServletConfig 对象,该对象用来返回初始化参数和 ServletContext。ServletContext 接口提供有关 Servlet 的环境信息。

  5. getServletInfo() 方法提供有关 Servlet 的描述信息,如作者、版本、版权。可以对它进行覆盖。

  6. doXxx() 方法客户端可以用 HTTP 中规定的各种请求方式来访问 Servlet,Servlet 采取不同的访问方式进行处理。不管用哪种请求方式访问 Servlet,Servlet 引擎都会调用 Servlet 的 service() 方法,service() 方法是所有请求方式的入口。

    • doGet() 用于处理 GET 请求;
    • doPost() 用于处理 POST 请求;
    • doHead() 用于处理 HEAD 请求;
    • doPut() 用于处理 PUT 请求;
    • doDelete() 用于处理 DELETE 请求;
    • doTrace() 用于处理 TRACE 请求;
    • doOptions() 用于处理 OPTIONS 请求。

7. Servlet 线程安全

由于 Servlet 运用了单例模式,即在整个应用中,每一个 Servlet 类只有一个实例对象,所以我们需要分析这个唯一的实例中的类成员是否线程安全。

接下来,我们来看下面的的示例:

public class ServletDemo extends HttpServlet 
    //1.定义用户名成员变量
    //private String username = null;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        String username = null;
        //synchronized (this) 
            //2.获取用户名
            username = req.getParameter("username");

            try 
                Thread.sleep(3000);
             catch (InterruptedException e) 
                e.printStackTrace();
            

            //3.获取输出流对象
            PrintWriter pw = resp.getWriter();

            //4.响应给客户端浏览器
            pw.print("welcome:" + username);

            //5.关流
            pw.close();
        //
    

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        doGet(req,resp);
    

启动两个浏览器,输入不同的参数,访问之后发现输出的结果都是一样,所以出现线程安全问题:

通过上面的测试我们发现,在 Servlet 中定义了类成员后,多个浏览器都会共享类成员的数据。每一个浏览器端就代表是一个线程,那么多个浏览器就是多个线程,所以测试的结果说明了多个线程会共享 Servlet 类成员中的数据。那么,其中任何一个线程修改了数据,都会影响其他线程。因此,我们可以认为 Servlet 不是线程安全的。

分析产生这个问题的根本原因,其实就是因为 Servlet 是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都可能会改变,而不是重新初始化。

要解决这个线程安全问题,需要在 Servlet 中定义类成员时慎重。

  • 如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题的。
  • 但如果类成员并非共用,或者每次使用都有可能对其赋值(如上图示例),那么就要考虑线程安全问题了,解决方案是把它定义到 doGet 或者 doPost 方法中。

8. Servlet 映射配置

Servlet 支持三种映射方式,以达到灵活配置的目的。

首先编写一个Servlet,代码如下:

public class ServletDemo extends HttpServlet 

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        System.out.println("ServletDemo5接收到了请求");
    

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        doGet(req,resp);
    

方式一:精确映射。

此种方式,只有和映射配置一模一样时,Servlet 才会接收和响应来自客户端的请求。

方式二:/开头+通配符。

此种方式,只要符合目录结构即可,不用考虑结尾是什么。

例如:映射为:/servlet/*

方式三:通配符+固定格式结尾。

此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)

例如:映射为:*.do

优先级:

通过测试我们发现,Servlet 支持多种配置方式,但是由此也引出了一个问题,当有两个及以上的 Servlet 映射都符合请求 URL 时,由谁来响应呢?

注意:HTTP 协议的特征是一请求一响应的规则。那么有一个请求,必然有且只有一个响应。所以,映射规则的优先级如下:

  1. 精确匹配
  2. /开头+通配符
  3. 通配符+固定格式结尾

9. 多路径映射 Servlet

这其实是给一个 Servlet 配置多个访问映射,从而可以根据不同请求 URL 实现不同的功能。

示例 Servlet:

public class ServletDemo extends HttpServlet 

    /**
     * 根据不同的请求URL,做不同的处理规则
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
 

Java Web 三层架构详解

java 三层架构ssh

一个spring2.5+hibernate3.2+struts2.0组合框架,使用spring IoC来管理应用 所有bean,包括struts2 action,充分发挥了spring轻量级框架 优势。

 摘 要: 针对当前Web应用程序开发面临的问题,结合目前比较流行的开源框架Spring、Struts和hibernate,提出了一种开发J2EE Web应用的轻量级解决方案,以帮助开发人员在短期内搭建结构清晰、可复用性好、维护方便的Web应用程序。并且,通过案例具体说明了如何将这一方案应用到实际项目中。
关键词: J2EE  MVC  Struts  Spring  Hibernate

    大型企业级Web应用系统的开发通常要求有一个良好的软件架构、便于协作开发和扩展升级,而传统的开发模式不能很好地满足这些要求。本文针对当前Web应用程序开发面临的问题,结合目前比较流行的开源框架SSH(Spring、Struts、Hibernate),提出一种开发J2EE 企业级Web应用的轻量级解决方案,并通过案例具体说明如何将这一方案应用到实际项目中。
1 框架技术
    著名的软件大师Ralph Johnson对框架(Framework)进行了如下的定义: 框架是整个系统或系统的一部分的可重用设计,由一组抽象的类及其实例间的相互作用方式组成[1] 。
    框架一般具有即插即用的可重用性、成熟的稳定性以及良好的团队协作性。J2EE复杂的多层结构决定了大型的J2EE项目需要运用框架和设计模式来控制软件质量。目前,市场上出现了一些商业的、开源的基于J2EE的应用框架,其中主流的框架技术有:基于MVC模式的Struts框架和基于IoC模式的 Spring框架以及对象/关系映射框架Hibernate等。
1.1  表示层框架Struts
     Struts是一个在JSP Model2基础上实现的MVC框架,主要分为模型(Model)、视图(Viewer)和控制器(Controller)三部分,其主要的设计理念是通过控制器将表现逻辑和业务逻辑解耦,以提高系统的可维护性、可扩展性和可重用性[2] 。Struts框架的体系结构如图1所示。

技术分享


  下面就图1所示的体系结构图分析Struts框架中的MVC组件。
    (1)视图:视图部分主要由JSP页面组成,其中没有流程逻辑、业务逻辑和模型信息,只有标记。Struts自身包含了一组标记库(TagLib),这也是Struts的精华之一,灵活运用它们可以简化JSP页面的代码,提高开发效率。
    (2)控制器:Struts中的Controller主要是其自身提供的ActionServlet。ActionServlet接收所有来自客户端的请求并根据配置文件(struts-config.xml)中的定义将控制转移到适当的Action对象。
    (3)模型:Struts没有定义具体Model层的实现,Model层通常是和业务逻辑紧密相关的,有持续化的要求。目前在商业领域和开源世界,都有一些优秀的工具可以为Model层的开发提供便利。
1.2  业务逻辑层框架Spring
    Spring是一个解决了许多J2EE开发中常见问题并能够替代EJB技术的强大的轻量级框架。这里所说的轻量级指的是 Spring框架本身,而不是指Spring只能用于轻量级的应用开发。Spring的轻盈体现在其框架本身的基础结构以及对其他应用工具的支持和装配能力。与EJB这种庞然大物相比,Spring可使程序研发人员把各个技术层次之间的风险降低。
    Spring框架的核心是控制翻转IoC(Inversion of Control)/依赖注入DI(Dependence Injection)机制。IoC是指由容器中控制组件之间的关系(这里,容器是指为组件提供特定服务和技术支持的一个标准化的运行时的环境)而非传统实现中由程序代码直接操控,这种将控制权由程序代码到外部容器的转移,称为“翻转”[3] 。DI是对IoC更形象的解释,即由容器在运行期间动态地将依赖关系(如构造参数、构造对象或接口)注入到组件之中[3] 。 Spring采用设值注入(使用Setter方法实现依赖)和构造子注入(在构造方法中实现依赖)的机制,通过配置文件管理组建的协作对象,创建可以构造组件的IoC容器。这样,不需要编写工厂模式、单例模式或者其他构造的方法,就可以通过容器直接获取所需的业务组件。Spring框架的结构如图2所示。

 

技术分享


   Spring框架由七个定义明确的模块组成,且每个模块或组件都可以单独存在,或者与其他一个或多个模块联合实现。Spring Core Container是一个用来管理业务组件的IoC容器,是Spring应用的核心;Spring DAO和Spring ORM不仅提供数据访问的抽象模块,还集成了对Hibernate、JDO和iBatis等流行的对象关系映射框架的支持模块,并且提供了缓冲连接池、事务处理等重要的服务功能,保证了系统的性能和数据的完整性;Sprnig Web模块提供了Web应用的一些抽象封装,可以将Struts、Webwork等Web框架与Spring整合成为适用于自己的解决方案。
    Spring框架可以成为企业级应用程序一站式的解决方案,同时它也是模块化的框架,允许开发人员自由地挑选适合自己应用的模块进行开发。Spring框架式是一个松耦合的框架,框架的部分耦合度被设计为最小,在各个层次上具体选用哪个框架取决于开发者的需要。
1.3 数据持久层框架Hibernate
    O/R mapping技术是为了解决关系型数据库和面向对象的程序设计之间不匹配的矛盾而产生的。Hibernate是目前最为流行的O/R mapping框架,它在关系型数据库和Java对象之间做了一个自动映射,使得程序员可以以非常简单的方式实现对数据库的操作。Hibernate工作原理如图3所示。

 

技术分享


   Hibernate通过对JDBC的封装,向程序员屏蔽了底层的数据库操作,使程序员专注于OO程序的开发,有助于提高开发效率。程序员访问数据库所需要做的就是为持久化对象编制xml映射文件[4] 。
    底层数据库的改变只需要简单地更改初始化配置文件(hibernate.cfg.xml或者hibernate.properties)即可,不会对应用程序产生影响。
     Hibernate有自己的面向对象的查询语言HQL,HQL功能强大,支持目前大部分主流的数据库,如Oracle、DB2、MySQL、 Microsoft SQL Server等,是目前应用最广泛的O/R映射工具。Hibernate为快速开发应用程序提供了底层的支持。
2 基于SSH组合框架的Web应用模型设计与实现
2.1 集成SSH的新型J2EE框架
  前面分析了基于J2EE的三种框架技术,下面通过集成以上三种框架技术来对传统的J2EE Web开发模型加以改进,以形成一种新的、轻量型的J2EE架构。
  集成SSH框架 的系统框架图 如图4所示,系统从职责上分为四层:表示层、业务逻辑层、数据持久层和域模块层。其中使用Struts作为系统的整体基础架构,负责MVC的分离,在 Struts框架的模型部分,利用Hibernate框架对持久层提供支持,业务层用Spring支持。具体做法是:用面向对象的分析方法根据需求提出一些模型,将这些模型实现为基本的Java对象,然后编写基本的DAO接口,并给出Hibernate的DAO实现,采用Hibernate架构实现的 DAO类来实现Java类与数据库之间的转换和访问,最后由Spring完成业务逻辑。

 

技术分享


  系统的基本业务流程是:在表示层中,首先通过JSP页面实现交互界面,负责传送请求(Request)和接收响应(Response),然后Struts根据配置文件 (struts-config.xml)将ActionServlet接收到的Request委派给相应的Action处理。在业务层中,管理服务组件的 Spring IoC容器负责向Action提供业务模型(Model)组件和该组件的协作对象数据处理(DAO)组件完成业务逻辑,并提供事务处理、缓冲池等容器组件以提升系统性能和保证数据的完整性。而在持久层中,则依赖于Hibernate的对象化映射和数据库交互,处理DAO组件请求的数据,并返回处理结果。
  采用上述开发模型,不仅实现了视图、控制器与模型的彻底分离,而且还实现了业务逻辑层与持久层的分离。这样无论前端如何变化,模型层只需很少的改动,并且数据库的变化也不会对前端有所影响,大大提高了系统的可复用性。而且由于不同层之间耦合度小,有利于团队成员并行工作,大大提高了开发效率。
2.2 基于SSH框架 的Web应用系统的实现
  下面将通过一个实际的系统来展示如何进行基于SSH框架 的Web应用开发。该系统是为某通信公司运营部开发的一个问答式系统,功能类似于百度知道和新浪爱问。由于系统的模块较多,下面就以一个用户管理模块为例来说明系统的开发实现过程,并将按照数据持久层、业务逻辑层、表示层的顺序说明系统构建过程。
  (1)数据持久层
  数据持久层由Java对象持久化类和数据访问对象(DAO)组成。每个数据库表都对应着一个持久化对象,这样就给予了开发者使用OO思想设计和开发的便利,同时也屏蔽了具体的数据库和具体的数据表、字段,消除了对数据库操作的硬编码在重用性上的弊端。用户信息表的部分结构如表1所示。

技术分享


  Hibernate通过映射(Mapping)文件将对象(Object)与关系型数据(Relational)相关联,因此需要编写和数据库表相对应的Java持久化类以及对应的映射文件。有了Java持久化类后就可以在此基础上实现数据访问类。在Spring框架中,数据访问类可以从辅助类 HibernateDaoSupport继承,这极大地方便了Hibernate框架在Spring中的使用,相应的部分代码如下:
      public class UserDao 
          extends HibernateDaoSupport {
      public int add(User user) {
        return Integer.ParseInt(this.getHibernateTemplate().save(user).toString());
      }
      public List findAll() {
        return this.getHibernateTemplate().loadAll(User.class);
      }
      }
  具体的Hibernate数据源、session工厂、事务管理、缓冲连接池等功能都由业务层的Spring容器提供。
  (2)业务逻辑层
  业务逻辑层由Spring框架支持,提供了处理业务逻辑的服务组件。开发者需要对业务对象建模,抽象出业务模型并封装在Model组件中。由于数据持久层实现了Java持久化类并且封装了数据访问对象(DAO),因此可以在Model组件中方便地调用DAO组件来存取数据。Spring的IoC容器负责统一管理Model组件和DAO组件以及Spring所提供的事务处理、缓冲连接池等服务组件。
  在用户管理模块中,通过业务建模创建了用户模型UserService类,封装了对用户的权限管理以及积分管理等功能。UserService类通过调用数据访问类UserDao实现对用户数据的操作。这些组件的关系将通过配置Spring框架的applicationContext.xml联系起来,配置文件的主要内容如下:
      

技术分享   
      
     (3)表示层
     表示层结合JSP和Struts的TagLib库处理显示功能,利用ActionServlet将请求(*.do)映射到相应的Action,并由Action调用业务逻辑的服务组件,然后根据处理结果跳转到Forword对象指定的响应页面。
     业务流程的部署由struts-config.xml完成。下面以一个显示所有用户信息的请求(ListUser.do)为例来说明配置文件的使用。
      
     技术分享
      
      
    
    基于J2EE的Web应用以其层次性、平台无关性的优势已经逐渐成为了电子商务、电子政务主要的解决方案。本文针对传统的J2EE Web应用开发的弊端,提出了一种利用轻量级框架来快速搭建Web应用的解决方案,并且通过其在实际项目中的应用,证明了采用此方案可以帮助开发人员在短时间内建立结构清晰、可重用性好、维护扩展方便的Web应用程序。
参考文献
[1]  GAMMA E, HELM R, JOHNSON R, et al. Design patterns:Elements of reusable object-oriented software[M]. Addison  Wesley, 1994.
[2]  孙卫琴.精通Struts:基于MVC的Java Web设计与开发[M]. 北京:电子工业出版社,2004.
[3]  JOHNSON R, HOELLER J, ARENDSEN A, et al. Java/J2EE application framework reference document. V1.1.
 2004.
[4]  徐长盛,戴超.一种快速开发Web应用程序方法的研究[J]. 计算机工程与设计,2004,(12):2237-2239.
[5]  夏昕,曹晓钢,唐勇.深入浅出Hibernate[M]. 北京:电子工业出版社,2005.
[6]  JOHNSON R.Expert one-on-one J2EE design and development[M]. 魏海萍译.北京:电子工业出版社,2003.

在用ssh 开发web应用时,需要对生成 各个类文件进行组织,下面就对一个可行 目录方案进行介绍:


 

譬如应用中有一个用户管理模块,则在公共包下建立一个user包,如该公共包可以为com.simon.oa,

在user包下包括如下子包

1、controler包

该包放置各种struts action。

2、dao包

该包放置各类dao(data access object),也就是放置对数据库访问 实现类,在用myeclipse中 “Hibernate Reverse Engineering”进行反向操作时在某一个目录中就会生成对应某个表 DAO,生成后可将该DAO拖到dao包中。在某些应用中将DAO作为接口,在该接口中包括所有对数据库 操作方法,然后在dao包建立一个hibernate包,在hibernate包中放置对DAO接口 实现,譬如:UserDAO接口有一个实现类为UserDaoImpl,将该类放置到hibernate包中,实际 开发倾向于后一种方式,因为对这个DAO接口可以实现spring IoC操作。(不知道myeclipse对此是怎么考虑 ,这个问题让我纠缠了很久,误将DAO理解成一个能够进行实际操作 类,而不是一个接口,以后开发要注意 )

3、model包

该包中放置hibernate反向工程生成 bean和该bean对应 .hbm.xml文件。

4、service包

该包放置业务操作类,譬如用户服务类,一般情况将该用户操作类提取一个接口,然后在service包下生成一个impl包,在impl包中才放置用户操作接口 实现类。该用户接口实现类中调用DAO接口对数据库进行操作,而调用该实现类 方法在struts action中。

5、vo包(value object)

vo包中 中包括struts中使用 POJO及actionform等信息。

VO:  Value Object
DTO: Data Transfer Object
个人理解VO和DTO是类似 东西,原则上VO和DTO只有Public Fields,主要用于进程之间数据传递 问题,VO和DTO不会传递到表示层,在业务层就会被吸收。但看到很多人在建立VO和DTO时,也含有Setter,Getter属性和一些其它 辅助方法,这也无可厚非,我自己也不能确定这对不对。

以上是关于Java Web 开发详解的主要内容,如果未能解决你的问题,请参考以下文章

Java Web Servlet详解!!

Java Web Servlet详解

Java Web servlet 详解

Servlet 3.0 新特性详解 (转载)

Java 中的 Filter 过滤器详解

Servlet基础知识,ServletContext,ServletConfig对象详解