Spring揭秘——读书笔记

第2章 IoC的基本概念

2.1 控制反转

IoC的全称为Inversion of Control,中文通常翻译为“控制反转”,它还有一个别名叫做依赖注入(Dependency Injection)。
(也有另一种说法,把依赖注入看成是IoC的一种方式)

在这里插入图片描述
以前我们每次依赖某个对象时,都要主动去获取(直接new对象)。
如今,IoC帮我们管理这些对象,被注入对象需要什么,IoC就把相应的依赖对象注入到被注入对象中。
IoC Service Provider在这里就是通常的IoC容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里。

2.2 依赖注入的三种方式

三种依赖注入的方式,即构造方法注入(constructor injection)、setter方法注入(setter injection)以及接口注入(interface injection)。

2.2.1 构造方法注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部(通常是IoC容器)知道它需要哪些依赖对象。

2.2.2 setter方法注入

当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。

2.2.3 接口注入

被注入对象如果想要IoC ServiceProvider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。

在这种情况下,实现的接口和接口中声明的方法名称都不重要。重要的是接口中声明方法的参数类型,必须是“被注入对象”所依赖对象的类型。

2.2.4 三种方法比较

  • 接口方法:基本退役。

  • 构造方法注入:优点就是,对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。
    在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。

  • setter方法注入:因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态。

小结

IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式

第3章 IoC Service Provider

3.1 IoC Service Provider 的职责

  • 业务对象的构建管理: 在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做。所以IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来。

  • 业务对象间的依赖绑定: IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

3.2 IoC Service Provider 如何管理对象间的依赖关系

  • 文本文件
  • XML文件
  • 编写代码

最常见的:XML配置文件

<bean id="newsProvider" class="..FXNewsProvider">
	<property name="newsListener">
		<ref bean="djNewsListener"/>
	</property>
	<property name="newPersistener">
		<ref bean="djNewsPersister"/>
	</property>
</bean>

<bean id="djNewsListener"
	class="..impl.DowJonesNewsListener">
</bean>
<bean id="djNewsPersister"
	class="..impl.DowJonesNewsPersister">
</bean>

第4章 Spring的IoC容器之BeanFactory

在这里插入图片描述
Spring提供了两种容器类型:BeanFactory和ApplicationContext。

  • BeanFactory: 基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。
  • ApplicationContext: ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

4.2 BeanFactory的对象注册与依赖绑定方式

4.2.1 直接编码方式

略。不常用

4.2.2 外部配置文件方式

  1. Properties配置:略。不常用

  2. XML配置格式:常用

在这里插入图片描述
在这里插入图片描述

4.2.3 注解方式(常用)

在这里插入图片描述
@Autowired是这里的主角,它的存在将告知Spring容器需要为当前对象注入哪些依赖对象。
而@Component则是配合Spring 2.5中新的classpath-scanning功能使用的。现在我们只要再向Spring的配置文件中增加一个“触发器”,使用@Autowired和@Component标注的类就能获得依赖对象的注入了。

在这里插入图片描述
<context:component-scan/>会到指定的包(package)下面扫描标注有@Component的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依赖对象。

4.3 BeanFactory的XML配置

4.3.1 <beans> 和 <bean>

<beans>是XML配置文件中最顶层的元素,它下面可以包含0或者1个<description>和多个<bean>以及<import>或者<alias>。

<description> :在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的。

<import>: 加载别的XML配置文件。

<alias>: 可以通过<alias>为某些<bean>起一些“外号”(别名),通常情况下是为了减少输入。

4.3.2 <bean> 的属性

  • id属性:通过id属性来指定当前注册对象的beanName是什么。除了可以使用id来指定<bean>在容器中的标志,还可以使用name属性来指定<bean>的别名(alias)

  • class属性: 每个注册到容器的对象都需要通过<bean>元素的class属性指定其类型。

4.3.3 如何在XML中表达依赖性

1. 构造方法注入

在这里插入图片描述
简化写法:
在这里插入图片描述
<ref>元素:通过这个元素来指明容器将为djNewsProvider这个<bean>注入通过<ref>所引用的Bean实例。

  • type属性:
    如果一个类有两个构造方法,分别都是传入一个参数,且参数类型不同。此时,为了区分使用不同的构造方法,可以通过指定构造方法的参数来解决这一问题。
    在这里插入图片描述
    指定使用参数类型为int的构造方法:
    在这里插入图片描述

  • Index属性:
    当某个业务对象的构造方法同时传入了多个类型相同的参数时,通过Index属性将这些配置中的信息与实际对象的参数一一对应。
    在这里插入图片描述
    在这里插入图片描述

2. setter方法注入

<property>有一个name属性(attribute),用来指定该<property>将会注入的对象所对应的实例变量名称。
在这里插入图片描述
setter方法注入和构造方法注入可以混合使用:
在这里插入图片描述
在这里插入图片描述

3. <property>和<constructor-arg>中可用的配置项

(1) <value>
可以通过value为主体对象注入简单的数据类型,不但可以指定String类型的数据,而且可以指定其他Java语言中的原始类型以及它们的包装器(wrapper)类型,比如int、Integer等。
在这里插入图片描述
(2) <ref>
使用ref来引用容器中其他的对象实例,可以通过ref的local、parent和bean属性来指定引用的对象的beanName是什么。

在这里插入图片描述
local只能指定与当前配置的对象在同一个配置文件的对象定义的名称。
parent则只能指定位于当前容器的父容器中定义的对象引用。
bean通吃,通常下就使用bean。

(3)<idref>
如果要为当前对象注入所依赖的对象的名称,而不是引用,使用idref,容器在解析配置的时候就可以帮你检查这个beanName到底是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。
在这里插入图片描述
在这里插入图片描述
两种写法的目的相同。

