Spring原理剖析

0.524字数 20739阅读 835

Spring模块

Core(核心容器)

说明

核心容器提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现

BeanFactory使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开

IOC(控制反转模式 )

不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在 Spring 框架中是 IOC 容器) 负责将这些联系在一起,保持应用程序对象依赖松散耦合

控制权应用代码转到外部容器控制权的转移,是所谓反转

注入方式

服务需要实现专门的接口,通过接口,由对象提供这些服务,可以从对象查询依赖性

通过 JavaBean 的属性(例如 setter 方法)分配依赖性(常用)

依赖性以构造函数的形式提供,不以 JavaBean 属性的形式公开

org.springframework.beans包,这个包通常不是由用户直接使用,而是由服务器将其用作其他多数功能的底层中介

BeanFactory 接口

它是工厂设计模式的实现,允许通过名称创建和检索对象。BeanFactory也可以管理对象之间的关系

Bean 是被消极加载的,这意味在需要 bean 之前,bean 本身不会被初始化

支持两种对象模型

单例

原型

Bean工厂继承关系(BeanFactory )

ListableBeanFactory

HierarchicalBeanFactory

AutowireCapableBeanFactory

DefaultListableBeanFactory

Bean定义:完整的描述了在 Spring 的配置文件中你定义的 节点中所有的信息,包括各种子节点,在 Spring 的内部他就被转化成 BeanDefinition 对象

Bean解析类

BeanDefinitionReader

BeanDefinitionDocumentReader

XmlBeanDefinitionReader

创建BeanFactory

Ioc 容器实际上就是 Context 组件结合其他两个(Core和BeanFactory)组件共同构建了一个 Bean 关系网

构建的入口就在 AbstractApplicationContext 类的 refresh 方法中

Spring Context(上下文)

说明

Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息

Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能

组件

ApplicationContext

ConfigurableApplicationContext

表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息

下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类

WebApplicationContext

为 web 准备的 Context 他可以直接访问到 ServletContext

Resource

所有的资源都被可以通过 InputStream 这个类来获取,所以也屏蔽了资源的提供者

ResourceLoader 接口负责资源的统一加载

Context 是把资源的加载、解析和描述工作委托给了 ResourcePatternResolver 类来完成,他相当于一个接头人,他把资源的加载、解析和资源的定义整合在一起便于其他组件使用

Spring AOP

说明

通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP

Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中

什么是AOP

AOP(Aspect Orient Programming),也就是面向切面编程

面向对象编程(OOP)是从静态角度考虑程序结构, 

面向切面编程(AOP)是从动态角度考虑程序运行过程 

AOP的作用

处理一些具有横切性质的系统性服务,如事务管理、安全检查、缓存、对象池管理等

AOP的实现原理

AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由AOP 框架动态生成的一个对象,该对象可作为目标对象使用

AOP 代理包含了目标对象的全部方法,但AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法

AOP实现

静态AOP

机制:静态织入

原理:在编译期,切面直接以字节码的形式编译目标字节码文件中

优点:对系统无性能影响

缺点:灵活性不够

动态AOP

机制:动态代理

原理:在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中

优点:相对于静态AOP更加灵活

缺点:切入的关注点需要实现接口。对系统有一点性能影响

代表:JDK动态代理

接口 + InvocationHandler + 目标对象 = 代理

动态字节码生成

机制:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类

原理:没有接口也可以织入

优点:扩展类的实例方法为final时,则无法进行织入

