首先引入spring的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
然后在resources目录下创建spring的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 http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
最基本的bean配置方法如下所示
<bean id="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>
该类如下所示
public class BookDao implements heimaSpring.dao.BookDao {
private BookDao() {
System.out.println("在spring中,BookDao 构造器采用反射方式,所以即使构造方法采用private修饰也能够调用,但是只能调用无参构造方法,如果加参数则会报错");
}
public void printSelf(){
System.out.println("BookDao");
}
public static void main(String[] args) {
var bookDao = new BookDao();
bookDao.printSelf();
}
}
配合如下代码即可看到该类的对象已经进入Bean之中,并可以将其从Bean之中取出使用
import heimaSpring.dao.iml.BookDao;
import heimaSpring.service.iml.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 加载配置文件,得到容器对象
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源,通过bean的id来获取bean
var bookdao = (BookDao) ctx.getBean("bookDao");
bookdao.printSelf();
}
}
但是这依然无法解决代码之间耦合的问题,下列类BookService使用了一个BookDao来作为其成员变量,正常情况下我们需要new一个对象出来,但是这样一旦BookDao代码进行改变,BookService也要相应的进行改变。为了解决这种问题,我们不仅要将对象的创建权交给Bean,而且要将Bean中的BookDao交给BookService中的成员变量。想要做到这一点,我们需要在配置文件之中指明两者关系。
public class BookService implements InitializingBean, DisposableBean {
private BookDao bookDao;
//在配置文件之中指明Dao和Service的关系
private BookService() {
this.bookDao = bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void printDao(){
bookDao.printSelf();
}
//可以通过bean配置初始化和销毁方法
public void init(){System.out.println("bean init");}
public void destoy(){System.out.println("bean destoy");}
public void printSelf(){
System.out.println("BookService");
}
public static void main(String[] args) {
BookService bookService = new BookService();
bookService.printDao();
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("bean afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("bean destroy");
}
}
具体的配置方法如下
<bean id="bookService" class="heimaSpring.service.iml.BookService" init-method="init" destroy-method="destoy">
<!--7.配置server与dao的关系,把dao放进service中-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean-->
<property name="bookDao" ref="bookDao" ></property>
</bean>
同时,也要在BookServiceImpl类中,为BookDao提供setter方法。
- 注意:配置中的两个bookDao的含义是不一样的 name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前 面加set找对应的setBookDao()方法进行对象注入
- ref="bookDao"中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给 bookService进行注入
- 完整的配置文件如下
<?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">
<!-- 1.导入spring依赖-->
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-context</artifactId>-->
<!-- <version>5.2.10.RELEASE</version>-->
<!-- </dependency>-->
<!-- 2. 配置bean-->
<!--bean标签标示配置bean,id属性标示给bean起名字,class属性表示给bean定义类型-->
<!--scope负责处理bean中的对象数量,默认为singleton,即多次调用只会生成同一个对象,想要生成不同的对象,需要将其改为prototype,当设置为prototype时,destroy-method不会被执行-->
<!--可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法,但是配置文件中方法的优先级比较高-->
<!-- 当因为开发习惯导致的id出现重名时,可以使用name属性为其起别名,name可以起多个别名,其中以,分隔-->
<bean id="bookService" name="service1,service2" class="heimaSpring.service.iml.BookService" init-method="init" destroy-method="destoy" scope="prototype">
<!--7.配置server与dao的关系,把dao放进service中-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean,建议使用id值,但是也可以使用name-->
<property name="bookDao" ref="bookDao" ></property>
</bean>
<bean id="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>
</beans>
这里要着重说明一下scope属性,默认为单例,一般情况下也都是单例,这意味着每次使用的都是同一个对象,如果一个类需要创建多个不同的对象,建议不要将其放入Bean之中。
- bean在容器中是单例的,会不会产生线程安全问题?
- 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的, 因为所有请求线程共用一个bean对象,所以会存在线程安全问题。 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的, 因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 哪些bean对象适合交给容器进行管理?
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些bean对象不适合交给容器进行管理?
- 封装实例的域对象,因为会引发线程安全问题,所以不适合。
- 接下来描述Bean的实例化方法
- 实例化bean的三种方式,构造方法,静态工厂和实例工厂
- 其中构造方法采取的反射方式调用无参构造方法
- 静态工厂方法已经快被淘汰了
- 实例工厂操作如下,首先创建UserDaoFactory,该类要有方法能提供bean中所需要的实例化对象
public class UserDaoFactory {
public UserDaoImp getUserDao(){
return new UserDaoImp();
}
}
然后在配置文件之中加入这两行
<!-- 采取实例化工厂创建bean-->
<!-- 实例化工厂运行的顺序是:-->
<!-- 创建实例化工厂对象,对应的是第一行配置-->
<!-- 调用对象中的方法来创建bean,对应的是第二行配置-->
<!-- factory-bean:工厂的实例对象-->
<!-- factory-method:工厂对象中的具体创建对象的方法名-->
<!-- factory-method:具体工厂类中创建对象的方法名-->
<bean id="userFactory" class="heimaSpring.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
为了简化这种配置方法,spring提供了另外一种方法
//实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
//查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
// T getObject() throws Exception;
// Class<?> getObjectType();
// default boolean isSingleton() {
// return true;
// }
// 方法一:getObject(),被重写后,在方法中进行对象的创建并返回
// 方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象
// 方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?思路很简单,就是从容器中获取该对象的多个值,打印到控制台,查看是否为同一个对象。
public UserDao getObject() throws Exception {
return new UserDaoImp();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
对应的配置文件为
<!-- spring中简化实例化工厂方法,测试方法等无需更改-->
<!-- 该方法这在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。-->
<bean id="userDaoSpring"name="userDaoSpring"class="heimaSpring.factory.UserDaoFactoryBean"/>
bean生命周期
首先是在配置文件之中定义初始化和销毁方法,这种方式优先级比较高且经常使用
<!--可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法,但是配置文件中方法的优先级比较高-->
<bean id="bookService" name="service1,service2" class="heimaSpring.service.iml.BookService" init-method="init" destroy-method="destoy" scope="prototype">
<!--7.配置server与dao的关系,把dao放进service中-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean,建议使用id值,但是也可以使用name-->
<property name="bookDao" ref="bookDao" ></property>
也可以像下面这么做
//可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法
public class BookService implements InitializingBean, DisposableBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("bean afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("bean destroy");
关闭容器的方法
// 加载配置文件,得到容器对象,ClassPathXmlApplicationContext继承自ApplicationContext,为了验证容器的销毁,所以使用ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//程序结束后直接退出虚拟机,所以无法看到destory方法,想要调用该方法,需要先关闭容器,再退出虚拟机
//关闭容器方法有两种,一种是直接采取close方法暴力退出
// ctx.close();
//注册一个关闭钩子,且该方法可以在任意地方执行
ctx.registerShutdownHook();
DI相关
向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
- 依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型
- 简单类型(基本数据类型与String)
- Spring就是基于上面这些知识点,为我们提供了两种注入方式,分别是:
- setter注入:
- 简单类型
- 引用类型
- 构造器注入:
- 简单类型
- 引用类型
setter注入
使用setter注入引用类型的方法之前已经有了演示,采取以下方式即可,但是前提是要在类中提供对应的setter方法
property name="bookDao" ref="bookDao"
引用类型使用的是ref,但是简单数据类型还是使用ref么? ref是指向Spring的IOC容器中的另一个bean对象的,对于简单数据类型,没有对应的bean对象, 该如何配置? 对于简单数据类型使用的是
<property name="age" value="50"/>
- 接下来将对以下类进行配置
package heimaSpring.service.iml;
import heimaSpring.dao.BookDao;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import java.util.List;
import java.util.Map;
//可以继承InitializingBean, DisposableBean接口并使用其中方法,则无需在配置文件中配置初始化和销毁方法
public class BookService implements InitializingBean, DisposableBean {
private String userName;
private Integer age;
private Map<String,String> map;
private List<Integer> list;
private BookDao bookDao;
//在配置文件之中指明Dao和Service的关系
public BookService(String userName, Map<String, String> map) {
this.userName = userName;
this.map = map;
}
@Override
public String toString() {
return "BookService{" +
"userName='" + userName + ''' +
", age=" + age +
", map=" + map +
", list=" + list +
", bookDao=" + bookDao +
'}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
public BookDao getBookDao() {
return bookDao;
}
private BookService() {
this.bookDao = bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void printDao(){
bookDao.printSelf();
}
//可以通过bean配置初始化和销毁方法
public void init(){System.out.println("bean init");}
public void destoy(){System.out.println("bean destoy");}
public void printSelf(){
System.out.println("BookService");
}
public static void main(String[] args) {
BookService bookService = new BookService();
bookService.printDao();
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("bean afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("bean destroy");
}
}
对上面的类进行注入的配置文件如下
<?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="bookDao" class="heimaSpring.dao.iml.BookDao"></bean>
<bean id="bookService" class="heimaSpring.service.iml.BookService">
<property name="bookDao" ref="bookDao"></property>
<property name="age" value="50"/>
<property name="list">
<list>
<value>10</value>
<value>10</value>
<value>10</value>
</list>
</property>
<constructor-arg name="map">
<map>
<entry key="1" value="one"></entry>
<entry key="2" value="two"></entry>
</map>
</constructor-arg>
<constructor-arg name="userName" value="yuanfengyu"></constructor-arg>
</bean>
</beans>
构造器注入和setter注入区别不大,只是标签从property变为了constructor-arg,且标签内部的name顺序并无要求。
- 但是这样就会造成name属性和类中的变量名强耦合,当类中的变量名变化时name属性也必须相应的变化,虽然变量名一般情况下并不会改变,所以上述方法仍旧是主流方法
- 删除name属性,添加type属性,按照类型注入
<constructor-arg name="java.lang.String" value="yuanfengyu"></constructor-arg>
- 按照变量顺序注入,但是又会带来新的耦合
<constructor-arg index="1" value="100"/>
介绍完两种参数的注入方式,具体我们该如何选择呢?
- 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现 强制依赖指对象在创建的过程中必须要注入指定的参数
- 可选依赖使用setter注入进行,灵活性强 可选依赖指对象在创建过程中注入的参数可有可无
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选 依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
自动配置
自动装配方式:
- 按类型(常用)
- 实际上就是取消property改用autowire
<!-- 需要注入属性的类中对应属性的setter方法不能省略-->
<!-- 被注入的对象必须要被Spring的IOC容器管理-->
<!-- 按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException-->
<bean id="bookService" class="heimaSpring.service.iml.BookService" autowire="byType">
- 按名称(byName)
<!-- 对外部类来说,setBookDao方法名,去掉set后首字母小写是其属性名-->
<!-- 为什么是去掉set首字母小写?-->
<!-- 这个规则是set方法生成的默认规则,set方法的生成是把属性名首字母大写前面加set形成-->
<!-- 的方法名-->
<!-- 所以按照名称注入,其实是和对应的set方法有关,但是如果按照标准起名称,属性名和set对-->
<!-- 应的名是一致的-->
<!-- 如果按照名称去找对应的bean对象,找不到则注入Null-->
<!-- 当某一个类型在IOC容器中有多个对象,按照名称注入只找其指定名称对应的bean对象,不会报错-->
<!-- 两种方式介绍完后,以后用的更多的是按照类型注入。-->
<!-- 最后对于依赖注入,需要注意一些其他的配置特征:-->
<!-- 1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作-->
<!-- 2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用-->
<!-- 3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推-->
<!-- 荐使用-->
<!-- 4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效-->
集合注入
<!-- 注入数组类型-->
<!-- <property name="array">-->
<!-- <array>-->
<!-- <value>100</value>-->
<!-- <value>200</value>-->
<!-- <value>300</value>-->
<!-- </array>-->
<!-- </property>-->
<!-- 注入Set类型数据-->
<!-- <property name="set">-->
<!-- <set>-->
<!-- <value>itcast</value>-->
<!-- <value>itheima</value>-->
<!-- <value>boxuegu</value>-->
<!-- <value>boxuegu</value>-->
<!-- </set>-->
<!-- </property>-->
<!-- 注入Properties类型数据-->
<!-- <property name="properties">-->
<!-- <props>-->
<!-- <prop key="country">china</prop>-->
<!-- <prop key="province">henan</prop>-->
<!-- <prop key="city">kaifeng</prop>-->
<!-- </props>-->
<!-- </property>-->
<!-- property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写-->
<!-- <array>、<list>、<set>、<map>、<props>标签-->
<!-- List的底层也是通过数组实现的,所以<list>和<array>标签是可以混用-->
<!-- 集合中要添加引用类型,只需要把<value>标签改成<ref>标签,这种方式用的比较少-->