前言
上篇 提到 《重学 Spring》 专栏主要是为了记录 Spring 底层实现原理。过去3年的时间我已经写了从 Spring IoC 到 Spring Boot 60 篇的文章,不过过去的文章多是介绍一些实现原理。
恰逢最近在部门内部做分享,借着这次机会对 Spring IoC 部分又进行了系统的整理,由于大家专业方向和水平不完全一致,因此更多的是让大家了解 Spring 这门技术,分享的内容也站在了更高的角度,希望达到鸟瞰全貌的目的。
PPT 多用于演讲,重点在于让观众短时间内理解演讲者的观点,因此表现形式就比较重要,有句话说的是 字不如表,表不如图
,因此 PPT 更多的是以大量的图片,简短的文字让观众一眼看明白。
不过博客与 PPT 有所不同,主要靠观众阅读,因此可以用更多的文字与代码表达想法。这篇内容相比前面一些文章更加大白话,更符合大众口味,旨在让大家理解 Spring IoC 的特性。
重学 Spring,从 IoC 谈起
学习 Spring,一定是先从 Spring 的 IoC 容器谈起,这是为什么呢?
事实上这和 Spring 的诞生背景有一定的关系。Java 从 1995 年诞生后由于 ”write once, run any where“ 的特性,各应用开发商均在 Java 标准版的基础上开发企业级的 API。为了简化企业级应用的开发,Sun 公司联合了 IBM、Oracle 等多家公司共同制定了企业级应用系统开发规范,即 J2EE。EJB 就是 J2EE 规范的一部分,作为业务逻辑层的中间件技术,定义了 EJB 组件如何与 EJB 容器进行交互。
不过呢,EJB 实在太难用了,不仅 API 复杂,而且应用与这套 API 耦合程度太高了,必须实现特性的接口才可以,更要命的是它的 RPC 调用会应用性能下降很多。
这个时候,有一个叫 Rod Johnson 的哥们有了新想法,他写了一本书叫 《Expert One-on-One J2EE Design and Development》,提出可以用普通的 Java 类和依赖注入解决 J2EE 的缺陷,这本书中的开源框架被称为 interface 21,这就是 Spring 的前身。
其中依赖注入就是 IoC 的实现方式之一,Spring 最初也是按照 IoC 容器的方式来进行设计,因此只要学习 Spring 就会先从 IoC 的相关概念学起。
Spring 核心特性
不过呢,Spring 可不仅仅是一个 IoC 容器,它在 IoC 容器的基础上扩展了很多能强有力的功能,了解这些功能特性才能知道 Spring 能做什么。
Spring 的核心特性有 9 个,下面分条介绍。
控制反转(IoC)
控制反转的目的是借助容器实现具有依赖关系的对象之间的解耦。
假定有如下的两个 Service。
public interface IServiceA {
String format(Object obj);
}
public class ServiceAImpl implements IServiceA {
@Override
public String format(Object obj) {
return String.valueOf(obj);
}
}
public interface IServiceB {
String format(Object obj);
}
public class ServiceBImpl implements IServiceB {
private IServiceA serviceA;
public ServiceBImpl(IServiceA serviceA) {
this.serviceA = serviceA;
}
@Override
public String format(Object obj) {
return this.serviceA.format(obj);
}
}
IServiceA 与 IServiceB 都定义了格式化对象的方法,其中 IServiceB 的实现依赖了 IServiceA,将 ServiceAImpl 和 ServiceBImpl 两个类配置到 Spring 容器中作为 bean,然后 ServiceBImpl 就可以直接使用 IServiceA 的实现,而不用关系依赖从哪来。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="serviceA" class="com.zzuhkp.demo.bean.ServiceAImpl"/>
<bean id="serviceB" class="com.zzuhkp.demo.bean.ServiceBImpl">
<constructor-arg name="serviceA" index="0" ref="serviceA"/>
</bean>
</beans>
public class Application {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:context.xml");
IServiceB serviceB = context.getBean(IServiceB.class);
System.out.println(serviceB.format("hello spring ioc"));
context.close();
}
}
如果 ServiceBImpl 依赖的 ServiceAImpl 不满足需求,直接修改 bean 配置,将其替换为其他 IServiceA 的实现即可,从而实现了依赖解耦。
面向切面编程(AOP)
面向切面编程(AOP)主要用来补充面向对象编程(OOP),而不是用来替换 OOP,OOP 模块化的单位是类 Class,而 AOP 模块化的单位则是切面 Ascpet。AOP 将跨域多个 Class 的关注点模块化到 Aspect,如日志、鉴权等等。
Aspect 可以在方法执行前后执行一些动作。由于 Java 是静态强类型语言,因此无法修改原有类型,通常的做法是动态生成原有类型的代理类型,在执行原有类型的方法时,添加额外的逻辑。
JDK 自带的动态代理基于接口,如果原有类型没有实现接口,则只能代理类型只能继承原有类型,Spring 选用的是 CGLIB 动态创建代理类。Spring 在 bean 的生命周期中预留了很多回调,在生命周期阶段创建 bean 的代理即可。
Spring AOP 的使用方式可以参见《Spring AOP 的三种使用方式》,这里不再赘述。
国际化(i18n)
国际化也是 Java Web 开发常用的一个功能,常用于 Web 站点渲染、校验使用的一些文案,不同国家的人展示不同的语言,这样对用户会更为友好。
JDK 自身已经对国际化进行了支持,不过 JDK 自身的国际化只能根据国家信息,从某个资源文件中根据某个 key 获取对应的值,这个值本身可能还包含一些占位符需要进行格式化。
Spring 的国际化将 JDK 国际化与消息格式化进行了整合,提供了一个 MessageSource 接口,使用上更为简便,直接将其作为依赖注入即可。
更多 Spring 国际化的内容,可以参见《Spring 国际化支持之 MessageSource》,不再赘述。
数据绑定(data binding)
数据绑定是指将元数据设置到对象的属性中,用户直接使用数据绑定的场景并不多,在 Spring 内部有两种地方使用数据绑定,一处是 xml 配置中的 bean 属性设置到对象中,另一处是将 http 请求数据设置到对象中。
如果你想了解数据绑定的更多内容,可以参见《聊聊 Spring 核心特性中的数据绑定 (DataBinder)》。
类型转换(type convert)
类型转换最初用于将 XML 配置的属性设置到 bean 时将文本内容转换为对象字段的具体类型。
由于 java bean 规范已经有了一个 PropertyEditor 接口可用于类型转换,因此 Spring 最初复用了这个接口。
不过后来 Spring 官方发现这个接口的职责不够清晰,除了类型转换还有一些 GUI 程序使用的方法,另外就是只局限于将字符串转换为其他类型,而不能进行任意类型的转换,因此 Spring 3.0 提供了新的接口 ConversionService 替换 PropertyEditor,这个接口允许任意类型之间的转换。
类型转换常发生在数据绑定期间,除了 XML 配置的类型转换,另一处使用场景仍然是将 HTTP 请求数据的字符串形式转换为具体的类型。
如果你想了解更多关于类型转换的内容,可以参见《Spring 核心特性之类型转换(PropertyEditor、ConversionService)》。
校验(bean validation)
校验的目的是避免客户端传入不合法的数据,为了避免程序中充斥着大量的校验代码,Java 社区提出了 JSR-303 规范用于数据校验,不过这套规范是指定义应该如何校验,并涉及到实现。
通常我们使用的 bean validation 的实现是 Hibernate,Spring 官方将 JSR-303 规范与 Spring 生态整合到一起,大大简化了校验的使用方式。
更多校验相关内容,也可以参考 《Spring 参数校验最佳实践及原理解析》。
资源管理(resource)
在 Java 中,资源的表现形式有多种,如 URL、InputStream、File,这些资源又可以分布在本地、类路径、网络,加载方式也多种多样,Spring 将其整合到一起,使用 Resource 统一表示抽象的资源,简化了对资源的操作方式。
更多资源相关内容,可以参考 《Spring 资源管理 (Resource)》。
事件(event)
Spring 事件基于 Java 标准的观察者模式,Spring 在应用上下文的生命周期中会发布一些事件通知用户,此外 Spring 还允许用户发送和监听自定义的事件,而且发布的事件还支持泛型。如果想要解耦代码,也可以考虑使用 Spring 的事件。
更多 Spring 事件处理,可以参考《Spring 事件处理机制详解,带你吃透 Spring 事件》
表达式(spring expression)
Spring 的表达式类似于在 JSP 中使用的表达式,直接使用的场景也不太多,在框架中使用可能会多一些,例如缓存框架可以通过方法上注解属性中的 Spring 表达式读取缓存的 key,进一步做 CURD,这块内容我暂为总结,后续将会补上。
总结
本文主要对 Spring 核心特性进行了简单的介绍,由于其涉及的内容较多,感兴趣的同学可以参考我前面的文章。