(4)内部<bean>
有时,可能我们所依赖的对 7
象只有当前一个对象引用,或者某个对象定义我们不想其他对象通过<ref>引用到它。这时,我们可以使用内嵌的<bean>,将这个私有的对象定义仅局限在当前对象。
在这里插入图片描述
(5)<list>
<list>对应注入对象类型为java.util.List及其子类或者数组类型的依赖对象。在这里插入图片描述
(6)<set>
类似list,list是有序注入, set是无序的。
在这里插入图片描述
(7) <map>
在这里插入图片描述
对于<map>来说,它可以内嵌任意多个<entry>,每一个<entry>都需要为其指定一个键和一个值,就跟真正的java.util.Map所要求的一样。

  • 指定entry的key。可以使用key或者key-ref,也可以使用entry的内置元素key。
  • 指定entry的value。任意使用任何元素来指定键,也可以使用<entry>的value或者value-ref属性。

在这里插入图片描述
(8) <props>
<props>是简化后了的,或者说是特殊化的map,因为Properties只能指定String类型的键(key)和值。

在这里插入图片描述
(9) <null/>
对于String类型来说,如果以<value>来注入,那么得到的是“”而不是null。如果想得到null,那么就要使用<null/>

在这里插入图片描述

4. depends-on

通常情况下,可以直接通过之前提到的所有元素,来显式地指定bean之间的依赖关系。这样,容器在初始化当前bean定义的时候,会根据这些元素所标记的依赖关系,首先实例化当前bean定义所依赖的其他bean定义。但是,如果某些时候,我们没有通过类似的元素明确指定对象A依赖于对象B的话,如何让容器在实例化对象A之前首先实例化对象B呢?

在这里插入图片描述

5. autowire

通过<bean>的autowire属性,可以指定当前bean定义采用某种类型的自动绑定模式。

Spring提供了5种自动绑定模式,即no、byName、byType、constructor和autodetect。

  • no
    容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工明确配置各个bean之间的依赖关系,以下代码演示的两种配置是等效的:
    在这里插入图片描述
    其他的自动装配不写了,因为手动绑定大多数情况下更好,依赖关系一目了然。

6. dependency-check

我们可以使用每个<bean>的dependency-check属性对其所依赖的对象进行最终检查。

基本不用。

7. lazy-init

延迟初始化(lazy-init)这个特性的作用,主要是可以针对ApplicationContext容器的bean初始化行为施以更多控制。

在这里插入图片描述
这样,ApplicationContext容器在启动的时候,只会默认实例化not-lazy-init-bean而不会实例化lazy-init-bean。

如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么毫无疑问,按照依赖决计的顺序,容器还是会首先实例化lazy-init-bean,然后再实例化后者,那么延迟初始化的良好打算“泡汤”。如果我们真想保证lazy-init-bean一定会被延迟初始化的话,就需要保证依赖于该bean定义的其他bean定义也同样设置为延迟初始化。

4.3.4 继承

如果想新建子类继承FXNewsProvide进行扩展,那么创建子类:
在这里插入图片描述
如果他们使用的是相同的IFXNewsPersister,可以引入继承配置:
在这里插入图片描述
我们在声明subNewsProvider的时候,使用了parent属性,将其值指定为superNewsProvider,这样就继承了superNewsProvider定义的默认值,只需要将特定的属性进行更改,而不要全部又重新定义一遍。

parent属性还可以与abstract属性结合使用,达到将相应bean定义模板化的目的。

在这里插入图片描述
newsProviderTemplate的bean定义通过abstract属性声明为true,说明这个bean定义不需要实例化。

容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化的时候实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化。对于ApplicationContext容器尤其如此,因为默认情况下,ApplicationContext会在容器启动的时候就对其管理的所有bean进行实例化,只有标志为abstract的bean除外。

4.3.5 bean的scope

scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session和global session类型。不过这三种类型有所限制,只能在Web应用中使用。

1. singleton

标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

在这里插入图片描述
通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope,所以,下面三种配置形式实际上达成的是同样的效果:

在这里插入图片描述

2. prototype

对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例。
在这里插入图片描述
在这里插入图片描述

3. request, session, global session

它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用。

  • request
    在这里插入图片描述
    XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。

  • session
    session通常用来存放用户的登录信息:
    在这里插入图片描述
    与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。

  • global sessioon
    global session只有应用在基于portlet(啥玩意儿?)的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。

4. 自定义scope类型

要实现自己的scope类型,首先需要给出一个Scope接口的实现类,接口定义中的4个方法并非都是必须的,但get和remove方法必须实现。

在这里插入图片描述

4.3.6 工厂方法与FactoryBean

在这里插入图片描述
针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象(这里是Foo)。

1. 静态工厂方法

假设某个第三方库发布了BarInterface,为了向使用该接口的客户端对象屏蔽以后可能对BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类StaticBarInterface-Factory:

在这里插入图片描述
为了将该静态工厂方法类返回的实现注入Foo,我们使用以下方式进行配置(通过setter方法注入方式为Foo注入BarInterface的实例):
在这里插入图片描述
class指定静态方法工厂类,factory-method指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。

有的工厂类的工厂方法可能需要参数来返回相应实例,可以通过<constructor-arg>来指定工厂方法需要的参数:

在这里插入图片描述

在这里插入图片描述

2. 非静态工厂方法(Instance Factory Method)

现在为BarInterface提供非静态的工厂方法实现类,该类定义如下代码所示:在这里插入图片描述
因为工厂方法为非静态的,我们只能通过某个NonStaticBarInterfaceFactory实例来调用该方 法,那么也就有了如下的配置内容:
在这里插入图片描述
NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,而bar的定义则与静态工厂方法的定义有些不同。现在使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。

