《Spring揭秘》

本篇博客主要是根据《Spring揭秘》所做的读书笔记。

第一章

介绍了spring框架的历史和包含的东西,自己看即可。

第二章

简单介绍了一下,什么是IoC,以及几种注入的方法。

什么是IoC

IoC的功能,简单来讲就是我原先想要做一道番茄炒蛋,那么我必须去菜市场自己买来番茄和鸡蛋,然后炒着吃。但是这里就有一个问题,我必须依赖于番茄和鸡蛋,没有它们我做不了菜。而IoC的职能就是,在我开始做菜之前,它会帮我送来番茄和鸡蛋,这样我就能安心炒菜了。之后如果我要炒其他菜,我只需要通知IoC,让它为我准备好材料,而不需要自己亲自准备材料了。也就是原先我自己掌握原材料,现在是IoC来帮我管理,这里就发生了控制反转。这里的原材料,对应的就是一个个Java对象。

IoC的注入方法

当然IoC需要知道怎么构造这些对象,不然它怎么送来呢?那就有以下这些注入的方法:

  • 通过构造方法注入。构造方法本来就是用来创造对象的,所以只需要准备好构造方法的参数列表中的对象,自然而然就可以创造出一个对象。
  • 通过setter方法注入。setter方法也可以为对象添加(更改)值,所以也可以用setter方法,而且setter方法可以被继承、可以设置默认值、不像构造方法一旦执行就马上创建出了新对象等,所以比较推荐使用。
  • 通过接口注入。几乎完全看不到使用了,所以不做介绍。

第三章

IoC容器的职能

说白了它的职能其实非常简单:创建出对象,解决对象之间的依赖关系(依赖绑定)。

创建对象上面已经讲了,这里聊一聊解决对象之间的依赖关系的几种方法:

  • 直接写代码告知。这种方法非常简单,我直接告诉一个IoC容器,将A.classnew a()这个方法绑定,当我们需要这个特定的对象的时候,请把容器中对应的那个实例返还给我们。
  • 配置文件。配置文件其实就是spring最广为使用的方法之一,随便找个spring的配置bean的文件看一眼就懂了。
  • 使用元数据(注解)。同样是spring最广为使用的方法之一。

第四章

从这一章开始,我们就开始深入了解spring里面的IoC容器——BeanFactoryApplicationContext(见第五章)

spring的IOC容器除了能够提供上面说的创建对象和依赖注入这两种职能外,还兼顾AOP支持,生命周期管理等功能。

  • BeanFactory,采用懒加载的模式,所以它启动速度快,所需资源少。
  • ApplicationContext,它在BeanFactory之上构建,且构建之后会完成初始化并且绑定,所以会稍微慢点并且消耗比较多的资源,但是它功能齐全。

bean

上图是它们之间的关系。

BeanFactory源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";

// 还有一些重载方法,这里省略了
Object getBean(String var1) throws BeansException;

boolean containsBean(String var1);

boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

// 其余省略

从源码中我们可以看到,最基本的就是能够根据id来获取bean对象,确认这个bean对象是否包含在IOC容器中以及确认这个bean对象是单例还是多例。

###BeanFactory的依赖注入

既然BeanFactory是一个IOC的容器,那么它必然需要知道如何来进行依赖注入,令人兴奋的是,它支持第三章所讲述的全部三种的依赖注入方法。

直接编码

spring又抽象了BeanDefinationRegistry这个接口,用这个接口来注册管理bean。而有一个实现类叫DefaultListableBeanFactory则是同时实现了这两个接口(也就是它既可以注册,也可以作为一个BeanFactory),所以用它就可以完成直接编码的任务。下面是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Demo01 {

public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanFactory factory = bind(beanFactory);
Person p = (Person) factory.getBean("person");
System.out.println(p);

}

