Spring IoC基础应用
# Spring IoC基础应用
本篇讲解Spring IoC的三种应用方式:纯XML方式、XML+注解方式、纯注解方式。
# 启动 IoC 容器的⽅式
需要引入的依赖:
<!--引入Spring IoC容器功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--引入Spring Web功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
# 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.example.SpringConfig</param-value> </context-param> <!--使用监听器启动Spring的IoC容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
# BeanFactory与ApplicationContext
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能和规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext具备BeanFactory提供的全部功能。通常,我们称BeanFactory为Spring IoC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(XML,Java配置类)等等。
# 纯XML模式
XML⽂件头:
<?xml version="1.0" encoding="UTF-8"?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<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">
# Bean实例化的三种⽅式
⽅式⼀:使⽤⽆参构造函数。在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.example.transfer.service.impl.TransferServiceImpl" />
⽅式⼆:使⽤静态⽅法创建。在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的。
例如,我们在做JDBC操作时,会⽤到 java.sql.Connection
接⼝的实现类,如果是MySQL数据库,那么⽤的就是 JDBC4Connection
,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection()
,因为我们要注册驱动,还要提供URL和凭证信息,通过 DriverManager.getConnection
⽅法来获取连接。在我们实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,通常会在设计时使⽤⼯⼚模式解耦,那么当接⼊Spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置。
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.example.transfer.factory.BeanFactory" factory-method="getTransferService" </>
⽅式三:使⽤实例化⽅法创建。此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。通常在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可采⽤下⾯的配置⽅式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory" class="com.example.transfer.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>
方式二、三是为了将我们自己new的对象加入到SpringIoC容器中。
# Bean的作用域及⽣命周期
在Spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤域(scope)。作⽤域官⽅提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:
<!--配置service对象-->
<bean id="transferService" class="com.example.transfer.service.impl.TransferServiceImpl" scope="singleton"></bean>
不同作用域的生命周期:
scope | 生命周期 | |
---|---|---|
singleton,单例模式 | 单例模式的bean对象⽣命周期与容器相同 | 对象出⽣:当创建容器时,对象就被创建。对象活着:只要容器在,对象⼀直活着。 对象死亡:销毁容器时,对象就被销毁。 |
prototype,多例模式 | 多例模式的Bean对象,Spring框架只负责创建,不负责销毁。 | 对象出⽣:当使⽤对象时,创建新的对象实例。对象活着:只要对象在使⽤中,就⼀直活着。 对象死亡:当对象⻓时间不⽤时,被Java的垃圾回收器回收。 |
# Bean标签属性
在基于XML的IoC配置中,bean
标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让Xpring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:
属性 | 描述 |
---|---|
id | ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。 |
class | ⽤于指定创建Bean对象的全限定类名。 |
name | ⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。 |
factory-bean | ⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。 |
factory-method | ⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效;``如配合class属性使⽤,则⽅法必须是static的。 |
scope | ⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。 |
init-method | ⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。 |
destory-method | ⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。 |
# DI依赖注⼊的XML配置
依赖注⼊分类:
- 按照注⼊的⽅式分类:
- 构造函数注⼊:利⽤带参构造函数实现对类成员的数据赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring 框架会报错(配合lombok使用更简洁)。
- set⽅法注⼊:通过类成员的set⽅法实现数据的注⼊(使⽤最多)。
- 按照注⼊的数据类型分类:
- 基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
- 其他Bean类型:注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
- 复杂类型(集合类型):注⼊的数据类型是Aarry,List,Set,Map,Properties其中⼀种类型。
在使⽤构造函数注⼊时,涉及的标签是constructor-arg,它有如下属性:
属性 | 描述 |
---|---|
name | ⽤于给构造函数中指定名称的参数赋值。 |
index | ⽤于给构造函数中指定索引位置的参数赋值。 |
value | ⽤于指定基本类型或者String类型的数据。 |
ref | ⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。 |
在使⽤set⽅法注⼊时,涉及的标签是property,它有如下属性:
属性 | 描述 |
---|---|
name | 指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟) |
value | 指定注⼊的数据。它⽀持基本类型和String类型。 |
ref | 指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。 |
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private int sex;
private float money;
// ...setter
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
}
<bean id="accountDao" class="com.example.transfer.dao.impl.JdbcAccountDaoImpl" >
<!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性-->
<!--<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="18"/>
<property name="money" value="100.0"/>-->
<!--<constructor-arg index="0" ref="connectionUtils"/>
<constructor-arg index="1" value="zhangsan"/>
<constructor-arg index="2" value="18"/>
<constructor-arg index="3" value="100.0"/>-->
<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="18"/>
<constructor-arg name="money" value="100.0"/>
</bean>
关于复杂数据类型注⼊,⾸先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对)。接下来就是注⼊的⽅式的选择,只能在构造函数和set⽅法中选择,我们的示例选⽤set⽅法注⼊:
private String[] myArray;
private Map<String,String> myMap;
private Set<String> mySet;
private Properties myProperties;
// ...setter
<bean id="beanId">
<!--set注入注入复杂数据类型-->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
</bean>
在List结构的集合数据注⼊时, array、list , set 这三个标签通⽤,另外注值的 value 标签内部可以直接写值,也可以使⽤ bean 标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配好的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。同时 entry 标签中也可以使⽤ key-ref、value-ref 标签,但是不能使⽤ bean 标签。⽽ property 标签中不能使⽤ ref 或者 bean 标签引⽤对象。
# XML与注解组合模式
实际企业开发中,纯XML模式使⽤已经很少。因为引⼊注解功能,不需要引⼊额外的jar,开发便捷。
XML+注解组合模式,由于XML⽂件依然存在,所以,Spring IoC容器的启动仍然从加载XML开始。那么什么地方用xml,什么地方用注解?建议:⾃⼰开发的bean定义使⽤注解,第三⽅jar中的bean定义在xml,⽐如Druid数据库连接池。
使用注解模式需要使用开启注解扫描,下面是个例子:
<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">
<!--开启注解扫描,base-package指定扫描的包路径-->
<context:component-scan base-package="com.example.transfer"/>
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
# XML中标签与注解的对应关系
XML配置 | 对应注解定义 |
---|---|
bean标签 | @Component("beanId") ,注解加在类上,bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;``另外,针对分层代码开发提供了@Componenet的三种别名 @Controller、@Service、@Repository 分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已。 |
scope属性 | @Scope("prototype") ,默认单例,注解加在类上 |
init-method属性 | @PostConstruct ,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
destory-method属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
# DI依赖注⼊的注解实现⽅式
# @Autowired
@Autowired
为Spring提供的注解,需要导⼊包 org.springframework.beans.factory.annotation.Autowired
。采取的策略为按照类型注⼊。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配Spring容器会中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着 @Qualifier
使⽤。@Qualifier
告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
# @Resource
@Resource
注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource
。默认按照 ByName ⾃动注⼊。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao 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>
# 纯注解模式
纯注解模式是目前的主流。将XML中遗留的内容全部以注解的形式迁移出去,最终删除XML。此外,注解的模式是从Java配置类启动的。启动方式参见上文。
常用注解:
注解 | 描述/对应替代XML配置 |
---|---|
@Configuration | 表明当前类是⼀个配置类 |
@ComponentScan | 替代 context:component-scan |
@PropertySource | 引⼊外部属性配置⽂件 |
@Import | 引⼊其他配置类 |
@Value | 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息 |
@Bean | 将⽅法返回对象加⼊ Spring IoC容器 |
下面是个配置类示例:
@Configuration
@ComponentScan({"com.example.transfer"})
@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("dataSource")
public DataSource createDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}