Servlet 的应用程序设计

2023-11-12

目录

1、基于 Servlet Controller 的应用程序设计

2、基于 Filter Dispatcher 的应用程序设计

3、使用表单验证器

4、添加数据库访问

5、依赖注入


        在该模型中,用一个 Servlet 或者过滤器充当控制器 Controller。所有现代的 Web 框架都是基于此模型实现的。像 Struts 1 和 Spring MVC 这类框架是在它们的 MVC 架构中使用一个 Servlet Controller,而另一个流行的框架:Struts 2,则是使用过滤器。

        应用程序架构,MVC(Model-View-Controller)设计模式

1、基于 Servlet Controller 的应用程序设计

        本次实现的应用程序可以用来输入产品信息,并可以提交输入信息。之后,应用程序会给用户发送一个确认页面,显示所保存产品的详细信息。//是的,这个程序就是如此简单,它完全基于 Servlet/JSP 实现

        这个应用程序能够执行下面这两个 Action:

  1. 显示 Add Product 表单。这个 Action 将登录表单发送到浏览器。调用这个 Action 的 URI 必须包含字符串 product_input
  2. 保存产品,并返回一个产品的确认页面。调用这个 Action 的 URI 必须包含字符串 product_save

        此应用程序的目录结构如下图所示://注意,此程序中没有 web.xml 文件

        代码清单 Product 类:

import java.io.Serializable;

public class Product implements Serializable {

    private static final long   serialVersionUID = 748392348L;
    private              String name;
    private              String description;
    private              float  price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}

        代码清单 ProductForm 类:

public class ProductForm {

    private String name;
    private String description;
    private String price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }
}

        代码清单 SaveProductAction 类:

import com.my.servlet.programmer.model.Product;

public class SaveProductAction {
	public void save(Product product) {
		// 将Product插入数据库
	}
}

        代码清单 ControllerServlet 类://非常简单的一个Action控制器,通过 URI 路径进行匹配

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.my.servlet.programmer.action.SaveProductAction;
import com.my.servlet.programmer.form.ProductForm;
import com.my.servlet.programmer.model.Product;

/**
 * 基于Servlet的控制器实现
 */
@WebServlet(name = "ControllerServlet", urlPatterns = {"/product_input", "/product_save"})
public class ControllerServlet extends HttpServlet {