public static BeanFactory bind(BeanDefinitionRegistry registry) {
AbstractBeanDefinition person = new RootBeanDefinition(Person.class);
// 1.注册
registry.registerBeanDefinition("person", person);

// 2.指定依赖关系,这里使用setter注入
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("name", "张三");
propertyValues.addPropertyValue("age", "18");
person.setPropertyValues(propertyValues);

// 3.完成
return (BeanFactory) registry;

}
}

虽然我们一直说的都是你和BeanFactory进行打交道,来获取对象,但是其实真正的“对象”是BeanDefinition;每一个对象,都在BeanFactory中有一个BeanDefinition来对应,它包含了对象的一切信息。而RootBeanDefinitionChildBeanDefinition则是它的两个实现类。(PS:BeanDefinition是被AbstractBeanDefinition所继承的,所以代码里并不是直接用的BeanDefinition)。

通过配置文件

主要是propertiesxml,当然你如果愿意也可以用自己的文件格式(但是解析你得自己来了)。由于目前properties用的比较少了,所以下面我介绍一下xml格式的。

其核心思想很简单,就是通过BeanDefinitionRead这个类来加载这些配置文件的信息,然后映射到BeanDefinition中,把BeanDefinition注册到BeanDefinitionRegistry中,然后由BeanDefinitionRegistry完成注册和加载。本质上其实和直接编码完全一致,但是完成了解耦,所以非常爽。

首先我们需要提供一个bean.xml文件:

1
2
3
4
<bean id="person" class="cn.chenlangping.Person">
<property name="name" value="李四"/>
<property name="age" value="22"/>
</bean>

然后就更加简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo02 {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanFactory factory = bindWithXML(beanFactory);
Person p = (Person) factory.getBean("person");
System.out.println(p);
}

public static BeanFactory bindWithXML(BeanDefinitionRegistry registry) {
XmlBeanDefinitionReader read = new XmlBeanDefinitionReader(registry);
read.loadBeanDefinitions("classpath:bean.xml");
return (BeanFactory) registry;
}
}

直接读取并生成即可。

进一步深入解析xml配置文件

最顶层的标签,<beans>,是所有的最高层,下面是它的一些属性(这些属性对其下所有bean都生效)

  • default-lazy-init,是否进行延迟初始化,默认false
  • default-autowise,自动绑定的方式,默认no
  • default-dependency-check,做依赖检查,默认no
  • default-init-method,指定初始化方法名字
  • default-destroy-method,指定对象销毁方法。

上面的这些属性,是对其内部所有的bean都生效,所以可以理解为就是简化操作而已。

之后就是四种并列的标签了,分别是<bean> <description> <import><alias>,下面我们了解下几乎不怎么用到的三个:

  • <description>:添加写描述性的信息….真没人用
  • <import>:看一下使用方法:<import resource="xx.xml"/>,看上去还算有用,但是实际中我们的容器本来就可以增加多个配置,所以xml中的这个功能倒是略显鸡肋了。
  • <alias>:为bean起外号,比如某个bean的id超级超级长,可以用<alias name="longlonglongname" alias="short"/>来替换。

接下来就是我们的重头戏,<bean>这个标签了

  • id:就像身份证一样唯一来标示这个bean,但是并不是必须的。

  • class:用以指定具体的类型,基本上是必须的。

  • 然后分别是用构造器和使用setter方法注入的示例:

1
2
3
4
5
6
7
8
<bean id="person" class="cn.chenlangping.Person">
<constructor-arg type="java.lang.String">
<value>张三</value>
</constructor-arg>
<constructor-arg index="1">
<value>14</value>
</constructor-arg>
</bean>
1
2
3
4
<bean id="person" class="cn.chenlangping.Person">
<property name="name" value="李四"/>
<property name="age" value="22"/>
</bean>

值得注意的是,如果使用setter进行注入,请确保有一个无参的构造器;而且这两个可以同时使用。

