核心容器
1. 核心概念
IoC
-
IoC (Inversion of Control)控制反转
-
从主动new对象转换为由外部提供对象
-
对象的创建控制权由程序转移到外部
-
-
Spring实现了此思想
-
Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的外部
-
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
-
DI
-
DI(Dependency Injection)依赖注入
-
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
目标
-
充分解耦
-
使用IoC容器管理bean (IoC)
-
在IoC容器内将有依赖关系的bean进行关系绑定(DI)
-
最终效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
2. 入门案例
IoC
-
pom.xml导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.19</version> </dependency>
-
创建applicationContext.xml
<bean id="bookDao" class="com.tommychan.dao.BookDao"/> ...
-
获取Ioc容器,拿到bean
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); } }
DI
-
applicationContext.xml中配置service与dao的关系
<bean id="bookDao" class="com.tommychan.dao.BookDao"/> <bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl"> <!-- name 表示配置哪一个具体的属性 ref 表示参照哪一个容器里已经配置了的 bean --> <property name="bookDao" ref="bookDao"/> </bean>
-
在com.tommychan.service.impl.BookServiceImpl中提供dao的setter方法
3. Bean
基础配置
Bean的别名配置
-
一个Bean可以配置多个名称
使用
name
指定,用逗号、分号或空格分隔<bean id="bookDao" name="dao,dao1,dao2" class="com.tommychan.dao.BookDao"/>
Bean的作用范围
-
使用
scope
-
singleton
单例创建(默认) -
prototype
非单例
<bean id="bookDao" name="dao,dao1,dao2" class="com.tommychan.dao.BookDao" scope="prototype"/>
-
-
适合交给容器进行管理的bean
-
表现层对象 servlet
-
业务层对象 service
-
数据层对象 dao
-
工具对象 utils
-
-
不适合交给容器进行管理的bean
- 封装实体的域对象
Bean的实例化
-
使用无参构造方法实例化,若没有无参构造方法(如被覆盖)则会抛出异常
BeanCreationException
-
使用静态工厂
<bean id="bookDao" class="com.tommychan.factory.BookDaoFactory" factory-method="getBookDao"/>
-
实例工厂
- 方式一 (比较冗余,Spring针对此进行改良,见方法二)
<bean id="bookFactory" class="com.tommychan.factory.BookDaoFactory"/> <!-- 先配置Factory bean 配合使用 实际无意义--> <bean id="bookDao" factory-bean="bookFactory" factory-method="getBookDao"/>
- 方法二(Spring改良后)
- 直接创建BookDaoFactoryBean对象
public class BookDaoFactoryBean implements FactoryBean<BookDao> { /** * 代替原始实例工厂创建对象的方法 * @return 对象 * @throws Exception 异常 */ @Override public BookDao getObject() throws Exception { return new BookDaoImpl(); } @Override public Class<?> getObjectType() { return BookDao.class; } /** * 控制单例创建 * @return true 单例 * false 非单例 */ @Override public boolean isSingleton() { return true; } }
2. 配置
<bean id="bookDao" class="com.tommychan.factory.BookDaoFactoryBean"/>
Bean的生命周期
概念
-
生命周期:从创建到消亡的完整过程
-
bean生命周期:bean从创建到销毁的整体过程
-
bean生命周期控制:在bean创建后到销毁前做一些事情
控制bean的生命周期
方法一:使用配置文件
-
提供控制方法
public class BookDaoImpl implements BookDao{ private BookDao bookDao; @Override public void init() { System.out.println("init..."); } @Override public void destroy() { System.out.println("destroy..."); } }
-
配置文件指定方法
<bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
方法二:实现接口完成
-
实现接口
InitializingBean
DisposableBean
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; @Override public void destroy() throws Exception { System.out.println("service destroy ..."); } @Override public void afterPropertiesSet() throws Exception { System.out.println("service init..."); } public void setBookDao(BookDaoImpl bookDao) { this.bookDao = bookDao; } }
bean的销毁操作
-
容器关闭前触发bean的销毁
-
关闭容器方法
- 手工关闭
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.close();
- 注册关闭钩子,在退出虚拟机前关闭容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.registerShutdownHook();
总结
-
初始化容器
-
创建对象(内存分配)
-
执行构造方法
-
执行属性注入(set操作)
-
执行bean初始化方法
-
-
使用bean
- 执行业务操作
-
关闭/销毁容器
- 执行bean销毁方法
4. 依赖注入
前言
向一个类中传递数据的方式
-
普通方法(set方法)
-
构造方法
依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢
-
引用类型
-
简单类型(基本数据类型与String)
依赖注入方式
-
setter注入
-
简单类型
-
引用类型
-
-
构造器注入
-
简单类型
-
引用类型
-
setter注入
引用类型
-
bean中定义引用类型属性,并提供可访问的set方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; public void setBookDao(BookDaoImpl bookDao) { this.bookDao = bookDao; } }
-
配置文件中使用property标签ref属性
<bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl"> <bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/> <property name="bookDao" ref="bookDao"/> <!-- name 表示配置哪一个具体的属性 -- 即 private BookDao bookDao; ref 表示参照哪一个容器里已经配置了的 bean 即 <bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/> --> </bean>
简单类型
-
bean中定义简单类型属性,并提供可访问的set方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private int num; private String name; public void setNum(int num) { this.num = num; } public void setName(String name) { this.name = name; } }
-
配置文件中使用property标签value属性
<bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl"> <property name="name" value="hello"/> <property name="num" value="10"/> </bean>
构造器注入
-
bean中定义constructor
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; private int num; private String name; public BookServiceImpl(BookDao bookDao, int num, String name) { this.bookDao = bookDao; this.num = num; this.name = name; } }
-
配置文件
<bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/> <bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl"> <constructor-arg name="bookDao" ref="bookDao"/> <constructor-arg name="num" value="1"/> <constructor-arg name="name" value="hello"/> <!-- 关于参数适配 --> <!-- 方式一 :通过类型指定--> <constructor-arg type="int" value="1"/> <constructor-arg type="java.lang.String" value="hello"/> <!-- 方式二:通过index指定---> <constructor-arg index="1" value="1"/> <constructor-arg index="2" value="hello"/> </bean>
注入方式的选择
-
强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
-
可选依赖使用setter注入进行,灵活性强
-
Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
-
如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
-
实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
-
自己开发的模块推荐使用setter注入
依赖自动装配
-
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
-
自动装配方式
-
按类型(常用)
-
按名称
-
按构造方法
-
不启用自动装配
-
-
配置文件中使用bean标签 autowire属性设置自动装配类型
-
提供setter方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
-
配置文件中使用autowired并指定类型
<bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl" autowire="byType"/>
<bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/> <bean id="bookService" class="com.tommychan.service.impl.BookServiceImpl" autowire="byName"/>
-
-
自动装配用于引用类型依赖注入,不能对简单类型进行操作
-
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
-
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
-
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
5. 集合注入
-
了解格式即可
-
需要提供setter方法
public class BookDaoImpl implements BookDao{
private BookDao bookDao;
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void init() {
System.out.println(toString());
}
@Override
public void destroy() {
System.out.println("destroy...");
}
@Override
public String toString() {
return "BookDaoImpl{" +
"bookDao=" + bookDao +"\n"+
", array=" + Arrays.toString(array) + "\n"+
", list=" + list + "\n"+
", set=" + set + "\n"+
", map=" + map + "\n"+
", properties=" + properties +
'}';
}
}
<bean id="bookDao" class="com.tommychan.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy">
<property name="array">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<property name="list">
<list>
<value>hello</value>
<value>world</value>
</list>
</property>
<property name="set">
<set>
<value>hello</value>
<value>world</value>
</set>
</property>
<property name="map">
<map>
<entry key="country" value="China"/>
<entry key="city" value="Hubei"/>
</map>
</property>
<property name="properties">
<props>
<prop key="city">Hubei</prop>
<prop key="country">China</prop>
</props>
</property>
</bean>
6. 数据源对象管理
- 以druid数据源为例
<!--管理 DruidDataSource对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="20020426cyh"/>
</bean>
一个问题:我们通常不会将value写死,而是使用properties文件配置
7. 加载properties文件
-
开启context命名空间
-
使用context空间加载properties文件
-
使用
${}
占位符读取properties中的属性
- jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.username=root
jdbc.password=20020426cyh
- 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"
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
">
<!--管理 DruidDataSource对象-->
<!-- 1.开启context命名空间 -->
<!--xmlns:context="http://www.springframework.org/schema/context"-->
<!--http://www.springframework.org/schema/context-->
<!--http://www.springframework.org/schema/context/spring-context.xsd-->
<!-- 2.使用context空间加载properties文件 -->
<!-- <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/> -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<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>
提示:
-
我们通常使用
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
同时加载当前工程中的所有properties文件 -
对于导入的jar包和类路径,上面的方式无法加载properties文件,改为
location="classpath*:*.properties"
即可
8. 容器
- 配置文件的加载
//1.类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.文件路径加载配置文件
ApplicationContext ctx1 =
new FileSystemXmlApplicationContext("D:\\Codes\\Spring\\src\\main\\resources\\applicationContext.xml");
//3.加载多个配置文件
ApplicationContext ctx3 = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
- 获取bean的方式
//1.通过名称获取bean
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
//2.通过名称和类型获取
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
//3.通过类型获取
BookDao bookDao1 = ctx.getBean(BookDao.class);
-
BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
-
ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
-
ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
-
ApplicationContext接口常用初始化类
-
ClassPathXmlApplicationContext
-
FileSystemXmlApplicationContext&#
-