4.1 BeanFactory和ApplicationContext的关系
- IOC实现beans.xml和BeanFactory
- ApplicationContext接口(容器的高级接口)是BeanFactory接口(基础容器)的一个子接口,所以ApplicatonContext在BeanFactory的接口上定义了更多的功能,比如资源加载、国际化支持等等。这个设计正是Spring容器设计的优雅之处,顶层接口BeanFactory只定义了少数的基础功能和基础规范,子接口作为扩展,实现类只需要根据需求实现适合的接口,如果把所有功能都定义在顶层接口,那么实现类不需要的功能也要一一实现,该类就变得冗余复杂了。
4.2 启动IOC容器的方式
- JAVA环境启动IOC容器的方式
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
- Web环境下启动IoC容器方式
从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置⽂件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.atm.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
4.3 纯XML模式
4.3.1 编写applicationContext.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--id标识对象,class是类的全限定类名-->
<bean id="accountDao"
class="com.atm.dao.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService"
class="com.atm.service.TransferServiceImpl">
<!--set+ name 之后锁定到传值的set⽅法了,通过反射技术可以调⽤该⽅法传⼊对应的值-->
<property name="AccountDao" ref="accountDao"/>
</bean>
<!--配置新增的三个Bean-->
<bean id="connectionUtils" class="com.atm.utils.ConnectionUtils"/>
<!--事务管理器-->
<bean id="transactionManager" class="com.atm.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
</beans>
4.3.2 使用IOC容器
在web开发中,使用web环境下启动ioc容器方式(4.2),编写好配置文件,启动tomcat。
- 如何从容器中获取bean ```java private TransferService transferService = null ;
@Override public void init() throws ServletException { // 1.通过web工具类获取到容器 WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); // 2.从容器中获取bean transferService = (TransferService) webApplicationContext.getBean(“transferService”); }
<a name="c3TZk"></a>
## 4.3.2 IOC实例化bean的三种方式
1. **方式一**:使用无参构造器(推荐),通过全限定类名反射就是调用无参构造器实例化。
2. 另外两种方法是为了将我们自己new的对象加入到容器里面,比如某个方法的返回值
- **方式二**:使用静态方法
```java
public class GetBeanFactory {
public static ConnectionUtils getStaticConnectionUtils() {
return new ConnectionUtils();
}
}
<!--需要定位到GetBeanFactory类里的静态方法getStaticConnectionUtils()-->
<bean id="connectionUtils" class="com.atm.factory.GetBeanFactory" factory-method="getStaticConnectionUtils"/>
- 方式三:实例化方法
```xmlpublic class GetBeanFactory {
public ConnectionUtils getConnectionUtils() {
return new ConnectionUtils();
}
}
<a name="EhrfE"></a>
## 4.3.3 Bean的作用范围和生命周期
1. 作用范围的改变
- 在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。
| scope | descrition |
| --- | --- |
| singleton | (默认值)(单例模式)为每个Spring IoC容器将一个bean定义的范围限定为一个对象实例。 |
| prototype | (多例模式)将单个bean定义的范围限定为任意数量的对象实例。 |
| request | 将单个bean定义的范围限定为单个HTTP请求的生命周期;也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的基础上创建的。仅在可感知web的Spring ApplicationContext上下文中有效。 |
| session | 将单个bean定义的范围限定为HTTP会话的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
| application | 将单个bean定义的范围限定为ServletContext的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
| websocket | 将单个bean定义的范围限定为WebSocket的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效。 |
- 配置方式
```xml
<!--使用scope属性配置bean的作用范围-->
<bean id="transferService"
class="com.atm.service.impl.TransferServiceImpl" scope="singleton">
</bean>
- 生命周期
- 单例模式:singleton:
- 对象出⽣:当创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象⼀直活着。
- 对象死亡:当销毁容器时,对象就被销毁了。
- ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
- 多例模式:prototype
- id属性:⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
- class属性:⽤于指定创建Bean对象的全限定类名。
- name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔(一般不使用)。
- factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。(4.3.2示例)
- factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。(4.3.2示例)
- scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
- init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。
4.3.5 依赖注入的两种方式
set方法注入:就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
public class JdbcAccountDaoImpl {
// 需要注入的属性,类、普通值、复杂属性等等
private ConnectionUtils connectionUtils;
private String age;
private String[] myArray;
private Map<String, String> myMap;
private Set<String> mySet;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setAge(String age) {
this.age = age;
}
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
}
<!--通过property标签实现set方法注入,set+name之后锁定到传值的set⽅法了,通过反射技术可以调⽤该⽅法传⼊对应的值-->
<bean id="accountDao"
class="com.atm.dao.JdbcAccountDaoImpl">
<!--set注入使用property标签,如果注入的是另外一个bean使用ref属性,如果注入的是普通纸使用value属性-->
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="age" value="18"/>
<!--复杂属性的注入-->
<property name="myArray">
<array>
<value>value1</value>
<value>value2</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>value1</value>
<value>value2</value>
</set>
</property>
</bean>
构造器注入:
public class JdbcAccountDaoImpl {
// 需要注入的属性,类、普通值、复杂属性等等
private ConnectionUtils connectionUtils;
private String age;
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils,String age) {
this.connectionUtils = connectionUtils;
this.age = age;
}
}
<bean id="accountDao" class="com.atm.dao.JdbcAccountDaoImpl">
<!-- 构造器注入,name表示参数名称,index表示参数索引位置 -->
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg index="1" value="18"/>
</bean>
4.4 XML与注解结合模式
4.4.1 注意事项
实际企业开发中,纯xml模式使⽤已经很少了
- 引⼊注解功能,不需要引⼊额外的jar
- xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
第三⽅jar中的bean定义在xml,⽐如driud数据库连接池;⾃⼰开发的bean定义使⽤注解
4.4.2 xml标签与注解对应关系
IOC相关 | xml形式 | 对应注解 | | —- | —- | | 标签 | @Component(“accountDao”),注解加在类上
bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 | | 标签的scope属性 | @Scope(“prototype”),默认单例,注解加在类上 | | 标签的init-method属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 | | 标签的destory-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |依赖注入相关
- @Autowired(推荐使⽤)
- @Autowired采取的策略为按照类型注⼊。
- @Qualifier
- 使用@Autowierd时,当一个类型有多个Bean时,那么就会造成无法具体注入哪个bean的情况,这时就需要配合@Qualifier来使用,@Qualifier(name=”jdbcAccountDaoImpl”) 可以指定具体注入哪个对象
@Resource。
- @Resource 默认按照 ByName ⾃动注⼊。@Resource(name=”manDao”,type=”ManDao”) 。
- 如果同时指定name和type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
- 如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配。
- @Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
4.4.3 开启注解扫描
```xml <?xml version=”1.0” encoding=”UTF-8”?> <beans xmlns=”http://www.springframework.org/schema/beans“ xmlns:context=”http://www.springframework.org/schema/context“ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=”
“>http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
<a name="nTb0p"></a>
# 4.5 纯注解模式
1. 在xml与注解结合中,第三⽅jar中的bean定义在xml,⽐如driud数据库连接池;⾃⼰开发的bean定义使⽤注解;所以全注解就是把定义在xml中的配置迁移到配置类中。
2. 代码
```java
@Configuration
@ComponentScan("com.atm") // 替代 context:component-scan
@PropertySource({"classpath:jdbc.properties"}) // ⼊外部属性配置⽂件
//@Import() 可以把其它启动类关联进来
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
- 启动容器参考4.2
4.6 IOC的高级特性
4.6.1 lazy-init延迟加载
- Bean的延迟加载:ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。
如果不需要singleton Bean在容器初始化时就被立即实例化,可以把bean设置为延迟实例化。
<!--lazy-init的值默认为false 如果是注解模式则用@Lazy-->
<bean id="connectionUtils" class="com.atm.utils.ConnectionUtils" lazy-init="true"/>
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
- 如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。
也可以在容器层次中通过在元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:
<!--全局延迟加载-->
<beans default-lazy-init="true">
</beans>
如果⼀个 bean 的 scope 属性为 scope=”pototype” 时,即使设置了 lazy-init=”false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
- 应用场景:
- 开启延迟加载⼀定程度提⾼容器启动和运转性能
- 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源
4.6.2 FactoryBean和BeanFactory
- 总结:BeanFactory是IOC容器的顶层接口,定义了一些容器的基础行为,是负责创建和管理Bean的一个工厂。而FactoryBean本身就是一个Bean(工厂Bean),我们可以借助它来自定义其它复杂Bean的创建过程,定义完再放到容器中管理。
- Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。
- 代码示例
Company类
@Data
public class Company {
private String name;
private String address;
private int scale;
}
CompanyFactoryBean类
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器
// 的单例对象缓存池中Map
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
// 返回FactoryBean创建的Bean类型
@Override
public Class<?> getObjectType() {
return Company.class;
}
// 返回作⽤域是否单例
@Override
public boolean isSingleton() {
return true;
}
}
xml配置
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="atm,广州,500/>
</bean>
通过companyBean这个id获取的Bean对象是Company类,如果需要获取CompanyFactoryBean类则id改为&companyBean。
4.6.3 后置处理器
- Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
- BeanPostProcessor 的使用
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
/**
* 定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理
* 如果要对具体的某个bean处理,可以通过⽅法参数判断,
* 注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
// bean:第⼀个参数是每个bean的实例
// beanName:第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判
// 断我们将要处理的具体的bean。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
- BeanFactoryPostProcessor
- BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer
- 此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法
- 其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改。
- BeanDefinition的⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
- BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
- 注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象
4.6.4 Spring Bean的生命周期