代表:Cglib动态代理(依赖ASM

接口或类 + MethodInterceptor + 目标对象 = 代理

自定义加载器

机制:在运行期,目标加载前,将切面逻辑加到目标字节码里

原理:可以对绝大部分类进行织入

优点:代码中如果使用了其他类加载器,则这些类将不会被织入

代表:Javassist

字节码转换

机制:在运行期,所有类加载器加载字节码,前进行拦截

原理:可以对所有类进行织入

代表:Javassit +Instrumentation

Spring对AOP的支持

Spring 中AOP 代理由Spring 的IoC 容器负责生成、管理,其依赖关系也由IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他Bean 实例作为目标,这种关系可由IoC 容器的依赖注入提供

Spring 默认使用Java 动态代理来创建AOP 代理, 这样就可以为任何接口实例创建代理了。当需要代理的类不是代理接口的时候, Spring 自动会切换为使用CGLIB 代理,也可

强制使用CGLIB

AOP 编程

定义普通业务组件

定义切入点,一个切入点可能横切多个业务组件

定义增强处理,增强处理就是在AOP 框架为普通业务组件织入的处理动作

所以进行AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成AOP 代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法

Spring中AOP的实现

基于Annotation 的“零配置”方式:使用@Aspect、@Pointcut 等Annotation 来标注切入点和增强处理

首先启用Spring 对@AspectJ 切面配置的支持

定义切面Bean

当启动了@AspectJ 支持后,只要在Spring 容器中配置一个带@Aspect 注释的Bean, Spring 将会自动识别该Bean 并作为切面处理

定义@Before 增强处理

定义@AfterReturning 增强处理

定义@AfterThrowing 增强处理

定义@After 增强处理

After 增强处理与AfterReturning 增强处理有点相似,但也有区别

AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入

After增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入

@Around 增强处理

访问目标方法的参数

定义切入点

所谓切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称

一个切入点表达式

一个包含名字和任意参数的方法签名

基于XML 配置文件的管理方式:使用Spring 配置文件来定义切入点和增强点

使用Spring ProxyFactoryBean创建代理

使用 ProxyFactoryBean 来创建 AOP 代理的最重要的优点之一是 IoC 可以管理通知切入点。 这是一个非常的强大的功能,能够实现其他 AOP 框架很难实现的特定的方法。例如,一个通知本身可以引用应用对象(除了目标对象,它在任何 AOP 框架中都可以引用应用对象),这完全得益于依赖注入所提供的可插入性

ProxyFactoryBean的proxyInterfaces属性,指明要代理的接口

ProxyFactoryBean的target属性,指明要代理的目标类 ,这个目标类实现了上面proxyInterfaces属性指定的接口

ProxyFactoryBean的interceptorNames属性,指明要在代理的目标类中插入的Adivce

ProxyFactoryBean还有一个proxyTargetClass属性,如果这个属性被设定为“true”,说明 ProxyFactoryBean要代理的不是接口类,而是要使用CGLIB方式来进行代理,后面会详细讲解使用CGLIB方式来进行代理

IntroductionIntercepter

影响了目标物件的行为定义,直接增 加了目标物件的职责

Spring DAO

说明

JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构

Spring ORM

说明

Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map

所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构

Spring Web 模块

说明

Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成

Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作

Spring MVC 框架

说明

MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现

通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI

Spring事务

概述

事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做

ACID

原子性(Atomicity)

即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做

一致性(Consistency)

在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏

如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态

隔离性(Isolation)

并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性

持久性(Durability)

事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失

常见问题

丢失更新、

两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的

脏读

一个事务看到了另一个事务未提交的更新数据

不可重复读、

在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据

不可重复读的重点是修改

幻读

一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样

幻读的重点在于新增或者删除

隔离级别

未提交读(Read Uncommitted)

最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读

提交读(Read Committed)

一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读

可重复读(Repeatable Read)

保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能出现丢失更新、脏读、不可重复读,但可能出现幻读

序列化(Serializable)

最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁乐观锁来解决这些问题

数据库系统的角度来看,锁分为以下三种类型

独占锁(Exclusive Lock)

独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、 UPDATE或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放

共享锁(Shared Lock)

共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放

更新锁(Update Lock)

更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,无法对其作更新锁锁定

程序员的角度看,锁分为以下两种类型

悲观锁(Pessimistic Lock)

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

乐观锁(Optimistic Lock)

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受

而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据

数据库事务类型

本地事务

就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上

分布式事务

涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库

Java事务类型

JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务

JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务

Java EE事务类型

本地事务:使用JDBC编程实现事务

全局事务:由应用程序服务器提供,使用JTA事务

编程实现

声明式事务: 通过注解或XML配置文件指定事务信息

编程式事务:通过编写代码实现事务

Spring事务使用

说明

结构

DataSource

JdbcDataSource

SessionFactory

EntityManager

TransactionManager

DataSourceTransactionManager

HibernateTransactionManager

JpaTransactionManager

代理机制

Bean和代理

每个Bean有一个代理

所有Bean共享一个代理基类

使用拦截器

使用TX标签配置的拦截器

全注解配置

无论哪种配置方式,一般变化的只是代理机制这部分

5种实现方式

每个Bean都有一个代理

每个Bean用TransactionProxyFactoryBean代理

所有Bean共享一个代理基类

所有Bean都集成TransactionProxyFactoryBean

使用拦截器

拦截器:TransactionInterceptor

根据beanName匹配后进行自动代理:BeanNameAutoProxyCreator

使用tx标签配置的拦截器

全注解

Transactional

Spring扩展

BeanPostProcessor

BeanPostProcessor是Spring容器的一个扩展点,可以进行自定义的实例化、初始化、依赖装配、依赖检查等流程,即可以覆盖默认的实例化,也可以增强初始化、依赖注入、依赖检查等流程

BeanFactory <- AbstractBeanFactory <-AbstractAutowireCapableBeanFactory <- DefaultListableBeanFactory:AbstractAutowireCapableBeanFactory.createBean()负责Bean的初始化到消亡周期

resolveBeanClass(mbd, beanName);解析Bean class,若class配置错误将抛出CannotLoadBeanClassException

mbd.prepareMethodOverrides();准备和验证配置的方法注入,若验证失败抛出BeanDefinitionValidationException

Object bean = resolveBeforeInstantiation(beanName, mbd)

执行InstantiationAwareBeanPostProcessor的实例化的预处理回调方法postProcessBeforeInstantiation(自定义的实例化,如创建代理)

执行InstantiationAwareBeanPostProcessor的实例化的后处理回调方法postProcessAfterInitialization(如依赖注入),如果3.1处返回的Bean不为null才执行

如果3处的扩展点返回的bean不为空,直接返回该bean,后续流程不需要执行

Object beanInstance = doCreateBean(beanName, mbd, args);执行spring的创建bean实例的流程

createBeanInstance(beanName, mbd, args);实例化Bean

instantiateUsingFactoryMethod 工厂方法实例化

构造器实例化

如果之前已经解析过构造器

autowireConstructor:有参调用autowireConstructor实例化

instantiateBean:无参调用instantiateBean实例化

如果之前没有解析过构造器

通过SmartInstantiationAwareBeanPostProcessor 的determineCandidateConstructors 回调方法解析构造器,第二个BeanPostProcessor扩展点,返回第一个解析成功(返回值不为null)的构造器组

autowireConstructor:如果(6.2.2.1返回的不为null,且是有参构造器,调用autowireConstructor实例化

instantiateBean: 否则调用无参构造器实例化

applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName):执行Bean定义的合并

执行MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition回调方法,进行bean定义的合并

addSingletonFactory(beanName, new ObjectFactory())

SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference,当存在循环依赖时,通过该回调方法获取及早暴露的Bean实例

populateBean(beanName, mbd, instanceWrapper);装配Bean依赖

InstantiationAwareBeanPostProcessorpostProcessAfterInstantiation;第五个BeanPostProcessor扩展点,在实例化Bean之后,所有其他装配逻辑之前执行,如果false将阻止其他的InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation的执行和从(9.2到(9.5的执行,通常返回true

autowireByName、autowireByType:根据名字和类型进行自动装配

InstantiationAwareBeanPostProcessorpostProcessPropertyValues,完成其他定制的一些依赖注入,如

AutowiredAnnotationBeanPostProcessor执行@Autowired注解注入

CommonAnnotationBeanPostProcessor执行@Resource等注解的注入

PersistenceAnnotationBeanPostProcessor执行@ PersistenceContext等JPA注解的注入

RequiredAnnotationBeanPostProcessor执行@Required注解的检查等等

checkDependencies:依赖检查

applyPropertyValues:应用明确的setter属性注入

initializeBean(beanName, exposedObject, mbd);执行初始化Bean流程

invokeAwareMethods(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware):调用一些Aware标识接口注入如BeanName、BeanFactory

BeanPostProcessorpostProcessBeforeInitialization:在调用初始化之前完成一些定制的初始化任务,如:

BeanValidationPostProcessor完成JSR-303 @Valid注解Bean验证

InitDestroyAnnotationBeanPostProcessor完成@PostConstruct注解的初始化方法调用

ApplicationContextAwareProcessor完成一些Aware接口的注入(如EnvironmentAware、ResourceLoaderAware、ApplicationContextAware),其返回值将替代原始的Bean对象

invokeInitMethods : 调用初始化方法

InitializingBean的afterPropertiesSet:调用InitializingBean的afterPropertiesSet回调方法

通过xml指定的自定义init-method:调用通过xml配置的自定义init-method

BeanPostProcessorpostProcessAfterInitialization

AspectJAwareAdvisorAutoProxyCreator(完成xml风格的AOP配置()的目标对象包装到AOP代理对象)

AnnotationAwareAspectJAutoProxyCreator(完成@Aspectj注解风格( @Aspect)的AOP配置的目标对象包装到AOP代理对象),其返回值将替代原始的Bean对象

registerDisposableBeanIfNecessary(beanName, bean, mbd) :注册Bean的销毁方法(只有非原型Bean可注册)

单例Bean的销毁流程

DestructionAwareBeanPostProcessor的postProcessBeforeDestruction : 如InitDestroyAnnotationBeanPostProcessor完成@PreDestroy注解的销毁方法注册和调用

DisposableBean的destroy:注册/调用DisposableBean的destroy销毁方法

通过xml指定的自定义destroy-method: 注册/调用通过XML指定的destroy-method销毁方法

Scope的registerDestructionCallback:注册自定义的Scope的销毁回调方法,如RequestScope、SessionScope等

Spring内置的BeanPostProcessor

ApplicationContextAwareProcessor

容器启动时会自动注册

注入那些实现ApplicationContextAware、MessageSourceAware、ResourceLoaderAware、EnvironmentAware、

EmbeddedValueResolverAware、ApplicationEventPublisherAware标识接口的Bean需要的相应实例

在postProcessBeforeInitialization回调方法中进行实施

CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor继承InitDestroyAnnotationBeanPostProcessor,当在配置文件有或会自动注册

提供对JSR-250规范注解的支持@javax.annotation.Resource、@javax.annotation.PostConstruct和@javax.annotation.PreDestroy等的支持、

通过@Resource注解进行依赖注入

postProcessPropertyValues:通过此回调进行@Resource注解的依赖注入

用于执行@PostConstruct 和@PreDestroy 注解的初始化和销毁方法的扩展点

postProcessBeforeInitialization()将会调用bean的@PostConstruct方法

postProcessBeforeDestruction()将会调用单例 Bean的@PreDestroy方法(此回调方法会在容器销毁时调用)

AutowiredAnnotationBeanPostProcessor

当在配置文件有或会自动注册

提供对JSR-330规范注解的支持和Spring自带注解的支持

Spring自带注解的依赖注入支持,@Autowired和@Value

determineCandidateConstructors:决定候选构造器

postProcessPropertyValues:进行依赖注入

对JSR-330规范注解的依赖注入支持,@Inject

RequiredAnnotationBeanPostProcessor

当在配置文件有或会自动注册

提供对@Required注解的方法进行依赖检查支持

postProcessPropertyValues:如果检测到没有进行依赖注入时抛出BeanInitializationException异常

PersistenceAnnotationBeanPostProcessor

当在配置文件有或会自动注册

通过对JPA @javax.persistence.PersistenceUnit和@ javax.persistence.PersistenceContext注解进行依赖注入的支持

postProcessPropertyValues: 根据@PersistenceUnit/@PersistenceContext进行EntityManagerFactory和EntityManager的支持

AbstractAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator都是继承AbstractAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreator提供对()声明式AOP的支持、

AnnotationAwareAspectJAutoProxyCreator提供对()注解式(@AspectJ)AOP的支持

当使用配置时自动注册AspectJAwareAdvisorAutoProxyCreator

使用时会自动注册AnnotationAwareAspectJAutoProxyCreator

predictBeanType:预测Bean的类型,如果目标对象被AOP代理对象包装,此处将返回AOP代理对象的类型

postProcessBeforeInstantiation:配置TargetSourceCreator进行自定义TargetSource创建时,会创建代理对象并中断默认Spring创建流程

getEarlyBeanReference和postProcessAfterInitialization是二者选一的,而且单例Bean目标对象只能被增强一次,而原型Bean目标对象可能被包装多次

BeanValidationPostProcessor

默认不自动注册,Spring3.0开始支持

提供对JSR-303验证规范支持

根据afterInitialization是false/true决定调用postProcessBeforeInitialization或postProcessAfterInitialization来通过JSR-303规范验证Bean,默认false

BeanPostProcessor如何注册

如ApplicationContextAwareProcessor会在ApplicationContext容器启动时自动注册,而CommonAnnotationBeanPostProcessor和AutowiredAnnotationBeanPostProcessor会在当你使用或配置时自动注册

只要将BeanPostProcessor注册到容器中,Spring会在启动时自动获取并注册

BeanPostProcessor的执行顺序

如果使用BeanFactory实现,非ApplicationContext实现,BeanPostProcessor执行顺序就是添加顺序

如果使用的是AbstractApplicationContext(实现了ApplicationContext)的实现,则通过如下规则指定顺序

PriorityOrdered(继承了Ordered),实现了该接口的BeanPostProcessor会在第一个顺序注册,标识高优先级顺序,即比实现Ordered的具有更高的优先级

Ordered,实现了该接口的BeanPostProcessor会第二个顺序注册

Spring自定义功能标签

XML通常通过DTD、XSD定义,但DTD的表达能力较弱,XSD定义则能力比较强,能够定义类型,出现次数等。自定义标签需要XSD支持,在实现时使用Namespace扩展来支持自定义标签

工作过程

Spring通过XML解析程序将其解析为DOM树,通过NamespaceHandler指定对应的Namespace的BeanDefinitionParser将其转换成BeanDefinition

再通过Spring自身的功能对BeanDefinition实例化对象

制作定义标签

编写XSD文件

META-INF/spring.schemas:配置XSD文件

在解析XML文件时将XSD重定向到本地文件,避免在解析XML文件时需要上网下载XSD文件。通过现实org.xml.sax.EntityResolver接口来实现该功能

META-INF/spring.handlers

指定NamespaceHandler(实现org.springframework.beans.factory.xml.NamespaceHandler)接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子类

NamespaceHandler实现中注册标签和解析器

实现配置 BeanDefinitionParser 

解析XML,实例构造想要的Bean为BeanDefinition对象

配置自定义标签并获取对应的Bean

Spring自定义注解

使用Spring扩展机制

Spring PropertyPlaceholderConfigurer配置扩展

通过扩展PropertyPlaceholderConfigurer类自定义Spring取配置值的行为

如通过扩展PropertyPlaceholderConfigurer,内部封装调用ZooKeeper动态配置获取,从而把ZooKeeper的Name Service集成到现有的Spring容器中

Spring设计模式

单例模式

Spring的Bean默认是单例的(Singleton)

工厂模式

简单工厂模式(Simple Factory) 

简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口

在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定哪一个产品类应当被实例化, 如同一个交通警察站在来往的车辆流中,决定放行那一个方向的车辆向那一个方向流动一样

组成

工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在Java中它往往由一个具体类实现

抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在Java中由接口或者抽象类来实现

具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现

工厂方法模式(Factory Method) 

工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做

组成

抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现

具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象

抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现

具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现

工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代 码。可以看出工厂角色的结构也是符合开闭原则的

可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。因为如果不能避免这种情 况,可以考虑使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现

简单工厂和工厂方法模式的比较

工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式

反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了

应用场景

当一个类不知道它所必须创建的对象的类的时候

当一个类希望由它的子类来指定它所创建的对象的时候

当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候

抽象工厂模式(Abstract Factory) 

应用场景

一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的

这个系统有多于一个的产品族,而系统只消费其中某一产品族

同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来

系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现

总结

简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的

工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构

论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式

代理模式

对其他对象提供一种代理以控制对这个对象的访问

代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象

代理模式的三个角色

抽象角色:声明真实对象和代理对象的共同接口

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象

Spring动态代理

JDK动态代理(JdkDynamicAopProxy )

JDK动态代理只能针对实现了接口的类生成代理

接口 + InvocationHandler + 目标对象 = 代理

Cglib动态代理(Cglib2AopProxy )

CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的

如果目标对象没有实现接口,则默认会采用CGLIB代理

接口或类 + MethodInterceptor + 目标对象 = 代理

Facade门面(外观)模式

概述

外观模式,我们通过外观的包装,使应用程序只能看到外观对象,而不会看到具体的细节对象,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性

问题

为了降低复杂性,常常将系统划分为若干个子系统。但是如何做到各个系统之间的通信和相互依赖关系达到最小呢?

解决方案

为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度

适用性

当你要为一个复杂子系统提供一个简单接口时,子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类

这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图

这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层

客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性

当你需要构建一个层次结构的子系统时,使用 Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系

构建模式的组成

外观角色(Facade):是模式的核心,他被客户Client角色调用,知道各个子系统的功能。同时根据客户角色已有的需求预订了几种功能组合

子系统角色(Subsystem classes):实现子系统的功能,并处理由Facade对象指派的任务。对子系统而言,Facade和Client角色是未知的,没有Facade的任何相关信息;即没有指向Facade的实例

客户角色(client):调用Facade角色获得完成相应的功能

效果优点

优点

对客户屏蔽子系统组件减少了客户处理的对象数目使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少

实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可

降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象

只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类

缺点

不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性

在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”

与其他相关模式对比

抽象工厂模式

Abstract Factory式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。 Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类

中介模式

Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能

Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在

Adapter模式

适配器模式是将一个接口通过适配来间接转换为另一个接口

外观模式的话,其主要是提供一个整洁的一致的接口给客户端

总结

根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口

外观模式是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,外观类充当了客户类与子系统类之间的“第三者”,同时降低客户类与子系统类的耦合度。外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器

外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道

外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能

不要试图通过外观类为子系统增加新行为 ,不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现

模式扩展

一个系统有多个外观类

在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能

不要试图通过外观类为子系统增加新行为

不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现

外观模式与迪米特法则

外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器

抽象外观类的引入

外观模式最大的缺点在于违背了“开闭原则”

当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的

命令模式

命令模式角色

Client:创建一个具体命令(ConcreteCommand)对象并确定其接收者

Command 命令:声明了一个给所有具体命令类的抽象接口

ConcreteCommand:具体命令,定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法

Invoker 请求者:负责调用命令对象执行请求,相关的方法叫做行动方法

Receiver 接受者:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法

命令模式的优点

更松散的耦合

命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现

更动态的控制

命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活

很自然的复合命令

命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大

更好的扩展性

由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化

Tomcat命令模式

把 Tomcat 中两个核心组件 Connector 和 Container,比作一对夫妻。男的将接受过来的请求以命令的方式交给女主人。对应到 Connector 和 Container,Connector 也是通过命令模式调用 Container 的

模块

Connector 作为抽象请求者(Invoker )

HttpConnector 作为具体请求者(Invoker )

HttpProcessor 作为命令(Command)

Container 作为命令的抽象接受者(Receiver)

ContainerBase 作为具体的接受者(Receiver)

客户端就是应用服务器 Server 组件了(Client)

Server 首先创建命令请求者 HttpConnector 对象,然后创建命令 HttpProcessor 命令对象。再把命令对象交给命令接受者 ContainerBase 容器来处理命令。命令的最终是被 Tomcat 的 Container 执行的。命令可以以队列的方式进来,Container 也可以以不同的方式来处理请求,如 HTTP1.0 协议和 HTTP1.1 的处理方式就会不同

更多例子请看文章“Java设计模式之命令模式”中的遥控器例子

适用情况

系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互

命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的

系统需要在不同的时间指定请求、将请求排队和执行请求

系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作

系统需要将一组操作组合在一起,即支持宏命令

命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联

装饰者模式

说明

装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展

装饰模式的结构

抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象

具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类

装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口

具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任

装饰模式的简化

如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类

如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做

装饰模式的优点

装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了

通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合

装饰模式的缺点

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像

在JAVA I/O库中的应用

装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了

由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式

结构

抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口

具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口

抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口

具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream

半透明的装饰模式

装饰模式适配器模式都是“包装模式(Wrapper Pattern)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别

理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致

而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合

装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致

透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致

如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式也是可以接受的,称为“半透明”的装饰模式

在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽

半透明的装饰模式实际上就是处于适配器模式与装饰模式之间的灰色地带。如果将装饰模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰模式倒可以成为这种合并后的“包装模式”的代表

InputStream类型中的装饰模式是半透明的

现实世界与理论总归是有一段差距的。纯粹的装饰模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰模式

观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器

适配器模式

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

适用场景

系统需要使用现有的类,而这些类的接口不符合系统的接口

想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工

两个类所做的事情相同或相似,但是具有不同接口的时候

旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候

使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能

角色

目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类

源(Adapee)角色:现在需要适配的接口

适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类

类适配器模式

class Adapter extends Adaptee implements Target

适配器角色Adapter扩展了Adaptee,同时又实现了目标(Target)接口。由于Adaptee没有提供targetMethod()方法,而目标接口又要求这个方法,因此适配器角色Adapter实现了这个方法

对象的适配器模式

与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API

与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类

Adaptee类并没有targetMethod()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的

类适配器和对象适配器的权衡

类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式

对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理  Adaptee的子类了

对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓

对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法

对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源

对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee

对于对象适配器,需要额外的引用来间接得到Adaptee

建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的

适配器模式的优点

更好的复用性: 系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用

更好的扩展性: 在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能

通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑

复用了现存的类,解决了现存类和复用环境要求不一致的问题

将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码

一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口

适配器模式的缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构

Spring中的适配器模式

Spring aop框架对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,上述三种通知类型是Spring aop框架定义的,它们是aop联盟定义的Advice的子类型

AdvisorAdapter是一个适配器接口,它定义了自己支持的Advice类型,并且能把一个Advisor适配成MethodInterceptor

Adapter

MethodBeforeAdviceAdapter

AfterReturningAdviceAdapter

ThrowsAdviceAdapter

责任链(Chain of Responsibility )模式

说明

责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求

发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任

责任链可能是一条直线、一个环链或者一个树结构的一部分

结构

抽象处理者(Handler)角色定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作

具体处理者(ConcreteHandler)角色具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家

适用场景

有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定

在不明确指定接收者的情况下,向多个对象中的一个提交一个请求

处理一个请求的对象集合应被动态指定

跟命令模式区别

职责链模式对于请求的处理是不知道最终处理者是谁,所以是运行动态寻找并指定

而命令模式中对于命令的处理时在创建命令是已经显式或隐式绑定了接收者

在 Tomcat 中这种设计模式几乎被完整的使用,tomcat 的容器设置就是责任链模式,从 Engine 到 Host 再到 Context 一直到 Wrapper 都是通过一个链传递请求

监听模式

概念

Event Object

继承java.util.EventObject

事件状态对象,用于listener的相应的方法之中,作为参数,一般存在与listerner的方法之中

Event Source

具体的事件源,比如说,你点击一个button,那么button就是event source,要想使button对某些事件进行响应,你就需要注册特定的listener

Event Listener

继承 java.util.EventListener

对每个明确的事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在事件监听者(EventListener)接口中。 实现了事件监听者接口中一些或全部方法的类就是事件监听者

Spring监听模式

Tomcat的ServletContextListener

说明

Event Object:ServletContextEvent 

Event Listener:ServletContextListener

Event Source: Container加载Web 应用程序或容器移除Web 应用程序时

监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期

当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由ServletContextListener 来处理

ServletContextListener 接口中定义了处理ServletContextEvent事件的两个方法

contextInitialized:当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化

contextDestroyed:当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器

在 web.xml 中进行如下配置,以使得监听器可以起作用(listener和listener-class)

Servlet 容器会在启动或终止 Web 应用时,会调用监听器的相关方法。在 web.xml 文件中,  元素用于向容器注册监听器

在Container 加载Web 应用程序时(例如启动 Container 之后),会呼叫contextInitialized() ,而当容器移除Web 应用程序时,会呼叫contextDestroyed () 方法

当 Web 应用启动时,Servlet 容器先调用contextInitialized() 方法,再调用lifeInit 的init() 方法;当Web 应用终止时,Servlet 容器先调用lifeInit 的destroy() 方法,再调用contextDestroyed() 方法

由此可见,在Web 应用的生命周期中,ServletContext 对象最早被创建,最晚被销毁

Spring的ContextLoaderListener

本质上是个ServletContextListener

监听器是启动根IoC容器并把它载入到Web容器的主要功能模块,也是整个Spring Web应用加载IoC的第一个地方

ConfigurableWebApplicationContext.refresh()自动装配ApplicationContext的配置信息

Spring的ApplicationListener

说明

Event Object:

ApplicationEvent

是事件,它就是媒介,充当介质的作用

Event Listener ( Observer)

ApplicationListener

需要到容器中注册。他要关心他所关心的ApplicationEvent 

Event Source:

ApplicationContext(ApplicationEventPublisher)

ApplicationEventMulticaster

SUBJECT一个代理。他会管理我们的 ApplicationListener

Spring提供了ApplicationEventMulticaster接口,负责管理ApplicationListener和发布ApplicationEvent。ApplicationContext会把相应的事件相关工作委派给ApplicationEventMulticaster接口实现类来做

当ApplicationContext接收到事件后,事件的广播是Spring内部给我们做的,不需要了解具体的细节

其实在 Spring读取配置文件之后,利用反射,将所有实现ApplicationListener的Bean找出来,注册为容器的事件监听器

当接收到事件的时候,Spring会逐个调用事件配置文件中的监听器

ContextClosedEvent:当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有 单例Bean都被销毁

ContextRefreshedEvent: 当ApplicationContext初始化或者刷新时触发该事件

ContextStartedEvent: 当容器调用ConfigurableApplicationContext的 Start()方法开始/重新开始容器时触发该事件

ContextStoppedEvent: 当容器调用ConfigurableApplicationContext 的Stop()方法停止容器时触发该事件

RequestHandleEvent: 在Web应用中,当一个http请求(request)结束触发该事件

观察者模式区别

模型结构不同

EventListener是传统的c/s界面事件模型,分事件源和事件(状态)角色,事件源要经过事件的包装、成为事件的属性之一再传递给事件监听/处理者,这个事件监听者就相当于观察者。我记得好像VB或C#也是这种模型

Observer模式的模型就简洁多了,没有分事件源和事件,二者合二为一为一个角色:被观察者,从字面语义上也应该这样,另一个是观察者角色

就是我上面说的Observer模式比较EventListener的不同之处还在于将大部分工作收归Observable根类实现了、包括定义监听者队列、通知方法都实现了,我们只要继承、调用和传值就行了

现在想想可能是EventListener监听机制是从c/s时代就延续下来的,而Observer模式则是和iterator迭代器模式同样被整合进jdk的,所以现在这两种功用存在交叉的api会同时存在

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

Observer模式的典型应用

侦听事件驱动程序设计中的外部事件

侦听/监视某个对象的状态变化

发布者/订阅者(publisher/subscriber)模型中,当一个外部事件(新的产品,消息的出现等等)被触发时,通知邮件列表中的订阅者

Observer模式的优点

对象之间可以进行同步通信

可以同时通知一到多个关联对象

对象之间的关系以松耦合的形式组合,互不依赖

Observer模式的结构

Subject(被观察者): 被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表

ConcreteSubject: 被观察者的具体实现。包含一些基本的属性状态及其他操作

Observer(观察者): 接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知

ConcreteObserver: 观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理

Spring观察者模式应用:跟监听模式类似,参考监听模式

策略模式

定义

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化

策略模式,又叫算法簇模式,就是定义了不同的算法族,并且之间可以互相替换,此模式让算法的变化独立于使用算法的客户

策略模式的好处在于你可以动态的改变对象的行为

设计原则是把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口,然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为

策略模式结构

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换

策略模式仅仅封装算法,提供新的算法插入到已有系统中,以及老算法从系统中“退休”的方法,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的

包含三个角色

Context(上下文):使用不同策略的环境,它可以根据自身的条件选择不同的策略实现类来完成所要的操作。它持有一个策略实例的引用。创建具体策略对象的方法也可以由他完成

Strategy( 抽象策略):抽象策略,定义每个策略都要实现的策略方法

Concrete Strategy( 具体策略对象):具体策略实现类,实现抽象策略中定义的策略方法

认识策略模式

策略模式的重心

策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性

算法的平等性

策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的

所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现

运行时策略的唯一性

运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个

公有的行为

经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口

策略模式的优点

策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复

使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后

策略模式的缺点

客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况

由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观

Spring策略模式的实现

Spring 中策略模式使用有多个地方,如 Bean 定义对象的创建以及代理对象的创建等

Spring 的代理方式有两个 Jdk 动态代理和 CGLIB 代理,这两个代理方式的使用正是使用了策略模式

抽象策略是 AopProxy 接口

Cglib2AopProxy 和 JdkDynamicAopProxy 分别代表两种策略的实现方式

ProxyFactoryBean 就是代表 Context 角色,它根据条件选择使用 Jdk 代理方式还是 CGLIB 方式

三个类(AopProxyFactory,DefaultAopProxyFactory,ProxyCreatorSupport)主要是来负责创建具体策略对象,ProxyFactoryBean 是通过依赖的方法来关联具体策略对象的,它是通过调用策略对象的 getProxy(ClassLoader classLoader) 方法来完成操作

策略模式和工厂模式的区别

用途不一样

工厂是创建型模式,它的作用就是创建对象

策略是行为型模式,它的作用是让一个对象在许多行为中选择一种行为

关注点不一样

一个关注对象创建

一个关注行为的封装

解决不同的问题

工厂模式是创建型的设计模式,它接受指令,创建出符合要求的实例;它主要解决的是资源的统一分发,将对象的创建完全独立出来,让对象的创建和具体的使用客户无关。主要应用在多数据库选择,类库文件加载等

策略模式是为了解决的是策略的切换与扩展,更简洁的说是定义策略族,分别封装起来,让他们之间可以相互替换,策略模式让策略的变化独立于使用策略的客户

工厂相当于黑盒子,策略相当于白盒子

模板方法模式

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

为了提高代码的复用性和系统的灵活性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计,在模板方法模式中,将实现功能的每一个步骤所对应的方法称为基本方法(例如“点单”、“吃东西”和“买单”),而调用这些基本方法同时定义基本方法的执行次序的方法称为模板方法(例如“请客”)

在模板方法模式中,可以将相同的代码放在父类中,例如将模板方法“请客”以及基本方法“点单”和“买单”的实现放在父类中

而对于基本方法“吃东西”,在父类中只做一个声明,将其具体实现放在不同的子类中,在一个子类中提供“吃面条”的实现,而另一个子类提供“吃满汉全席”的实现

好处

提高了代码的复用性

还可以利用面向对象的多态性,在运行时选择一种具体子类,实现完整的“请客”方法,提高系统的灵活性和可扩展性

两种方式

继承

回调

变化的东西是一段代码,而且这段代码会用到原类中的变量,用回调对象

我们去实现这个回调方法,就把变化的东西集中到这里了

Spring的JdbcTemplate用的是回调的方式实现模板方法模式

设计模式区分

Façade模式、Adapter模式、Bridge模式与Decorator模式区别

Façade模式注重简化接口

Adapter模式注重转换接口

Bridge模式注重分离接口(抽象)与其实现

Decorator模式注重稳定接口的前提下为对象扩展功能

推荐阅读更多精彩内容