接下来是propertyconstructor-arg都通用的一些配置项:

  • value:我们一般将其作为bean标签内部的一个属性,但是它其实也可以单独作为一个标签,上面构造器注入就是。
  • ref:就是引用别的bean,和value一样,我们也是基本把它当成一个属性来用。
  • 内部bean:就是只给当前的对象所用,这个bean就可以没有id(本来也不想给别人用),当然你要加上id也是没有问题的。
  • list:就是Java中的list子类。
  • set:同list
  • map:<entry key="key1" value="value">
  • <null/>:这个值得特别讲一讲,在使用String的时候,如果你没写这个标签而是留空了,那么默认这个String"",而只有这个标签才代表了是Null

如果某些类需要依赖另外一些类,而你又没有写ref,那么就需要depent-on来显式指定。

在bean中,我们可以通过autowire来明确指定bean之间的依赖关系,有下面几种:

  • no:默认值。依赖你手工配置。
  • byName:如果你的类中定义了某个属性,然后如果bean中的name和你类中的属性(变量)名字一样,那么就会自动注入。
  • byType:如果没找到对应的type,那么不做设置;如果有多个type,那么就会出现问题。
  • contructor:这个用来匹配构造方法的参数类型从而完成自动绑定。

推荐还是不要使用上面的这些属性来完成自动配置,现在idea能够自动提示,我个人是完全不在意来多敲几个字符的。而且难道@Autowired它不香吗?

其它的一些注解(xml配置),我个人觉得比较简单,就没有放入。

BeanFactory背后的流程

我们可以将spring的IOC容器实现其功能的过程,分成两个部分:容器的启动bean的实例化

容器的启动过程

最开始的阶段,毫无疑问需要读取我们之前的xml配置文件,然后经过一通解析,就可以封装出一个个的BeanDefinition,这些BeanDefinition会注册到BeanDefinitionRegistry,然后准备过程就完成啦。

bean的实例化

通过上面的这些阶段,我们需要的信息已经准备完毕,当调用容器的getBean方法的时候,或者隐式调用getBean的时候,容器就会实例化对象了。

所以简单概括下:一个阶段画图纸,另外一个阶段做成品。

当然,spring这么优秀的框架,提供了一套完美的机制让我们来插手容器和bean创建的每一个过程。这个设计思路,也完全可以用到自己设计开发的框架上去。

插手容器的启动

容器的启动本质上就是准备好BeanDefinition的过程,spring则是提供了一种叫BeanFactoryPostProcessor的接口来让我们对BeanDefinition来做一些操作。而如果我们的这些操作还是有顺序的,则还需要实现核心包下面的Ordered接口。当然spring为我们已经实现了两个不错的实现类,分别是PropertyPlaceholderConfigurer和PropertyOverrideConfigurer。

由于BeanFactory比较落后…所以需要我们手动创建这些类,然后进行注册。而ApplicationContext则会自动识别并使用。下面详细来看看这两个实现类好了。

PropertyPlaceholderConfigurer

我们可以在我们的配置文件中使用占位符,比如<value>${jdbc.password}</value>,然后在另外一个文件中指定:jdbc.password=123456这样子的。所以如果不用这个类,那么最后在BeanDefinition中的将会是${jdbc.password},所以需要这个类来完成最后的替换工作。

PropertyOverrideConfigurer

Emmm 这个和上面的是一样的,只不过上面的是“显式”的,而这个比较隐蔽。

CustomEditorConfigurer

上面两个都是对BeanDefinition做了改动,而这个类则不会对BeanDefinition进行改动。在开始这个自定义的BeanFactoryPostProcessor之前,我们先需要了解一下PropertyEditor。

PropertyEditor

我们在xml中配置的所有信息,本质上都是字符串,比如<value>2019/01/01</value>,那我们就需要把这个字符串转换成Date对象,但是spring并不知道要怎么转换呀,所以需要我们通过使用一种叫PropertyEditor的类来辅助。当然spring自然也是提供了一些PropertyEditor的。

