Spring AOP
Spring AOP
SpringAOP是 AOP技术在Spring 中的具体实现,它是构成Spring 框架的另一个重要基石。Spring AOP构建于IoC之上,和IoC”浑然天成”,统一于Spring容器之中。本章将从 Spring AOP的实现技术入手,分析它的底层结构和实现。
代理模式
在日常生活中经常可以看到这么一种场景,某公司开业庆典需要请某明星剪彩及登台献唱,通常这个公司并不是直接和该明星联系,而是和该明星的经纪人联系行程、活动细节等事宜,也就是说这里的经济人就相当于该明星的代理,代理明星和公司对接行程以及落实活动细节,而明星则只负责执行安排即可。
如果由明星本人去事无巨细的落实和敲定每一件事情,不但工作很繁重,也会影响明星对自身业务的专注。
同理,在软件开发过程中也会有一些繁琐重复的工作去做,例如在WEB开发中,经常会对数据库进行操作,有时需要去监测某些功能的性能、执行时间、并记录执行日志。再或者说我们需要对每个方法的调用进行记录。这些都是重复且繁琐的功能,如果在业务代码中加入这些操作会产生以下几个弊端:
1使得业务类不在专注于业务,除了执行业务以外还需要监控自身性能、记录日志等操作,同时也违背了职责单一原则。
2因为记录日志、监控性能等代码都是重复的,如果在每个业务类中都加入这些代码,无疑会造成大量的代码冗余
3不利于维护,假设现在只需要监控若干个方法,如果需求有变,需要监控所有的方法,那么去添加这些代码无疑是很巨大的工作量
那么,如何解决这个问题呢,可以模拟公司请明星唱歌的情况创建一个“经纪人”也就是我们所说的代理。代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。Java中代理模式又可以分为静态代理和动态代理,下面就这两种代理方式分别进行讲解。
静态代理
静态代理要求被代理对象和代理对象需要实现相同的接口,下面首先创建接口:
1 | package cn.bytecollege.proxy; |
接着定义被代理对象,代码如下:
1 | package cn.bytecollege.proxy; |
接着定义代理对象,因为代理对象是代理被代理对象,并且由代理对象调用被代理对象的方法,因此需要在代理对象中封装被代理对象,代码如下:
1 | package cn.bytecollege.proxy; |
至此,一个简单的代理模式就完成了,从上例中可以看出真正执行sayHello()的还是Hello对象,只不过和外界直接交流的并不是Hello对象,而是为Hello对象编写的代理对象。如果此时,要对被Hello对象的sayHello()方法进行执行时间的监控,那么只需要在代理对象中添加相关代码,并不需要修改Hello中的代码,示例如下:
1 | package cn.bytecollege.proxy; |
新建测试类进行测试:
1 | package cn.bytecollege.proxy; |
运行结果如下:
动态代理
在上面的静态代理中可以看出如果要为一个类添加代理类,那么就得先抽象接口,然后再定义代理类,代理类也需要实现相同的接口,这无疑增加了很多代码量,那么有没有一种不需要额外添加接口就能实现的代理呢?这就需要使用动态代理了,动态代理有以下2个特点:
- 代理对象不需要接口实现,也就是说不需要定义额外的接口
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
动态代理又分为两种,即由JDK提供的JDK动态代理和CGLIB动态代理。下面将就这两种代理进行讲解:
JDK动态代理
JDK的动态代理主要涉及java.lang.reflect包中的两个类∶Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
1 | public static Object newProxyInstance(ClassLoader loader, |
- ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
以4.1.2.1中代码为例,使用JDK动态代理为Hello创建代理对象:
1 | package cn.bytecollege.proxy; |
新建测试类,测试使用JDK动态代理创建的代理对象:
1 | package cn.bytecollege.proxy; |
可以看出在上例的代码中,首先创建目标对象,也就是被代理的对象;接着创建一个生成代理对象的工厂,并在工厂的构造方法中传入目标对象;在创建完工厂以后使用工厂为目标类生成代理对象,并调用代理对象的方法。
可以从上面的示例中看出,代理对象并不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理,也可以立即为代理工厂动态为接口生成了一个子类实现。
运行结果如下:
CGLIB动态代理
在上面的代理模式中发现不管是静态代理也好,还是动态代理也好都要求目标对象实现一个接口,如果某个目标对象仅仅就是一个对象,没有实现任何接口的话显然前两种代理方式就没有办法解决这个问题了,这时就可以使用以目标对象子类的方式实现代理。也就是为目标对象动态生成一个子类代理,从而实现对目标对象功能的扩展。
下面我们先模拟这种情况,仍旧以Hello为例,为Hello创建子类对象:
1 | package cn.bytecollege.proxy; |
1 | package cn.bytecollege.proxy; |
新建测试类测试子类代理对象:
1 | public class Test { |
在上面的示例中,简单模拟了CGLIB生成代理的过程,下面将学习CGLIB的具体使用方法:
使用CGLIB需要引入相关的jar文件,但是Spring核心中已经包括了CGLIB相关的jar包,因此导入Spring-core依赖即可。
首先创建目标对象所属的类:
1 | package cn.bytecollege.proxy; |
接着定义代理工厂生成代理对象:
1 | package cn.bytecollege.proxy; |
在代理工厂中首先需要封装目标对象,并且需要为目标对象动态生成子类对象,当调用代理的方法时,会拦截该方法的执行,并进入intercept方法,此处即是增加监控业务逻辑的位置。
定义测试类测试:
1 | package cn.bytecollege.proxy; |
运行结果如下:
通过上述3中代理模式可以看出一个简单的区别:
- 静态代理要求目标对象和代理对象都要实现相同的接口
- JDK动态代理只要求目标对象实现接口即可,代理类不需要
- CGLIB不要求目标对象和代理对象实现接口,会动态的为目标类生成一个子类对象。
在Spring的AOP编程中同时使用了JDK动态代理和CGLIB动态代理,选择何种代理方式由目标类是否实现接口决定,如果目标类实现了接口则使用JDK动态代理,如果没有实现接口则使用CGLIB动态代理。
AOP概述
AOP基本概念
AOP(Aspect Oriented Programming)是OOP(Object Oriented Programming)即面向对象编程的一种补充和完善。以Java语言为例,其提供了封装、继承和多态等概念,实现了面向对象编程。开发人员可以使用Java语言将现实世界中各种事物抽象成Java语言中的对象,一类对象有共同的行为和特性。
虽然面向对象编程语言实现了纵向的对每个对象的行为进行归类和划分,实现了高度的抽象,但是,不同对象间的共性却不适合用面向对象编程的方式实现。如学生对象和汽车对象,都要实现与其自身业务逻辑无关的监控,在这种场景下,使用面向对象编程的方式,可最好的解决方案就是让学生对象和汽车对象都集成监控接口,然后学生对象和汽车对象分别实现监控方法。这种编程方式的缺点是,由于监控逻辑并不是对象本身的核心功能,并且不同对象的监控逻辑实现基本上相同——都是监控某个时间发生了某件事,只是记录的对象不同而已,这会导致监控对象行为的代码逻辑散落在系统的各个地方,并且几乎都是重复的代码,与对象的核心功能并无很强的关联性。这样的设计会导致大量的代码重复,并且不利于模块的复用。
AOP的出现,恰好解决了这个棘手的问题。其提供“横向”的切面逻辑,将与多个对象有关的公共模块分装成一个可重用模块,并将这个模块整合成为Aspect,即切面。切面就是对与具体的业务逻辑无关的,却是许多业务模块共同的特性或职责的一种抽象,其减少了系统中的重复代码,因此降低了模块的耦合度,更加有利于扩展。
AOP将软件系统分为两部分:核心逻辑和横切逻辑。核心逻辑主要处理系统正常的业务逻辑,横切逻辑不关注系统核心逻辑,其只关注与系统核心逻辑并非强相关的逻辑。
AOP相关概念
连接点(JoinPoint):特定点是程序执行的某个特定位置,如类开始初始化前、类初始化后、类的某个方法调用前/调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就被称为”连接点”。Spring 仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时及方法调用前后这些程序执行点织入增强。从某种程度上来说,AOP 也可以看成一个黑客(因为它要向目前类中嵌入额外的代码逻辑),连接点就是 AOP向目标类打入楔子的候选锚点。 连接点由两个信息确定∶一是用方法表示的程序执行点;二是用相对位置表示的方位。如在 Test.foo()方法执行前的连接点,执行点为 Test.foo(),方位为该方法执行前的位置。Spring 使用切点对执行点进行定位,而方位则在增强类型中定义。
切点(PointCut):每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。但在为数众多的连接点中,如何定位某些感兴趣的连接点呢?AOP 通过”切点”定位特定的接连点。借助数据库查询的概念来理解切点和连接点的关系再合适不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。 在 Spring 中,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。确切地说,应该是执行点而非连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体的连接点上,还需要提供方位信息。
增强(Advice):增强是织入目标类连接点上的一段程序代码,我们大可按照这一思路去理解增强,在 Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点的方位信息和切点信息,就可以找到特定的连接。正因为增强既包含用于添加到目标连接点上的一段执行逻辑,又包含用于定位连接点的方位信息,所以 Spring 所提供的增强接口都是带方位名的,如 BeforeAdvice、AfterReturningAdvice、ThrowsAdvice 等。BeforeAdvice 表示方法调用前的位置,而AfterReturningAdvice 表示访问返回后的位置。所以只有结合切点和增强,才能确定特定的连接点并实施增强逻辑。
目标对象(Target):增强逻辑的织入目标类。如果没有 AOP,那么目标业务类需要自己实现所有的逻辑,在 AOP 的帮助下,只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用 AOP 动态织入特定的连接点上。
引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过 AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
. 织入(Weaving):织入是将增强添加到目标类的具体连接点上的过程。AOP就像一台织布机,将目标类、增强或者引介天衣无缝地编织到一起。根据不同的实现技术,AOP有3种织入方式。 - 编译期织入,这要求使用特殊的 Java编译器。
- 类装载期织入,这要求使用特殊的类装载器。
- 动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而 AspectJ采用编译期织入和类装载期织入。
- 代理(Proxy):一个类被 AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以可以采用与调用原类相同的方式调用代理类。
- 切面(Aspect):切面由切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中。
AOP的实现者
AOP 工具的设计目标是把横切的问题(如性能监视、事务管理)模块化。使用类似OOP的方式进行切面的编程工作。位于 AOP 工具核心的是连接点模型,它提供了一种机制,可以定位到需要在哪里发生横切。
- AspectJ: AspectJ是语言级的 AOP实现,2001年由 Xerox PARC 的 AOP小组发布,目前版本已经更新到1.8.9。AspectJ扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有一个专门的编译器用来生成遵守 Java字节编码规范的Class 文件。其主页位于http∶//www.eclipse.org/aspectj。
- AspectWerkz :AspectWerkz是基于 Java的简单、动态、轻量级的 AOP框架,该框架于2002年发布,由 BEASystems提供支持。它支持运行期或类装载期织入横切代码,所以它拥有一个特殊的类装载器。现在,AspectJ和 AspectWerkz项目已经合并,以便整合二者的力量和技术创建统一的 AOP 平台。它们合作的第一个发布版本是 AspectJ 5∶扩展 AspectJ 语言,以基于注解的方式支持类似AspectJ 的代码风格。
- JBoss AOP: JBoss AOP于2004年作为 JBoss 应用程序服务器框架的扩展功能发布,读者可以从以下地址了解到JBoss AOP的更多信息∶http∶//www.jboss.org/products/aop。
- Spring AOP: Spring AOP使用纯 Java 实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。Spring 并不尝试提供最完整的AOP实现,相反,它侧重于提供一种和 Spring IoC容器整合的AOP 实现,用以解决企业级开发中的常见问题。在 Spring中可以无缝地将 Spring AOP、IoC和AspectJ整合在一起。
基于@AspectJ的AOP
AspectJ是一个面向切面的框架,其可以生成遵循Java字节码规范的Class文件。Spring AOP和AscpectJ之间的关系:Spring使用了和AspectJ一样的注解,并使用AspectJ来做切入点解析和匹配。但是Spring AOP运行时并不依赖于AspectJ的编译器或者织入器等特性。
使用AspectJ方式配置AOP
在AspectJ中使用“@Aspect”注解来标示一个切面;使用“@Pointcut”注解标示切入点;各种通知类型通过“@Before(前置通知)”“@Around(环绕通知)”“@AfterReturning(后置通知)”和“@AfterThrowing(异常通知)”等注解来实现。
下面将通过案例阐述AspectJ的各种注解的使用。
首先,添加Aspectj相关依赖:
1 | <dependency> |
定义Person类,其包含一个说话方法say(),Person类的代码如下:
1 | package cn.bytecollege.bean; |
定义切面,代码如下:
1 | package cn.bytecollege.aspect; |
在Spring中配置扫描注解包,并开启AOP代理:
1 |
|
新建测试类获取Person对象,并调用say()方法:
1 | package cn.bytecollege.test; |
运行测试类,结果如下图:
可以看到,在整个过程中没有修改Person中代码仅仅使用了几个点简单的注解,就完成了对功能的监控,下面将就上例中出现的相关注解进行讲解。
切点表达式函数
AspectJ 5.0 的切点表达式由关键字和操作参数组成,如切点表达式 “execution(* cn.bytecollege.bean..(..))”为操作参数。在这里,execution 代表目标类执行某一方法,而”* cn.bytecollege.bean..(..)”描述目标方法的匹配模式串,二者联合起来表示目标类Person方法的连接点。为了描述方便,将execution()称作函数,而将匹配串”* cn.bytecollege.bean..(..)”称作函数的入参。
execution()是最常用的切点函数,其语法如下∶
1 | execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) |
除了返回类型模式、方法名模式和参数模式外,其他项都是可选的。
Spring支持9个@ApsectJ切点表达式函数,它们用不同的方式描述目标类的连接点。根据描述对象的不同,可以大致分为4种类型。
- 方法切点函数∶通过描述目标类方法的信息定义连接点。
- 方法入参切点函数∶通过描述目标类方法入参的信息定义连接点。
- 目标类切点函数∶通过描述目标类类型的信息定义连接点。
- 代理类切点函数∶通过描述目标类的代理类的信息定义连接点。这4种类型的切点函数通过下表进行说明。
函数中的通配符
有些函数的入参可以接受通配符,@AspectJ支持3种通配符。
- 星号(*):匹配任意字符,但它只能匹配上下文中的一个元素。
- 省略号(…):匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联 合使用,而在表示入参时则单独使用。
- 加号(+):表示按类型匹配指定类的所有类,必须跟在类名后面,如 com.smart.Car+。 继承或扩展指定类的所有类,同时还包括指定类本身。
@AspectJ 函数按其是否支持通配符及支持的程度,可以为以下3类。
- 支持所有通配符:execution()和 within(),如 within(com.smart.)、within(com. smart.service..*Service+)等。
- 仅支持”+”通配符:args()、this()和 target(),如 args(com.smart.Waiter+)、target (java.util.List+)等。虽然这 3 个函数可以支持”+”通配符,但其意义不大,因为对于这些函数来说,使用和不使用”+”都是一样的,如 target(com.smart.Waiter+)和 target(com.smart.aspectj. Waiter)是等价的。
- 不支持通配符∶@args()、@within()、@target()和@annotation(),如@argscom.smart. anno.NeedTest()和@within(com.smart.anno.NeedTest)。
此外,args()、this()、target()、@argsO、@within()、@target()和@annotation()这7 个函数除了可以指定类名外,也可以指定变量名,并将目标对象中的变量绑定到增强的方法中。
不同增强类型
@AspectJ也为各种增强类型提供了不同的注解类,它们位于 org.aspectj.lang.annotation.*包中。这些注解类拥有若干个成员,可以通过这些成员完成定义切点信息、绑定连接点参数等操作。此外,这些注解的存留期限都是 RetentionPolicy.RUNTIME,标注目标都是 ElementType.METHOD。下面逐一学习@AspectJ 所提供的几个增强注解。
1.@Before 前置增强,相当于 BeforeAdvice。Before 注解类拥有两个成员。
- value∶该成员用于定义切点。
- argNames∶由于无法通过 Java 反射机制获取方法入参名,所以如果在 Java 编 译时未启用调试信息,或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意二者名字必须完全相同),多个参数名用逗号分隔。
2.@AfterReturning 后置增强,相当于AfterReturningAdvice。AfterReturning 注解类拥有4个成员。
- value∶该成员用于定义切点。
- pointcut∶表示切点的信息。如果显式指定 pointcut 值,那么它将覆盖 value 的设 置值,可以将 pointcut 成员看作 value 的同义词。 口 returning∶将目标对象方法的返回值绑定给增强的方法。
- argNames∶ 如前所述。
3.@Around 环绕增强,相当于MethodInterceptor。Around 注解类拥有两个成员。
- value∶ 该成员用于定义切点。
- argNames∶如前所述。
4.@AfterThrowing 抛出增强,相当于ThrowsAdvice。AfterThrowing 注解类拥有4 个成员。
- value∶该成员用于定义切点。
- pointcut∶表示切点的信息。如果显式指定 pointcut 值,那么它将覆盖 value 的设 置值。可以将 pointcut 成员看作 value 的同义词。
- throwing:将抛出的异常绑定到增强的方法
- argNames∶如前所述。
5.@After 不管是抛出异常还是正常退出,该增强都会得到执行。该增强没有对应的增强接口,可以把它看成 ThrowsAdvice 和 AfterReturningAdvice 的混合物,一般用于释放资源,相当于try{}finally{的控制流。After注解类拥有两个成员。
- value∶ 该成员用于定义切点。
- argNames∶如前所述。
- @DeclareParents 引介增强,相当于IntroductionInterceptor。DeclareParents 注解类拥有两个成员。
- value∶该成员用于定义切点,它表示在哪个目标类上添加引介增强。
- defaultImpl∶默认的接口实现类。 除引介增强外,其他增强都很容易理解。
Bean的生命周期
在第2章的学习中可以知道Spring IOC容器管理的Bean都是单例模式,即每个Bean只有一个实例化的Bean对象存在于Spring IOC容器中,因此Spring IOC容器需要负责管理Bean的创建、使用和销毁的整个过程,也就是Bean的生命周期。
Spring IoC容器中的Bean的生命周期可以分为以下4类:
●Bean自身方法。
●Bean生命周期接口方法。
●容器级生命周期接口方法。
●工厂后处理器接口方法。
各个阶段设计的具体接口和方法如下:
SpringBean生命周期各个阶段 | 相关接口及方法 |
---|---|
Bean自身方法 | Bean本身定义的方法:配置文件中init-method和destory-method指定的方法 |
Bean生命周期接口方法 | InitializingBean接口DisposableBean接口BeanNameAware接口ApplicationContextAware接口BeanFactoryAware接口 |
容器级生命周期接口方法(一般称为后置处理器) | InstantiationAwareBeanProcessor接口实现BeanPostProcessor接口实现 |
工厂级生命周期接口方法(容器级生命周期) | AspectJWeavingEnablerConfigurationClassPostProcessorCustomAutowireConfigurer等 |
Bean生命周期
下面先以Bean自身方法和Bean生命周期接口方法为例,演示其各个生命周期的执行时序。
●init-method:指定某个方法在Bean实例化完成,依赖关系设置结束后执行。
●destroy-method:指定某个方法在Bean销毁之前被执行。
●InitializingBean接口:指定在Bean实例化完成,依赖关系设置结束后执行(在init-method之前执行)。
●DisposableBean接口:指定某个方法在Bean销毁之前被执行(在destory-method之前执行)。
●ApplicationContextAware接口:在实例化Bean时,为Bean注入ApplicationContext。
●BeanNameAware接口:在实例化Bean时,为Bean注入beanName。
以下代码BeanLifecycle将实现上述4个接口InitializingBean、DisposableBean、ApplicationContextAware和BeanNameAware,并通过在XML文件中配置该Bean的init-method和destroy-method。通过BeanLifecycle例子将可以更加清晰地阐述Bean自身方法和Bean生命周期接口方法的生命周期。
1 | package cn.bytecollege.bean; |
如上述代码中的注释所示,BeanLifecycle这个Bean的生命周期将按照序号1~8顺序执行。
在配置文件中进行如下配置:
1 |
|
下面是对BeanLifecycle准备的测试代码:
1 | package cn.bytecollege.test; |
上述测试代码很简单,就是通过从Spring IoC容器中注入的BeanLifecycle对象,调用其sayHello()方法。测试代码运行后的结果如图所示。
通过执行结果可以得到Bean自身方法和Bean生命周期接口方法的执行时序:
- 执行构造器。
- 为对象中的属性注入值
- 执行BeanNameAware接口的setBeanName(String name)方法。
- 执行BeanFactoryAware接口的setBeanFactory()方法
- 执行ApplicationContextAware接口的setApplicationContext(ApplicationContextapplication Context)方法。
- 执行InitializingBean接口的afterPropertiesSet()方法。
- 执行init-method指定的方法。
- 执行运行时Bean中的业务方法。
- 执行DisposableBean接口的destroy()方法。
- 执行destroy-method指定的方法。
Bean自身方法和Bean生命周期接口方法执行的生命周期如图所示:
容器生命周期
下面将介绍容器级生命周期接口方法的执行时序。容器级生命周期接口方法有InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口,一般也将其实现类称为后处理器。容器级生命周期接口的实现独立于Spring IoC容器中的Bean,其是以容器扩展的形式注册到Spring中的。无论Spring IoC管理任何的Bean,这些后处理器都会发生作用。因此后处理器影响范围是全局的Spring IoC容器中的Bean。用户可以通过编写合理的后处理器来实现相关Bean加工处理逻辑。
- BeanPostProcessor接口:此接口的方法可以对Bean的属性进行更改。
- InstantiationAwareBeanPostProcessor接口:此接口可以在Bean实例化前、Bean实例化后分别进行操作,也可以对Bean实例化之后进行属性操作(为BeanPostProcessor的子接口)
- InstantiationAwareBeanPostProcessorAdapter:适配器类。
BeanPostProcessor、InstantiationAwareBeanPostProcessor和InstantiationAwareBeanPostProcessorAdapter三者的关系如图所示。
InstantiationAwareBeanPostProcessorAdapter最终实现了BeanPostProcessor这个顶级接口。下面以InstantiationAwareBeanPostProcessorAdapter为例,讲解容器级生命周期接口方法的执行时序。下面代码将通过ContainerLifecycle类继承InstantiationAwareBean-PostProcessorAdapter来阐述容器级生命周期接口方法的执行时序,具体代码如下:
1 | package cn.bytecollege.bean; |
如上述所示,ContainerLifecycle类继承了InstantiationAwareBeanPostProcessorAdapter,重写了其中postProcessBeforeInstantiation、postProcessPropertyValues和postProcessAfterInitialization方法。其执行顺序如下。
- ContainerLifecycle:构造器最先执行。
- postProcessBeforeInstantiation:接口方法和实例化Bean之前调用
- postProcessPropertyValues:设置某个属性时调用。
- postProcessAfterInitialization:接口方法和实例化Bean之后调用。
还是以上例的BeanLifecycle类为例,调用BeanLifecycle类的sayHello()方法,测试代码如下:
1 | package cn.bytecollege.test; |
配置文件如下:
1 |
|
运行结果如下:
工厂级生命周期
工厂级生命周期接口的生命周期,实现代码如下:
1 | package cn.bytecollege.bean; |
配置文件代码如下:
1 |
|
继续使用上例中的测试类,运行结果如下图: