Spring中涉及到的设计模式详解
一、单例模式
1、介绍
要点:
- 某个类只能有一个实例;
- 它必须自行创建这个实例;
- 它必须自行向整个系统提供这个实例。
实现:
- 单例模式的类只提供私有的构造函数(这样就不会被继承);
- 类定义中含有一个该类的静态私有对象;
- 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
使用单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
2、饿汉式
在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
如果从资源利用效率角度来讲,比懒汉式单例类稍差些。但是从速度和反应时间角度来讲,则比懒汉式要稍好些。
public class Singleton {
// 直接创建对象
public static Singleton instance = new Singleton();
// 私有化构造函数
private Singleton() {
}
// 返回对象实例
public static Singleton getInstance() {
return instance;
}
}
3、懒汉式
Public class Singleton2{
//声明变量
Private static final Singleton2 instance=null;
//私有的默认构造函数
Private Singleton1(){
}
//静态工厂方法,提供对外方法
Public synchronized static Singleton2 getInstance(){
If(instance==null){
Instance=new Singleton2();
}
Return instance;
}
}
这种写法和饿汉式的区别在于:实例并没有直接实例化,而是在静态工厂方法被调用的时候才进行的,而且对静态工厂方法使用了同步化,以处理多线程并发的环境。
不管是饿汉式还是懒汉式,他们的构造方法都是私有的,因此都不能被继承。
我们克服前两种单例类不能被继承的缺点,我们可以使用另外一种特殊化的单例模式,它被称为单例注册表。
4、单例注册表
Import java.util.HashMap;
Public class RegSingleton{
Static private HashMap registry=new HashMap();
//静态块,在类被加载时自动执行
Static{
RegSingleton rs=new RegSingleton();
Registry.put(rs.getClass().getName(),rs);
}
//受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点
Protected RegSingleton(){}
//静态工厂方法,返回此类的唯一实例
public static RegSingleton getInstance(String name){
if(name==null){
name=” RegSingleton”;
}if(registry.get(name)==null){
try{
registry.put(name,Class.forName(name).newInstance());
}Catch(Exception ex){ex.printStackTrace();}
}
Return (RegSingleton)registry.get(name);
}
}
4、使用
- Spring依赖注入Bean实例默认是单例的。
- singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。 - Spring框架对单例的支持是采用单例注册表的方式进行实现的
<bean id="date" class="java.util.Date"/>
<bean id="date" class="java.util.Date" scope="singleton"/>
<bean id="date" class="java.util.Date" singleton="true"/>
以上三种创建对象的方式是完全相同的,容器都会向客户返回Date类的单例引用。那么如果我不想使用默认的单例模式,每次请求我都希望获得一个新的对象怎么办呢?很简单,将scope属性值设置为prototype(原型)就可以了
<bean id="date" class="java.util.Date" scope="prototype"/>
通过以上配置信息,Spring就会每次给客户端返回一个新的对象实例。
二、工厂模式
引子:
话说十年前,有一个爆发户,他家有三辆汽车(Benz(奔驰)、Bmw(宝马)、Audi(奥迪)),还雇了司机为他开车。不过,爆发户坐车时总是这样:上Benz车后跟司机说“开奔驰车!”,坐上Bmw后他说“开宝马车!”,坐上 Audi后他说“开奥迪车!”。
你一定说:这人有病!直接说开车不就行了?!而当把这个爆发户的行为放到我们程序语言中来,我们发现C语言一直是通过这种方式来坐车的!
幸运的是这种有病的现象在OO语言中可以避免了。下面以Java语言为基础来引入我们的主题:工厂模式!
工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
\1. 简单工厂模式(Simple Factory)
\2. 工厂方法模式(Factory Method)
\3. 抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且更具一般性。
在什么样的情况下我们应该记得使用工厂模式呢?大体有两点:
1.在编码时不能预见需要创建哪种类的实例。
2.系统不应依赖于产品类实例如何被创建、组合和表达的细节
1、简单工厂模式
它由三种角色组成:
(1)工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的Driver类。
(2)抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。如例中的Car接口。
(3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。
该模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层薄薄的封装。该模式通过向工厂传递类型来指定要创建的对象,其UML类图如下:
//抽象产品
abstract class Car{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//具体产品
class Benz extends Car{
public void drive(){
System.out.println(this.getName()+"----go-----------------------");
}
}
class Bmw extends Car{
public void drive(){
System.out.println(this.getName()+"----go-----------------------");
}
}
//简单工厂
class Driver{
public static Car createCar(String car){
Car c = null;
if("Benz".equalsIgnoreCase(car))
c = new Benz();
else if("Bmw".equalsIgnoreCase(car))
c = new Bmw();
return c;
}
}
//老板
public class BossSimplyFactory {
public static void main(String[] args) throws IOException {
//老板告诉司机我今天坐奔驰
Car car = Driver.createCar("benz");
car.setName("benz");
//司机开着奔驰出发
car.drive();
}
}
这便是简单工厂模式了。那么它带了了什么好处呢?
首先,符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责“消费”产品(正如暴发户所为)。
下面我们从开闭原则上来分析下简单工厂模式。当暴发户增加了一辆车的时候,只要符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。(即创建一个新的车类,继承抽象产品Car)那么 对于产品部分来说,它是符合开闭原则的——对扩展开放、对修改关闭;但是工厂类不太理想,因为每增加一辆车,都要在工厂类中增加相应的商业逻辑和判 断逻辑,这显自然是违背开闭原则的。
而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝类坏了。
正如我前面提到的简单工厂模式适用于业务简单的情况下或者具体产品很少增加的情况。而对于复杂的业务环境可能不太适应了。这就应该由工厂方法模式来出场了!!
2、工厂方法模式
和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂,其UML类图如下:
也就是定义一个抽象工厂,其定义了产品的生产接口,但不负责具体的产品,将生产任务交给不同的派生类工厂。这样不用通过指定类型来创建对象了。
话说暴发户生意越做越大,自己的爱车也越来越多。这可苦了那位司机师傅了,什么车它都要记得,维护,都要经过他来使用!于是暴发户同情他说:我给你分配几个人手,你只管管好他们就行了!于是工厂方法模式的管理出现了。代码如下:
//抽象产品
abstract class Car{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//具体产品
class Benz extends Car{
public void drive(){
System.out.println(this.getName()+"----go-----------------------");
}
}
class Bmw extends Car{
public void drive(){
System.out.println(this.getName()+"----go-----------------------");
}
}
//抽象工厂
abstract class Driver{
public abstract Car createCar(String car) throws Exception;
}
//具体工厂(每个具体工厂负责一个具体产品)
class BenzDriver extends Driver{
public Car createCar(String car) throws Exception {
return new Benz();
}
}
class BmwDriver extends Driver{
public Car createCar(String car) throws Exception {
return new Bmw();
}
}
//老板
public class Boss{
public static void main(String[] args) throws Exception {
Driver d = new BenzDriver();//左父右子 多态的实现,编译的是父类
Car c = d.createCar("benz");//运行的是子类
c.setName("benz");
c.drive();
}
}
使用开闭原则来分析下工厂方法模式。当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。(即当有新产品时,只要创建并基础抽象产品;新建具体工厂继承抽象工厂;而不用修改任何一个类)工厂方法模式是完全符合开闭原则的!
让我们来看看简单工厂模式、工厂方法模式给我们的启迪:
如果不使用工厂模式来实现我们的例子,也许代码会减少很多——只需要实现已有的车,不使用多态。但是在可维护性上,可扩展性上是非常差的(你可以想象一下添加一辆车后要牵动的类)。因此为了提高扩展性和维护性,多写些代码是值得的。
3、抽象工厂模式
先来认识下什么是产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。
图中的BmwCar和BenzCar就是两个产品树(产品层次结构);而如图所示的BenzSportsCar和BmwSportsCar就是一个产品族。他们都可以放到跑车家族中,因此功能有所关联。同理BmwBussinessCar和BenzBusinessCar也是一个产品族。
可以这么说,它和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。
而且使用抽象工厂模式还要满足一下条件:
1.系统中有多个产品族,而系统一次只可能消费其中一族产品
2.同属于同一个产品族的产品以其使用。
来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):
(1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
(2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。
(3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
(4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
其UML类图如下:
从上面类图结构中可以清楚的看到如何在工厂方法模式中通过增加新产品接口来实现产品的增加的。
接下来我们通过小米和苹果产品生产的例子来解释该模式。
为了弄清楚上面的结构,我们使用具体的产品和工厂来表示上面的UML类图,能更加清晰的看出模式是如何演变的:
Phone类:手机标准规范类(AbstractOhone)
public interface Phone {
void make();
}
MiPhone类:制造小米手机(MiPhone)
public class MiPhone implements Phone {
public MiPhone() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("make xiaomi phone!");
}
}
IPhone类:制造苹果手机(iPhone)
public class IPhone implements Phone {
public IPhone() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("make iphone!");
}
}
PC类:定义PC产品的接口(AbstractPC)
public interface PC {
void make();
}
MiPC类:定义小米电脑产品(MIPC)
public class MiPC implements PC {
public MiPC() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("make xiaomi PC!");
}
}
MAC类:定义苹果电脑产品(MAC)
public class MAC implements PC {
public MAC() {
this.make();
}
@Override
public void make() {
// TODO Auto-generated method stub
System.out.println("make MAC!");
}
}
下面需要修改工厂相关的类的定义:
AbstractFactory类:增加PC产品制造接口
public interface AbstractFactory {
Phone makePhone();
PC makePC();
}
XiaoMiFactory类:增加小米PC的制造(ConcreteFactory1)
public class XiaoMiFactory implements AbstractFactory{
@Override
public Phone makePhone() {
return new MiPhone();
}
@Override
public PC makePC() {
return new MiPC();
}
}
AppleFactory类:增加苹果PC的制造(ConcreteFactory2)
public class AppleFactory implements AbstractFactory {
@Override
public Phone makePhone() {
return new IPhone();
}
@Override
public PC makePC() {
return new MAC();
}
}
演示:
public class Demo {
public static void main(String[] arg) {
AbstractFactory miFactory = new XiaoMiFactory();
AbstractFactory appleFactory = new AppleFactory();
miFactory.makePhone(); // make xiaomi phone!
miFactory.makePC(); // make xiaomi PC!
appleFactory.makePhone(); // make iphone!
appleFactory.makePC(); // make MAC!
}
}
再如:
//BenzCar抽象产品(Bmw和Audi同理,省略)
abstract class BenzCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//BenzCar的几种具体产品(Bmw和Audi同理)
class BenzSportCar extends BenzCar{
public void drive(){
System.out.println(this.getName()+"----BenzSportCar-----------------------");
}
}
class BenzBusinessCar extends BenzCar{
public void drive(){
System.out.println(this.getName()+"----BenzBusinessCar-----------------------");
}
}
//BmwCar的抽象工厂
abstract class BmwCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//BmwCar的各个具体产品的具体工厂
class BmwSportCar extends BmwCar{
public void drive(){
System.out.println(this.getName()+"----BmwSportCar-----------------------");
}
}
class BmwBusinessCar extends BmwCar{
public void drive(){
System.out.println(this.getName()+"----BmwBusinessCar-----------------------");
}
}
//AudiCar的抽象工厂
abstract class AudiCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//AudiCar的各个具体产品的具体工厂
class AudiSportCar extends AudiCar{
public void drive(){
System.out.println(this.getName()+"----AudiSportCar-----------------------");
}
}
class AudiBusinessCar extends AudiCar{
public void drive(){
System.out.println(this.getName()+"----AudiBusinessCar-----------------------");
}
}
//抽象工厂
abstract class Driver3{
public abstract BenzCar createBenzCar(String car) throws Exception;
public abstract BmwCar createBmwCar(String car) throws Exception;
public abstract AudiCar createAudiCar(String car) throws Exception;
}
//具体工厂
class SportDriver extends Driver3{
public BenzCar createBenzCar(String car) throws Exception {
return new BenzSportCar();
}
public BmwCar createBmwCar(String car) throws Exception {
return new BmwSportCar();
}
public AudiCar createAudiCar(String car) throws Exception {
return new AudiSportCar();
}
}
class BusinessDriver extends Driver3{
public BenzCar createBenzCar(String car) throws Exception {
return new BenzBusinessCar();
}
public BmwCar createBmwCar(String car) throws Exception {
return new BmwBusinessCar();
}
public AudiCar createAudiCar(String car) throws Exception {
return new AudiBusinessCar();
}
}
//老板
public class BossAbstractFactory {
public static void main(String[] args) throws Exception {
Driver3 d = new BusinessDriver();
AudiCar car = d.createAudiCar("");
car.drive();
}
}
其中:BenzSportCar和BenzBusinessCar属于产品树;同理BmwSportCar和BmwBusinessCar。而BenzSportCar和BmwSportCar和AudiSportCar属于产品族。
所以抽象工厂模式一般用于具有产品树和产品族的场景下。
总结:
上面介绍的三种工厂模式有各自的应用场景,实际应用时能解决问题满足需求即可,可灵活变通,无所谓高级与低级。
此外无论哪种模式,由于可能封装了大量对象和工厂创建,新加产品需要修改已定义好的工厂相关的类,因此对于产品和工厂的扩展不太友好,利弊需要权衡一下。
4、使用(IoC)
抽象工厂模式的缺点:如果需要增加新的产品树,那么就要新增三个产品类,比如VolvoCar,VolvoSportCar,VolvoSportCar,并且要修改三个工厂类。这样大批量的改动是很丑陋的做法。
所以可以用简单工厂配合反射来改进抽象工厂:
角色:
(1)抽象产品:它是具体产品继承的父类或者是实现的接口,例子中是BenzCar、BmwCar、AudiCar
(2)具体产品:具体工厂角色所创建的对象就是此角色的实例,如BenCar中的BenzSportCar、BenzBusinessCar
(3)具体工厂:利用反射,通过传过来的指定具体产品类名,来创建对应的具体类,并将其向上转型为其父类(BenzCar、BmwCar、AudiCar)
向上转型的好处:实现了多态,相当于new一个具体类时采用左父右子的方式,编译的是父类,运行时是用子类的方法。这样具体工厂 中不用为每个具体类都实现构造接口。只需要有几个抽象类就创建几个构造接口
角色:
//BenzCar的抽象类
abstract class BenzCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//BenzCar的各个实现类
class BenzSportCar extends BenzCar{
public void drive(){
System.out.println(this.getName()+"----BenzSportCar-----------------------");
}
}
class BenzBusinessCar extends BenzCar{
public void drive(){
System.out.println(this.getName()+"----BenzBusinessCar-----------------------");
}
}
//BmwCar的抽象类
abstract class BmwCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//BmwCar的各个实现类
class BmwSportCar extends BmwCar{
public void drive(){
System.out.println(this.getName()+"----BmwSportCar-----------------------");
}
}
class BmwBusinessCar extends BmwCar{
public void drive(){
System.out.println(this.getName()+"----BmwBusinessCar-----------------------");
}
}
//AudiCar的抽象类
abstract class AudiCar{
private String name;
public abstract void drive();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//AudiCar的各个实现类
class AudiSportCar extends AudiCar{
public void drive(){
System.out.println(this.getName()+"----AudiSportCar-----------------------");
}
}
class AudiBusinessCar extends AudiCar{
public void drive(){
System.out.println(this.getName()+"----AudiBusinessCar-----------------------");
}
}
/**
* 简单工厂通过反射改进抽象工厂及其子工厂
* @author Administrator
* 没有抽象工厂,直接生产各个car,且有几个抽象类,就只要有几个方法接口
* 通过向上转型,转为每个实现car的抽象父类,相当于左父右子,编译的是父类,
* 运行期间运行的是指定子类的方法,实现动态加载
*/
class Driver3{
public static BenzCar createBenzCar(String car) throws Exception {
return (BenzCar) Class.forName(car).newInstance();
}
public static BmwCar createBmwCar(String car) throws Exception {
return (BmwCar) Class.forName(car).newInstance();
}
public static AudiCar createAudiCar(String car) throws Exception {
return (AudiCar) Class.forName(car).newInstance();
}
}
//客户端
public class SimpleAndAbstractFactory {
public static void main(String[] args) throws Exception {
AudiCar car = Driver3.createAudiCar("com.java.pattendesign.factory.AudiSportCar");
car.drive();
}
}
三、代理模式
1、介绍
**代理模式的定义:**代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。
为什么要用代理模式?
- **中介隔离作用:**在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- **开闭原则,增加功能:**代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
2、静态代理
静态就意味着代理对象代理谁在编译时期就确定了,其实现就是通过去实现被代理类的接口然后再通过组合来实现。
被代理类接口:
package com.ustc;
public interface Sub {
public void doSomething();
}
被代理类:
package com.ustc;
import java.sql.SQLOutput;
public class SubImp implements Sub {
@Override
public void doSomething() {
System.out.println("i have done");
}
}
代理类:
package com.ustc;
public class SubProxy implements Sub {
//通过组合来实现对需要被代理的类的增强
private Sub object=null;
public SubProxy (){
}
public SubProxy(Sub object){
this.object=object;
}
@Override
public void doSomething() {
//在这儿对被代理的类进行增强,前处理
System.out.println("enhance the object");
//在这调用被代理类的对象的对应方法
SubProxy subProxy =new SubProxy(new SubImp());
subProxy.object.doSomething();
//对被代理的对象进行后处理
System.out.println("handle somethings");
}
}
可以看到,代理对象代理谁,在编译时期就已经确定了,这样就会存在一个缺点:每个需要被代理的都要有一个代理类去代理它,且代理类要实现被代理的类的接口的所有方法,代码冗余量大。====》动态代理可以弥补这一缺点。
3、动态代理
代理对象在编译时期并不确定,在运行时期通过反射来动态的创建代理对象,从而在运行时期才确定被代理的对象是谁。这样就可以提高代码复用性。(不用为每个被代理的对象去创建一个代理类,且被代理类的方法服用率大大提升)。
(1)介绍
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
Proxy
类中使用频率最高的方法是:newProxyInstance()
,这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
这个方法一共有 3 个参数:
- loader :类加载器,用于加载代理对象。
- interfaces : 被代理类实现的一些接口;
- h : 实现了
InvocationHandle
r 接口的对象;
要实现动态代理的话,还必须需要实现InvocationHandler
来自定义处理逻辑。 当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler
接口类的 invoke
方法来调用。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()
方法有下面三个参数:
- proxy :动态生成的代理类
- method : 与代理类对象调用的方法相对应
- args : 当前 method 方法的参数
也就是说:你通过Proxy
类的 newProxyInstance()
创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的 invoke()
方法。 你可以在 invoke()
方法中自定义处理逻辑,比如在方法执行前后做什么事情。
(2)使用步骤
- 定义一个接口及其实现类;
- 自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
(3)示例
这样说可能会有点空洞和难以理解,我上个例子,大家感受一下吧!
1.定义发送短信的接口
public interface SmsService {
String send(String message);
}Copy to clipboardErrorCopied
2.实现发送短信的接口
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}Copy to clipboardErrorCopied
3.定义一个 JDK 动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author shuang.kou
* @createTime 2020年05月11日 11:23:00
*/
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
Copy to clipboardErrorCopied
invoke()
方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke()
方法,然后 invoke()
方法代替我们去调用了被代理对象的原生方法。
4.获取代理对象的工厂类
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}Copy to clipboardErrorCopied
getProxy()
:主要通过Proxy.newProxyInstance()
方法获取某个类的代理对象
5.实际使用
DebugProxy debugProxy = new DebugProxy(new SmsServiceImpl());
SmsService smsService = debugProxy.getProxy(SmsService.class);
smsService.send("java");Copy to clipboardErrorCopied
运行上述代码之后,控制台打印出:
before method send
send message:java
after method sendCopy to clipboardErrorCopied
**动态代理总结:**虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
(4)动态代理和静态代理对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
4、CGLIB动态代理
(1)介绍
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
Copy to clipboardErrorCopied
- obj :被代理的对象(需要增强的对象)
- method :被拦截的方法(需要增强的方法)
- args :方法入参
- methodProxy :用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
(2)使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
(3)示例
不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>Copy to clipboardErrorCopied
1.实现一个使用阿里云发送短信的类
package github.javaguide.dynamicProxy.cglibDynamicProxy;
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}Copy to clipboardErrorCopied
2.自定义 MethodInterceptor
(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}Copy to clipboardErrorCopied
3.获取代理类
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}Copy to clipboardErrorCopied
4.实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");Copy to clipboardErrorCopied
运行上述代码之后,控制台打印出:
before method send
send message:java
after method send
(4)JDK动态代理和CGLIB动态代理对比
- JDK 动态代理只能只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
5、使用
AOP(面向切面编程):AOP采用了横向的抽取机制,取代了传统的纵向继承的体系结构。
如何理解上边这句话呢?
试想一个场景:
有一个基础类,我们需要对其进行增强,传统我们怎么做?===》继承,继承是纵向的,所以叫传统的纵向继承。
而现在我们通过横向抽取,即编写一个增强类(里边放增强方法),这样增强类就和被增强类在同一等级,然后代理类通过组合去排序这两个类中的方法,是不是也达到了增强的目的,同时避免了继承,达到了降低耦合度的作用呢?
Spring-AOP的例子:
(1)需要被代理的类与接口:
public interface UserService {
public void addUser();
public void updataUser();
public void deleUsr();
}
//target :需要被代理的类
public class UserServiceImp implements UserService {
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("addUser");
}
@Override
public void updataUser() {
// TODO Auto-generated method stub
System.out.println("updateUser");
}
@Override
public void deleUsr() {
// TODO Auto-generated method stub
System.out.println("deleUser");
}
}
(2)切面类:(放置增强方法)
public class MyAspect implements org.aopalliance.intercept.MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("前");
// 调用目标方法
Object proceed = mi.proceed();
System.out.println("后");
return proceed;
}
}
然后写配置文件,Spring工厂通过配置文件生成代理类(这里用到了AOP的一个框架AspectJ,自行查看)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 把目标类交给spring -->
<bean id="userServiceId" class="com.ustc.AOP.auto.UserServiceImp"></bean>
<!-- 把切面类交给spring -->
<bean id="myAspectId" class="com.ustc.AOP.auto.MyAspect"></bean>
<!-- 通过aopconfig把他们连接 PointCut:切入点 表达式:用来匹配切入点的。也就是哪个方法需要加强。 -->
<!-- advisor:特殊的切面,只有一个通知和一个切入点 pointcut——ref:这种切面的切入点 advice-ref就是通知。把他们连成一个切面 -->
<!-- * 要确定目标类,aspectj 切入点表达式,导入jar包 -->
<!-- com.springsource.org.aspectj.weaver.jar -->
<!-- 切入点表达式:execution(* com.ustc.AOP.auto.UserServiceImp.*(..)-->
<aop:config >
<aop:pointcut expression="execution(* com.ustc.AOP.auto.UserServiceImp.*(..))" id="myPointCut"/>
<aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
之后再获取UserService是,得到的就是其代理对象。
四、观察者模式
1、介绍
观察者一般可以看做是第三者,比如在学校上自习的时候,大家肯定都有过交头接耳、各种玩耍的经历,这时总会有一个“放风”的小伙伴,当老师即将出现时及时“通知”大家老师来了。再比如,拍卖会的时候,大家相互叫价,拍卖师会观察最高标价,然后通知给其它竞价者竞价,这就是一个观察者模式。
对于观察者模式而言,肯定有观察者和被观察者之分。比如在一个目录下建立一个文件,这时系统会通知目录管理器增加目录,并通知磁盘减少空间,在这里,文件就是观察者,目录管理器和磁盘就是被观察者。
观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。UML结构图如下:
其中,
Subject类是主题,它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象;
Observer类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;
ConcreteSubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;
ConcreteObserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。
案例:
我们接到一个来自气象局的需求:气象局需要我们构建一套系统,这系统有两个公告牌,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌。
结合上面的类图,我们现在将观察者模式应用到WeatherData项目中来。于是有了下面这张类图:
- 主题接口
/**
* 主题(发布者、被观察者)
*/
public interface Subject {
/**
* 注册观察者
*/
void registerObserver(Observer observer);
/**
* 移除观察者
*/
void removeObserver(Observer observer);
/**
* 通知观察者
*/
void notifyObservers();
}
2. 观察者接口
观察者一般是一个接口,每一个实现该接口的实现类都是具体观察者。
/**
* 观察者
*/
public interface Observer {
void update();
}
- 公告牌用于显示的公共接口
public interface DisplayElement {
void display();
}
- 下面我们再来看看WeatherData是如何实现的
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;//温度
private float humidity;//湿度
private float pressure;//气压
private List<Float> forecastTemperatures;//未来几天的温度
public WeatherData() {
this.observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer observer) {
this.observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
this.observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity,
float pressure, List<Float> forecastTemperatures) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.forecastTemperatures = forecastTemperatures;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public List<Float> getForecastTemperatures() {
return forecastTemperatures;
}
}
- 显示当前天气的公告牌CurrentConditionsDisplay
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private WeatherData weatherData;
private float temperature;//温度
private float humidity;//湿度
private float pressure;//气压
public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
this.weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("当前温度为:" + this.temperature + "℃");
System.out.println("当前湿度为:" + this.humidity);
System.out.println("当前气压为:" + this.pressure);
}
@Override
public void update() {
this.temperature = this.weatherData.getTemperature();
this.humidity = this.weatherData.getHumidity();
this.pressure = this.weatherData.getPressure();
display();
}
}
- 显示未来几天天气的公告牌ForecastDisplay
public class ForecastDisplay implements Observer, DisplayElement {
private WeatherData weatherData;
private List<Float> forecastTemperatures;//未来几天的温度
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
this.weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("未来几天的气温");
int count = forecastTemperatures.size();
for (int i = 0; i < count; i++) {
System.out.println("第" + i + "天:" + forecastTemperatures.get(i) + "℃");
}
}
@Override
public void update() {
this.forecastTemperatures = this.weatherData.getForecastTemperatures();
display();
}
}
到这里,我们整个气象局的WeatherData应用就改造完成了。两个公告牌CurrentConditionsDisplay
和ForecastDisplay
实现了Observer
和DisplayElement
接口,在他们的构造方法中会调用WeatherData
的registerObserver
方法将自己注册成观察者,这样被观察者WeatherData
就会持有观察者的应用,并将它们保存到一个集合中。当被观察者``WeatherData状态发送变化时就会遍历这个集合,循环调用观察者
公告牌更新数据的方法。后面如果我们需要增加或者删除公告牌就只需要新增或者删除实现了
Observer和
DisplayElement`接口的公告牌就好了。
观察者模式将观察者和主题(被观察者)彻底解耦,主题只知道观察者实现了某一接口(也就是Observer接口)。并不需要观察者的具体类是谁、做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现了Observer
接口的对象列表。
好了,我们测试下利用观察者模式重构后的程序:
public class ObserverPatternTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
List<Float> forecastTemperatures = new ArrayList<Float>();
forecastTemperatures.add(22f);
forecastTemperatures.add(-1f);
forecastTemperatures.add(9f);
forecastTemperatures.add(23f);
forecastTemperatures.add(27f);
forecastTemperatures.add(30f);
forecastTemperatures.add(10f);
weatherData.setMeasurements(22f, 0.8f, 1.2f, forecastTemperatures);
}
}
输出结果:
当前温度为:22.0℃
当前湿度为:0.8
当前气压为:1.2
未来几天的气温
第0天:22.0℃
第1天:-1.0℃
第2天:9.0℃
第3天:23.0℃
第4天:27.0℃
第5天:30.0℃
第6天:10.0℃
2、使用
Spring 事件驱动模型中的三种角色
(1)事件角色
ApplicationEvent
(org.springframework.context
包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject
并实现了 java.io.Serializable
接口。
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent
的实现(继承自ApplicationContextEvent
):
-
ContextStartedEvent
:ApplicationContext
启动后触发的事件; -
ContextStoppedEvent
:ApplicationContext
停止后触发的事件; -
ContextRefreshedEvent
:ApplicationContext
初始化或刷新完成后触发的事件; ContextClosedEvent
:ApplicationContext
关闭后触发的事件。
(2)事件监听者角色
ApplicationListener
充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()
方法来处理ApplicationEvent
。ApplicationListener
接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent
就可以了。所以,在 Spring中我们只要实现 ApplicationListener
接口实现 onApplicationEvent()
方法即可完成监听事件
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
(3)事件发布者角色
ApplicationEventPublisher
充当了事件的发布者,它也是一个接口。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher
接口的publishEvent()
这个方法在AbstractApplicationContext
类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster
来广播出去的。
事件流程总结:
- 定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; - 定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; - 使用事件发布者发布消息: 可以通过
ApplicationEventPublisher
的publishEvent()
方法发布消息。
举例:
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
当调用 DemoPublisher
的 publish()
方法的时候,比如 demoPublisher.publish("你好")
,控制台就会打印出:接收到的信息是:你好
。
五、适配器模式
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
1、spring AOP中的适配器模式
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
。Advice 常用的类型有:BeforeAdvice
(目标方法调用前,前置通知)、AfterAdvice
(目标方法调用后,后置通知)、AfterReturningAdvice
(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor
、AfterReturningAdviceAdapter
、AfterReturningAdviceInterceptor
。Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor
接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor
负责适配 MethodBeforeAdvice
)。
2、spring MVC中的适配器模式
在Spring MVC中,DispatcherServlet
根据请求信息调用 HandlerMapping
,解析请求对应的 Handler
。解析到对应的 Handler
(也就是我们平常说的 Controller
控制器)后,开始由HandlerAdapter
适配器处理。HandlerAdapter
作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式? Spring MVC 中的 Controller
种类众多,不同类型的 Controller
通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet
直接获取对应类型的 Controller
,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
假如我们再增加一个 Controller
类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
六、装饰者模式
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream
家族,InputStream
类下有 FileInputStream
(读取文件)、BufferedInputStream
(增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream
代码的情况下扩展了它的功能。
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式(这一点我自己还没太理解具体原理)。Spring 中用到的包装器模式在类名上含有 Wrapper
或者 Decorator
。这些类基本上都是动态地给一个对象添加一些额外的职责
七、模板模式
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
public abstract class Template {
//这是我们的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void PrimitiveOperation1(){
//当前类实现
}
//被子类实现的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
}
}
Spring 中 jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
八、策略模式
- 实现方式:Spring框架的资源访问Resource接口 。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
- Resource 接口介绍
- source 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。
- Resource 接口主要提供了如下几个方法:
- getInputStream():定位并打开资源,返回资源对应的输入流。每次调用都返回新的输入流。调用者必须负责关闭输入流。
- exists():返回 Resource 所指向的资源是否存在。
- isOpen():返回资源文件是否打开,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏。
- getDescription():返回资源的描述信息,通常用于资源处理出错时输出该信息,通常是全限定文件名或实际 URL。
- getFile:返回资源对应的 File 对象。
- getURL:返回资源对应的 URL 对象。
最后两个方法通常无须使用,仅在通过简单方式访问无法实现时,Resource 提供传统的资源访问的功能。
- Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。
- Spring 为 Resource 接口提供了如下实现类:
- UrlResource:访问网络资源的实现类。
- ClassPathResource:访问类加载路径里资源的实现类。
- FileSystemResource:访问文件系统里资源的实现类。
- ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类.
- InputStreamResource:访问输入流资源的实现类。
- ByteArrayResource:访问字节数组资源的实现类。
Operation2();
protected abstract void PrimitiveOperation3();