3. FactoryBean

FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口。

这种类型的Bean本身就是生产对象的工厂(Factory)。

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.springframework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。

要实现并使用自己的FactoryBean其实很简单, org.springframework.beans.factory.FactoryBean只定义了三个方法,如以下代码所示:

public interface FactoryBean {
	Object getObject() throws Exception;
	Class getObjectType();
	boolean isSingleton();
}

getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑;
getObjectType()方法仅返回getObject()方法所返回的对象的类型,如果预先无法确定,则返回null;
isSingleton()方法返回结果用于表明,工厂方法(getObject())所“生产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回true,否则返回false;

如果我们想每次得到的日期都是第二天:
在这里插入图片描述
注册到容器:
在这里插入图片描述
NextDayDateDisplayer的定义如下:
在这里插入图片描述

4.3.7 方法注入 方法替换

在这里插入图片描述
虽然FXNewsBean拥有prototype类型的scope,但当容器将一个FXNewsBean的实例注入MockNewsPersister之后,MockNewsPersister就会一直持有这个FXNewsBean实例的引用。虽然每次输出都调用了getNewsBean()方法并返回了FXNewsBean 的实例,但实际上每次返回的都是MockNewsPersister持有的容器第一次注入的实例。这就是问题之所在。换句话说,第一个实例注入后,MockNewsPersister再也没有重新向容器申请新的实例。所以,容器也不会重新为其注入新的FXNewsBean类型的实例。

解决问题的关键在于保证getNewsBean()方法每次从容器中取得新的FXNewsBean实例,而不是每次都返回其持有的单一实例。

1.方法注入

只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例即可。

在这里插入图片描述
通过<lookup-method>的name属性指定需要注入的方法名,bean属性指定需要注入的对象,当getNewsBean方法被调用的时候,容器可以每次返回一个新的FXNewsBean类型的实例。

2.方法替换

方法替换可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方法替换可以帮助我们实现简单的方法拦截功能。(AOP)

假设某天我看FXNewsProvider不爽,想替换掉它的getAndPersistNews方法默认逻辑,这时,我就可以用方法替换将它的原有逻辑给替换掉。

首先,我们需要给出org.springframework.beans.factory.support.MethodReplacer的实现类,在这个类中实现将要替换的方法逻辑。

假设我们只是简单记录日志,打印简单信息,那么就可以
给出一个类似代码:

在这里插入图片描述

在这里插入图片描述

4.4 容器背后的秘密

在这里插入图片描述

4.4.1 “战略性观望”

在这里插入图片描述
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段。

1.容器启动阶段

在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData
进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。

在这里插入图片描述

2.Bean实例化阶段

经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。

该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。

4.4.2 插手“容器的启动”

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

我 们 可 以 通 过两种方式来应用BeanFactoryPostProcessor , 分别针对基本的IoC 容器BeanFactory和较为先进的容器ApplicationContext。

在这里插入图片描述
对于ApplicationContext来说,情况看起来要好得多。因为ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它。

在这里插入图片描述

1.PropertyPlaceholderConfigurer

通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。

PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。

在这里插入图片描述
所有这些占位符所代表的资源,都放到了jdbc.properties文件中,如下所示:
在这里插入图片描述
当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}、${jdbc.driver}。当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了。

2.PropertyOverrideConfigurer

PropertyPlaceholderConfigurer可以通过占位符,来明确表明bean定义中的property与properties文件中的各配置项之间的对应关系。

可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。

如果要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个PropertyOverrideConfigurer使用的配置文件:
在这里插入图片描述
下面是针对dataSource定义给出的PropertyOverrideConfigurer的propeties文件配置信息:
在这里插入图片描述
这样,当按照如下代码,将PropertyOverrideConfigurer加载到容器之后,dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖:
在这里插入图片描述

3.CustomEditorConfigurer

p70 待补

4.4.3 了解bean的一生

容器启动之后,并不会马上就实例化相应的bean定义。我们知道,容器现在仅仅拥有所有对象的BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBean方法可以被客户端对象显式调用,也可以在容器内部隐式地被调用。隐式调用有如下两种情况。

  • 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这时容器内部就会首先实例化对象B,以及对象 A依赖的其他还没有实例化的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。

  • ApplicationContext启动之后会实例化所有的bean定义。但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。

在这里插入图片描述

1.Bean的实例化与BeanWrapper

容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。

org.springframework.beans.factory.support.InstantiationStrategy定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。CglibSubclassingInstantiation-Strategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。
默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。

至此,第一步结束。

BeanWrapper接口通常在Spring框架内部使用,它有一个实现类org.springframework.beans.BeanWrapperImpl。其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。

使用BeanWrapper对bean实例操作很方便,可以免去直接使用Java反射API(Java Reflection API)操作对象实例的烦琐。
在这里插入图片描述

2. 各色的Aware接口

当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。

P.76

3.BeanPostProcessor

Bean-PostProcessor会处理容器内所有符合条件的实例化后的对象实例。

该接口声明了两个方法,分别在两个不同的时机执行:

public interface BeanPostProcessor
{
Object postProcessBeforeInitialization(Object bean, String beanName) throws 
BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws 
BeansException; 
}

通常比较常见的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供代理实现。

  • 自定义BeanPostProcessor

