Spring 学习笔记 IoC 基础
Spring IoC
Ioc 是什么
IoC —— Inversion of Control(控制反转)什么是控制?什么是反转?
控制反转了什么?
在很早之前写项目不用 Spring 的时候,都是在 Class 中成员属性中 new 对象的方式。是我们主动去创建对象也就是我们控制了对象的创建。
public class ServiceImpl implements Service {
private Dao dao = new DaoImpl();
}
而 Spring IoC 会主动去创建对象不需要我们主动去创建,也就是 IoC 控制了对象的创建。
public class ServiceImpl implements Service {
@Autowired
private Dao dao;
}
只需要加上 @Autowired 注解就能自动帮我们注入需要依赖的对象,容器帮助我们查找和注入对应的对象也就是反转。
依赖注入
IoC 也称依赖注入(dependency injection, DI) DI 是一个实例化过程,在这个过程中对象需要依赖的类也称组件之间的依赖关系,由容器在创建 bean 时注入这些依赖项。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。IoC 和 DI 它们是一个概念不同维度的描述。
IoC 基础
org.springframework.beans 和 org.springframework.context 包是 Spring 框架的基础,BeanFactory 接口提供了能够管理任何类型对象的配置机制。ApplicationContext 是 BeanFactory 的扩展实现。
- 更容易与 Spring 的 AOP 特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用程序层特定的上下文,如 web 应用程序中使用的 WebApplicationContext
ApplicationContext 表示 IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置文件元数据获取要实例化、配置和组装哪些对象。配置可以通过:xml、java 注解或 java 代码表示。
Spring 提供了 ApplicationContext 接口的实现,通常创建使用 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 或 AnnotationConfigApplicationContext 的实例。系统与容器相结合这样在容器初始化完毕后,就可以使用 IoC 容器了。
IoC 配置
在 Spring 中配置 bean 有常用的两种方式:
- 基于 xml 的形式:基于 xml 配置将这些 bean 配置为 顶级元素为
- 基于 java 注解的形式:基于注解将使用 @Configuration 中配置 @Bean
示例:以下显示了声明了一个 Person 的 bean
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
<?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">
<!--
id 属性是标识单个 bean 定义且是唯一值不可重复
class 属性是定义 bean 的类型并使用完整的路径表示
-->
<bean id="person" class="com.feil.springtest.ioc.test01.Person">
<property name="name" value="123"/>
</bean>
</beans>
实例化容器
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:conf/spring/application-ioc.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
System.out.println(person.getName());
}
运行结果
com.feil.springtest.ioc.test01.Person@ff5b51f
123
Process finished with exit code 0
Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的比如 xml 配置形式。
在容器内部这些配置元数据被封装在 BeanDefinition 对象内,一个 BeanDefinition 对象就相当于一个 标签的内容。
Bean 命名
每个 bean 都有一个或多个标识。bean 通常只有一个标识符 id。基于 xml 的配置元数据中,可以使用 id 属性、name 属性或两者都使用来指定 bean 标识符。id 属性允许您只指定一个 id 通常都是类名首字母小写声明。如果还希望 bean 使用其他别名可以在 name 属性中指定他们,以逗号、分号或空格分隔。
实例化 bean
使用基于 xml 的配置元数据,则指定要在元素的 class 属性中实例化的对象的类型(或类)。这个类属性(在内部是 BeanDefinition 实例上的一个类属性)。通过这个 class 属性通过反射调用其构造函数直接创建 bean。Spring 一般都是通过这样的形式来创建 bean。
还有其他方式实例化一个 bean:
使用静态工厂方法来实例化一个 bean
public class ClientService {
private ClientService() {}
public static ClientService createInstance() {
return new ClientService();
}
}
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
使用实例方法实例化一个 bean
public class DefaultServiceLocator {
public ClientService createClientServiceInstance() {
return new ClientServiceImpl();
}
}
<bean id="serviceLocator" class="examples.DefaultServiceLocator"></bean>
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
一个工厂类可以声明很多 createInstance 方法,只需要在 xml 配置就可以获取多个不同的 bean 实例了。在 Spring 的文档中 “factory bean” 是在 Spring 容器中配置的 bean,它通过静态或实例工厂方法来创建对象。相反 FactoryBean 是特定于 Spring 中 FactoryBean 接口的实现。
依赖
依赖可以理解为几个对象一起通过写作来实现目标功能。容器在创建 bean 时注入这些依赖项,使用依赖注入代码更加简洁,当 bean 具有其他依赖 bean 关系时,不需要主动去查找对应的 bean 实例,也不知道对应 bean 具体的实现到底是什么,特别是依赖关系是接口或者抽象类时实现了解耦的功能。
构造函数依赖注入
基于构造器的依赖注入是容器来选择调用一个构造函数来完成的,构造函数有若干个参数,每个参数代表一个依赖项。下面例子显示了一个只能通过构造函数注入的类。
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<beans>
<bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
<constructor-arg ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
</beans>
基于 setter 的依赖注入
基于 setter 的依赖注入是通过容器调用 bean 里的 setter 方法来实现的。下面例子显示了一个只能通过使用纯 setter 注入注入依赖关系的类。
public class SimpleMovieLister {
private MovieFinder movieFinder;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<beans>
<bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
</beans>
注入集合类型
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- java.util.Properties -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- java.util.List -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- java.util.Map -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- java.util.Set -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
懒加载
默认情况下 Spring IoC 容器会在不管是否使用到 bean 都会提前创建好。这样的好处是配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后才发现。当不需要提前加载可以通过 bean 标签中的 lazy-init 属性来延迟初始化,延迟初始化的 bean 告诉 IoC 容器在第一次请求时创建bean 实例,而不是在启动时。
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
还可以全局的设置所有 bean 都延迟初始化
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配
Spring IoC 容器可以自动装配 bean 之间的关系。自动装配可以显著减少指定属性或构造函数参数的需要。
使用基于 xml 的配置元数据,可以使用元素的 autowire 属性为 bean 定义指定 autowire 模式。自动装配功能有四种模式。
模式 | 说明 |
---|---|
no | 默认没有自动装配,Bean 引用必须由 ref 元素定义 |
byName | 通过属性名,Spring 寻找和属性名相同的 bean |
byType | 通过属性类型,Spring 寻找和依赖项匹配的类型 bean,如果存在多个则会异常 |
constructor | 类似于 byType,但适用于构造函数参数。如果容器中没有构造函数参数类型的bean,则会异常 |
autowire no
<bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
autowire byName
当一个 bean 节点带有 byName 属性时:
- 将查找该类中的所有 set 方法并将 set 去掉首字母小写
- 去 Spring IoC 容器中查询时候有此名称 id 的 bean
- 如果有则注入,如果没有就抛出异常
<bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister" autowire="byName">
<bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
autowire byType
使用 byType 要保证:此依赖的 bean 类型在 Spring IoC 容器中是唯一的。
<bean id="simpleMovieLister" class="com.xxx.xxx.SimpleMovieLister" autowire="byType">
<bean id="movieFinder" class="com.xxx.xxx.MovieFinder"/>
全局 autowire 配置
<beans default-autowire="byName">
</beans>
基于注解自动装配
首先需要 context:annotation-config/ 开启注解的属性。
- @Autowired 按类型自动装配
- @Qualifier + @Autowired 按 byName 自动装配 @Qualifier 不能单独使用
- @Resource 如果指定了 name 则按属性进行 byName 查找;如果指定了 type 则按 byType 查找;其次进行默认 byName 查找;如果以上都不行则按 byType 方式自动装配,都不成功则报异常
Bean 范围
Spring IoC 容器中默认 bean 的实例是单例的全局唯一,但是 Spring IoC 容器中提供了 6 种 bean 的作用域。
范围 | 描述 |
---|---|
singleton | (默认值)为每个 Spring IoC 容器将单个 bean 定义范围限定为单个对象实例 |
prototype | 使得每次从容器中获取都是全新的一个 bean 实例 |
request | 每个 HTTP 请求都有自己的 bean 实例 |
session | 将 bean 定义的范围限定为 HTTP 会话的生命周期内 |
application | 将 bean 定义的范围限定为 ServletContext 的生命周期 |
websocket | 将 bean 定义的范围限定为 WebSocket 的生命周期 |
singleton 的范围
只管理一个单例 bean 所有对 id 或 id 匹配该 bean 的获取请求都会导致 Spring IoC 容器返回该 bean 的特定实例。
定义一个bean定义,并且它的作用域是一个单例对象时,Spring IoC容器将创建该bean定义定义的对象的一个实例。此单一实例存储在此类单例 bean 的缓存中,对于该命名 bean 的所有后续请求和引用都返回缓存的对象。
prototype 的范围
bean 声明非单例原型范围会在每次发出对特定 bean 的请求时创建一个新的 bean 实例。也就是说,bean 被注入到另一个 bean 中,或者您通过容器上的 getBean() 方法调用请求它会返回一个新的 bean 实例。
Web 应用中的作用域
在 Web 范围的 ApplicationContext 时,才可以使用 request、session、Application 和 websocket 范围。
request 的范围
为每个 HTTP 请求使用 LoginAction bean 定义来创建 LoginAction bean 的新实例。
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
在使用包扫描注解开发时可以使用 @RequestScope 注释将组件分配给请求范围
@RequestScope
@Component
public class LoginAction {
// ...
}
session 的范围
为单个 HTTP session 的生命周期使用 UserPreferences bean 定义来创建 UserPreferences bean的新实例
<bean id="loginAction" class="com.something.LoginAction" scope="session"/>
在使用包扫描注解开发时可以使用 @SessionScope 注释将组件分配给请求范围
@SessionScope
@Component
public class LoginAction {
// ...
}
application 的范围
为整个 web 应用程序使用一次 LoginAction bean 定义来创建 LoginAction bean 的新实例
<bean id="loginAction" class="com.something.LoginAction" scope="application"/>
在使用包扫描注解开发时可以使用 @ApplicationScope 注释将组件分配给请求范围
@ApplicationScope
@Component
public class LoginAction {
// ...
}
自定义范围 bean
bean 的范围是可扩展的,可以自定义范围,要将自定义范围集成到 Spring IoC 容器中需要实现 org.springframework.beans.factory.config.Scope 接口,接口中有 5 个方法:
- Object get(String name, ObjectFactory objectFactory):从自定义范围内获取对象
- Object remove(String name): 从自定义范围内删除对象
- void registerDestructionCallback(String name, Runnable destructionCallback):将 Scope 实现注册销毁后的回调方法,如果想要注册销毁相应的对象则由 Spring IoC 容器注册相应的销毁回调
- Object resolveContextualObject(String key):用于解析相应的上下文数据,比如 request 作用域将返回 request 中的属性。
- String getConversationId():作用域的会话标识,比如 session 作用域将是 sessionId。
让我们来看一看 thread 线程范围的作用域
代码实现
public class SimpleThreadScope implements Scope {
// 实现
}
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
xml 实现
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
</bean>
<bean id="thing1" class="x.y.Thing1">
</bean>
</beans>
自定义 bean 的性质
Spring 框架提供了许多接口,您可以使用它们定制 bean 的性质,有如下几个类型的接口
- 生命周期回调
- ApplicationContextAware 和 BeanNameAware
- 其他 Aware 接口
生命周期回调
要想与 Spring IoC 容器 bean 生命周期管理进行交互可以实现 InitializingBean 和 DisposableBean 接口容器会主动调用 afterPropertiesSet()、destroy() 让 bean 在初始化和销毁 bean 时执行某些操作。
JSR-250 @PostConstruct 和 @PreDestroy 注解通常被认为是 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 没有耦合到特定于 spring 的接口。
如果不希望使用 JSR-250 注解,但仍然希望降低耦合,请考虑 init-method 和 destroy-method bean 定义 xml 元数据。
使用不同的初始化方法为同一个 bean 配置多个生命周期机制,调用顺序如下
- 用注解 @PostConstruct
- InitializingBean 接口的 afterPropertiesSet() 方法
- 自定义配置的init()方法
销毁方法调用顺序
- 用注解 @PreDestroy
- DisposableBean 接口的 destroy() 方法
- 自定义配置的 destroy() 方法
ApplicationContextAware 和 BeanNameAware
当 Spring IoC 容器创建 bean 发现 bean 实现了 org.springframework.context.ApplicationContextAware 时将为该 bean 提供 ApplicationContext 的引用
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
当 Spring IoC 容器创建 bean 发现 bean 实现了org.springframework.beans.factory.BeanNameAware 时将为该 bean 提供 ApplicationContext 的引用将为该 bean 提供当前的 beanName
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
其他 Aware 接口
- ApplicationContextAware
- ApplicationEventPublisherAware
- BeanClassLoaderAware
- BeanFactoryAware
- BeanNameAware
- BootstrapContextAware
- LoadTimeWeaverAware
- MessageSourceAware
- NotificationPublisherAware
- ResourceLoaderAware
- ServletConfigAware
- ServletContextAware
Bean 扩展
BeanPostProcessor
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanPostProcessor 接口定义了回调方法,您可以实现这些方法来提供您自己的实例化逻辑、依赖项解析逻辑等等。您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的执行顺序。
- BeanPostProcessor 的实例可以操作 bean 实例。也就是说,Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 实例执行它们的工作。
- BeanPostProcessor 实例的作用域是每个 Spring IoC 容器。
后置处理器可以对 bean 实例执行任何操作通常检查回调接口,或者使用代理包装 bean。
如何定义一个 BeanPostProcessor
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
测试
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
BeanFactoryPostProcessor
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
接口的作用类似于 BeanPostProcessor,但有一个主要区别:BeanFactoryPostProcessor 操作bean 配置元数据。也就是说,Spring IoC 容器允许 BeanFactoryPostProcessor 读取配置元数据,并可能在容器实例化除 BeanFactoryPostProcessor 实例之外的任何 bean 之前更改它。此外,BeanFactoryPostProcessor 实例的范围是 Application 的范围
示例:类名替换 PropertyPlaceholderConfigurer
可以使用 PropertyPlaceholderConfigurer 使 bean 属性值获取从 bean 定义外部化到一个单独的文件中,比如 properties。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer 应用于替换数据源某些属性的元数据。要替换的值指定为表单 ${property-name} 的占位符,它遵循 Ant、log4j和 JSP EL 样式。
我们来看一看 PropertyPlaceholderConfigurer 实际实现了 BeanFactoryPostProcessor 接口,并重写了 postProcessBeanFactory 方法来实现。
/**
* {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
* {@linkplain #processProperties process} properties against the given bean factory.
* @throws BeanInitializationException if any properties cannot be loaded
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
还可以使用 PropertyPlaceholderConfigurer 替换类名称,这在您必须在运行时选择特定实现类时有时很有用
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/something/strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.something.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
使用 FactoryBean
FactoryBean 从名字来看以 bean 结尾那应该就是一个 bean 吧,没错它确实是一个 bean,不同于普通 Bean 的是:它是实现了 FactoryBean 接口的 Bean,根据该 Bean 的 ID 从BeanFactory 中获取的实际上是 FactoryBean 的 getObject() 返回的对象,而不是FactoryBean 本身,如果要获取 FactoryBean 对象,请在 id 前面加一个 & 符号来获取。
FactoryBean 接口提供了三种方法:
- Object getObject():返回工厂创建的对象的实例。实例可以共享,这取决于该工厂返回的是单例还是原型
- isSingleton():factoryBean 创建的实例是否是单实例
- getObjectType():返回 getObject() 方法返回的对象类型
以上就是重新学习 Spring IoC 的笔记,到最后发现写了好长一篇,帮助了自己巩固记忆,如果写的不足之处望指出谢谢各位观看!