    private static final long serialVersionUID = 1579L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        //uri格式:/contextName/resourceName, 例如: /app/product_input
        //默认情况下,contextName为空,所以contextName为空情况下,资源路径格式为:/resourceName
        String uri = request.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        //1-通过URI路径进行匹配,执行一个Action
        if (action.equals("product_input")) {
        } else if (action.equals("product_save")) {
            // 创建表单
            ProductForm productForm = new ProductForm();
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            // 创建对象
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
                product.setPrice(Float.parseFloat(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // 执行一个Action方法
            SaveProductAction saveProductAction = new SaveProductAction();
            saveProductAction.save(product);
            // 将模型存储在视图的作用域变量中
            request.setAttribute("product", product);
        }
        // 2-转发到视图
        String dispatchUrl = null;
        if (action.equals("product_input")) {
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if (action.equals("product_save")) {
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }
        if (dispatchUrl != null) {
            RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}

        层叠样式表 main.css 代码:

#global {
    text-align: left;
    border: 1px solid #dedede;
    background: #efefef;
    width: 300px;
    padding: 20px;
    margin: 100px auto;
}

        JSP 页面 ProductDetails.jsp 代码:

<!DOCTYPE HTML>
<html>
<head>
    <title>Save Product</title>
    <style type="text/css">@import url(/css/main.css);</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
    <h5>Details:</h5>
    Product Name: ${product.name}<br/>
    Description: ${product.description}<br/>
    Price: $${product.price}
    </p>
</div>
</body>
</html>

        JSP 页面 ProductForm.jsp 代码: 

<!DOCTYPE HTML>
<html>
<head>
    <title>Add Product Form</title>
    <style type="text/css">@import url(/css/main.css);</style>
</head>
<body>
<div id="global">
    <h3>Add a product</h3>
    <form method="post" action="product_save">
        <table>
            <tr>
                <td>Product Name:</td>
                <td><input type="text" name="name"/></td>
            </tr>
            <tr>
                <td>Description:</td>
                <td><input type="text" name="description"/></td>
            </tr>
            <tr>
                <td>Price:</td>
                <td><input type="text" name="price"/></td>
            </tr>
            <tr>
                <td><input type="reset"/></td>
                <td><input type="submit" value="Add Product"/></td>
            </tr>
        </table>
    </form>
</div>
</body>
</html>

        程序启动后的跳转主页 index.html:

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Apache Tomcat Examples</title>
</head>
<body>
<h3>
    welcome to servlet !!!
</H3>
</body>
</html>

        Maven 工程的 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
  <artifactId>servlet-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

    <!--javax.servlet-api-->
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

        以上,便是该应用程序的所有代码。接下来,启动一个 tomcat 容器,访问产品输入页面(/product_input),成功访问后,页面如下图所示:

        在页面的表单中输入相关数据后,点击 Add Product,会跳转到产品详情页,如下图所示:

2、基于 Filter Dispatcher 的应用程序设计

        虽然 Servlet 是 MVC 应用程序中最常用的 Controller,但也可以用过滤器作为 Controller。不过注意,过滤器无权充当欢迎页面。因为只输入域名将不会调用过滤器分发器(Filter Dispatcher)。Struts 2 用过滤器作为 Controller,因为过滤器还用于提供静态的内容。//使用过滤器实现一个控制器

        在基于 Servlet Controller 的应用程序设计上,只需对代码内容进行一些简单的更改:即注销 ControllerServlet,添加 DispatherFilter。

        代码清单 DispatherFilter 类://对全局进行拦截,根据路径进行匹配

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import com.my.servlet.programmer.action.SaveProductAction;
import com.my.servlet.programmer.form.ProductForm;
import com.my.servlet.programmer.model.Product;

/**
 * 使用过滤器实现Controller
 */
@WebFilter(filterName = "DispatcherFilter", urlPatterns = {"/*"})
public class DispatcherFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String uri = req.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        //1-通过URI路径进行匹配,执行一个Action
        if (action.equals("product_input")) {
            // do nothing
        } else if (action.equals("product_save")) {
            // 创建表单
            ProductForm productForm = new ProductForm();
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            // 创建对象
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
                product.setPrice(Float.parseFloat(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // 执行一个Action方法
            SaveProductAction saveProductAction = new SaveProductAction();
            saveProductAction.save(product);
            request.setAttribute("product", product);
        }

        // 2-转发到视图
        String dispatchUrl = null;
        if (action.equals("product_input")) {
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if (action.equals("product_save")) {
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }
        if (dispatchUrl != null) {
            RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        } else {
            // 让静态内容通过
            filterChain.doFilter(request, response);
        }
    }
}

        可以发现,除了基于过滤器的实现,其他代码逻辑基本上与 ControllerServlet 类一致,当然,基本的操作也与 ControllerServlet 类一样,浏览器操作对后端代码的变化并无感知,在此不做赘述。//简单来说,就是替换了控制器的实现,基于过滤器的拦截,需要考虑到过滤器的顺序

        由于过滤器的目标是所有包含静态内容的 URL,因此如果没有调用任何 Action,那么就需要调用 filterChain.doFilter() 方法。

3、使用表单验证器

        在执行 Action 时,输入验证是一个重要的步骤。验证范围从简单的任务到复杂的都有,例如,简单的有检验某个输入域中是否有值,复杂的有验证信用卡号码,等等。现代的 MVC 框架经常同时提供编程式和声明式的验证方法。在编程式验证中,是通过编写代码来验证用户的输入。在声明式验证中,则是在 XML 文档或者属性文件中提供验证规则。

        接下来,为 Servlet 程序提供一个简单的验证器,用来校验页面输入的值。ProductValidator 类提供了一个 ProductForm 的 validate 方法。验证器可以确保产品的名称不为空,以及其价格是一个有效的数字。validate 方法返回个包含验证错误消息的 String 的 List。List 为空意味着验证成功。

        代码清单 ProductValidator 类:

import java.util.ArrayList;
import java.util.List;
import com.my.servlet.programmer.form.ProductForm;

/**
 * 校验器
 */
public class ProductValidator {

    // 字段校验逻辑
    public List<String> validate(ProductForm productForm) {
        List<String> errors = new ArrayList<>();
        String name = productForm.getName();
        if (name == null || name.trim().isEmpty()) {
            errors.add("产品名称不能为空");
        }
        String price = productForm.getPrice();
        if (price == null || price.trim().isEmpty()) {
            errors.add("产品价格不能为空");
        } else {
            try {
                Float.parseFloat(price);
            } catch (NumberFormatException e) {
                errors.add("无效价格");
            }
        }
        return errors;
    }
}

        在此次示例程序中,仍然使用 Servlet 作为控制器(即放弃过滤器的控制器实现方式),定义完校验器 ProductValidator 后,需要在 ControllerServlet 中引入校验器的逻辑,完整的代码清单如下://在原有基础上增加了校验逻辑

import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.my.servlet.programmer.action.SaveProductAction;
import com.my.servlet.programmer.form.ProductForm;
import com.my.servlet.programmer.model.Product;
import com.my.servlet.programmer.validator.ProductValidator;

@WebServlet(name = "ControllerServlet", urlPatterns = {"/product_input", "/product_save"})
public class ControllerServlet extends HttpServlet {

    private static final long serialVersionUID = 98279L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String uri = request.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        String dispatchUrl = null;
        //1-根据Url进行匹配
        if (action.equals("product_input")) {
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if (action.equals("product_save")) {
            //实例化表单
            ProductForm productForm = new ProductForm();
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            //表单校验器
            ProductValidator productValidator = new ProductValidator();
            List<String> errors = productValidator.validate(productForm);
            if (errors.isEmpty()) {
                //表单校验通过创建产品
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(productForm.getDescription());
                product.setPrice(Float.parseFloat(productForm.getPrice()));
                //保存产品信息
                SaveProductAction saveProductAction = new SaveProductAction();
                saveProductAction.save(product);

                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {
                //如果校验失败,向页面返回错误信息
                request.setAttribute("errors", errors);
                request.setAttribute("form", productForm);
                dispatchUrl = "/jsp/ProductForm.jsp";
            }
        }
        // 2-重定向
        if (dispatchUrl != null) {
            RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
            //解决输出到页面的中文乱码
            response.setCharacterEncoding("UTF-8");
            rd.forward(request, response);
        }
    }
}

        为了在页面展示错误信息,相应的也需要添加一些页面的逻辑,本次示例只要修改 ProductForm.jsp 页面即可。

        代码清单 ProductForm.jsp 页面:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=utf-8"%> <!--解决页面显示中文乱码-->
<!DOCTYPE HTML>
<html>
<head>
    <title>Add Product Form</title>
    <style type="text/css">@import url(/css/main.css);</style>
</head>
<body>
<div id="global">
    <h3>Add a product</h3>
    <!--错误信息展示-->
    <c:if test="${requestScope.errors != null}">
        <p id="errors">
            错误信息:
        <ul>
            <c:forEach var="error" items="${requestScope.errors}">
                <li>${error}</li>
            </c:forEach>
        </ul>
        </p>
    </c:if>
    <form method="post" action="product_save">
        <table>
            <tr>
                <td>Product Name:</td>
                <td><input type="text" name="productName"
                           value="${form.name}"/></td>
            </tr>
            <tr>
                <td>Description:</td>
                <td><input type="text" name="description"
                           value="${form.description}"/></td>
            </tr>
            <tr>
                <td>Price:</td>
                <td><input type="text" name="price"
                           value="${form.price}"/></td>
            </tr>
            <tr>
                <td><input type="reset"/></td>
                <td><input type="submit" value="Add Product"/></td>
            </tr>
        </table>
    </form>
</div>
</body>
</html>

        在上边 JSP 页面中,引入了 jstl 标签库。因此,还需在 pom.xml 文件中引入相应的 jar 包,完整的 pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
  <artifactId>servlet-test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

    <!--javax.servlet-api-->
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--jstl标签库-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

        最后,此示例程序的完整目录结构如下:

        接下来,启动 tomcat 容器,访问产品输入页面(/product_input),成功访问后,不输入任何信息便点击 Add Product 按钮,将显示如下页面://校验器生效

4、添加数据库访问

        在数据库中访问数据时,最重要且最费时的操作经常是建立连接。按规则,设计良好的应用程序数据库连接应该始终是采用连接池的。目前常见的数据库连接池有:DBCP(Apache),C3P0, Druid(阿里),HikariCP 等

        如果使用的是 Tomcat 7 以上版本,那么它里面则已经内建了连接池(DBCP)。连接池目前并不需要亲自管理,而是让 Servlet/JSP 容器来完成。下面是利用 JNDI (Java 命名和目录接口) 查找,从容器管理的连接池中获取 JDBC 连接的代码示例。

    private DataSource dataSource;

    private DataSourceCache() {
        Connection connection = null;
        Context context = null;
        try {
            context    = new InitialContext();
            //java:comp/env/: 是JNDI树中的节点,可以在其中找到当前Java EE组件(web应用程序或EJB)的属性。
            //jdbc/myServletDB: 在tomcat中配置的资源名称
            dataSource = (DataSource) context.lookup("java:comp/env/jdbc/myServletDB");
            connection = dataSource.getConnection();
        } catch (NamingException e) {
        }
    }

        调用 DataSource 中的 getConnection 方法比较快,因为连接永远不会被关闭;关闭连接时,只要将连接返回到池中即可。但是,JNDI 查找比较慢,因此,被返回的 DataSource
经常会被缓存起来。

        为了让 Servlet 容器来管理连接池,需要对容器进行配置。在 Tomcat 中,是通过在应用程序的 Context 元素下声明 Resource 元素来实现的。例如,下面的 Tomcat 上下文中就包含了一个带有内部连接池的 DataSource 资源。//context.xml文件在tomcat的conf目录下

<Context>
	<Resource name="jdbc/myServletDB"
			  auth="Container" 
			  type="javax.sql.DataSource"
              maxTotal="100" 
			  maxIdle="30" 
			  maxWaitMillis="10000"
              username="root" 
			  password="root" 
			  driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/servlet_db?useUnicode=true&amp;serverTimezone=GMT-8&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
</Context>

        虽然目前的应用程序大多是利用一个依赖注入框架来管理数据库连接,但是也有许多的应用程序仍然还是依赖早期的 JNDI 查找方法,因此尝试 JNDI 查找仍然具有意义。

        关于在 Tomcat 中配置数据库连接,可以查看官方文档的一些介绍,点击这里

        DAO(Data Access Objet,数据访问对象)设计模式:在数据库中访问数据的一种办法是单独利用一个模块来管理获得连接和构建 SQL 语句的代码复杂性。DAO 模式就是用来很好地完成这项工作。下边是本次程序设计中使用到的 DAO 模式://这个图很有用,尤其是理解后边的依赖注入

        添加了数据库连接以及 DAO 类后,就可以演示一个简单的 Servlet 数据库存储程序了,因为前边已经贴了不少代码,本次 Servlet 程序的完整项目代码,可以直接点击这个链接进行下载

        代码下载后,本地需要有 Tomcat 以及安装 Mysql 数据库,修改成本地配置后即可运行。

5、依赖注入

        在本次程序设计中,为了便于理解,使用 Dependencylnjector 类代替依赖注入框架。(在现实的应用程序中,可以选择使用某种适当的框架。) 这个类是专门为本次程序设计的,有没有容器都很容易被实例化。一旦被实例化,就必须调用它的 start 方法,准备一个可供其他组件使用的 C3PO 连接池。用完之后,应该调用它的 shutdown 方法。

        代码清单 Dependencylnjector 类

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
import com.my.servlet.programmer.action.GetProductsAction;
import com.my.servlet.programmer.action.SaveProductAction;
import com.my.servlet.programmer.dao.DataSourceCache;
import com.my.servlet.programmer.dao.ProductDAO;
import com.my.servlet.programmer.dao.ProductDAOImpl;
import com.my.servlet.programmer.validator.ProductValidator;

/**
 * 依赖注入
 */
public class DependencyInjector {

    private DataSource dataSource;

    /**
     * 创建一个数据库连接池
     */
    public void start() {
//        dataSource = defaultDatasource();
        dataSource = c3p0DataSource();
    }

    /**
     * 默认数据源
     */
    private DataSource defaultDatasource() {
        return DataSourceCache.getInstance().getDataSource();
    }

    /**
     * C3P0数据源
     */
    private DataSource c3p0DataSource() {
        // C3P0 dataSource
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        try {
            cpds.setDriverClass("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            e.printStackTrace();
        }
        cpds.setJdbcUrl(
            "jdbc:mysql://localhost:3306/servlet_db?useUnicode=true&serverTimezone=GMT-8&characterEncoding=UTF-8&useSSL=false");
        cpds.setUser("root");
        cpds.setPassword("root");
        cpds.setMinPoolSize(5);
        cpds.setAcquireIncrement(5);
        cpds.setMaxPoolSize(20);
        return cpds;
    }

    /**
     * 销毁一个数据库连接池
     */
    public void shutDown() {
        try {
            DataSources.destroy(dataSource);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * 根据类型创建类型实例
     */
    public Object getObject(Class type) {
        if (type == ProductValidator.class) {
            return new ProductValidator();
        } else if (type == ProductDAO.class) {
            return createProductDAO();
        } else if (type == GetProductsAction.class) {
            return createGetProductsAction();
        } else if (type == SaveProductAction.class) {
            return createSaveProductAction();
        }
        return null;
    }

    private GetProductsAction createGetProductsAction() {
        GetProductsAction getProductsAction = new GetProductsAction();
        // 向 getProductsAction 中注入 ProductDAO
        getProductsAction.setProductDAO(createProductDAO());
        return getProductsAction;
    }

    private SaveProductAction createSaveProductAction() {
        SaveProductAction saveProductAction = new SaveProductAction();
        // 向 saveProductAction 中注入 ProductDAO
        saveProductAction.setProductDAO(createProductDAO());
        return saveProductAction;
    }

    private ProductDAO createProductDAO() {
        ProductDAO productDAO = new ProductDAOImpl();
        // 向 productDAO 注入DataSource
        productDAO.setDataSource(dataSource);
        return productDAO;
    }
}

        在 Servlet Controller 的 init 方法中实例化 Dependencylnjector,并在它的 destroy 方法中调用 Dependencylnjector 的 shutdown方法。Servlet 不再创建自己的依赖,而是从 Dependencylnjector 处获得。

        代码清单 ControllerServlet 类:

import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.my.servlet.programmer.action.GetProductsAction;
import com.my.servlet.programmer.action.SaveProductAction;
import com.my.servlet.programmer.form.ProductForm;
import com.my.servlet.programmer.model.Product;
import com.my.servlet.programmer.util.DependencyInjector;
import com.my.servlet.programmer.validator.ProductValidator;

/**
 * 控制器实现
 */
@WebServlet(name = "ControllerServlet", urlPatterns = {"/product_input", "/product_save", "/product_list"})
public class ControllerServlet extends HttpServlet {

    private static final long               serialVersionUID = 6679L;
    private              DependencyInjector dependencyInjector;

    @Override
    public void init() {
        //初始化依赖注入器
        System.out.println("start init...");
        dependencyInjector = new DependencyInjector();
        dependencyInjector.start();
        System.out.println("end init...");
    }

    @Override
    public void destroy() {
        dependencyInjector.shutDown();
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        String uri = request.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        String dispatchUrl = null;

        //根据url路径进行匹配
        if (action.equals("product_input")) {
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if (action.equals("product_save")) {
            //1-实例化表单
            request.setCharacterEncoding("UTF-8"); //防止中文乱码
            ProductForm productForm = new ProductForm();
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            //2-依赖注入校验器
            ProductValidator productValidator = (ProductValidator) dependencyInjector.getObject(ProductValidator.class);
            List<String> errors = productValidator.validate(productForm);
            if (errors.isEmpty()) {
                //3-创建product实例
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(productForm.getDescription());
                product.setPrice(Float.parseFloat(productForm.getPrice()));

                //4-依赖注入存储Action
                SaveProductAction saveProductAction = (SaveProductAction) dependencyInjector.getObject(SaveProductAction.class);
                saveProductAction.save(product);

                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {
                request.setAttribute("form", productForm);
                request.setAttribute("errors", errors);
                dispatchUrl = "/jsp/ProductForm.jsp";
            }
        } else if (action.equals("product_list") || action.isEmpty()) {
            //依赖注入查询Action
            GetProductsAction getProductsAction = (GetProductsAction) dependencyInjector.getObject(GetProductsAction.class);
            List<Product> products = getProductsAction.getProducts();
            request.setAttribute("products", products);
            dispatchUrl = "/jsp/ProductList.jsp";
        }

        // 重定向
        if (dispatchUrl != null) {
            RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl);
            response.setCharacterEncoding("UTF-8");
            rd.forward(request, response);
        }
    }
}

        至此,一个简单的依赖注入就做好了,其功能演示并无变化,所以不做过多赘述。

        之前通过 ProductDAOImpl 的 getConnection 方法获取的连接是来自一个由容器维护的 JNDI 对象,因此,如果要想测试 DAO 对象,就必须先将整个应用程序部署在一个容器中,并用一个浏览器输入 DAO 对象的值。这么做将损失很大的生产效率,所以在本在程序设计中通过依赖注入替换成了 C3P0 的 DataSource,因为有了依赖注射器,程序中的每一个组件都可以独立进行测试

        本次 Servlet 程序的完整项目代码,可以直接点击这个链接进行下载

        至此,全文介绍完毕。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Servlet 的应用程序设计 的相关文章

  • 计算机操作系统之期末考试复习——进程的基本状态及转换

    进程的基本状态 就绪状态 Ready 进程已处于准备好运行的状态 即进程已分配到除CPU以外的所有必要资源后 只要获得CPU 便可立即执行 执行状态 Running 进程以获得CPU 其程序正在执行的状态 阻塞状态 Block 正在执行的进
  • Python调用海康威视网络相机之——python调用海康威视C++的SDK

    运行环境 Win10系统 64位 Anaconda3 python 3 5 5 基于anaconda环境 opencv 3 4 2 早前就已经安装了 本文不会讲如何安装 Visual Studio 2015 硬件 DS 2CD3T56DWD
  • 记录qt窗口在拖动过程中出现的问题

    问题描述 在窗口拖动的过程中刚开始可以流畅的拖动窗口 但是一小会儿之后出现窗口拖不动的现象 或者按下鼠标在拖动区域内可以流畅拖动 但是按下鼠标朝一个方向拖动后 释放鼠标 按照此操作操作几次后 出现窗口拖不动的情况 但是过一会儿后窗口又可以被
  • 使用Python批量将EXCEL转为CSV文件

    今天遇到一个批量将100多个EXCEL 且每个excel中有多个sheet页 转为CSV文件的需求 同事使用wps右键逐个拆分 效率实在太慢 网上查了一下 使用pandas库中的to csv 方法可以解决这个问题 下面这段代码可以批量处理e
  • hadoop join之map side join

    在本例中 我们仍然采用上一例中的数据文件 之所以存在reduce side join 是因为在map阶段不能获取所有需要的join字段 即 同一个key对应的字段可能位于不同map中 Reduce side join是非常低效的 因为shu
  • 前端向后台传值的方式及相关注解

    文章目录 传参方式 QueryString传参 访问路径传参 form表单传参 ajax方式进行JSON传参 常用注解 RequestBody RequestParam 为前端提供接口 需要对传参方式有所熟悉 以下有几种常用的传参方式和常见
  • 连接vc显示服务器无法解释客户端请,vc服务器和客户端配置

    vc服务器和客户端配置 内容精选 换一换 当出现以下问题时 可以参考本章节排查解决 可以直接访问后端业务 但是无法通过负载均衡访问后端业务 通过私网IP可以访问负载均衡 但是公网IP无法访问负载均衡 后端服务器健康检查异常 客户端通过负载均
  • NDK与Cygwin配置

    1 Android 开发环境搭建 Android开发环境搭建不是重点 相信看此文章的很多人都已经搭建成功 这里随便概述性的说说 下载 JDK 下载 Eclipse 下载 Android SDK 启动Eclipse 安装 ADT 2 下载安装
  • java中optional 常用用法

    Java中的Optional是一个容器对象 它可以包含一个非空值 也可以为空 它的主要作用是在编写代码时避免空指针异常 java 8 中Optional的完整用法如下 1 创建Optional对象 可以通过of 方法创建一个包含非空值的Op
  • SpringCloud Ribbon(负载均衡)

    1 SpringCloud Ribbon 是一个客户端的负载均衡组件 主要提供负载均衡算法 2 Ribbon中负载均衡算法实现 AbstractLoadBalancerRule 负载均衡策略的抽象类 在该抽象类中定义了负载均衡器ILoadB
  • LDO的dropout voltage

    目录 从一个设计错误谈起 Dropout压降 从芯片内部电路结构理解dropout压降 MOS管 gt 低dropout压降的LDO 进一步降低dropout压降的方法 在系统设计中 电源管理是不可或缺的 而LDO 线性稳压器 是电源管理器
  • Vue生命周期详解

    Vue的生命周期 Vue实例有一个完整的生命周期 也就是从开始创建初女台化数据 编译模板 挂载DOM 渲染一更新一渲染 卸载等一系列过程 我们称这是Vue的生命周期 在Vue官网有这样一张图 生命周期 vue实例从创建到销毁的过程 声明周期
  • android卡刷包自制rom之添加删除,添加/删除ROM内置APP的教程

    ROM闪存软件包是Android闪存中必不可少的东西删除刷机包自带软件 但是大多数ROM闪存软件包都与许多应用程序集成在一起 其中许多不是您想要的 也没有您想要的应用程序 使用Mushroom ROM Assistant快速删除和添加APK
  • 偏最小二乘法(PLS)计算流程

    根据相关文献 将PLS的计算流程整理如下 供大家参考 1 输入样本矩阵X 输出值矩阵Y分别对X Y进行中心化处理 归一化方差 得到矩阵 E0和F 2 令n 0 3 计算矩阵En FF En 计算En FF En的最大特征根对应的单位特征向量
  • AndroidStudio会在@NonNull后面自动添加@org.jetbrains.annotations.Nullable

    今天呀在AndroidStudio 4 1中一直有个问题困扰着我 那就是AndroidStudio会在 NonNull后面自动添加 org jetbrains annotations Nullable 这 自动添加就算了还报错 每次都要删除
  • 区块链搬砖实战

    前言 相信不少币友在数字货币交易的时候都发现了 不同的交易平台不同的数字货币都存在一定的差价 这时候就引出了 搬砖的概念 搬砖的概念 由于各种因素导致各平台的虚拟货币的价格有价格差 产生了套利空间 运用平台之间价格差来谋求利益的行为俗称 搬
  • vs code终端修改字体大小以及其它样式

    1 在文件 gt 首选项 gt 设置 2 用户 gt 工作台 gt 找到settings json点击进入 3 在 settings json文件里添加需要修改的样式 terminal integrated cursorBlinking t
  • 软件资源下载链接

    1 Dreamweaver DW cs5 链接 https pan baidu com s 1kVqqpqJ 密码 9hc4 DW cc2014 链接 https pan baidu com s 1skQvBCL 密码 3kd8 DW cc
  • 数据库系统——复习总结

    数据库系统

随机推荐

  • cvCanny检测边缘,连通重要的非连通区域

    这个函数就是使用canny边缘检测算子检测图象的边缘 在opencv下使用这个函数之前最好将图象平滑处理一下 要不然可能检测不到边缘 检测到的边缘 这些边缘大多还不是连通区域 可以通过3 3的模板将一些相近的边缘连接起来 也可以用cvDil
  • Tesseract-OCR的配置和应用

    1 百度搜索Tesseract OCR下载 Tesseract orc setup 3 02 02 exe 要记得自己的安装目录 博主的安装路径为 C Program Files x86 Tesseract OCR 等会配置环境变量要用 如
  • C# Selenium chromedriver 隐藏Devtool控制台窗口

    爬取网页信息时 使用了C Selenium WebDriver dll chromedriver Chrome 除了chromedriver控制台窗口 可以通过CDS HideCommandPromptWindow true隐藏 还有出现一
  • Health Kit基于数据提供专业方案,改善用户睡眠质量

    什么是CBT I 中国社科院等机构今年发布的 中国睡眠研究报告2023 内容显示 2022年 受访者的每晚平均睡眠时长为7 40小时 近半数受访者的每晚平均睡眠时长不足8小时 47 55 16 79 的受访者的每晚平均睡眠时长不足7小时 这
  • C++异常处理机制(超级详细)

    目录 0 异常处理机制简介 1 传统错误处理机制 通过函数返回值 2 异常处理机制语法 3 异常接口声明 4 异常类型和声明周期 4 1throw基本类型异常 int float char 4 2throw字符串类型异常 4 3throw类
  • 数据可视化(学会用matplotlib绘图)

    1 绘制简单的折线图 import matplotlib pyplot as plt squres 1 4 9 16 25 plt plot squres 把列表传给plot 这个函数尝试根据这些数字绘制出有意义的图形 plt show 打
  • 【取模软件PCtoLCD2002使用教程】

    1 打开取模软件PCtoLCD 2 左上角模式选择为字符模式 3 点击选项 4 设置如下 然后点击确定 5 以16x16汉字取模为例 字宽字高都改为16 然后在输入栏输入汉字 点击生成字模生成的字模如下 然后将字模复制到例程lcdfont
  • 距离向量算法_RIP协议及距离向量算法(上)【44】

    1 RIP协议 RIP 全称Routing Information Protocol 即路由信息协议 RIP是一种分布式的基于距离向量的路由选择协议 是因特网的协议标准 最大优点的简单 RIP协议要求网络中每一个路由器都维护从它自己到其它每
  • Centos 安装mysql8(YUM方式)

    1 执行安装命令 root localhost wget https dev mysql com get mysql80 community release el8 4 noarch rpm root localhost yum modul
  • JAVA 定义静态map并赋值

    private static final Map
  • C语言入门经典三,c语言入门经典第4版和第3版有什么区别

    问 微软的C语言和其他C语言有什么区别吗 答 不知道楼主说的是所谓 微软的c 是指什么概念 个人意见 仅供参考 1 如果是指微软推出的c语言的编译器ms c的话 其实就是c语言各个编译器之间的区别 如果你想深入了解 最好是学习下c标准的制定
  • 【ICS大作业】

    零 摘要 本文对给定的hello程序的生命周期进行了系统性分析 程序经预处理生成hello i 编译生成hello s 汇编生成hello o 最后链接成可执行目标文件hello Shell收到 hello的指令 调用fork函数创建进程
  • 再临SpringBoot——WebFlux处理流程

    文章目录 WebFlux初次尝试 处理过程源码分析 SpringMvc通常是Servlet应用 因此 可能被当前线程阻塞 以远程调用为例 由于阻塞的缘故 导致Servlet容器使用较大的线程池处理请求 而Spring WebFlux通常是非
  • 第一篇——开始

    第一篇 开始 个人简介 学习经历 学习过程 后记 个人简介 个人简介 以山河作礼 学习经历 作为一名本科大一的软件工程专业学生 我已经在CSDN学习了近一年的时间 同时也深入学习了C语言半年 在我的CSDN博客上 我将记录下我在学习过程中的
  • Vue3.0监听props方法

    学习vue3 0记录下props监听 第一种直接监听这个props export default defineComponent props isOpen Boolean emits close modal null setup props
  • 算术表达式的前缀式、中缀式、后缀式相互转换

    中缀表达式 中缀记法 中缀表达式是一种通用的算术或逻辑公式表示方法 操作符以中缀形式处于操作数的中间 中缀表达式是人们常用的算术表示方法 虽然人的大脑很容易理解与分析中缀表达式 但对计算机来说中缀表达式却是很复杂的 因此计算表达式的值时 通
  • 02Nginx源码分析之总结内存开辟的相关函数(ngx_alloc.c,ngx_palloc.c)

    02Nginx源码分析之总结内存开辟的相关函数 ngx alloc c ngx palloc c 前言 写这一篇的目的是为了方便接下来的阅读 否则每次都要对应查找相应的函数 非常麻烦 并且注意 下面所说的内存对齐 实际上是通过倍数来对齐的
  • Mac下使用Xcode的freopen读取输入输出

    很多使用Mac的小伙伴都知道 类似于win或者linux系统下的直接freopen是不行的 或者说强制读入也是不可的 于是 就是去想办法去读入它了 那么怎么读文件和写文件呢 创建 txt 文件 找到文本编辑 点击新建文稿 但是发现 怎么是这
  • 我要自学网视频教程ajax,这么逆天的自学网站,还是第一次见,难怪考不上清华北大...

    原标题 这么逆天的自学网站 还是第一次见 难怪考不上清华北大 有很多朋友 工作了N年之后 工资一直不涨 其实就是没有找到正确的方法 这个时候呢 我们就需要用到一些非常好用的自学网站 去帮助自己在所在的领域进行充电啦 首选的当然就是下面几个啦
  • Servlet 的应用程序设计

    目录 1 基于 Servlet Controller 的应用程序设计 2 基于 Filter Dispatcher 的应用程序设计 3 使用表单验证器 4 添加数据库访问 5 依赖注入 在该模型中 用一个 Servlet 或者过滤器充当控制