假设系统中所有的IFXNewsListener实现类需要从某个位置取得相应的服务器连接密码,而且系统中保存的密码是加密的,那么在IFXNewsListener发送这个密码给新闻服务器进行连接验证的时候,首先需要对系统中取得的密码进行解密,然后才能发送。我们将采用BeanPostProcessor技术,对所有的IFXNewsListener的实现类进行统一的解密操作。

  1. 标注需要解密的实现类
    为了能够识别那些需要对服务器连接密码进行解密的IFXNewsListener实现,我们声明了接口PasswordDecodable,并要求相关IFXNewsListener实现类实现该接口。
    在这里插入图片描述
  2. 实现相应的BeanPostProcessor对符合条件的Bean实例进行处理
    在这里插入图片描述
    在这里插入图片描述
  3. 将自定义的BeanPostProcessor注册到容器
    对于ApplicationContext容器来说,事情则方便得多,直接将相应的BeanPostProcessor实现类通过通常的XML配置文件配置一下即可。
    对于ApplicationContext容器来说,事情则方便得多,直接将相应的BeanPostProcessor实现类通过通常的XML配置文件配置一下即可。ApplicationContext容器会自动识别并加载注册到容器的BeanPostProcessor,如下配置内容将我们的PasswordDecodePostProcessor注册到容器:
    在这里插入图片描述
  4. InitializingBean和init-method
    在这里插入图片描述
    在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还
    不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。

Spring还提供了另一种方式来指定自定义的对象初始化操作,那就是在XML配置的时候,使用<bean>的init-method属性。
为了省去挨个<bean>的设置init-method这样的烦琐,我们还可以通过最顶层的<beans>的default-init-method统一指定这一init()方法名。

在这里插入图片描述
在执行别的方法之前,会先执行 setupHolidays 对某些参数进行处理,保证后续方法的正确性。

  1. DisposableBean与destroy-method
    当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或者其对应的bean定义是否通过的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。

也就是销毁之前,执行个方法。(类似JVM里的终结?)
在这里插入图片描述

第5章 Spring IoC容器ApplicationContext

作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。

5.1 统一资源加载策略

5.1.1 Spring中的Resource

Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访问接口。

Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类。

  • ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArray-InputStream并返回。
  • ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。
  • FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。
  • UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作。
  • InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。可能的情况下,以ByteArrayResource以及其他形式资源实现代之。

也可以自定义实现类,根据场景对资源进行封装。

5.1.2 ResourceLoader,“更广义的URL”

Resource接口提供了对资源的封装,ResourceLoader接口则负责查找和定位这些资源。

实现类:

  • DefaultResouceLoader
  • FileSystemResourceLoader
  • ResoucePatternResolver
    在这里插入图片描述

5.1.3 ApplicationContext与ResourceLoader

在这里插入图片描述
ApplicationContext间接地继承了DefaultResourceLoader,同时实现了ResourcePatternResolver。

说白了,ApplicationContext的实现类在作为Resource-Loader或者ResourcePatternResolver时候的行为,完全就是委派给了PathMatchingResource-PatternResolver和DefaultResourceLoader来做。

5.2 国际化信息支持

5.3 容器内部事件发布

5.3.1 自定义事件发布

Java SE提供了实现自定义事件发布(Custom Event publication)功能的基础类,即java.util.EventObject类和java.util.EventListener接口。所有的自定义事件类型可以通过扩展EventObject来实现,而事件的监听器则扩展自EventListener。

给出自定义事件类型:
在这里插入图片描述
在这里插入图片描述
事件监听器接口定义首先继承了java.util.EventListener,然后针对不同的事件发布时机提供相应的处理方法定义。

在这里插入图片描述

组合事件类和监听器,发布事件:在这里插入图片描述
在这里插入图片描述

5.3.2 Spring 的容器内事件发布类结构分析

ApplicationContext 容器内部允许以org.springframework.context.ApplicationEvent的形式发布事件, 容器内注册的org.springframework.context.Application-Listener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件。也就是说,一旦容器内发布ApplicationEvent及其子类型的事件,注册到容器的ApplicationListener就会对这些事件进行处理。

  • ApplicationEvent: 自定义事件类型
  • ApplicationListener: 自定义事件监听器
  • ApplicationContext: 发布事件,实际上委托给了ApplicationEventMulticaster来做。
    在这里插入图片描述

5.3.3 Spring 容器内事件发布的应用

Spring的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处理,并不适合分布式、多进程、多容器之间的事件通知。

要让我们的业务类支持容器内的事件发布,需要它拥有ApplicationEventPublisher的事件发布支持。所以,需要为其注入ApplicationEventPublisher实例。可以通过如下两种方式为我们的业务对象注入ApplicationEventPublisher的依赖:

  • 使用ApplicationEventPublisherAware接口。
  • 使用ApplicationContextAware接口。

自定义事件:继承ApplicationEvent
在这里插入图片描述
监听器:继承ApplicationListener
在这里插入图片描述
发布事件:实现ApplicationEventPublisherAware接口
在这里插入图片描述
注册到容器:
在这里插入图片描述

5.4 多配置模块加载的简化

通过ApplicationContext,我们只要以String[]形式传入这些配置文件所在的路径,即可构造并启动容器。

在这里插入图片描述
实际上,如果通过在某一个主配置文件中使用<import>分别加载其余的配置文件,然后容器就可以通过加载这个主配置文件,来加载其他的配置文件了。

第6章 Spring IoC容器之扩展篇

6.1 Spring 2.5 的基于注解的依赖注入

6.1.1 注解版的自动绑定(@Autowired)

@Autowired是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些依赖。

在这里插入图片描述
在这里插入图片描述
@Autowired不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数。
在这里插入图片描述
将FXNews相关类定义使用@Autowired标注之后,只要在IoC容器的配置文件中追加AutowiredAnnotationBeanPostProcessor就可以让整个应用开始运作了,如下所示:
在这里插入图片描述

