对于简单的Java Web项目,咱们的项目仅仅包含几个jsp页面,因为项目比较小,咱们一般能够经过连接方式进行jsp页面间的跳转。css
可是若是是一个中型或者大型的项目,上面那种方式就会带来许多维护困难,代码复用率低等问题。所以,咱们推荐使用MVC模式。html
MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计模式。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑汇集到一个部件里面,在须要改进和个性化定制界面及用户交互的同时,不须要从新编写业务逻辑,达到减小编码的时间。前端
MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器。vue
使用的MVC的目的:在于将M和V的实现代码分离,从而使同一个程序可使用不一样的表现形式。好比Windows系统资源管理器文件夹内容的显示方式,下面两张图中左边为详细信息显示方式,右边为中等图标显示方式,文件的内容并无改变,改变的是显示的方式。无论用户使用何种类型的显示方式,文件的内容并无改变,达到M和V分离的目的。java
在网页当中:react
下图说明了三者之间的调用关系:程序员
用户首先在界面中进行人机交互,而后请求发送到控制器,控制器根据请求类型和请求的指令发送到相应的模型,模型能够与数据库进行交互,进行增删改查操做,完成以后,根据业务的逻辑选择相应的视图进行显示,此时用户得到这次交互的反馈信息,用户能够进行下一步交互,如此循环。angularjs
常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另一些模式如:MVP、MVVM。web
注意:咱们应该避免用户经过浏览器直接访问jsp页面。数据库
最典型的MVC就是jsp+servlet+javabean模式:
每一个控制器中能够定义多个请求URL,每一个用户请求都发送给控制器,请求中的URL标识出对应的Action。Action表明了Web应用能够执行的一个操做。一个提供了Action的Java对象称为Action对象。一个Action类型能够支持多个Action(在Spring MVC以及Struts2中),或一个Action(在struts1中)。
注意:Struts一、Spring MVC和JavaServer Fces使用Servlet做为控制器,而Struts2使用Filter做为控制器。
Struts2框架:Struts2是基于MVC的轻量级的web应用框架。Struts2的应用范围是Web应用,注重将Web应用领域的平常工做和常见问题抽象化,提供一个平台帮助快速的完成Web应用开发。基于Struts2开发的Web应用天然就能实现MVC,Struts2着力于在MVC的各个部分为开发提供相应帮助。
下面经过代码来简单解释一下(这里只是简单使用):
Login.html(位于WebContent下)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form id="form1" name="form1" action="/action/Login.action" method="post"> 登陆<br> 用户名:<input name="username" type="text"><br> 密码:<input name="password" type="password"><br> <input type="submit" value="登陆"> </form> </body> </html>
LoginAction.Java(位于包com.dc365.s2下)
if(username.equals("1") && password.equals("1")) { return "Success"; } return "Error";
struts.xml(位于src下)
<struts> <package name="default" namespcase="/action" extends="struts-default"> <action name="Login" class="com.dc365.s2.LoginAction"> <result name="Success">Success.jsp</result> <result name="Error">Error.jsp</result> </action> </package> </struts>
注意:除了上面代码,还须要在web.xml里面配置前端过滤器FilterDispatcher。
用户首先在Login.html中输入用户名和密码,点击登录,用户请求(请求路径为/action/Login.action)首先到达前端控制器FilterDispatcher,FilterDispatcher根据用户请求的URL和配置在struts.xml找到对应的Login,而后根据对应的class的路径进入相应的login.Java,在这里判断以后,返回success或error,而后根据struts.xml中的result值,指向相应的jsp页面。
接下来咱们将会演示基于MVC框架的4个不一样的示例:
建立一个名为appdesign1的Dynamic Web Project项目,Servlet版本选择3.0,其功能设定为输入一个产品信息。具体为:
示例应用支持以下两个action(每一个action对应一个URL):
示例应用由以下组件组成:
示例应用目录结构以下:
注意:因为咱们采用的是Servlet3.0版本,web.xml能够不须要,具体能够参考博客Servlet2.5版本和Servlet3.0版本。
项目右键属性、部署路径设置以下:
Product类是一个封装了产品信息的JavaBean。Product类包含三个属性:name,description和price:
package appdesign1.model; import java.io.Serializable; import java.math.BigDecimal; public class Product implements Serializable { private static final long serialVersionUID = 748392348L; private String name; private String description; private BigDecimal 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 BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } }
Product类实现了java.io.Serializable接口,其实例能够安全地将数据保存到HttpSession中。根据Serializable的要求,Product类实现了一个serialVersionUID 属性。
表单类与HTML表单相对应,是后者在服务器的表明。ProductForm类看上去同Product类类似,这就引出一个问题:ProductForm类是否有存在的必要:
package appdesign1.form; 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; } }
实际上,经过表单对象能够将ServletRequest中的表单信息传递给其它组件,好比校验器Validator(后面会介绍,主要用于检查表单输入数据是否合法)。若是不使用表单对象,则应将ServletRequest传递给其它组件,然而ServletRequest是一个Servlet层的对象,是不该当暴露给应用的其它层。
另外一个缘由是,当数据校验失败时,表单对象将用于保存和显示用户在原始表单上的输入。
注意:大部分状况下,一个表单类不须要事先Serializable接口,由于表单对象不多保存在HttpSession中。
ContrlooerServlet类继承自javax.servlet.http.HttpServlet类。其doGet()和doPost()方法最终调用process()方法,该方法是整个Servlet控制器的核心。
可能有人好奇,为什么Servlet控制器命名为ControllerServlet,实际上,这里听从了一个约定:全部Servlet的类名称都带有Servlet后缀。
package appdesign1.controller; 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 appdesign1.action.SaveProductAction; import appdesign1.form.ProductForm; import appdesign1.model.Product; import java.math.BigDecimal; //Servlet3.0使用注解指定访问Servlet的URL @WebServlet(name = "ControllerServlet", urlPatterns = { "/input-product", "/save-product" }) 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 { String uri = request.getRequestURI(); /* * uri is in this form: /contextName/resourceName, * for example: /appdesign1/input-product. * However, in the event of a default context, the * context name is empty, and uri has this form * /resourceName, e.g.: /input-product */ int lastIndex = uri.lastIndexOf("/"); String action = uri.substring(lastIndex + 1); // execute an action 根据不一样的uri执行不一样的action String dispatchUrl = null; if ("input-product".equals(action)) { // no action class, just forward dispatchUrl = "/jsp/ProductForm.jsp"; } else if ("save-product".equals(action)) { // create form 建立一个表单对象、保存表单信息 ProductForm productForm = new ProductForm(); // populate action properties productForm.setName(request.getParameter("name")); productForm.setDescription( request.getParameter("description")); productForm.setPrice(request.getParameter("price")); // create model 建立一个Model类 Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) { } // execute action method 保存表单 SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product); // store model in a scope variable for the view request.setAttribute("product", product); dispatchUrl = "/jsp/ProductDetails.jsp"; } //请求转发 if (dispatchUrl != null) { RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); } } }
ControllerServlet的process()方法处理全部输入请求。首先是获取URI和action名称:
String uri = request.getRequestURI(); int lastIndex = uri.lastIndexOf("/"); String action = uri.substring(lastIndex + 1);
在本示例中,action值只会是input-product或sava-product。
接着,当action值为sava-product,process()方法执行以下步骤:
process()方法中判断action的if代码块以下:
if ("input-product".equals(action)) { // no action class, just forward dispatchUrl = "/jsp/ProductForm.jsp"; } else if ("save-product".equals(action)) { .... }
对于input-product,无需任何操做;而针对save-product,则建立一个ProductForm对象和Product对象,并将前者的属性值复制到后者。
再次,process()方法实例化SavaProductAction类,并调用其save()方法:
// create form 建立一个表单对象、保存表单信息 ProductForm productForm = new ProductForm(); // populate action properties productForm.setName(request.getParameter("name")); productForm.setDescription( request.getParameter("description")); productForm.setPrice(request.getParameter("price")); // create model 建立一个Model类 Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) { } // execute action method 保存表单 SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product);
而后,将Product对象放入HttpServletRequest对象中,以便对应的视图能够访问到:
// store model in a scope variable for the view request.setAttribute("product", product); dispatchUrl = "/jsp/ProductDetails.jsp";
最后,process()方法转到视图,若是action是input-product,则转到ProductForm.jsp页面,不然转到ProductDetails.jsp页面:
//请求转发 if (dispatchUrl != null) { RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); }
这个应用这有一个action类,负责将一个product对象持久化,例如数据库。这个action类名为SaveProductAction:
package appdesign1.action; import appdesign1.model.Product; public class SaveProductAction { public void save(Product product) { // insert Product to the database } }
在这个示例中,SaveProductAction类的save()方法是一个空实现。
示例应用包含两个jsp页面。第一个页面ProductForm.jsp对应input-product操做,第二个页面ProductDetails.jsp对应sava-product操做。
ProductForm.jsp:
<!DOCTYPE html> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <form method="post" action="save-product"> <h1>Add Product <span>Please use this form to enter product details</span> </h1> <label> <span>Product Name :</span> <input id="name" type="text" name="name" placeholder="The complete product name"/> </label> <label> <span>Description :</span> <input id="description" type="text" name="description" placeholder="Product description"/> </label> <label> <span>Price :</span> <input id="price" name="price" type="number" step="any" placeholder="Product price in #.## format"/> </label> <label> <span> </span> <input type="submit"/> </label> </form> </body> </html>
注意:不要用HTML table标签来布局表单,使用CSS。
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>
ProductForm页面包含了一个HTML表单。ProductDetails页面经过EL表达式语言访问HttpServletRequest所包含的product对象。
此外,该实例存在一个问题,即用户能够直接经过浏览器访问这两个jsp页面,咱们能够经过如下方式避免这种直接访问:
main.css:
form { margin-left:auto; margin-right:auto; max-width: 450px; background: palegreen; padding: 25px 15px 25px 10px; border:1px solid #dedede; font: 12px Arial; } h1 { padding: 20px; display: block; border-bottom:1px solid grey; margin: -20px 0px 20px 0px; color: mediumpurple; } h1>span { display: block; font-size: 13px; } label { display: block; } label>span { float: left; width: 20%; text-align: right; margin: 14px; color: mediumpurple; font-weight:bold; } input[type="text"], input[type="number"] { border: 1px solid #dedede; height: 30px; width: 70%; font-size: 12px; border-radius: 3px; margin: 5px; } input[type="submit"] { background: mediumseagreen; font-weight: bold; border: none; padding: 8px 20px 8px 20px; color: black; border-radius: 5px; cursor: pointer; margin-left:4px; } input[type="submit"]:hover { background: red; color: yellow; }
将项目部署到tomcat服务器,而后启动服务器,假设示例应用运行在本机的8000端口上,则能够经过以下URL访问应用:
http://localhost:8008/appdesign1/input-product
完成表单输入后,表单提交到以下服务器URL上:
http://localhost:8008/appdesign1/save-product
虽然Servlet是MVC框架中最多见的控制器,可是过滤器也能够充当控制器。Struts2就是使用过滤器做为控制器,是由于该过滤波器也可用于提供静态页面。
public void init(FilterConfig filterConfig) throws ServletException
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException
public void destroy();
public void doFilter(ServletRequest request,ServletResponse response) throws IOException,ServletException
注意:过滤器没有做为欢迎页面(即仅仅在浏览器地址栏中输入域名)的权限,仅输入域名时不会调用过滤器分派器。
下面咱们采用一个名为FilterDispactcher的过滤器替代appdesign1项目中的Servlet控制器,项目命名为appdesign2,目录结构以下:
package appdesign2.filter; 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 appdesign2.action.SaveProductAction; import appdesign2.form.ProductForm; import appdesign2.model.Product; import java.math.BigDecimal; //Servlet3.0新增了注解的特性,指定过滤器的访问URL @WebFilter(filterName = "DispatcherFilter", urlPatterns = { "/*" }) public class DispatcherFilter implements Filter { //过滤器初始化 @Override public void init(FilterConfig filterConfig) throws ServletException { } //过滤器销毁 @Override public void destroy() { System.out.println("** 过滤器销毁。"); } //执行过滤操做 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { System.out.println("** 执行doFilter()方法以前。"); HttpServletRequest req = (HttpServletRequest) request; String uri = req.getRequestURI(); /* * uri is in this form: /contextName/resourceName, for * example /appdesign2/input-product. However, in the * case of a default context, the context name is empty, * and uri has this form /resourceName, e.g.: * /input-product */ // action processing int lastIndex = uri.lastIndexOf("/"); String action = uri.substring(lastIndex + 1); String dispatchUrl = null; if ("input-product".equals(action)) { // do nothing dispatchUrl = "/jsp/ProductForm.jsp"; } else if ("save-product".equals(action)) { // create form ProductForm productForm = new ProductForm(); // populate action properties productForm.setName(request.getParameter("name")); productForm.setDescription( request.getParameter("description")); productForm.setPrice(request.getParameter("price")); // create model Product product = new Product(); product.setName(productForm.getName()); product.setDescription(product.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) { } // execute action method SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product); // store model in a scope variable for the view request.setAttribute("product", product); dispatchUrl = "/jsp/ProductDetails.jsp"; } // forward to a view if (dispatchUrl != null) { RequestDispatcher rd = request .getRequestDispatcher(dispatchUrl); rd.forward(request, response); } else { // let static contents pass filterChain.doFilter(request, response); } System.out.println("** 执行doFilter()方法以后。"); } }
doFilter()方法的内容同appdesign1中的process()方法。
因为过滤器的过滤目标是包括静态内容在内的全部网址,所以,若没有相应的action,则须要调用filterChain.doFilter();
else { // let static contents pass filterChain.doFilter(request, response); }
要测试应用,将项目配置到tomcat服务器,启动服务器,并在浏览器输入以下URL:
http://localhost:8008/appdesign2/input-product
在Web应用执行action时,很重要的一个步骤就是进行输入校验。检验的内容能够是简单的,如检查一个输入是否为空,也能够是复杂的,如检验信用卡号。实际上,由于校验工做如此重要,Java社区专门发布了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,将Java世界的输入校验进行标准化。现代的MVC框架一般同时支持编程式和声明式两种校验方式。在编程式中,须要经过编码进行用户输入校验;而在声明式中,则须要提供包含校验规则的XML文档或者属性文件。
注意:即便您可使用HTML5或JavaScript执行客户端输入校验,也不要依赖它,由于精明的用户能够轻松地绕过它。始终执行服务器端输入验证!
本节的新应用(appdesign3)扩展自appdesign1,可是多了一个ProductValidator类:
package appdesign3.validator; import java.util.ArrayList; import java.util.List; import appdesign3.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("Product must have a name"); } //商品价格不能为空、也不能是非法数字 String price = productForm.getPrice(); if (price == null || price.trim().isEmpty()) { errors.add("Product must have a price"); } else { try { Float.parseFloat(price); } catch (NumberFormatException e) { errors.add("Invalid price value"); } } return errors; } }
注意:ProductValidator类中有一个操做ProductForm对象的validate()方法,确保产品的名字非空,其价格是一个合理的数字。validate()方法返回一个包含错误信息的字符串列表,若返回一个空列表,则表示输入合法。
如今须要让控制器使用这个校验器了,ControllerServlet代码以下:
package appdesign3.controller; 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 appdesign3.action.SaveProductAction; import appdesign3.form.ProductForm; import appdesign3.model.Product; import appdesign3.validator.ProductValidator; import java.math.BigDecimal; //Servlet3.0使用注解指定访问Servlet的URL @WebServlet(name = "ControllerServlet", urlPatterns = { "/input-product", "/save-product" }) 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(); /* * uri is in this form: /contextName/resourceName, * for example: /appdesign1/input-product. * However, in the case of a default context, the * context name is empty, and uri has this form * /resourceName, e.g.: /input-product */ int lastIndex = uri.lastIndexOf("/"); String action = uri.substring(lastIndex + 1); // execute an action 根据不一样的uri执行不一样的action String dispatchUrl = null; if ("input-product".equals(action)) { // no action class, there is nothing to be done dispatchUrl = "/jsp/ProductForm.jsp"; } else if ("save-product".equals(action)) { // instantiate action class 建立一个表单对象、保存表单信息 ProductForm productForm = new ProductForm(); // populate action properties productForm.setName( request.getParameter("name")); productForm.setDescription( request.getParameter("description")); productForm.setPrice(request.getParameter("price")); // validate ProductForm 表单输入校验 ProductValidator productValidator = new ProductValidator(); List<String> errors = productValidator.validate(productForm); if (errors.isEmpty()) { //表单输入正确 // create Product from ProductForm 建立一个Model类 Product product = new Product(); product.setName(productForm.getName()); product.setDescription( productForm.getDescription()); product.setPrice(new BigDecimal(productForm.getPrice())); // no validation error, execute action method 保存表单 SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product); // store action in a scope variable for the view request.setAttribute("product", product); dispatchUrl = "/jsp/ProductDetails.jsp"; } else { //表单输入有误,从新加载该页面 request.setAttribute("errors", errors); request.setAttribute("form", productForm); dispatchUrl = "/jsp/ProductForm.jsp"; } } // forward to a view 请求转发 if (dispatchUrl != null) { RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); } } }
新版的ControllerServlet类添加了初始化ProductValidator类,并调用了validate()方法的代码:
// validate ProductForm 表单输入校验 ProductValidator productValidator = new ProductValidator(); List<String> errors = productValidator.validate(productForm);
validate()方法接受一个ProductForm参数,它分装了输入到HTML表单的产品信息。若是不用ProductForm,则应将ServletRequest传递给校验器。
若是校验成功,validate()方法返回一个空列表,在这种状况下,将建立一个产品并传递给SaveProductAction,而后,控制器将Product对象存储在ServletRequest中,并转发到ProductDetails.jsp页面,显示产品的详细信息。若是校验失败,控制器将错误列表和ProductForm对象存储在ServletRequest中,并返回到ProductForm.jsp中。
if (errors.isEmpty()) { //表单输入正确 // create Product from ProductForm 建立一个Model类 Product product = new Product(); product.setName(productForm.getName()); product.setDescription( productForm.getDescription()); product.setPrice(new BigDecimal(productForm.getPrice())); // no validation error, execute action method 保存表单 SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product); // store action in a scope variable for the view request.setAttribute("product", product); dispatchUrl = "/jsp/ProductDetails.jsp"; } else { //表单输入有误,从新加载该页面 request.setAttribute("errors", errors); request.setAttribute("form", productForm); dispatchUrl = "/jsp/ProductForm.jsp"; } }
如今,须要修改appdesign3应用的ProductForm.jsp页面,使其能够显示错误信息以及错误的输入:
<!DOCTYPE html> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <form method="post" action="save-product"> <h1>Add Product <span>Please use this form to enter product details</span> </h1> ${empty requestScope.errors? "" : "<p id='errors'>" += "Error(s)!" += "<ul>"} <!--${requestScope.errors.stream().map( x -> "--><li>"+=x+="</li><!--").toList()}--> ${empty requestScope.errors? "" : "</ul></p>"} <label> <span>Product Name :</span> <input id="name" type="text" name="name" placeholder="The complete product name" value="${form.name}"/> </label> <label> <span>Description :</span> <input id="description" type="text" name="description" placeholder="Product description" value="${form.description}"/> </label> <label> <span>Price :</span> <input id="price" name="price" type="number" step="any" placeholder="Product price in #.## format" value="${form.price}"/> </label> <label> <span> </span> <input type="submit"/> </label> </form> </body> </html>
将项目配置到tomcat服务器,启动服务器,并在浏览器输入以下URL:
http://localhost:8008/appdesign3/input-product
若产品表单提交了无效数据,页面将显示错误信息,以下:
什么是依赖注入技术?若是不了解的话,能够参考博客:Spring MVC -- Spring框架入门(IoC和DI)。
示例appdesign4使用了一个自制的依赖注入器。在实际的应用中,咱们应该使用Spring。该示例用来生成pdf。它有两个action:
该应用的目录结构以下:
PDFAction类:
package action; import service.PDFService; public class PDFAction { private PDFService pdfService; public void setPDFService(PDFService pdfService) { this.pdfService = pdfService; } public void createPDF(String path, String input) { pdfService.createPDF(path, input); } }
PDFService类:
package service; import util.PDFUtil; public class PDFService { public void createPDF(String path, String input) { PDFUtil.createDocument(path, input); } }
PDFService使用了PDFUtil类,PDFUtil最终采用了apache pdfbox库来建立pdf文档,若是对建立pdf的具体代码有兴趣,能够进一步查看PDFUtil类。
package util; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; public class PDFUtil { public static void createDocument(String path, String input) { PDDocument doc = null; try { doc = new PDDocument(); PDFont font = PDType1Font.HELVETICA; PDPage page = new PDPage(); doc.addPage(page); float fontSize = 12.0f; PDRectangle pageSize = page.getMediaBox(); float centeredXPosition = (pageSize.getWidth() - fontSize / 1000f) / 2f; float stringWidth = font.getStringWidth(input); PDPageContentStream contentStream = new PDPageContentStream(doc, page); contentStream.setFont(font, fontSize); contentStream.beginText(); contentStream.moveTextPositionByAmount(centeredXPosition, 600); contentStream.drawString(input); contentStream.endText(); contentStream.close(); doc.save(path); doc.close(); } catch (Exception ex) { ex.printStackTrace(); } } }
这里的关键在于,PDFAction须要一个PDFService来完成它的工做,换句话说,PDFAction依赖于PDFService。没有依赖注入,你必须在PDFAction类中实例化PDFService类,这将使PDFAction更不可测试。除此以外,若是须要更改PDFService的实现,必须从新编译PDFAction。
使用依赖注入,每一个组件都有注入它的依赖项,这使得测试每一个组件更容易。对于在依赖注入环境中的类,必须使其支持注入。一种方法是为每一个依赖关系建立一个set方法。例如,PDFAction类有一个setPDFService方法,能够调用它来传递PDFService。注入也能够经过构造方法或者类属性进行。
一旦全部的类都支持注入,则能够选择一个依赖注入框架并将其导入项目。好比Spring框架、Google Guice、Weld和PicoContainer是一些好的选择。
appdesign4应用使用DependencyInjector类来代替依赖注入框架(在实际应用中,咱们应该使用一个合适的框架)。这个类专为appdesign4应用设计,利用Java的反射机制来实现(不懂的能够参考博客:Java基础 -- 深刻理解Java类型信息(Class对象)与反射机制),能够容易的实例化。一旦实例化,必须调用其start()方法来执行初始哈,使用后,应调其shutdown()方法来释放资源。在此示例中,start()和shutdown()都没有实现。
package util; import action.PDFAction; import service.PDFService; public class DependencyInjector { public void start() { // initialization code } public void shutDown() { // clean-up code } /* * Returns an instance of type. type is of type Class * and not String because it's easy to misspell a class name */ public Object getObject(Class type) { if (type == PDFService.class) { return new PDFService(); } else if (type == PDFAction.class) { PDFService pdfService = (PDFService) getObject(PDFService.class); PDFAction action = new PDFAction(); action.setPDFService(pdfService); return action; } return null; } }
要从DependencyInjector获取对象,须要调用其getObject()方法,并传递目标类对应的Class对象,DependencyInjector支持两种类型,即PDFAction和PDFService。例如,要获取PDFAction实例,你能够经过传递PDFAction.class来调用getObject():
PDFAction pdfAction = (PDFAction) dependencyInjector .getObject(PDFAction.class);
DependencyInjector(和全部依赖注入框架)的优雅之处在于它返回的对象注入了依赖。若是返回的对象所依赖的对象也有依赖,则所依赖的对象也会注入其自身的依赖。例如,从DependencyInjector获取的PDFAction已包含PDFService。无需在PDFAction类中本身建立PDFService。
appdesign4应用的Servlet控制器以下所示。请注意:在其init()方法中实例化DependencyInjector,并在其destroy()方法中调用DependencyInjector的shutdown()方法。Servlet再也不建立它自身的依赖项,相反,它从DependencyInjector获取这些依赖。
package servlet; import action.PDFAction; import java.io.IOException; import javax.servlet.ReadListener; 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 javax.servlet.http.HttpSession; import util.DependencyInjector; //Servlet3.0使用注解指定访问Servlet的URL @WebServlet(name = "ControllerServlet", urlPatterns = { "/form", "/pdf"}) public class ControllerServlet extends HttpServlet { private static final long serialVersionUID = 6679L; private DependencyInjector dependencyInjector; @Override public void init() { //实例化DependencyInjector dependencyInjector = new DependencyInjector(); dependencyInjector.start(); } @Override public void destroy() { //关闭DependencyInjector实例 dependencyInjector.shutDown(); } protected void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ReadListener r = null; String uri = request.getRequestURI(); /* * uri is in this form: /contextName/resourceName, * for example: /app10a/product_input. * However, in the case of a default context, the * context name is empty, and uri has this form * /resourceName, e.g.: /product_input */ int lastIndex = uri.lastIndexOf("/"); String action = uri.substring(lastIndex + 1); if ("form".equals(action)) { String dispatchUrl = "/jsp/Form.jsp"; RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); } else if ("pdf".equals(action)) { //建立pdf文档 HttpSession session = request.getSession(true); String sessionId = session.getId(); //利用dependencyInjector建立PDFAction对象 PDFAction pdfAction = (PDFAction) dependencyInjector .getObject(PDFAction.class); String text = request.getParameter("text"); //设置pdf在磁盘上文件路径E:\tomcat\wtpwebapps\appdesign4\result\sessionId.pdf String path = request.getServletContext() .getRealPath("/result/") + sessionId + ".pdf"; //System.out.println(path); //生成pdf文件,保存在path路径下 pdfAction.createPDF(path, text); // redirect to the new pdf StringBuilder redirect = new StringBuilder(); redirect.append(request.getScheme() + "://"); // http:// redirect.append("localhost"); // http://localhost int port = request.getLocalPort(); if (port != 80) { redirect.append(":" + port); // http://localhost:8008 } String contextPath = request.getContextPath(); // /appdesign4 if (!"/".equals(contextPath)) { redirect.append(contextPath); // http://localhost:8008/appdesign4 } redirect.append("/result/" + sessionId + ".pdf"); //System.out.println(redirect.toString()); response.sendRedirect(redirect.toString()); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { process(request, response); } }
Servlet控制器支持两种URL模式:
// redirect to the new pdf StringBuilder redirect = new StringBuilder(); redirect.append(request.getScheme() + "://"); // http:// redirect.append("localhost"); // http://localhost int port = request.getLocalPort(); if (port != 80) { redirect.append(":" + port); // http://localhost:8008 } String contextPath = request.getContextPath(); // /appdesign4 if (!"/".equals(contextPath)) { redirect.append(contextPath); // http://localhost:8008/appdesign4 } redirect.append("/result/" + sessionId + ".pdf"); //System.out.println(redirect.toString()); response.sendRedirect(redirect.toString());
该应用提供了两个测试类PDFActionTest和PdfBoxTest,因为依赖注入器,appdesign4中的每一个组件均可以独立测试,好比能够运行PDFActionTest类来测试类的createDocument()方法。
PDFActionTest类:
package test; import action.PDFAction; import util.DependencyInjector; public class PDFActionTest { public static void main(String[] args) { //建立DependencyInjector对象 DependencyInjector dependencyInjector = new DependencyInjector(); dependencyInjector.start(); //利用DependencyInjector建立PDFAction对象 PDFAction pdfAction = (PDFAction) dependencyInjector.getObject( PDFAction.class); //生成pdf文档 pdfAction.createPDF("E:/tomcat/wtpwebapps/appdesign4/result/1.pdf", "Testing PDFAction...."); dependencyInjector.shutDown(); } }
输出以下:
PdfBoxTest类:
package test; import util.PDFUtil; public class PdfBoxTest { public static void main(String[] args) { PDFUtil.createDocument("E:/tomcat/wtpwebapps/appdesign4/result/2.pdf", "Tod late"); } }
输出以下:
Form.jsp:
<!DOCTYPE html> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <form method="post" action="pdf"> <h1>Create PDF <span>Please use this form to enter the text</span> </h1> <label> <span>Text :</span> <input type="text" name="text" placeholder="Text for PDF"/> </label> <label> <span> </span> <input type="submit"/> </label> </form> </body> </html>
main.css:
form { margin-left:auto; margin-right:auto; max-width: 450px; background: palegreen; padding: 25px 15px 25px 10px; border:1px solid #dedede; font: 12px Arial; } h1 { padding: 20px; display: block; border-bottom:1px solid grey; margin: -20px 0px 20px 0px; color: mediumpurple; } h1>span { display: block; font-size: 13px; } label { display: block; } label>span { float: left; width: 20%; text-align: right; margin: 14px; color: mediumpurple; font-weight:bold; } input[type="text"], input[type="number"] { border: 1px solid #dedede; height: 30px; width: 70%; font-size: 12px; border-radius: 3px; margin: 5px; } input[type="submit"] { background: mediumseagreen; font-weight: bold; border: none; padding: 8px 20px 8px 20px; color: black; border-radius: 5px; cursor: pointer; margin-left:4px; } input[type="submit"]:hover { background: red; color: yellow; }
将项目配置到tomcat服务器,启动服务器,并在浏览器输入以下URL来测试应用:
http://localhost:8008/appdesign4/form
应用将展现一个表单:
若是在文本字段中输入一些内容并按提交按钮,服务器将建立一个pdf文件并发送重定向到浏览器:
请注意:重定向网址将采用此格式:
http://localhost:8008/appdesign4/result/sessionId.pdf
参考文章
[1]MVC简介(部分转载)
[2]Spring MVC 学习总结(一)——MVC概要与环境配置(IDea与Eclipse示例)(推荐)
[3]Spring MVC 学习总结(三)——请求处理方法Action详解
[4]Spring MVC学习指南