阅读建议:笔记内容有点长,建议知识点复习或查缺补漏时享用~
IoC容器
Inversion of Control 控制反转,目的是降低代码间的耦合度(耦合是不可避免的,只能降低)。
解释:把对象的创建和对象之间的调用过程,都交由Spring进行管理。
常见的实现方式有:依赖注入和依赖查找
IoC容器(底层原理)
总结:XML解析 + 工厂设计模式 + 反射
创建工厂类,其中实现了XML解析,获取了对应的类名和属性名,通过反射实现类的实例化。
class UserFactory {
public static UserDao getDao() {
String classValue = class属性值; //1.XML解析
Class clazz = Class.forName(classValue);
return (UserDao)clazz.newInstance(); //2.通过反射创建对象实例
}
}
BeanFactory接口
IoC是一种思想,这种思想体现在IoC容器的实现上,IoC容器的底层逻辑就是一个对象工厂。
Spring对于IoC容器的设计,提供了两种方式,对应两个接口
- BeanFactory
- ApplicationContext
二者的异同点
相同点:都能实现XML配置文件的解析
不同点:
①BeanFactory是最基本、最内层的实现方式,Spring内部使用的接口,不提供给开发人员使用;ApplicationContext是BeanFacory的子接口,提供更多更强大的功能,一般由开发人员使用。
②BeanFactory在加载配置文件时不会创建对象实例,而是在获取bean时才会创建;而ApplicationContext在加载配置文件时就会创建对象实例。
@Test
public void test01() {
//BeanFactory在加载配置文件时不会创建对象实例,而是在获取bean时才会创建
Resource resource = new ClassPathResource("beanbase.xml");
BeanFactory factory = new XmlBeanFactory(resource);
System.out.println("获取Bean前...");
User user = factory.getBean("user", User.class);
System.out.println(user);
}
@Test
public void test02() {
//ApplicationContext在加载配置文件时就会创建对象实例
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beanbase.xml");
System.out.println("获取Bean前...");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
日常开发建议
关于加载配置文件时创建对象与否的问题,考虑到用户交互体验,即用户操作的响应效率问题,推荐日常开发中使用ApplicationContext接口实现。
ApplicationContext接口主要实现类
-
FileSystemXmlApplicationContext
-
ClassPathXmlApplicationContext
见名知意,FileSystemXmlApplicationContext实现类的传参就是物理文件系统的路径,如果是windows操作系统,就是哪个盘符balabala对应的路径。
@Test
public void test03() {
//ApplicationContext在加载配置文件时就会创建对象实例
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("E:\\Java\\01 Spring5\\Spring_5_2_6\\XMLConfig\\src\\beanbase.xml");
System.out.println("获取Bean前...");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
}
IoC容器(Bean管理)
Bean管理包括两个操作:创建对象、注入属性。
基于XML配置文件方式
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>
-
创建对象
public class Book {
private String bookName;
//若声明了带参构造方法,则一定要记得声明无参构造方法
public Book() {
}
//带参构造方法,用于带参构造方法注入属性
public Book(String bookName) {
this.bookName = bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
<bean id="book" class="com.coffeeship.dao.Book"/>
bean标签,常用的属性:id(唯一标识)、class(类的全路径名)、name(作用类似id,不常用)
id在ApplicationContext接口子类对象通过getBean获取对象时会指定,建议类名的小驼峰即可。
默行认执的就是无参构造方法,这就是之后使用注解创建自定义bean的时候,要求一定有无参构造方法的原因,不过一般也通过注解实现无参和全参的构造方法。
对应以前的原始方法就是new对象:
Book book = new Book();
还有其他的博主说创建对象有三种方式,除了上述那种,还有以下两种:
通过静态方法创建对象 和 通过实例方法创建对象
public class CreateBean {
//通过静态方法
public static Book getBookStatic() {
return new Book();
}
//通过实例方法
public Book getBook() {
return new Book();
}
}
factory-bean属性,用于指定创建当前bean对象的工厂bean id,指定此属性之后,class属性失效。factory-method属性,用于指定创建当前bean对象的工厂方法,和class属性配合使用时,方法必须是static的;和 factory-bean配合使用时,class属性失效。
<!--通过静态方法方式创建对象-->
<!--<bean id="book" class="com.coffeeship.dao.CreateBean" factory-method="getBookStatic"/>-->
<!--通过实例方法方式创建对象-->
<bean id="createBean" class="com.coffeeship.dao.CreateBean"/>
<bean id="book" factory-bean="createBean" factory-method="getBook"/>
-
注入属性
DI:dependency input 依赖注入
方式一:set方法注入
property标签,属性name即类中的属性名,value即赋值
<bean id="book" class="com.coffeeship.dao.Book">
<property name="bookName" value="Java从入门到精通"/>
</bean>
对应以前的原始方法就是执行set方法:
Book book = new Book();
book.setBookName("Java从入门到精通");
方式二:带参构造方法注入
constructor-arg标签,属性name即类中的属性名,value即赋值
<bean id="book" class="com.coffeeship.dao.Book">
<constructor-arg name="bookName" value="Java从入门到精通"/>
</bean>
对应以前的原始方法就是通过带参构造方法创建对象:
Book book = new Book("Java从入门到精通");
注入属性的优化:p名称空间简化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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="book" class="com.coffeeship.dao.Book" p:bookName="Java从入门到精通"/>
</beans>
注入属性-特殊字面量值:空值或包含特殊符号
<bean id="book" class="com.coffeeship.dao.Book">
<property name="bookName">
<null></null>
</property>
</bean>
特殊符号的处理方式一:使用转义字符,<等
<bean id="book" class="com.coffeeship.dao.Book">
<property name="bookName" value="<<Java从入门到精通>>"/>
</bean>
特殊符号的处理方式一:使用<value><![CDATA[值的内容]]></value>
<bean id="book" class="com.coffeeship.dao.Book">
<property name="bookName">
<value><![CDATA[Java从入门到精通]]></value>
</property>
</bean>
注入属性:外部bean
property标签中不使用value属性,而使用ref属性,ref属性的值为外部bean的id
<bean id="emp" class="com.coffeeship.dao.Emp">
<property name="empName" value="coffeeship"/>
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.coffeeship.dao.Dept"/>
注入属性:内部bean
直接在property标签内声明bean
<bean id="emp" class="com.coffeeship.dao.Emp">
<property name="empName" value="coffeeship"/>
<property name="dept">
<bean id="dept" class="com.coffeeship.dao.Dept"/>
</property>
</bean>
插播一个可能会有小伙伴有困惑的点!内部bean和外部bean是什么意思??
外部bean:直接在beans标签内部直接定义的bean对象,外部bean可以被多个bean对象引用
内部bean:在某个bean标签的内部定义的bean对象,内部bean只能被某个对象的某个属性引用。
注入属性:级联赋值
方式一:
<bean id="emp" class="com.coffeeship.dao.Emp">
<property name="empName" value="coffeeship"/>
<property name="dept">
<bean id="dept" class="com.coffeeship.dao.Dept">
<!--层层嵌套,即所谓级联赋值-->
<property name="deptName" value="开发部"/>
</bean>
</property>
</bean>
方式二:要求对应内部bean由相应的get方法
<bean id="emp" class="com.coffeeship.dao.Emp">
<property name="empName" value="coffeeship"/>
<property name="dept.deptName" value="开发部"/>
</bean>
注入属性:数组、集合数据类型
public class Student {
private String[] courses;
private List<String> clubs;
private Map<String, Integer> grades;
private Set<String> hobbies;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setClubs(List<String> clubs) {
this.clubs = clubs;
}
public void setGrades(Map<String, Integer> grades) {
this.grades = grades;
}
public void setHobbies(Set<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Student{" +
"courses=" + Arrays.toString(courses) +
", clubs=" + clubs +
", grades=" + grades +
", hobbies=" + hobbies +
'}';
}
}
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">
<bean id="student" class="com.coffeeship.dao.Student">
<!--数组类型注入,使用array标签或list标签均可-->
<property name="courses">
<array>
<value>数据结构与算法</value>
<value>离散数学</value>
<value>计算机操作系统</value>
</array>
</property>
<!--list类型注入,使用list标签-->
<property name="clubs">
<list>
<value>篮球俱乐部</value>
<value>瑜伽俱乐部</value>
</list>
</property>
<!--map类型注入,使用map标签-->
<property name="grades">
<map>
<entry key="数学" value="88"/>
<entry key="英语" value="99"/>
</map>
</property>
<!--set类型注入,使用set标签,并添加重复值,验证set的无重复性-->
<property name="hobbies">
<set>
<value>手工</value>
<value>手工</value>
<value>学习</value>
</set>
</property>
</bean>
</beans>
测试代码
@Test
public void test05() {
//ApplicationContext在加载配置文件时就会创建对象实例
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("collectionbean.xml");
Student student = applicationContext.getBean("student", Student.class);
System.out.println(student);
}
结果
注意,除了数组、列表、map、set外,还有properties。
<bean id="student" class="com.coffeeship.dao.Student">
<property name="infos">
<props>
<prop key="class">三年一班</prop>
<prop key="province">Hunan Yueyang</prop>
</props>
</property>
</bean>
注入属性:设定自定义类的集合属性
<bean id="student" class="com.coffeeship.dao.Student">
<property name="books">
<list>
<ref bean="book1"/>
<ref bean="book2"/>
</list>
</property>
</bean>
<bean id="book1" class="com.coffeeship.dao.Book">
<property name="bookName" value="机器学习入门"/>
</bean>
<bean id="book2" class="com.coffeeship.dao.Book">
<property name="bookName" value="Springboot源码分析"/>
</bean>
注入属性:自定义类的集合属性提取
要求:引入util名称空间(名称空间引入有规律,参考beans)
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="books">
<value>机器学习入门</value>
<value>Springboot源码分析</value>
<value>MySQL从入门到精通</value>
</util:list>
<bean id="student" class="com.coffeeship.dao.Student">
<property name="books" ref="books"/>
</bean>
</beans>
普通bean与FactoryBean(工厂Bean)
普通bean:上面自定义的bean标签中都注明了class,且定义的类型就是获取bean的实例时对应的类型,即普通bean;而工厂bean意味着,配置文件中定义的bean类型可以和返回的类型不一样。
工厂bean的实现方式:实现FactoryBean接口,重写getObject()方法
public class MyBean implements FactoryBean {
//重写返回bean的方法,指定返回类型
@Override
public Student getObject() throws Exception {
Student student = new Student();
return student;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
<bean id="mybean" class="com.coffeeship.dao.MyBean"/>
@Test
public void test07() {
//ApplicationContext在加载配置文件时就会创建对象实例
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factorybean.xml");
Student student = applicationContext.getBean("mybean", Student.class);
System.out.println(student);
}
bean作用域
bean标签中scope属性的可选值
singleton:单例模式(默认)、prototype:多实例、
singleton时,加载配置文件时就会创建对象,prototype时,获取bean时才会创建多实例对象
<bean id="user" class="com.coffeeship.dao.User" scope="singleton"></bean>
<bean id="user" class="com.coffeeship.dao.User" scope="prototype"></bean>
request:请求域、session:会话域
这两个属性值很少使用,涉及到web端时,request表示一个对象对应一个request域,session表示一个对象实例对应一个session会话域。
bean的生命周期
bean对象从创建到销毁的过程
- 通过构造器创建bean实例
- set方法注入属性或其他bean的引用
- 调用bean的初始化方法init-method
- 获取到bean,可以使用
- 容器关闭时,调用bean的销毁方法destroy-method
public class Life {
private Integer age;
public Life() {
System.out.println("第一步!执行无参的构造方法");
}
public void setAge(Integer age) {
System.out.println("第二步!set方法注入属性");
this.age = age;
}
public void initMethod() {
System.out.println("第三步!bean的初始化方法");
}
public void destroyMethod() {
System.out.println("第五步!bean的销毁方法");
}
}
<?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="life" class="com.coffeeship.dao.Life" init-method="initMethod" destroy-method="destroyMethod">
<property name="age" value="99"/>
</bean>
</beans>
@Test
public void test09() {
//ApplicationContext在加载配置文件时就会创建对象实例
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
Life life = applicationContext.getBean("life", Life.class);
System.out.println("第四步!获取到bean");
System.out.println(life);
//关闭容器,注意close()方法是ClassPathXmlApplicationContext实现类中的
//若是ApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
//需要进行强转 eg.((ClassPathXmlApplicationContext) applicationContext).close();
// ((ClassPathXmlApplicationContext) applicationContext).close();
applicationContext.close();
}
bean的后置处理器
在初始化方法执行前后分别执行before方法和after方法
方法:创建后置处理器类,实现BeanPostProcessor接口,重写其中的两个方法
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步!初始化方法前执行");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步!初始化方法后执行");
return bean;
}
}
注意!后置处理器对xml配置文件中所有的bean均生效!
<?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="life" class="com.coffeeship.dao.Life" init-method="initMethod" destroy-method="destroyMethod">
<property name="age" value="99"/>
</bean>
<!--配置后置处理器的bean-->
<bean id="mybeanpost" class="com.coffeeship.beanpost.MyBeanPost"/>
</beans>
bean手动装配&自动装配
上述通过property标签中的value、ref等属性赋值的方式称为手动装配,而通过autowire属性指定装配规则(属性名或属性类型)进行装配,由Spring自动匹配属性和属性值注入。
autowire属性常用:byName | byType
注意,若属性值选用byType,则同一个类不能定义多个bean。
不装配:
<!--不装配-->
<bean id="emp" class="com.coffeeship.dao.Emp">
<!--自动装配-->
<!-- <bean id="emp" class="com.coffeeship.dao.Emp" autowire="byName">-->
</bean>
<bean id="dept" class="com.coffeeship.dao.Dept">
<property name="deptName" value="开发部"/>
</bean>
不装配,dept为null
自动装配,自动装入bean对象
引入外部属性文件.properties
要求在xml配置文件中引入context名称空间
classpath为类路径,即src目录。
<?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">
<!--直接赋值-->
<!-- <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/db2024"/>
<property name="username" value="root"/>
<property name="password" value="qts0922"/>
</bean>-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--引入外部配置文件-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.username}"/>
<property name="password" value="${prop.password}"/>
</bean>
</beans>
@Test
public void test02() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("datasource.xml");
DruidDataSource dataSource = applicationContext.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
}
基于注解方式
注解:代码的特殊标记
注解格式:@注解名(属性名=属性值,属性名=属性值...)
注解使用范围:类上、方法上、属性上、
注解的作用:简化xml配置,使代码更优雅
Spring中针对bean管理中创建对象,提供了如下4个注解
@Component:组件(通用)、@Service:业务层、@Controller:控制层(web层)、@Repository:持久层(dao层),但官方说明此4个注解在功能上无差异,建议service放service接口……起到见名知意的作用!
创建对象
要求除了核心依赖:bean、context、core、expression外
还需要引入依赖:aop、
开启组件扫描配置
要求xml配置文件引入context名称空间,通过component-scan标签实现
<?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">
<!--开启组件扫描
1.若扫描多个包,包名用逗号隔开
2.或直接写包的上层目录
-->
<context:component-scan base-package="com.coffeeship"/>
</beans>
//Component注解中的value属性可忽略,默认是类型把首字母小写
//value属性相当于bean标签中的id属性
//@Component(value = "userService")
@Service
public class UserService {
public void show() {
System.out.println("userService is showing...");
}
}
测试
@Test
public void test01() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.show();
System.out.println(userService);
}
输出
关于组件扫描配置中的其他属性use-default-filters
<!--use-default-filters 是否使用默认的扫描包过滤器,默认是扫描base-package下所有的类
false表明不使用默认的过滤器,紧接着就需要自定义过滤器,若是false,且没设置任何include-filter,则不会扫描任何类-->
<!-- <context:component-scan base-package="com.coffeeship" use-default-filters="false">
<!–include-filter表明扫描包含哪些符合规则的类,比如注解类型是org.springframework.stereotype.Service的–>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>-->
<!--未赋值use-default-filters属性为false,说明使用默认的过滤器,则扫描 扫描包下的所有类-->
<context:component-scan base-package="com.coffeeship">
<!--使用默认的过滤器了,则没必要设置include-filter,但可以设置exclude-filter,表明排除哪些符合规则的类-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
注入属性
@Autowired 根据属性类型自动装配,Spring提供
@Qualifier 根据属性名称自动装配,Spring提供
@Resource 根据属性类型或属性名称注入,JDK提供(扩展包javax中)
以上注解的使用比较简单,需要注意的是,@Autowired是根据属性类型注入,假设注入的属性是一个接口的实现类(多态的方式),eg.UserDao userDao = new UserDaoImpl(),但接口的实现类可能有多个,则此时需要增加@Qualifier注解,它通常和@Autowired一起搭配使用,eg.@Qualifier("userDaoImpl")
@Service
public class UserService {
//@Autowired,根据属性类型自动装配
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
public void show() {
System.out.println("userService is showing...");
userDao.show();
}
}
public interface UserDao {
public void show();
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void show() {
System.out.println("userDaoImpl is showing...");
}
}
@Resource 根据属性类型装配,@Resource(name="属性名") 根据属性名称装配
@Service
public class UserService {
//@Autowired,根据属性类型自动装配
// @Autowired
//@Qualifier("userDaoImpl")
// @Qualifier(value = "userDaoImpl")
// @Resource //根据属性类型自动装配
@Resource(name="userDaoImpl")
private UserDao userDao;
public void show() {
System.out.println("userService is showing...");
userDao.show();
}
}
字面量属性注入:@Value
@Service
public class UserService {
//@Autowired,根据属性类型自动装配
// @Autowired
//@Qualifier("userDaoImpl")
// @Qualifier(value = "userDaoImpl")
// @Resource //根据属性类型自动装配
@Resource(name = "userDaoImpl")
private UserDao userDao;
// @Value("脱口秀")
@Value(value = "脱口秀")
private String showName;
public void show() {
System.out.println("userService is showing...");
userDao.show();
System.out.println(showName);
}
}
完全注解开发
将目前为止仍在xml配置文件中配置的组件扫描配置,转化为注解实现!解决:配置类
@Configuration
@ComponentScan(basePackages = {"com.coffeeship"})
public class SpringConfig {
}
测试类
public class TestDemo {
@Test
public void test01() {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.show();
System.out.println(userService);
}
@Test
public void test02() {
//加载配置类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.show();
System.out.println(userService);
}
}
注意:实际开发中直接使用@SpringbootApplication注解表明一个程序入口方法,本身又包含@Configuration等注解。
另外,引入外部配置文件,eg.jdbc.properties,用到的注解@PropertySource({"classpath:jdbc.properties"}),搭配@Value("${prop.driverClassName}")将配置文件中的键值进行属性注入。
@Import:引入其他配置类
@Bean:将方法返回对象加入IoC容器
IoC高级特性
Lazy-Init 延迟加载
<!--lazy-init属性,延迟加载bean意味着加载配置文件时并不会创建bean实例,而是getBean时才创建-->
<!--默认是false,即加载配置文件的时候就实例化对象了,注意,此属性仅对scope为singleton时有效-->
<bean id="user" class="com.coffeeship.dao.User"/>
<!--<bean id="user" class="com.coffeeship.dao.User" lazy-init="true"/>-->
默认加载配置文件时就已经创建Bean对象了:
设置lazy-init属性为true后:
延迟加载的全局配置 default-lazy-init="true"
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
延迟加载的应用场景
1.开启延迟加载一定程度上是可以提高容器的启动效率的
2.更常见的应用延迟加载,是对于一些不太常用的Bean,将它们设置为延迟加载,仅在偶尔使用时再去加载,没必要在容器启动时就一直占用资源。虽然,一直不用,可能会被垃圾回收掉。
FactoryBean
注意区分BeanFactory,面试可能会问二者的区别,但其实二者根本没有可比性,仅仅都是接口而已。BeanFactory是容器的顶级接口,定义了容器的基础功能,比如Bean管理中的getBean()等等,可以与之进行区分的是其子接口ApplicationContext;而FactoryBean,即工厂Bean,需要和普通Bean进行区分对比,工厂Bean可以返回一个与传入的Class属性不一致的类对象,原因就在于其实现了FactoryBean接口中getObject()方法,在其中可以自定义返回的类对象。普通Bean返回的Bean对象就是传入的Class的对象。
FactoryBean接口除了getObject()和getObejctType()方法外,还有一个isSingleton()方法,若实现了isSingleton(),且返回true,则创建的Bean实例会放到applicationContext(容器)的单例缓存池中,缓存池的数据结构就是一个Map,键是bean id,值就是Bean对象。
在上面IoC容器的Bean管理 - 普通Bean和工厂Bean的案例中:
若将getBean中的bean id前加上'&',即可获取普通Bean。
@Test
public void test07() {
//ApplicationContext在加载配置文件时就会创建对象实例
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factorybean.xml");
// Student student = applicationContext.getBean("mybean", Student.class);
// System.out.println(student);
MyBean myBean = applicationContext.getBean("&mybean", MyBean.class);
System.out.println(myBean);
后置处理器
Spring提供的两种后置处理器的接口:
BeanPostProcessor:Bean级别(对象级别)
BeanFactoryPostProcessor:BeanFactory级别(容器级别)
参考Spring Bean的生命周期
第1步:实例化Bean
第2步:属性注入
第3步:调用BeanNameAware的setBeanName方法
第4步:调用BeanFactoryAware的setBeanFactory方法
第5步:调用ApplicationContextAware的setApplicationContext方法
第6步:调用BeanPostProcessor的预初始化方法
第7步:调用@PostConstruct,初始化后执行的方法
第8步:调用InitializingBean的afterPropertiesSet方法
第9步:调用自定义的初始化方法init-Method
第10步:调用BeanPostProcessor的后初始化方法
第11步:调用DisposableBean的destroy方法
第12步:调用自定义的销毁方法destroy-Method或@PreDestroy注解标识的方法
注意:@PostConstruct和@PreDestroy注解需要结合使用 Spring 框架或其他支持 JSR-250 注解的容器,以确保该注解生效。
对Bean的生命周期更深层次的了解,还可以去学习和查阅一下BeanDefinition这个接口。
BeanFactoryPostProcessor接口基本很少使用。
总结&面试题
关于IoC容器,首先,我们自己应该对它都有一个大概的理解。然后,应该懂得IoC容器的作用,再根据作用去构建底层逻辑。这个是个人的一个小看法,如有建议,欢迎交流!
1.你是怎么理解IoC的?
IoC,即控制反转。以前,对象的创建、对象之间复杂的调用关系都是由开发人员去负责的;而通过实现IoC,可以把这部分任务交给框架去负责。在Spring里,IoC思想是通过IoC容器实现的。
2.IoC容器的初始化过程
IoC容器要做的就是对象的创建和属性的注入。这两部分都可以通过XML配置和注解的方式实现。当我们通过ClassPathXmlApplicationContext去加载一个配置文件时,会调用其中的refresh()方法
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
这个方法又是在其继承的非直接父类AbstractApplicationContext中实现的
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
//刷新上下文环境
this.prepareRefresh();
//obtainFreshBeanFactory()是容器初始化的入口
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
其中,比较重要的就是obtainFreshBeanFactory()方法。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
return this.getBeanFactory();
}
后面一系列的方法调用,可以去最大的学习网站B站上搜索对应的源码解析视频,在写这篇笔记的时候,本人看了几遍这个视频,还是觉得脑子混乱,就不在这献丑了。加油!
或者通过AnnotationConfigApplicationContext加载一个配置类。
3.依赖注入(属性注入)的方式?
注入属性的方式在上面介绍xml配置和注解中,分别是xml配置可以通过property标签、p名称空间等实现,以及注解通过@Autowired、@Qualifier、@Resource、@Value来实现。
参考优秀文章: