Spring学习01:控制反转(IOC)与依赖注入(DI)
程序的耦合和解耦
耦合:程序间的依赖关系,在开发中应该做到编译时不依赖,运行时才依赖
解耦:使用反射来创建对象,避免使用new关键字,并通过读取配置文件来获取要创建对象的全限定类名
Spring基于XML的IOC配置
1.创建Maven项目并在pom.xml中导入依赖坐标
<dependencies>
<!-- 引入核心容器的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.在resources目录下创建bean.xml文件,把对象的创建交给spring来管理
<?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="accountService" class="com.chenpeng.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.chenpeng.dao.impl.AccountDaoImpl"></bean>
</beans>
每个<bean>
标签对应一个类,其中的属性:
- id:该类的唯一标识,其他类可以引用
- class:该类的全限定类名
3.在测试类中通过容器创建对象
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.创建对象
AccountService as = ac.getBean("accountService", AccountService.class);
常用的容器有三种:
ClassPathXmlApplicationContext
:从类的根路径下加载配置文件创建容器FileSystemXmlApplicationContext
:从磁盘路径上加载配置文件创建容器AnnotationConfigApplicationContext
:读取注解创建容器
bean标签
- 作用:配置spring管理的对象,默认调用类的无参构造函数创建对象
- 属性:
- id:类的唯一标识,其他类可以引用,也可作为getBean()方法的参数创建对象
- class:该类的全限定类名
- scope:指定对象的作用范围,可选值如下:
- singleton:单例对象,默认值
- prototype:多例对象
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
- init-method:指定类中的初始化方法名称,在对象创建成功后执行
- destroy-method:指定类中销毁方法名称(对多例对象无效)
bean的作用范围和生命周期
- 单例对象:scope=singleton
- 作用范围:每个应用只有一个该对象的实例,它的作用范围就是整个应用
- 生命周期:单例对象的生命周期和容器相同
- 出生:当容器创建时出生
- 活着:只要容器还在,对象就一直活着
- 死亡:容器销毁时,对象消亡
- 多例对象:scope=prototype
- 作用范围:每次访问对象时,都会重新创建对象实例
- 生命周期:多例对象的创建与销毁不受容器控制
- 出生:当我们使用对象时spring框架为我们创建
- 活着:对象只要是在使用过程中就一直活着
- 死亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收器回收
创建bean的三种方式
-
使用默认构造函数创建,在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
<bean id="accountService" class="com.chenpeng.service.impl.AccountServiceImpl"></bean>
-
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
创建普通工厂如下
public class InstanceFactory { public IAccountService getAccountService(){ return new AccountServiceImpl(); } }
<bean id="instanceFactory" class="com.chenpeng.factory.InstanceFactory"/> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>
其中
<bean>
标签的属性:- factory-bean属性:指定普通工厂的id
- factory-method属性:指定普通工厂中生产对象的方法
-
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
创建静态工厂如下
public class StaticFactory { public static IAccountService getAccountService(){ return new AccountServiceImpl(); } }
<bean id="accountService" class="com.chenpeng.factory.StaticFactory" factory-method="getAccountService"/>
其中
<bean>
标签的属性:- factory-method属性::指定静态工厂中生产对象的静态方法
Spring的依赖注入
依赖注入
通过控制反转,对象在创建的时候,由spring为我们将对象所依赖的对象的引用传递给它
注入数据的分类
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
依赖注入的方法
构造函数注入
-
使用的标签
<constructor-arg>
-
标签出现的位置:
<bean>
标签的内部 -
标签中的属性:
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始
- name:用于指定给构造函数中指定名称的参数赋值
- value:用于提供基本类型和String类型的数据
- 用于指定其他的bean类型数据(指的是在spring的IOC核心容器中出现过的bean对象)
-
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
-
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
public class AccountServiceImpl implements AccountService { //如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer age; private Date birthday; public AccountServiceImpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } }
<bean id="now" class="java.util.Date" scope="prototype"></bean> <bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="myname"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean>
set方法注入(更常用)
-
使用的标签:
<property>
-
标签出现的位置:
<bean>
标签的内部 -
标签中的属性:
- name:用于指定注入时所调用set方法名称
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据(指的是在spring的IOC核心容器中出现过的bean对象)
-
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
-
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行
public class AccountServiceImpl implements IAccountService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
<bean id="now" class="java.util.Date" scope="prototype"></bean> <bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl"> <property name="name" value="myname"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean>
复杂类型/集合类型的注入
集合字段及其对应的标签按照集合的结构分为两类,相同结构的集合标签之间可以互相替换
-
只用键的结构:
- 数组字段:
<array>
标签表示数组,<value>
标签表示数组内的成员 - List字段:
<list>
标签表示集合,<value>
标签表示集合内的成员 - Set字段:
<set>
标签表示集合,<value>
标签表示集合内的成员
- 数组字段:
-
键值对的结构:
- Map字段:
<map>
标签表示集合,<entry>
标签表示集合内的键值对,其key属性表示键,value属性表示值 - Properties字段:
<props>
标签表示集合,<prop>
标签表示键值对,其key属性表示键,标签内的内容表示值
public class AccountServiceImpl implements AccountService { private String[] myArray; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myArray) { this.myArray = myArray; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } @Override public void saveAccount() { System.out.println(Arrays.toString(myArray)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } }
<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl"> <property name="myStrs"> <array> <value>value1</value> <value>value2</value> <value>value3</value> </array> </property> <property name="myList"> <list> <value>value1</value> <value>value2</value> <value>value3</value> </list> </property> <property name="mySet"> <set> <value>value1</value> <value>value2</value> <value>value3</value> </set> </property> <property name="myMap"> <map> <entry key="key1" value="value1"></entry> <entry key="key2"> <value>value2</value> </entry> </map> </property> <property name="myProps"> <props> <prop key="key1">value1</prop> <prop key="key2">value2</prop> </props> </property> </bean>
- Map字段:
Spring基于注解的IOC配置
常用注解
用于创建对象的注解
- @Component
- 作用:用于把当前类对象存入spring容器中
- 属性:value:用于指定bean的id,不写时它的默认值是当前类名且首字母小写
- @Controller:一般用在表现层
- @Service:一般用在业务层
- @Repository:一般用在持久层
- @Controller,@Servic和@Repository的作用和属性和@Component一样,这些注解的作用相当于bean.xml中的
<bean>
标签
用于注入数据的注解
- @Autowired
- 作用:自动按照类型注入,只要容器中有唯一一个bean对象类型和要注入的变量类型匹配,就可以注入成功
- 注入过程:
- 如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错
- 如果IOC容器中有多个类型匹配时会报错,可以指定要注入的bean对象的id
- 出现位置:可以是变量上,可以是方法上
- 在使用注解注入时,set方法不是必须的
- @Qualifier
- 作用:在按照类注入的基础上再按照名称注入,在给类成员注入时不能单独使用,但是在给方法参数注入时可以
- 属性:
- value:用于指定注入的bean的id
- @Resource
- 作用:直接按照bean的id注入,可以独立使用
- 属性:
- name:用于指定注入bean的id
- @Value
- 作用:用于注入基本类型和String类型的数据
- 属性:
- value:用于指定数据的值,使用spring中的el表达式--SpEL
- 这些注解的作用相当于bean.xml中
<bean>
标签中的<property>
标签 - 注意:集合的类型注入只能通过xml实现
用于改变作用范围的注解
- @Scope
- 作用:用于指定bean的作用范围
- 属性:
- value:指定范围的取值,常用取值:Singleton和Prototype
- 该注解的作用相当于bean.xml中
<bean>
标签中的<scope>
标签
和生命周期相关的注解
- @ PostConstruct
- 作用:用于指定初始化方法
- @PreDestroy
- 作用:用于指定销毁方法
Spring的半注解配置和纯注解配置
半注解配置
半注解配置时,仍然使用ClassPathXmlApplicationContext类加载配置文件,但是需要在配置文件中告知spring创建容器时要扫描的包,然后将spring的注解加在类的定义上或者类对象上
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,需要导入context的名称空间和约束-->
<context:component-scan base-package="cn.maoritian"></context:component-scan>
</beans>
纯注解配置
纯注解配置时,使用配置类代替bean.xml,使用AnnotationConfigApplicationContext类从spring配置类中读取IOC配置
配置类中使用的注解
- @Configuration
- 作用:指定当前类是一个配置类
- 当被配置的类作为AnnotationConfigApplicationContext的参数时,可以不写
- @ComponentScan
- 作用:用于通过注解指定spring在创建容器时要扫描的包
- 属性:
- value:和basePackage的作用一样,都是用于指定创建容器时要扫描的包
- @Bean
- 作用:用于把当前方法中的返回值作为bean对象存入spring的IOC容器中
- 属性:
- name:用于指定bean的id,默认值是当前方法的名称
- 细节:使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用是一样的
- @Import
- 作用:用于导入其他的配置类
- @PropertySource
- 作用:用于指定properties的位置
- 属性:
- value:指定文件的名称和路径,关键字:classpath,表示类路径下
使用纯注解配置
-
dao层实现类
@Repository("accountDao") public class AccountDaoImpl implements AccountDao { @Autowired//自动按照类型注入 private QueryRunner runner; public List<Account> findAll() { try { return runner.query("select * from account",new BeanListHandler<Account>(Account.class)); } catch (SQLException e) { throw new RuntimeException(e); } } }
-
service层实现类
@Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public List<Account> findAll() { return accountDao.findAll(); } }
-
创建config包,在其中创建配置类
SpringConfiguration主配置类
@Configuration @ComponentScan("com.chenpeng")//创建容器时要扫描的包 @Import(JdbcConfig.class)//引入配置类,可以引入多个 @PropertySource("classpath:jdbc.properties")//从类的根路径下读取配置文件 public class SpringConfiguration { }
JDBCConfig配置类
/** * 数据库连接的配置类 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.user}") private String user; @Value("${jdbc.password}") private String password; /** * 创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean("dataSource") public DataSource createDataSource(){ ComboPooledDataSource ds = new ComboPooledDataSource(); try { ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(user); ds.setPassword(password); return ds; } catch (PropertyVetoException e) { throw new RuntimeException(e); } } }