@Autowired是按照类型进行匹配,如果当前@Autowired标注的依赖在容器中只能找到一个实例与之对应的话,那还好。可是,要是能够同时找到两个或者多个同一类型的对象实例,又该怎么办呢?我们自己当然知道应该把具体哪个实例注入给当前对象,可是,IoC容器并不知道,所以,得通过某种方式告诉它。这时,就可以使用@Qualifier对依赖注入的条件做进一步限定,使得容器不再迷茫。

假设FXNewsProvider使用的IFXNewsListener有两个实现,一个是DowJonesNewsListener,一个是ReutersNewsListener,
二者相关配置如下:
在这里插入图片描述
如果我们想让FXNewsProvider使用ReutersNewsListener,那么就可以在FXNewsProvider的类定义中使用@Qualifier指定这一选择结果,如下:
在这里插入图片描述
如果使用@Autowired来标注构造方法或者方法定义的话,同样可以使用@Qualifier标注方法参数来达到限定注入实例的目的。

在这里插入图片描述

6.1.2 @Resource

@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实例注入给@Resource所标注的对象。

在这里插入图片描述

6.1.3 classpath-scanning 功能

使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层包(base package)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。

在这里插入图片描述
<context:component-scan>默认扫描的注解类型是@Component。不过,在@Component语义基础上细化后的@Repository、@Service和@Controller也同样可以获得<context:component-scan>的青睐。@Component的语义更广、更宽泛,而@Repository、@Service和@Controller的语义则更具体。所以,同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。

在这里插入图片描述

<context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的命名规则,来生成那些添加到容器的bean定义的名称(beanName)。比如DowJonesNewsPersister通过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。

第7章 一起来看AOP

使用OOP,我们可以对业务需求等普通关注点进行很好的抽象和封装,并且使之模块化。
但是对于系统需求这一点来说,情况却有所不同。

比如一个有关贷款业务的管理系统,
从业务角度来说,该系统提供了顾客贷款申请、顾客信息管理、贷款信息管理、贷款回收发放等功能;
这些都属于普通的业务需求。通过OOP,可以很容易地按照功能划分这些模块。
在这里插入图片描述

但是,开发中为了调试,我们都需要对这些业务需求的实现对象添加日志记录功能;或者,业务方法的执行需要一定的权限限制,那么方法执行前肯定需要有相应的安全检查功能。这些都是系统功能的范畴。

虽然这些需求很明确(加入日志、安全检查),但是要把他们以OOP的方式实现并放到整个系统中去,那么系统中的每个业务对象都要添加这些需求的实现代码。

在这里插入图片描述
AOP(Aspect-Oriented Programming)让我们可以对类似日志和检查等系统需求进行模块化的组织,简化系统需求与实现之间的对比关系,进而使得整个系统的实现更加模块化。

在这里插入图片描述

7.3 Java平台上的AOP实现机制

7.3.1 动态代理

可以在运行期间,为相应的接口动态生成对应的代理对象。
我们可以将横切关注点逻辑封装到动态代理的InvocationHandler中,然后在系统运行期间,根据横切关注点需要织入的模块位置,将横切逻辑织入到相应的代理类中,以动态代理类为载体的横切逻辑,就可以和其他模块一起工作了。

7.3.2 动态字节码增强

我们可以为需要织入横切逻辑的模块类在运行期间,通过动态字节码增强,为这些系统模块生成相应的子类,而将横切逻辑加到这些子类中,让应用程序在执行期间使用的是这些动态生成的子类,从而达到将横切逻辑织入系统的目的。

7.4 AOP国家的公民

7.4.1 Joinpoint

在系统运行前,我们需要将AOP的功能模块织入到OOP的功能模块中。所以,我们需要知道在系统的哪些执行点上进行织入操作,这些系统执行点称w诶Joinpoint.

在这里插入图片描述

  • 方法调用(Method Call):当某个方法被调用的时候所处的程序执行点。

  • 方法调用执行(Method Call execution):某个方法内部执行开始时点。在这里插入图片描述
    对于同一对象,方法调用要先于方法执行。

  • 构造方法调用(Constructor Call):程序执行过程中对某个对象调用其构造方法进行初始化的时点。

  • 字段设置(Field Set):通过setter方法设置对象的某个属性的时点。

  • 字段获取(Field Get):某个对象属性被访问的时点。

  • 异常处理执行(Exception Handler Execution):在某些异常抛出后,对应的异常处理逻辑执行的时点。

  • 类初始化:类中某些静态类型或者静态块的初始化时点。在这里插入图片描述

7.4.2 Pointcut

Pointcut代表的是Joinpoint的表述方式。将横切逻辑织入到当前系统的过程中,需要参照Pointcut规定的Joinpoint信息,才知道应该往哪些系统执行点上织入横切逻辑。

1. Pointcut的表述方式

  • 直接指定Joinpoint所在方法名称
  • 正则表达式
  • 使用特定的Pointcut表述语言

2. Pointcut运算

Pointcut和Pointcut之间可以进行逻辑运算:
在这里插入图片描述

7.4.3 Advice

Advice是单一横切关注点逻辑的载体,它将代表会织入到Jointpoint的横切逻辑。相当于Class中的Method。

1. Before Advice

在Joinpoint之前执行的Advice类型。通常,它不会中断执行流程,但如果必要,可以通过在Before Advice中抛出异常的方式来中断当前程序。

通常,可以使用Before Advice做一些系统的初始化工作,比如设置系统初始值,获取必要系统资源等。

2. After Advice