我们如果要自己实现,要么实现这个接口,要么继承PropertyEditorSupport来少写点代码。

OK了解了这个,我们的CustomEditorConfigurer就是用来注册这些的PropertyEditor。

了解Bean的一生

上面我们已经对如何插手容器的启动有了了解,接下来就是对bean的一生来进行剖析了。

  1. 实例化bean对象。
  2. 设置对象的属性。
  3. 检查Aware接口并且设置相关的依赖。
  4. BeanPostProcessor进行前置处理
  5. 看看是否有InitializingBean,并且是否执行afterPropertiesSet
  6. 检查是否有配置init-method方法
  7. BeanPostProcessor进行后置处理
  8. 注册Destruction相关的回调接口
  9. 正式使用
  10. 判断是否实现了DispossableBean接口
  11. 检查是否配置了destory方法。

下面是对这些步骤的详细解释。

  1. 在容器的内部,采用“策略模式”来决定采用什么方法来初始化bean,一般通过反射或者cglib来生成。

  2. 我们说的创建bean,其实在内部创建的是BeanWrapper,也就是对bean进行了包装,之前有提到过,spring其实内部的配置文件是string类型的,我们需要使用PropertyEditor来告知如何将String转化为指定的对象,被告知的就是这个BeanWrapper。这样就可以设置对象的属性了。

  3. 到了这一步,对象实例化已经完成,且已经设置好了属性(和依赖),现在就会去检查目前的对象,它有没有实现一系列的以Aware。如果有实现,则根据对应的接口来给bean注入对应的依赖,有些bean在使用的时候可能会需要自己的beanName(id),或者需要BeanFactory,就是在这一步完成的。

  4. ApplicationContext靠的是BeanPostProcessor来处理Aware接口的,不过由于它和第三步紧紧挨着的,所以其实把第三步和第四步看成一块的也无妨。这里特别注意:BeanFactoryPostProcessor和BeanPostProcessor的区别,带factory的,是在第一阶段起作用,用来修改BeanDefinition的,而这个BeanPostProcessor则声明了两个方法,分别是postProcessBeforeInitialization和postProcessAfterInitialization,即上面的第四步和第七步所对应的“前置”和“后置”处理。同时这里也是我们使用AOP来进行对象代理的关键所在。其实还有一个特例,即InstantiationAwareBeanPostProcessor,它的位置其实在第0步,容器会首先检查有没有这个类型,有的话会让它来实例化对象。

  5. 1
    2
    3
    public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
    }

    显然就是在第三步第四步之后,如果还需要设置,可以实现这个类。但是并不推荐使用这个,因为我们有更好的init-method,而不需要拘泥于afterPropertiesSet()这个写死的方法名。同理对应DispossableBean接口和destory方法。

  6. 之前讲到过,单例创建的bean对象,会随着spring容器的关闭才消亡,但是spring它不会聪明到去自动调用这些回调方法,所以需要我们手动编写代码告知。

第五章

我很好奇为什么书里不把它放到第四章,和BeanFactory肩并肩的。

首先, ApplicationContext支持所有BeanFactory所支持的功能,而且还支持别的一些东西,要讲清楚这些“别的东西”,需要首先介绍下其他的一些接口。

资源策略

我们在使用spring框架的时候,肯定会使用到各种各样的资源,于是spring就为我们抽象出了一个Resource接口,然后一堆实现类用来针对各种各样的资源。

接着,又抽象出ResourceLoader这个接口来抽象如何定位资源的,里面有一个叫getResource方法来返回Resource。提供了两种实现类,分别是DefaultResourceLoader和FileSystemResourceLoader。

最后,还提供了一个叫ResourcePatternResolver的玩意儿来批量查找资源,本质上就是继承了ResourceLoader,并且新增了一个叫getResources的方法来返回资源数组。

