2.3.1 两种框架的依赖注入(DI)和AOP比较
本节先简单介绍CDI规范和DI规范、AOP的基本概念,然后分析这两种框架的不同应用。
1 CDI规范和DI规范
CDI(Contexts and Dependency Injection,上下文依赖注入)规范即JSR 299规范,是从Java EE 6开始引入的。其本身基于Java依赖注入(JSR 330),引入了@Inject、@Named等。而JSR 330仅用于DI并已实现。CDI规范添加了各种EE内容,如@RequestScoped、拦截器/装饰器、生产者、事件,以及与JSF、EJB等集成的基础。已经将EJB等Java EE组件重新定义为基于CDI的规范。CDI规范除了具有DI功能之外,其关键部分还在于CDI规范对Bean上下文的管理以及对Bean生命周期的管理以及这些上下文中的依赖关系。
DI(Dependency Injection,依赖注入)规范即JSR 330规范,具有在任何应用程序上进行Bean发现和Bean连接过程的功能。DI规范不仅可以使用在应用程序中,还可以应用在单元测试和模拟中。现有有很多DI框架,包括Guice、Weld、Spring、EJB 3.x和CDI本身。Weld和Spring还扩展了DI方案,并建立了自己的DI框架。
2 面向切面(AOP)概述
AOP(Aspect Oriented Programming,面向切面编程)是一种在OOP(Oriented Object Programming,面向对象编程)基础之上的一种更高级的设计思想。AOP侧重于切面组件,切面组件可以理解成封装了通用功能的组件,切面组件可以通过配置方式灵活地切入某一批目标对象方法上。AOP用于处理系统中分布于各个模块的横切注入点,比如事务管理、日志、缓存等。
AOP的实现方式有很多,主要分为构建时AOP实现和运行时AOP实现。
构建时AOP实现可以分为构建时静态AOP实现和构建时动态AOP实现。
(1)构建时静态AOP实现
构建时静态AOP实现是指在定义阶段定义好切面和指定的Bean注入点。当Java文件编译为类时,会在指定的Bean上增加切面代码。当Bean运行时,就可以直接调用切面代码。构建时静态AOP实现也可以称为编译时增强。其特征是在定义时必须指定注入点的Bean。AspectJ框架是构建时静态AOP实现的代表。
(2)构建时动态AOP实现
构建时动态AOP实现指在定义阶段定义好切面和注入点注解。当Java文件编译为类时,会使用动态生成字节码技术,对有注解的Bean增加子类并继承Bean,在Bean子类创建一个基于反射的动态代理,增加指向切面的代码。当Bean运行时,实际上就是Bean的子类在运行,就可以通过动态代理调用切面代码。其特征是在定义时要求Bean不能是final类(final类不支持继承)。cglib动态代理是构建时动态AOP实现的代表。cglib动态代理需要asm开源包。cglib动态代理不但可以对Bean实现动态代理,还可以对接口实现动态代理。
运行时AOP实现分为两类,一类是JDK动态代理AOP实现,另一类是JDK运行时拦截AOP实现。
(1)JDK动态代理AOP实现
JDK动态代理AOP实现是在定义阶段定义好切面和注入点注解。当应用程序初始化进行Bean装载时,针对Bean上的注入点注解创建动态代理,即利用反射机制生成一个实现代理接口的匿名类。当在运行时调用Bean时,在调用具体方法前调用动态代理的InvokeHandler来处理,实现切面功能。Spring AOP是JDK动态代理AOP实现的典型应用。Spring采用的是DI规范,在Bean工厂初始化装载Bean时实现Bean的动态代理注入。但Spring AOP只能对Bean进行AOP实现,接口则不行。所以,当只有接口而无具体实现的Bean时,Spring会切换到cglib动态代理实现模式。
(2)JDK运行时拦截AOP实现
JDK运行时拦截AOP实现在定义阶段定义好切面和注入点注解。当应用程序运行期间,通过拦截器(Interceptor)把上下文(Context)中带有注入点注解的实例化Bean装入切面组件来执行,然后返回到上下文(Context)中。Quarkus是JDK运行时拦截AOP实现的代理。
3 Spring Boot的控制反转(IoC)和面向切面(AOP)
Spring Boot是以Spring为核心的,Spring的核心是控制反转(IoC)和面向切面(AOP)。Spring以IoC、AOP为主要思想。在Spring框架中通过配置创建类对象,由Spring在运行阶段实例化、组装对象。AOP的思想是在执行某些代码前执行另外的代码,使程序更灵活、扩展性更好,可以随便地添加、删除某些功能。Servlet中的Filter便是一种AOP思想的实现。
4 Quarkus中的依赖注入(CDI)和AOP编程
Quarkus框架的CDI方案基于CDI规范。但Quarkus框架只实现了CDI规范的一部分功能,并不是完全符合TCK的CDI规范实现。在CDI规范中有一个Beans管理容器。首先加载的是核心的Beans,其次加载外部的Beans,但是外部的Beans需要被识别,因此需要进行转换。Quarkus Extensions程序就是把外部的Beans转换为Beans管理可以识别的Beans。
Quarkus支持的通用CDI规范如下。
1)程序模型。包括:①由Java类实现的托管Bean;②@PostConstruct和@PreDestroy生命周期回调;③Producer方法和fields、disposers;④Qualifiers;⑤Alternatives;⑥Stereotypes。
2)依赖注入和查找。包括:①字段、构造函数和初始化器/设置器注入;②类型安全分辨率;③通过编程查找javax.enterprise.inject.Instance;④客户端代理;⑤注入点元数据。
3)范围和上下文。包括:①@Dependent、@ApplicationScoped、@Singleton、@RequestScoped和@SessionScoped;②自定义范围和上下文。
4)拦截器。包括:①业务方法拦截器,如@AroundInvoke;②生命周期事件回调的拦截器,如@PostConstruct、@PreDestroy、@AroundConstruct。
5)事件和观察者方法。包括异步事件和事务观察者方法。
Quarkus不支持通用CDI规范的功能如下。
1)不支持@ConversationScoped。
2)不支持Decorators模式。
3)不支持便携式扩展(Portable Extensions)。
4)BeanManager仅实现以下方法:getBeans、createCreationalContext、getReference、getInjectable-Reference、resolve、getContext、fireEvent、getEvent和createInstance。
5)不支持专门化(Specialization)。
6)beans.xml描述符内容被忽略。
7)不支持钝化(Passivation)和钝化作用域(Passivating Scopes)。
8)超类上的拦截器方法尚未实现。
9)不支持@Interceptors注解。
当然,Quarkus还提供了大量的对CDI规范的增强功能,这里就不一一列出了。