在相应连接点之后执行的Advice类型,具体还分为下面三种:

  • After returning Advice : 只有当前Jointpoint处执行流程正常完成后,After returning Advice才执行。
  • After throwing Advice: 只有当前Jointpoint执行过程中抛出异常,才会执行。
  • After Advice:或许叫After(Finally) Advice更确切,该类型不论Jointpoint处执行正常结束还是抛出异常都会执行,就好像java中finally代码块一样。
    在这里插入图片描述

3. Around Advice

AOP实现大都采用拦截器的叫法,它可以在Jointpoint之前和之后都执行相应的逻辑,甚至中断或者忽略Jointpoint处原来程序执行的流程。

它自然也可以完成Before Advice和After Advice的功能。不过在通常情况下,还是应该根据场景选用更加具体的Advice类型。

4. Introduction

Introduction与前面几种Advice不同,它不是根据横切逻辑在Jointpoint处的执行时机来区分的,而是根据它可以完成的功能而区别于其他Advice类型。

Introduction可以为原有的对象添加新的特性或者行为,就好像你是一个普通人,为你添加了军人类型的Introduction之后,你就拥有军人的特性或者行为。

7.4.4 Aspect

Aspect是对系统的横切关注点逻辑进行模块化封装的AOP概念实体。
通常情况下,Aspect可以包含多个Pointcut以及相关Advice定义。
在这里插入图片描述

7.4.5 织入和织入器

只要经过织入过程后,以Aspect模块化的横切关注点才会集成到OOP的现存系统中。而完成织入过程的是织入器。

AspectJ有专门的织入器——ajc;Spring AOP采用一组类来完成最终的织入操作。

总之,Java平台各AOP实现的织入器不同,唯一相同的就是它们的职责——完成横切关注点逻辑到系统的最终织入。

7.4.6 目标对象

符合Pointcut所指定的条件,将在织入过程中被织入逻辑横切的对象,称为目标对象。

在这里插入图片描述

第8章 Spring AOP概述及其实现机制

8.1 Spring AOP概述

在这里插入图片描述

8.2 Spring AOP的实现机制

Spring AOP 采用动态代理机制和字节码生成技术实现。

与最初的AspectJ采用编译器将横切逻辑织入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。

8.2.1 设计模式之代理模式

代理处于访问者和被访问者之间,可以隔离这两者之间的直接交互,访问者与代理打交道就好像和被访问者打交道一样,因为代理通常几乎会全权拥有被代理者的职能,代理能够处理的访问请求就不需要劳烦被访问者来处理了。
从这个角度讲,代理可以减少被访问者的负担。另外,即使代理最终要将访问请求转发给真正的被访者者,它也可以在转发访问请求之前或者之后加入特定的逻辑,比如安全访问限制,或者像房产中介那样收取一定的中介费等。

在这里插入图片描述

  • ISubject : 被访问者的抽象
  • SubjectImpl : 被访问者的具体实现类
  • SubjectProxy:被访问者的代理实现类,该类持有一个ISubject接口的具体实例。
  • Client:代表访问者的抽象角色。在这个场景中,它会请求SubjectImpl实例,但它无法直接请求真正要访问的SubjectImpl,而是要通过ISubject资源的访问代理类SubjectProxy进行。

代理对象内部持有真实对象的引用。当Client通过request请求服务时,SubjectProxy将转发该请求给SubjectImpl。
从这个角度说,代理对象反而多此一举了。不过,代理对象的作用不局限于请求转发,更多时候是对请求添加更多访问限制。

在这里插入图片描述
在转发请求给被代理对象之前或者之后,都可以根据情况插入其他逻辑,比如在转发前和转发后记录时间;或者可以只在转发后对SubjectImpl的request()的值进行覆盖,返回不同的值;甚至可以不做转发。

代理对象就像是被代理对象的影子,只不过这个影子拥有更多的功能。
在这里插入图片描述

静态代理的弊端在于:
对于Jointpoint相同(比如request方法的执行,有很多个类都有这个方法,都需要代理对象进行拦截),但是对应的目标对象类型时不同的,我们都要为它们单独实现一个代理对象,而这些代理对象的横切逻辑是一样的,所以会有太多重复的代码。

8.2.2 动态代理

动态代理机制的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
如果我们要为两种类型添加相同的横切逻辑,我们只要实现一个InnvocationHandler就可以了。

在这里插入图片描述
在这里插入图片描述
即使还有更多的目标类型,只要它们织入的横切逻辑相同,都可以用Proxy类为它们生成相应的动态代理实例来满足要求。当相应的接口方法被调用时,对应的InvocationHandler就会拦截方法调用,并进行处理。

动态代理虽好,但是只能对实现了相应Interface的类使用,如果某个类没有实现任何的Interface,就无法使用动态代理对象。
对于没有实现任何接口的目标对象,Spring AOP会采用动态字节码为目标对象生成代理对象实例。

8.2.3 动态字节码生成

第9章 Spring AOP 一世

9.1 Spring AOP 中的 Joinpoint

Spring AOP中,仅支持方法级别的Jointpoint。
虽然Spring AOP仅提供方法拦截,但是在实际的开发过程中,这已经满足80%的开发需求了。

9.2 Spring AOP 中的 Pointcut

Spring中使用接口定义了两个方法来捕捉Joinpoint,并提供一个TruePointcut类型实例。
如果Pointcut类型为TruePointcut,那么默认会对系统中的所有对象,以及对象上所有被支持的Joinpoint进行匹配。

在这里插入图片描述

  • ClassFilter:对Joinpoint所处的对象进行Class级别的类型匹配
  • MethodMatcher:对Joinpoint所处的对象的方法进行匹配

9.2.1 常见的Pointcut

在这里插入图片描述
不同的Pointcut分别用于不同的匹配需求。