最后的最后,我们的ApplicationContext其实继承了ResourcePatternResolver,所以它完全可以实现配置文件的加载。如果你的bean需要来加载资源,那么完全可以用之前提到的Aware系列接口,把ApplicationContext注入到bean里面,就可以实现资源的加载了。

国际化支持

Java中使用了Locale来代表不同的国家和地区,ResourceBundle则被用来保存特定于某个Locale的信息,可以通过getBundle (String baseName, Locale locale)方法取得不同Locale对应的ResourceBundle,然后根据资源的键取得相应Locale的资源条目内容。

而spring则是更近一步,抽象了MessageSource这个接口,我们只需要传入对应的Locale、资源的键和相应的参数,就可以获得对应的信息;而这在Java中通常是根据Locale获得ResourceBundle,在去ResourceBundle中查询信息。

ApplicationContext则是继承了这个MessageSource接口,所以它完全可以用来当做获取信息的类。如果你的bean需要实现国际化支持,那么就给它声明一个MessageSource,然后注入messageSource即可。

事件发布

Java自带两个接口,分别是EventObject和EventListener接口。一个是事件,另外一个是监听器。监听器本质上是一个空接口,你在内部实现自己的方法,然后把事件传入进来,这样就能对事件进行操作了。最后就是发布事件,一般会定义一个事件发布者。

在spring中,ApplicationEvent 定义事件,ApplicationListener来监听容器内发布的所有事件。

ApplicationEvent有下面三个实现:

  • ContextClosedEvent:ApplicationContext容器在即将关闭的时候发布的事件类型。

  • ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的时候发布的事件类

    型。

  • RequestHandledEvent:Web请求处理后发布的事件,其有一子类ServletRequestHandledEvent提供特定于Java EE的Servlet相关事件。

而我们的ApplicationContext则继承了ApplicationEventPublisher接口,但是它其实是通过ApplicationEventMulticaster来实现的,所以在容器启动的时候就会检查是否存在名字为applicationEventMulticaster的bean,如果没有就创建一个。

多配置

emmm就是ApplicationContext支持多个配置文件….

第六章

这是IOC容器的最后一部分内容,主要讲了自动配置和基于注解的依赖注入。

自动依赖注入

@Autowired

之前讲到可以在xml文件中配置autowire=byType来进行依赖的注入,而@Autowired则是注解版本的依赖注入,只不过它要比byType更加灵活,也更加强大。

它的原理就是通过BeanPostProcessor(AutowiredAnnotationBeanPostProcessor),在实例化对象的时候检查一下有没有@Autowired注解,有的话就需要进行处理。所以我们需要在配置文件中导入这个组件:<bean class="org.springframeword.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>,当然在实际中我们并不是这么写的,而是通过<context:Component-scan base-package=".."来做的。

@Qualifier

因为我们可以通过类型同时找到不止一种的bean,这个时候就需要这个注解来直接点名自己需要的是哪一个了。

JSR250注解

注意这个是Java实现的。分别是@Resource和@PostConstruct以及@PreDestroy.

@Resource并不是通过类型,而是通过bean的名字来找bean的,另外两个注解分别对应init-methoddestory

当然,JSR250也是通过CommonAnnotationBeanPostProcessor这个后置的处理器来完成的。我们在使用的时候同样通过bean的导入来实现的。

到了这里,其实我们可以自己推断出,<context:Component-scan base-package=".."本质上就是为我们添加了这些注解所需要的BeanPostProcessor而已。

至此,ioc部分结束。接下来是AOP部分。

第七章

AOP的作用这里就不多说了,网上到处都是。实现了AOP的我们给他们取了一个名字,叫AOL,即Aspect-Oriented Language。当然AOL可以和实现普通类的一致,也可以用单独的一种语言,比如AspectJ就是扩展自java的一种AOL,它和Java是两种语言。用AOL实现AOP组件,最后还是要集成到OOP实体中,这个过程即成为织入(Weave)。

静态AOP

