* @version 1.0
* @intro:
*/
public class UserDaoImplMySQL implements UserDao {
@Override
public void save() {
System.out.println(“使用MySQL保存到数据库…”);
}
}
* 定义实现类-02:
package com.dfbz.dao.impl;
import com.dfbz.dao.UserDao;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class UserDaoImplOracle implements UserDao {
@Override
public void save() {
System.out.println(“使用Oracle保存到数据库…”);
}
}
* 定义UserService:
package com.dfbz.service;
import com.dfbz.dao.UserDao;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
userDao.save();
}
}
* 定义UserController:
package com.dfbz.controller;
import com.dfbz.service.UserService;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void save() {
userService.save();
}
}
* 测试类:
我们会发现,要保存一个用户必须手动的创建一个个对象,并将对象移除组装起来,最后组装成一个UserController,然后才可以调用;
@Test
public void test2() {
UserDao userDao = new UserDao();
UserService userService = new UserService(userDao);
UserController userController = new UserController(userService);
userController.save();
}
存在的问题:
* 1)UserController的过程非常复杂且需要我们来负责整个的创建过程,我们必须要对UserController的结构非常清晰才可以创建出来,实际开发中,UserController可能都不是我编写的;甚至整个User的业务我都没有参加,我只是想用一下UserController的某个功能;
* 2)因为是直接通过new生成的具体对象,这是一种硬编码的方式,违反了面向接口编程的原则,程序间存在严重的耦合,如果有一天我们从MyBatis的Dao切换到Hiberante的Dao那么将会需要改动源代码;
* 3)每一处使用UserController都需要这么几个步骤,频繁的创建对象,浪费资源;
#### 4.1.2 使用IOC容器的情况
@Test
public void test4() {
// 获取SpringIOC容器
ApplicationContext app =
new ClassPathXmlApplicationContext("spring.xml");
// 从IOC容器中获取一个UserController,具体UserController是如何创建出来的,我不管
UserController userController = (UserController) app.getBean("userController");
userController.save();
}
使用IOC之后,我们只管向容器索取所需的Bean即可。IOC便解决了以下的问题:
* 1)Bean之间的解耦,Bean不需要我来创建了,我们也不必对Bean的内部非常了解,只管像IOC容器获取即可;
* 2)当dao类需要跟换时,只需要在spring的配置文件中更换dao的实现类即可,原来的代码都不行做任何的改动;
* 3)IOC容器天生支持单例;
### 4.2 IOC概念
**控制反转**(Inversion of Control,缩写为**IOC**),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称**DI**)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
反转意味着我们把对象创建的控制权交出去,交给谁呢?交给IOC容器,由IOC容器负责创建特定对象,并填充到各个声明需要特定对象的地方。
### 4.3 IOC的实例化
在Spring 容器读取Bean配置创建Bean实例之前,必须对它进行实例化。只有在容器实例化后,才可以从容器里获取Bean实例并使用。
Spring提供了多种类型的容器实现。
* `BeanFactory`:容器的基本实现。
* `ApplicationContext`:提供了更多的高级特性,是BeanFactory的子接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFMTTbrX-1669719985321)(media/94.png)]
BeanFactory是Spring 框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory 。
无论使用何种方式,配置文件是相同的。
* `ConfigurableApplicationContext`扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close() ,让ApplicationContext具有启动、刷新和关闭上下文的能力。
* `ClassPathXmlApplicationContext`:通过classpath路径直接获得加载的xml文件(推荐使用)
ApplicationContext app = new ClassPathXmlApplicationContext(“spring.xml”);
* `FileSystemXmlApplicationContext`:通过文件路径来获得加载的xml文件。
ApplicationContext app = new FileSystemXmlApplicationContext(“D:/spring.xml”);
* `AnnotationConfigApplicationContext`:基于配置类注解来创建IOC容器
ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfig.class);
## 五、Bean的配置
### 5.1 创建Bean的方式
我们的Bean都交给了SpringIOC容器来进行管理,意味着Bean的创建由IOC容器来进行,SpringIOC容器提供了多种不同的方式来创建Bean;
* 准备实体类:
@Data
public class Book {
private String id;
private String name;
private Double price;
public Book() {
System.out.println("book初始化了");
}
}
#### 5.1.1 普通创建方式
普通创建方式就是之前我们案例中的创建方式
* spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!--
标签:用于声明一个类,在启动Spring框架的时候根据该配置的类创建对象到容器里面
name:设置对象名(唯一标识符)
id:设置对象名(唯一标识符,功能和name一样)
class:用于指定对象对应的类名
–>
#### 5.1.2 工厂方式
* BookFactory:
package com.dfbz.factory.factory;
import com.dfbz.entity.Book;
/**
* @author lscl
* @version 1.0
* @intro: 图书工厂类
*/
public class BookFactory {
public Book createBook(){
return new Book();
}
}
* spring.xml:
#### 5.1.3 静态工厂
* BookStaticFactory:
package com.dfbz.factory;
import com.dfbz.entity.Book;
/**
* @author lscl
* @version 1.0
* @intro: 图书工厂类(静态工厂)
*/
public class BookStaticFactory {
public static Book createBook(){
return new Book();
}
}
* spring.xml:
#### 5.1.4 FactoryBean方式
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现`org.springframework.beans.factory.FactoryBean`接口。
![在这里插入图片描述](https://img-blog.csdnimg.cn/7b1c35167fc34bdbacdc7406e9f3443a.png#pic_center)
* BookFactoryBean:
package com.dfbz.factory;
import com.dfbz.entity.Book;
import org.springframework.beans.factory.FactoryBean;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class BookFactoryBean implements FactoryBean {
/**
* SpringIOC容器创建bean时调用的方法
* @return : 创建的JavaBean
* @throws Exception
*/
@Override
public Book getObject() throws Exception {
return new Book();
}
/\*\*
* 要创建的JavaBean的类型
* @return
*/
@Override
public Class<?> getObjectType() {
return Book.class;
}
}
* spring.xml:
### 5.2 给Bean中属性赋值
我们前面创建的JavaBean,其属性值默认都是为空的;我们可以通过Spring的方式来给容器中的bean赋值;
#### 5.2.1 通过构造器
通过Bean提供的构造方法来给Bean设置值
* Book的构造函数:
public Book(String id, String name, Double price) {
System.out.println(“有参构造执行了…”);
this.id = id;
this.name = name;
this.price = price;
}
// 参数顺序不一致
public Book(Double price, String id, String name) {
System.out.println(“有参构造执行了…”);
this.id = id;
this.name = name;
this.price = price;
}
* spring.xml
#### 5.2.2 通过set方法
* spring.xml:
#### 5.2.3 p命名空间
p名称空间:为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
Spring从2.5版本开始引入了一个新的p命名空间,可以通过`<bean>`元素属性的方式配置Bean的属性。使用p命名空间后,基于XML的配置方式将进一步简化。
* spring.xml:
#### 5.2.4 c命名空间
与带有p命名空间的XML快捷方式类似,Spring 3.1中引入的c命名空间允许内联属性来配置**构造函数参数**
#### 5.3.5 级联赋值
级联赋值:有时候我们bean中的属性的类型并不是一个基本数据类型,而是一个对象,那么如何给这个对象中的某个属性进行赋值呢?
* Emp:
@Data
public class Emp {
private String id;
private String name;
// 所属部门
private Dept dept = new Dept();
}
* Dept:
@Data
public class Dept {
private String id;
private String name;
private String location;
}
* spring.xml:
<!--属性的级联设置-->
<property name="dept.id" value="001"></property>
<property name="dept.name" value="研发部"></property>
<property name="dept.location" value="泰国"></property>
<!--使用ref属性进行引用数据类型的注入-->
<property name="dept" ref="dept"></property>
#### 5.2.6 null值的处理
* null值的处理:
<!--报错,会把null当做字符串-->
<property name="dept">
<!--赋值null的正确写法-->
<null />
</property>
* 引用数据类型的处理:
<!--可以使用ref属性引用其他地方的dept-->
<!-- <property name="dept" ref="dept" />-->
<property name="dept">
<!--可以使用<ref>标签引用其他地方的dept-->
<!-- <ref bean="dept"></ref>-->
<!--使用bean标签再定义一个-->
<bean id="dept02" class="com.dfbz.entity.Dept">
<property name="id" value="004"></property>
<property name="name" value="市场部"></property>
<property name="location" value="越南"></property>
</bean>
</property>
### 5.3 赋值复杂类型
#### 5.3.1 数组和集合
在Spring中可以通过一组内置的XML标签来配置集合属性
数组和List配置java.util.List类型的属性,需要指定`<list>`标签,在标签里包含一些元素。这些标签可以通过指定简单的常量值,通过`<ref>`指定对其他Bean的引用。通过`<bean>`指定内置bean定义。通过`<null/>`指定空元素。甚至可以内嵌其`<value>`他集合。
数组的定义和List一样,都使用`<list>`元素。
配置java.util.Set需要使用`<set>`标签,定义的方法与List一样。
* 修改dept(需要提供get/set方法):
// 部门员工
private List empList = new ArrayList<>();
// 曾用名
private List usedName=new ArrayList<>();
* spring.xml:
<property name="usedName">
<list>
<!--基本数据类型和String类型-->
<value>技术部</value>
<value>开发部</value>
</list>
</property>
<property name="empList">
<list>
<!--引用数据类型-->
<ref bean="emp01"></ref>
<ref bean="emp02"></ref>
</list>
</property>
#### 5.3.2 properties和Map
* 提供一个Pojo类:
@Data
public class Pojo {
private Map map=new HashMap();
private Properties props=new Properties();
}
* spring.xml:
<!--map类型-->
<property name="maps">
<map>
<entry key="id" value="001"></entry>
<entry key="name" value="《诗经》"></entry>
<entry key="price" value="38.8"></entry>
</map>
</property>
<property name="prop">
<props>
<prop key="id">001</prop>
<prop key="name">《尚书》</prop>
<prop key="price">39.8</prop>
</props>
</property>
### 5.4 Bean的生命周期
Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
Spring IOC容器对bean的生命周期进行管理的过程:
* **1)默认情况下,在容器启动时,Spring会将所有的bean都初始化**
* 2)为bean的属性设置值(通过构造方法、set方法等)
* 3)调用bean的初始化方法init
* 4)bean可以使用了
* **5)默认情况下,当容器关闭时,Spring会将所有的bean销毁**
在配置bean时,通过bean标签的`init-method`和`destroy-method`属性为bean指定初始化和销毁方法
* 测试实体类:
@Data
public class Person {
private String name;
private Integer age;
private String addr;
public void init(){
System.out.println("初始化了");
}
public void destroy(){
System.out.println("销毁了");
}
public Person() {
System.out.println("person创建了");
}
}
* spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!--
init-method:bean初始化时调用的方法(构造方法执行之后)
destroy-method:bean销毁时调用的方法
–>
* 测试类型:
package com.dfbz.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
//获取SpringIOC容器
ConfigurableApplicationContext app =
new ClassPathXmlApplicationContext(“spring2.xml”);
@Test
public void test1() {
Object person = app.getBean("person");
System.out.println(person);
app.close();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2uC9wIX-1669719985322)(media/91.png)]
### 5.5 Bean的作用域
默认情况下,**IOC容器中的bean都是单例的,而且是容器启动时,会创建所有的bean**;
@Test
public void test2() {
Object person = app.getBean("person");
Object person2 = app.getBean("person");
System.out.println(person == person2); // true,同一个bean只会初始化一份
}
可以通过lazy-init属性来调整加载的时机,等到用到的时候再加载:
可以通过scpoe属性来调整bean的作用范围:
* 测试类:
@Test
public void test2() {
Object person = app.getBean(“person”);
Object person2 = app.getBean(“person”);
System.out.println(person == person2); //false
app.close(); // 容器关闭时,多实例的bean不会随之销毁
}
**注意:在多例中,容器创建时不会加载多实例的bean,而是等到具体用的时候再加载,每次获取的实例都是一个全新的实例,并且IOC容器销毁时,多实例的bean也不会随之销毁;而是将Bean的销毁交给了JVM的垃圾回收**
### 5.6 后置处理器
Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理
bean后置处理器时需要实现接口:`org.springframework.beans.factory.config.BeanPostProcessor`
* 定义MyProcess:
package com.dfbz.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class MyProcess implements BeanPostProcessor {
/\*\*
* 创建完对象后执行前置方法
* @param bean: 要创建出来的bean
* @param beanName: bean的名称
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“【”+beanName+"】执行了前置方法,类型为: "+ bean.getClass());
return bean;
}
/\*\*
* 对象的init方法执行完再执行后置方法
* @param bean: 要创建出来的bean
* @param beanName: bean的名称
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“【”+beanName+"】执行了后置方法,类型为: "+ bean.getClass());
return bean;
}
}
* spring.xml:
测试效果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0baf3f15b1b344be91be9c632e91ff45.png#pic_center)
### 5.7 bean的其他属性
#### 5.7.1 继承属性
#### 5.7.2 抽象属性
#### 5.7.3 依赖属性
<?xml version="1.0" encoding="UTF-8"?>
<!--
depends-on: 创建当前对象时必须先创建depends-on指定的对象(多个对象以逗号隔开)
–>
<bean id="person" class="com.dfbz.entity.Person"></bean>
### 5.8 引用外部属性文件
我们习惯性的把一些配置写在外部的`xxx.properties`文件中,Spring提供了context命名空间帮我们读取类路径下的配置文件
* 引入mysql和druid连接池依赖: