一、知识点
工厂Bean(Factory Bean)是作为创建IoC容器中其他Bean的工厂的一个Bean。概念上,工厂Bean与工厂方法非常类似,但是它是Bean构造期间可被Spring IoC容器识别的Spring专有Bean。
工厂Bean的基本要求是实现FactoryBean接口。为了方便,Spring提供了抽象模板类AbstractFactoryBean供你扩展。工厂Bean主要用于实现框架机制。下面是一些例子:
(1)在JNDI中查找对象(例如一个数据源)时,可以使用JndiObjectFactoryBean。
(2)使用经典Spring AOP为一个Bean创建代理时,可以使用ProxyFactoryBean。
(3)在IoC容器中创建一个Hibernate会话工厂时,可以使用LocalSessionFactoryBean。
工厂Bean是框架专用的,无法用在Spring IoC容器之外。
二、代码示例
(1)创建一个适用价格折扣的产品编写一个工厂Bean。
/* * Copyright 2013-2015 */ package com.jackie.codeproject.springrecipesnote.springioc; import org.springframework.beans.factory.config.AbstractFactoryBean; /** * Title: DiscountFactoryBean.java 类的功能说明 * * @author jackie * @since Apr 19, 2013 9:45:00 PM * @version V1.0 */ @SuppressWarnings("rawtypes") public class DiscountFactoryBean extends AbstractFactoryBean { private Product product; private double discount; @Override public Class getObjectType() { return product.getClass(); } @Override protected Object createInstance() throws Exception { product.setPrice(product.getPrice() * (1 - discount)); return product; } /** * @param product * the product to set */ public void setProduct(Product product) { this.product = product; } /** * @param discount * the discount to set */ public void setDiscount(double discount) { this.discount = discount; } }通过继承AbstractFactoryBean类,你的工厂Bean能够重载createInstance()方法以创建目标Bean实例。此外,你必须在getObjectType()方法中返回目标Bean的类型,是自动装配(Auto-wiring)功能正常工作。
(2)Bean配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"> <bean id="aaa" class="com.jackie.codeproject.springrecipesnote.springioc.DiscountFactoryBean"> <property name="product"> <bean class="com.jackie.codeproject.springrecipesnote.springioc.Battery"> <constructor-arg value="AAA" /> <constructor-arg value="2.5" /> </bean> </property> <property name="discount" value="0.2" /> </bean> <bean id="cdrw" class="com.jackie.codeproject.springrecipesnote.springioc.DiscountFactoryBean"> <property name="product"> <bean class="com.jackie.codeproject.springrecipesnote.springioc.Disc"> <constructor-arg value="CD-RW" /> <constructor-arg value="1.5" /> </bean> </property> <property name="discount" value="0.1" /> </bean> </beans>(3)Product类
/* * Copyright 2013-2015 */ package com.jackie.codeproject.springrecipesnote.springioc; /** * Title: Product.java * 产品抽象类 * * @author jackie * @since Apr 14, 2013 8:09:49 PM * @version V1.0 */ public abstract class Product { private String name; private double price; public Product() { } /** * <p>Title: </p> * <p>含参构造函数</p> * @param name * @param price */ public Product(String name, double price) { this.name = name; this.price = price; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the price */ public double getPrice() { return price; } /** * @param price the price to set */ public void setPrice(double price) { this.price = price; } public String toString() { return name + " " + price; } }(4)Battery类
/* * Copyright 2013-2015 */ package com.jackie.codeproject.springrecipesnote.springioc; /** * Title: Battery.java 类的功能说明 * * @author jackie * @since Apr 14, 2013 8:14:26 PM * @version V1.0 */ public class Battery extends Product { private boolean rechargeable; public Battery() { super(); } public Battery(String name, double price) { super(name, price); } /** * @return the rechargeable */ public boolean isRechargeable() { return rechargeable; } /** * @param rechargeable * the rechargeable to set */ public void setRechargeable(boolean rechargeable) { this.rechargeable = rechargeable; } }(5)Disc类
/* * Copyright 2013-2015 */ package com.jackie.codeproject.springrecipesnote.springioc; /** * Title: Disc.java 类的功能说明 * * @author jackie * @since Apr 14, 2013 8:18:30 PM * @version V1.0 */ public class Disc extends Product { private int capacity; public Disc() { super(); } public Disc(String name, double price) { super(name, price); } /** * @return the capacity */ public int getCapacity() { return capacity; } /** * @param capacity * the capacity to set */ public void setCapacity(int capacity) { this.capacity = capacity; } }(6)测试类ProductTest
package com.jackie.codeproject.springrecipesnote.springioc; import org.junit.After; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ProductTest { private ApplicationContext applicationContext; @Test public void testClassPathXmlApplicationContext() { // 使用ClassPathXmlApplicationContext是ApplicationContext接口的一个实现,它可以从classpath中装入一个XML配置文件 applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 使用getBean方法并为其传递配置的唯一的Bean名称,其返回类型为Object,须强制转型。 Product aaa = (Product) applicationContext.getBean("aaa"); System.out.println(aaa.toString()); Product cdrw = (Product) applicationContext.getBean("cdrw"); System.out.println(cdrw.toString()); } @After public void after(){ if (null != applicationContext) { applicationContext = null; } } }
一个复杂的项目不一定只有一个Play 应用组成。你可以把一个大项目分成几个小的应用,甚至你可以把一些和Play 应用没关系的逻辑提取到一些标准的JAVA 或SCALA 类库里面。
阅读这篇文章 SBT documentation on multi-project builds.可以获得更多的帮助。sub-projects 没有它们自己的构建文件,它们和它们的父工程共享构建文件。
添加一个简单的 sub-project你可以让你的应用依赖一个简单的库工程。要做的只是在你的‘project/Build.scala’构建文件里面添加一个新的工程定义:
import sbt._ import Keys._ import play.Project._ object ApplicationBuild extends Build { val appName = "my-first-application" val appVersion = "1.0" val appDependencies = Seq( //if it's a java project add javaCore, javaJdbc, jdbc etc. ) val mySubProject = Project("my-library", file("myLibrary")) val main = play.Project( appName, appVersion, appDependencies, path = file("myProject") ).dependsOn(mySubProject) }
在这里我们在应用的‘myLibrary’文件夹里定义了一个 sub-project。这个 sub-project 是一个标准的sbt 工程,使用默认的layout:
myProject └ app └ conf └ public myLibrary └ src └ main └ java └ scala project └ Build.scala
当你在你的build 里面启用sub-project ,你可以只关注于这个子项目,单独编译测试运行它。可以在Play 控制台输入 project 命令来列出所有的项目。
[my-first-application] $ projects [info] In file:/Volumes/Data/gbo/myFirstApp/ [info] * my-first-application [info] my-library
默认的项目是项目名按字母排序的地一个项目。你可以使用aaaMain 命令来使用你的主要项目。你可以使用 project 命令来改变当前工程。
[my-first-application] $ project my-library [info] Set current project to my-library
当你在开发模式下运行Play 应用,依赖的项目会自动编译,如果编译失败,你依然可以在浏览器里看到相关信息。
把你的web分成几个小web
由于Play 应用只是一个含有默认配置文件的标准的sbt 工程,所以它可以依赖其它的Play 应用。
这个配置和之前的非常接近。配置你的 sub-project 就像配置 play.Project 一样简单。
import sbt._ import Keys._ import play.Project._ object ApplicationBuild extends Build { val appName = "zenexity.com" val appVersion = "1.2" val common = play.Project( appName + "-common", appVersion, path = file("common") ) val website = play.Project( appName + "-website", appVersion, path = file("website") ).dependsOn(common) val adminArea = play.Project( appName + "-admin", appVersion, path = file("admin") ).dependsOn(common) val main = play.Project( appName, appVersion, path = file("main") ).dependsOn( website, adminArea ) }
这里我们定义了一完整的被分割成两个部分的项目:website 部分和 admin 部分。这两个部分通过一个common 模块彼此依赖。
如果你想在编译测试主工程时同时依赖的子项目也重新编译和测试,那么你需要添加一个 “aggregate”选项。
val main = PlayProject( appName, appVersion ).dependsOn( website, adminArea ).aggregate( website, adminArea )
注意:为了避免名字冲突,请确保你的sub-projects 的 controller 包括 Assets controller 使用和主项目不同的命名空间。
分割route文件由于 play 2.1 支持把 route 文件分割成为几部分。如果你想构建一个健壮的,可重用的多模块的Play 应用,这是一个非常方便的特性。
考虑一以下构建文件project/Build.scala
import sbt._ import Keys._ import play.Pr
MVC(模型—视图—控制器)模式
MVC将用户接口问题分割为三个截然不同的部分:模型、视图和控制器。模型存储应用的状态。视图解释模型中的数据并将它展示给用户。最后控制器处理用户的输入。
因为表示层是请求驱动的,所以“用户”可以是任意请求的发起者。控制器处理该请求。模型就是业务数据,而视图就是最终发生的应答。控制器是与请求发生联系的起点。控制器就是一个主管,首先规划要做哪些更新和要显示什么视图,然后调用被选择的模式和视图以执行真正的规划。模型的工作是管理对该状态的访问,为控制器和视图提供统一的接口。视图从模型中读取数据,并使用这些数据来生成应答。
从某种程度上来说,J2EE内置了MVC的概念。表达层通常由三个主要的组件构成:Servlet、JSP和JavaBean。JavaBean和EJB形式的模型提供对业务层数据的访问。控制器通常就是一些Servlet和支撑类的集合,用于处理输入和控制导航。最终,视图一般实现为JSP页面和静态HTML。遗憾的是,在J2EE背景中,视图和控制器的分界线很容易混淆。使用Servlet的RequestDispatcher对象来转移控制权,该对象可以用于在服务器内转发请求。通过使用RequestDispatcher,我们可以把请求发送给多个servlet、JSP页面或者不涉及客户端的静态HTML页面。视图是没有状态的。每次它被调用的时候,它必须读取模型数据并把它转换为HTML页面格式。视图必须能够从模型中读取出它的数据。因为控制器已经把JavaBean形式的模型存储在请求中了。虽然使用JavaBean作为模型、JSP作为视图、Servlet作为控制器的思想并不是必须的,但是这是一种很好的经验法则。更重要的是要理解模型、视图和控制器之间的分离怎么使应用可扩展和可维护。
FrontController(前端控制器)模式
控制器不仅是接触请求的起点,它还是我们编程量最多和设计自由度最大的地方。前端控制器(Front Controller)模式主张构建一个单独的控制器执行每个请求中都需要的公共功能,而将其他的功能委托给与页面相关的控制器。前端控制器(Front Controller)提供了一个统一的位置来封装公共请求处理。前端控制器模式中的主要参与者是控制器本身。它的工作相当简单:执行公共的任务,然后把控制转移给与页面相关的控制器。
页面控制器执行模型更新和视图选择,实际上有些类似MVC模式中的控制器角色。页面控制器可以是完全独立的servlet,但是它们通常都按照GOF提出的命令(Command)模式实现为许多个更简单的类。在这种设计中,许多不同种类的页面控制器共享一个简单的公共接口,而且通常被称作“动作”(源于Command模式)。因为前端控制器选择了页面控制器,所以页面控制器最终负责选择正确的动作和视图。在一个Web应用中,前端控制器几乎总是用一个Servlet来实现。虽然使用JSP页面在技术上也是可行的,但这通常是一个坏主意——JSP页面不应该用于实现复杂的逻辑。第二种更引人注目的选择是使用一个servlet过滤器作为前端控制器。
Decorator(装饰器)模式
装饰器(Decorator)模式把多个小的组件组合成一个大的组件。装饰器是由GOF提出的术语,以说明它本质上是一个包装器:一个包含单个子项目并提供与该子项完全相同接口的类。装饰器为它的子项“装饰”或者添加一项功能。当装饰器上有一个方法被调用的时候,它先做自身的预处理,然后调用子项上的相应方法。产生的结果是一个扩展的响应,就如同原始组件把装饰器的代码嵌入在自己内部一样。装饰器只含有一个子项,但是它们可以形成一条链。装饰器的一条链由多个装饰器组成,每个装饰器都将原始对象装饰在链的末端。每个装饰器的职责就是调用链中下一个对象的方法。装饰器的主要限制在于它只装饰一个组件。这点细节很重要,因为它保证了装饰器可以放置到任意长度的链中。虽然动态添加和删除装饰器很方便,但是过多地这么做会显著地增加复杂性。装饰器应该设计成彼此独立,同时也独立与目标对象,依赖性和顺序关系假设可能会在漏掉某个装饰器或者装饰器添加的顺序错误的时候产生很严重的缺陷。
装饰器的一种关键应用:动态扩展前端控制器。通过链接前端控制器上装饰器的不同组合,我们可以快速地增减控制器上的特性。并且,通过使用装饰器,我们可以有效地降低所有这些不同功能之间的耦合读。
装饰器代表着一种相当简单的扩展对象功能的方式,而面向方面的编程(Aspect-oriented Programming, AOP)则是一种以动态扩展对象为核心的编程方法学。AOP中的一个方面(Aspect)代表一个贯穿多个类的公共概念,例如日志系统。一个方面把用于执行某个动作的代码、它将应用到的目标以及何时应用它的特定条件等所有的东西都封装在一起。某个方面中的条件一旦满足,就执行它里面的代码。在简单的情况下,一个方面的工作看起来与装饰器有些相似。但是各方面提供了许多描述条件的复杂方式,这使它们比装饰器更加强大,同时也更复杂。
Serviceto Worker(服务工作者)模式
服务工作者(Service toWorker)模式建立在模型—视图—控制器模式和前端控制器模式的基础上。服务工作者(Service to Worker)模式的目标是维持动作、视图和控制器之间的分离。服务是指前端控制器这个处理请求的中心。在服务工作者模式中,一个称为调度器的对象执行管理工作者和视图任务。该调度器封装了页面的选择以及随后的工作者选择。它去除了应用的行为与前端控制器间的耦合。
服务工作者模式的最终结果是一个可扩展的前端控制器。
使用服务工作者模式,控制器已经被划分为一组可重用的组件:一个前端控制器,一些调度器和动作。添加新页面是动态的:简单的生产JSP视图和对应的动作类,然后将它们全部添加到一个配置文件中(例如XML文件)。需要添加、删除或者重现排序页面的时候,只需要修改配置文件即可。
ViewHelper(视图助手)模式
一个助手视图相当于模型和视图之间的中介。它读取特定的业务数据并进行转换,有时候直接转换成HTML,有时候则转换成一种中间数据模型。不是由视图来包含用于处理特定模型的特殊代码,而是由这种新视图来包含更通用的对助手的调用。视图助手在两个方面提高了可重用性:降低了一个视图中特殊代码的数量,助手使得视图可以更好的重用;此外,因为助手封装了与模型的特殊交互,所以助手本身也可以被重用。
当考虑使用JSP中的视图助手时,自定义标签马上就会跃入脑海。自定义标签正好适合——它把Java对象改编成JSP标注。将嵌入的代码转变为自定义的标签类降低了耦合性,因为标签定义了一个独立于底层对象的清晰接口。虽然很容易把所有的自定义标签看出视图助手,但是它们并不是相同的东西。一个助手视图是将数据翻译成一种很方便的视图格式的一个标签后者一组标签。
CompositeView(复合视图)模式
复合视图模式是基于GoF的复合模