静态AOP,第一代的AOP,AspectJ就是其代表。静态表现在,实现了相关的切入点之后,会通过编译器直接就织入到静态类中了。比如AspectJ就是用了ajc编译器,以字节码的形式编译到系统的各个功能模块里面,这样Java虚拟机只需要正常运行就可以了。

动态AOP

静态AOP的明显缺点是,如果我需要修改我织入的位置,那代码得重写,而且还得重新编译。于是就有了动态AOP,比如Spring AOP框架。同时,AspectJ融合了别的框架,也支持了动态织入,所以它既支持动态又支持静态。有点就是它是在系统启动后织入的,所以很灵活,易用性很高,缺点就是动态织入性能损失比较大,当然我觉得这个性能完全可以忍受。

动态代理

定义了接口之后,我们可以动态生成代理对象,所以我们只需要实现动态代理的接口——InvocationHandler,然后指明要织入的位置。缺点就是必须要实现接口。

动态字节码增强

我们的Java虚拟机实际上执行的class文件,而只要class文件符合标准,程序就能运行。所以我们可以对我们需要增强的类生成对应的子类,然后把我们的逻辑放到子类中,让程序在运行的时候执行的是这些被我们动态生成的子类。所以缺点就是,这些类不能是final的,否则无法继承了。

自定义类加载器

类加载器的作用就是把class文件交给虚拟机进行执行,那这不就是刚好符合我们的需要么,我们自定义一个类加载器,并且在提交给虚拟机的时候“偷偷”做些手脚,这样就完成了AOP。

AOL扩展

AOP有各种概念,在这里就有一一对应的实体,然后我们就可以随心所欲了。缺点就是:AOL是一门新的语言,你确定要付出代价来学习吗?

AOP的术语

JoinPoint:系统需要在哪些位置织入新的“功能”。基本上任意位置都可以作为joinpoint。

Pointcut:规定了JoinPoint的表达方式。一般会使用正则表达式来进行制定。

Advice:织入到JoinPoint的一些逻辑。这里其实可以和spring中的@Before这些注解对应起来。其中比较在意的就是Around Advice,它会对JoinPoint进行包裹,所以你可以在之前和之后都进行处理。

Aspect:AOP实体的代表,内部可以包含多个PointCut和Advice。

Weaver:把新增的逻辑切入到现有类中的,它可以是类加载器,也可以是编译器,而在Spring AOP中,最通用的则是ProxyFactory。

Target Object:就是原有的类,需要被增强的对象。

第八章

接下来就开始Spring AOP的旅程了。

首先需要了解设计模式中的代理模式,这部分就不写进来了。

静态AOP的囧境

要写的代码太多了,确实可以用设计模式来解决,但是如果有成百上千个对象,那么写代码会烦死人的

动态代理

动态代理主要有2个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler接口。具体代码就略了,比较简单。

我们这里的InvocationHandler,其实就是AOP里面的Advice。本质上,不同的Advice就是通过实现了不同逻辑的InvocationHandler来实现呢的。

第九章

AOP在Spring中目前是第二代,但是由于第一代AOP和第二代其本质上是一致的,所以先从这第一代开始吧。

JoinPoint

spring只支持方法执行的JoinPoint,满足了二八法则。通过对方法的拦截,我们变相也可以实现对属性的拦截(getter和setter)。如果还是不够,那么可以通过AspectJ来提供帮助。

Pointcut

1
2
3
4
5
6
7
public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE;

ClassFilter getClassFilter();

MethodMatcher getMethodMatcher();
}

其中的ClassFilter对应被增强的对象(通过match方法),而MethodMatcher则是相应的方法。
MethodMatcher有两个直接抽象类,分别是DynamicMethodMatcher(基本不用)和StaticMethodMatcher。