9.2.3 IoC容器中的Pointcut

Spring中的Pointcut实现都是普通的Java对象,同样可以注册到IoC容器中。
不过,我们通常不会这样使用。只是说明一下这样做是合情合理的。

9.3 Spring AOP 中的Advice

per-class 类型的Advice: 该类型的Advice的实例可以在目标对象类的实例之间共享。

1. Before Advice: 横切逻辑在Joinpoint之前执行

只需要实现MethodBeforeAdvice接口即可,实现before方法。
在这里插入图片描述

2. Throws Advice: 横切逻辑在程序出现异常时执行

需要实现 ThrowsAdvice 接口:
在这里插入图片描述
在这里插入图片描述

3. AfterReturningAdvice:方法正常返回时执行

实现AfterAReturningAdvice接口:
在这里插入图片描述
通过此接口,我们可以访问当前Joinpoint的方法返回值、方法、参数及所在的目标对象。

但是我们此接口不能更改返回值。如果想要更改,需要使用Around Advice。

4. Around Advice:几乎全能

通过实现MethodInterceptor接口:
在这里插入图片描述
在这里插入图片描述![在这里插入图片描述](https://img-blog.csdnimg.cn/20200120153408839.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDQ5NTE2Mg==,size_16,color_FFFFFF,t_70在这里插入图片描述
通过invoke方法的MethodInvocation参数,我们可以控制对Jointpoint的拦截行为。
通过调用MethodInvocation的proceed方法,可以让程序继续运行。

9.3.2 per-intance 类的 Advice

唯一一种:Introduction
它可以为目标类添加新的属性和行为。

9.4 Spring AOP 中的 Aspect

Advisor代表Spring中的Aspect,通常只持有一个Pointcut和一个Advice。在这里插入图片描述

9.4.1 PointcutAdvisor家族

在这里插入图片描述
使用不同的的PointcutAdvisor实现,限定了可以使用的Pointcut的类型。

9.4.2 IntroductionAdvisor分支

在这里插入图片描述

9.4.3 Ordered的作用

Ordered用于干预同一个Joinpoint处,多个Advice之间的运行顺序。

Spring默认会按照在容器中的声明顺序来执行这种Advisor。

最彻底的方法为每个Advisor明确指定顺序号。

在这里插入图片描述

9.5 Spring AOP 的织入

现在,我们已经准备好了要织入的内容(Advisor:持有Pointcut及Advice),现在要做的把这些内容织入到原有的逻辑中。

9.5.1 ProxyFactory

ProxyFactory是Spring AOP最基本的织入器。
在这里插入图片描述

  • 第一步:通过ProxyFactory的构造方法或者Setter传入要被织入的目标对象。
  • 第二步:指定Advisor。

1. 基于接口的代理

如果目标类实现了接口,那么我们可以为ProxyFactory明确指定代理的接口类型。

通过setInterfaces明确告知ProxyFactory,我们要对该接口类型进行代理。在这里插入图片描述
此处,再使用一个Name…Advisor的Advisor实现类来指定Pointcut和Advice。

这样,ProxyFactory只要检测到目标类实现了相应的接口,就会进行代理。
在这里插入图片描述

2. 基于类的代理

如果目标类没有实现任何接口,会使用CGLIB(动态字节码)进行基于类的代理。

在这里插入图片描述
在目标类实现了接口的时候,也可以强制使用基于类的代理。
可以将织入器的ProxyTargetClass设置为true。

3. Introduction的织入

Introduction只能应用于对象级别的拦截,不需要指定Pointcut,而只需要指定目标接口类型即可。

在这里插入图片描述

9.5.3 容器中的织入器——ProxyFactoryBean

1. 本质

在IoC容器中,使用ProxyFactoryBean作为织入器,它本质上是一个产生Proxy的FactoryBean。

返回对象的scope默认是singleton,即第一次生成代理对象后,之后会通过缓存生成代理对象,节约性能。

2. 使用

  • 基于接口
  • 基于类
    在这里插入图片描述在这里插入图片描述

第10章 Spring AOP 二世

10.1 @AspectJ形式的AOP

它让我们能够以POJO的形式定义Aspect,没有其他接口定义限制。
唯一需要的就是使用@Aspect注解标注这些类,让AOP找到它们,并织入系统。

10.1.1 @AspectJ形式的AOP先睹为快

在这里插入图片描述

  • 使用@Pointcut指定Pointcut定义
  • 通过@Around等注解来指定哪些方法定义了横切逻辑

假设定义目标对象类:
在这里插入图片描述
现在有两种方式将Aspect织入到这个方法中:

1. 编程方法织入

在这里插入图片描述

2. 自动代理织入

只需要在IoC容器配置文件中注册一下AutoProxyCreator的实现类AnnotationAwareAspectJAutoProxyCreator就可以了:
在这里插入图片描述
这样,该对象就会自动搜集容器中的Aspect,并用到Pointcut定义的目标对象上。

还有另外一种简介的配置方式:
在这里插入图片描述
在使用@AspectJ形式的AOP时,应该尽量使用自动代理。

10.1.2 @AspectJ形式的Pointcut

1. Pointcut声明方式

在这里插入图片描述

  • Pointcut Expression:该注解是方法级别的,不能脱离方法声明。它规定了Pointcut的匹配规则。
  • Pointcut Signature:它是Expression的载体,该方法返回值必须是void,没有其他限制。public修饰的可以被别的Aspect引用;private修饰的只能在本Aspect类中使用。
    在这里插入图片描述
    @Pointcut的value可以进行逻辑运算,得到更复杂的Pointcut设定:
    在这里插入图片描述
    对于系统中能够公用或者统一管理的一类Pointcut来说,可以声明一个专门的Aspect来定义这些Pointcut,让别的Aspect类来引用它们,避免重复定义。
    在这里插入图片描述

2. @AspectJ形式Pointcut表达式的标志符

  • execution
    使用最多的标志符;其中,方法的返回类型、方法名、参数部分的匹配模式是必须指定的,其他部分的匹配模式可以省略。
    在这里插入图片描述
    通配符的使用:
    *通配符可以匹配一个单词,可以在任何地方使用;
    …通配符可以在参数或者方法名中使用;
    在这里插入图片描述
    在这里插入图片描述

  • within
    within只接受类型声明,它会匹配指定类型下所有的Joinpoint。
    在这里插入图片描述

  • this 和 target
    this指代理对象;target指目标对象
    如果使用this(ObjectType)作为Pointcut,那么当目标对象的代理对象是ObjectType类型时,该Pointcut将匹配ObjectType类型中所有的Joinpoint。

    如果使用target(ObjectType)作为Pointcut,那么当目标对象是ObjectType类型时,该Pointcut将匹配ObjectType类型中所有的Joinpoint。

    实际上,代理对象和目标对象的类型通常是相同的,因为它们会实现同一个接口。所以这两个标志符的作用其实差不多。

  • args
    该标志符的作用是捕捉拥有指定参数类型、指定参数数量的方法级Joinpoint,而不管该方法在什么类型中被定义。
    在这里插入图片描述

  • @within
    如果使用@within声明了某种类型的注解,那么只要对象标注了该类型的注解,使用了@within标志符的Pointcut表达式就会匹配该对象内部所有Joinpoint。
    自定义注解:
    在这里插入图片描述
    声明类:
    在这里插入图片描述
    使用@within:
    在这里插入图片描述
    那么该类中method1,method2都将被匹配到。

  • @target
    目标对象中所有的Joinpoint都将被匹配。(和@within差不多)

  • @args
    检查当前的Joinpoint的方法参数类型,如果该参数类型拥有@args指定的注解,则被匹配。

在这里插入图片描述
如图,在执行handOut时,只有传入的参数是InterceptableTwo类型时,才会被匹配。

  • @annotation
    将会检查系统中所有对象的所有方法级别Joinpoint,如果被检测的方法有@annotation所指定的注解,则被匹配。
    比如对事物进行统一管理:
    在这里插入图片描述
    在这里插入图片描述
    将匹配所有被@Transactional修饰的方法。

10.1.3 @AspectJ形式的Advice

1. Before Advice

在这里插入图片描述
可以直接把Pointcut直接指定在value里,也可以单独写,然后value里写Pointcut Signature。
在这里插入图片描述
有时候,我们可能需要在Advice定义中访问Jointpoint处的参数:

  • 通过JointPoint访问:可以将Before Advice方法的第一个参数设置为JoinPoint类型,通过该类型的getArgs()方法,访问参数值。还可以使用getThis()获取当前代理对象,getTarget()获得当前目标对象。
  • 通过args标志符绑定,需要在Advice方法上声明需要的参数,然后再Pointcut定义种使用args声明参数,这两个名字必须保持一致:
    在这里插入图片描述
    我们也可以同时使用JointPoint以及args参数,但是JointPoint必须放在第一个位置。
    在这里插入图片描述

2. After Throwing Advice

它有一个独特参数,throwing,通过它,可以限定Advice方法的参数名,并在方法调用时,将相应的异常绑定到具体方法参数上。此处,将RuntimeException类型的异常绑定到了afterThrowing方法上:
在这里插入图片描述

3. After Returning Advice

在这里插入图片描述
可以通过returning属性访问返回值:
在这里插入图片描述

4. After(Finally) Advice

在这里插入图片描述

5. Around Advice

它的第一个参数必须是ProceedingJoinPoint类型,而且必须指定。因为我们需要通过该类型的proceed()方法继续调用链的执行。
在这里插入图片描述

6. Introduction

需要在Aspect中声明一个实例变量,它的类型对应的就是新增加的接口类型。(例子中的ICounter)

比如我们想要将ICounter的行为逻辑加到ITask类型上,假设ITask的实现类是MockTask,ICounter的实现类是CounterImpl:
在这里插入图片描述
@DeclareParents的value指定将用到的目标对象;defaultImpl指定了新增加接口的实现类。

将该Aspect注册到容器中:
在这里插入图片描述

10.1.4 @AspectJ中的Aspect更多话题

1. Advice的执行顺序

  • 当这些Advice都声明在同一个Aspect内的时候,那么它们执行的顺序由声明顺序而定。对于Before Advice,拥有最高优先级的最先执行;对于AfterReturning来说,拥有最高优先级的最后执行。

  • 当这些Advice声明不在同一个Aspect内的时候
    需要使用Spring的Ordered接口,让Aspect实现此接口,并实现getOrder()方法,返回值越小,优先级越高:
    在这里插入图片描述

2. Aspect的实例化模式

默认是singleton,也就是容器会实例化并持有每个Aspect定义的单一实例。

也可以指定别的模式,通过@Aspect注解的语句更改,Spring支持singleton, perthis和pertarget三种实例化模式。
在这里插入图片描述

  • perthis会为相应的代理对象实例化各自的Aspect实例
  • pertarget会为匹配的单独的目标对象实例化相应的Aspect实例

10.2 基于Schema的AOP

可以省去注解,直接使用Schema的配置文件进行配置。
在这里插入图片描述
//待补

第11章 AOP应用案例

发布了40 篇原创文章 · 获赞 1 · 访问量 1075

猜你喜欢

转载自blog.csdn.net/weixin_44495162/article/details/104007342