当然实际中肯定需要了解几种Pointcut的实现类。

  • NameMatchMethodPointcut:只要函数名字(注意是函数名,不是函数的签名)一致,就认为匹配。
  • JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut,这两个都是通过正则表达式来处理的,且必须匹配整个方法签名。
  • AnnotationMatchingPointcut:明显就是通过特定的注解来匹配的。
  • ComposablePointcut:这个Pointcut可以支持与、并等操作。
  • ControlFlowPointcut:最特殊的一个,它不仅能够指定被拦截的方法,还能指定拦截者是谁。当然性能比较差,只有真正需要的时候才用它。

以上是spring已经实现的Pointcut实现类,当然我们还需要学会自定义,无非是继承一下StaticMethodMatcher或者是DynamicMethodMatcher,重写一下它们的matches方法即可。

Advice

spring根据能否在目标对象类中的所有实例中共享这一原则,分成了两类Advice,per-class的和per-instance的。前者是对一个类的所有对象(中的方法)都生效,后者是对指定对象生效。

Before Advice

在相应的JoinPoint执行之前执行。一般它不打断正常的逻辑,除非加入了异常判断。只需要实现MethodBeforeAdvice这个接口,重写它的before方法即可。

Throws Advice

这个ThrowsAdvice是一个空接口,继承自AfterAdvice。

AroundAdvice

真正的类其实叫MethodInterceptor,它能实现各种advice所实现的功能。简单来说就是通过一个链把这些所有的切面连了起来,然后在invoke里面对其进行增强。

Introduction

上面的各种advice是在类的每个实例之间进行共享的,而这里的这个则是仅仅是对某个对象生效。

在spring中,靠的就是IntroductionInterceptor这个接口。实际中,一般会使用DelegatingIntroductionInterceptor实现类,只需要定义好新的接口以及该新接口的实现类,然后进行织入即可。

Aspect

在spring中,使用的是Advisor代表了Aspect,但是Advisor本身有点特殊,通常只持有一个Pointcut和一个Advice。主要有两大家族:PointcutAdvisor和IntroductionAdvisor。

PointcutAdvisor

DefaultPointAdvisor就是它的实现类,除了Introduction之外的advice,其余都可以通过它来使用。使用方法也很简单,创建好Pointcut和Advice之后,在构造方法里面丢入或者直接用set方法即可。

NameMatchMethodPointcutAdvisor则是加了限定,从名字中就看出来是对pointcut进行了限制,只允许NameMatchMethod;同理还有RegexpMethodPointcutAdvisor。

所以,默认情况下,用DefaultPointAdvisor就足够啦。

IntroductionAdvisor

IntroductionAdvisor只能对应于类级别的拦截,且advice类型只能使用Introduction的。默认也就一个DefaultIntroductionAdvisor。

Ordered

有些时候,我们需要让Aspect(Advisor)有顺序。默认情况下,先声明的bean顺序号小,优先级最高,越先拦截。所以我们需要在bean注入的时候,为order注入序号。

AOP织入

到目前为止,所有的组件都已经完全准备好了,这个时候只需要用“胶水”把它们粘合在一起即可。spring使用的是ProxyFactory。

1
2
3
4
ProxyFactory factory = new ProxyFactory(proxyInterfaces.class);
Advisor advisor = null;
factory.addAdvisor(advisor);
factory.getProxy().doSomething();

第一行指定了你需要增强的类名,第二个就是设置好Aspect(Advisor),然后织入即可,就是那么简单。

只要目标类实现了一个接口,那么ProxyFactory默认就是通过接口(也就是JDK的方法)进行织入的。这里需要重点注意的是,代理类它本质上是一个实现了被代理的接口的一个Proxy类,所以代理类和被代理类之间,不能进行强制类型转换

如果目标类没实现接口,那么默认情况下就使用cglib进行类的增强。当然就算实现了某个接口,仍然可以指定其使用cglib,由于cglib是通过生成子类来完成代理功能的,所以是可以进行类型转换的。

ProxyFactory的本质