Spring 笔记:(一)IoC容器基础【更新中】
Spring6 (for 理解&记忆)
1、IoC容器基础
2、Spring高级特性
3、SpringEL表达式
4、AOP面向切片
5、数据库框架整合
6、实现原理探究
(一)IoC容器基础
Spring为了简化开发而生,它是轻量级的IoC和AOP的容器框架。

Spring框架的Maven依赖坐标:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.10</version>
</dependency>一、用IoC容器管理bean
如何让Spring帮我们管理bean?
在resource中创建Spring配置文件。
(在Resource中创建的文件,编译时会被一起放到类路径下)
// 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="student" class="com.test.bean.Student"/>
</beans>在main中,创建应用程序上下文,它代表的就是IoC容器,负责实例化、配置和组装Bean
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlAppicationContext("spring.xml");
Student student = (Student) context.getBean("student");
student.hello();
}给bean起别名
<bean name="a" class="com.test.bean.Student"/>
<alias name="a" alias="test"/>设置作用域
默认情况下为单例模式,获取的对象始终为同一个,即singleton作用域,可以修改作用域,每次都获取新的对象
<bean class="com.test.bean.Student" scope="prototype"/>设置懒加载
如果希望单例模式下,bean不用一开始就加载,可以开启懒加载,需要时再加载。
<bean class="com.test.bean.Student" lazy-init="true"/>设置加载顺序
单例模式下,bean由IoC容器加载,但加载顺序我们不清楚,如果某个bean依赖于另一个bean,另一个bean必须要在这个bean之前创建,我们可以使用depends-on来设定前置加载bean。
<bean name="teacher" class="com.test.bean.Teacher"/>
<bean name="student" class="com.test.bean.Student" depends-on="teacher"/>二、DI依赖注入&IoC控制反转
解决痛点
使用DI前:
public class ClassA {
private Teacher teacher = new ArtTeacher();
}
public class ClassB {
private Teacher teacher = new ArtTeacher();
}
public class ClassC {
private Teacher teacher = new ArtTeacher();
}- 代码重复:创建逻辑分散在各个类中,修改时需要修改多处代码
- 违反开闭原则:每次需求变更都需要修改ClassX类,难维护
- 代码耦合:ClassX类直接依赖于具体实现类,硬编码,耦合高
使用DI后:
当需要新增一种Teacher时:
- 新增实现类
- 修改配置文件
- 不需要修改任何已有的业务类
<!-- 所有依赖关系在配置文件中一目了然 -->
<bean name="artTeacher" class="com.test.bean.ArtTeacher"/>
<bean name="programTeacher" class="com.test.bean.ProgramTeacher"/>
<bean name="student1" class="com.test.bean.Student">
<property name="teacher" ref="artTeacher"/>
</bean>
<bean name="student2" class="com.test.bean.Student">
<property name="teacher" ref="programTeacher"/>
</bean>对于Springboot,利用配置类方式 - 新增业务类完全不需要修改业务代码
配置类方式,简单工厂模式,业务类依赖接口不依赖具体实现类,具体实现类由配置类提供bean,简单工厂的参数由application.properties配置提供,新增业务类只需修改简单工厂,修改依赖的业务类只需修改配置
@Configuration
public class TeacherConfiguration {
@Bean
public Teacher teacher(@Value("${teacher.type:art}") String type) {
switch(type) {
case "art": return new ArtTeacher();
case "program": return new ProgramTeacher();
case "math": return new MathTeacher(); // 新增时只需修改这里
default: return new ArtTeacher();
}
}
}
// 业务类完全不变
@Component
public class Student {
@Autowired
private Teacher teacher; // 注入哪个Teacher由配置决定
}application.properties配置:
# 需要切换时只需修改这里
teacher.type=math如何进行依赖注入
注入bean
public class Student {
private Teacher teacher;
}在Student类中注入Teacher:
<bean name="teacher" class="com.test.bean.ProgramTeacher"/>
<bean name="student" class="com.test.bean.Student">
<property name="teacher" ref="teacher"/>
</bean>其中,property的name是student的属性名,ref是要注入的bean的name。
public class Student {
private Teacher teacher;
//要使用依赖注入,我们必须提供一个set方法(无论成员变量的访问权限是什么)命名规则依然是驼峰命名法
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
...要使用依赖注入,我们必须提供一个set方法,set + 属性名。
这样就注入成功了。
当需要修改Teacher实现类时,只需要修改xml。
<!-- 只需要修改这里的class即可,现在改为ArtTeacher -->
<bean name="teacher" class="com.test.bean.ArtTeacher"/>
<bean name="student" class="com.test.bean.Student">
<property name="teacher" ref="teacher"/>
</bean>注入值
从ref变成value
<bean name="student" class="com.test.bean.Student">
<property name="name" value="ZhangSan"/>
</bean>注入List/Map/Set等集合&数组
同样属性和对应set方法,几个属性就几个property。
public class Student {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
}<bean id="student" class="com.test.bean.Student">
<!-- 注入 List -->
<property name="hobbies">
<list>
<value>篮球</value>
<value>编程</value>
<value>阅读</value>
</list>
</property>
<!-- 注入 Set -->
<property name="subjects">
<set>
<value>数学</value>
<value>英语</value>
<value>物理</value>
<value>数学</value> <!-- 重复项会被自动去重 -->
</set>
</property>
<!-- 注入 Map -->
<property name="scores">
<map>
<entry key="数学" value="95"/>
<entry key="英语" value="88"/>
<entry key="物理" value="92"/>
</map>
</property>
<!-- 注入数组 -->
<property name="nicknames">
<array>
<value>小明</value>
<value>明明</value>
<value>阿明</value>
</array>
</property>
</bean>注入Properties:对应 Java 类型:java.util.Properties
<property name="config">
<props>
<prop key="timeout">30s</prop>
<prop key="retries">3</prop>
</props>
</property>注入其他 Bean 到集合中:集合不仅可以存基本类型(String, int 等),也可以存 其他 bean 对象。
public class Student {
private List<Teacher> teachers;
public void setTeachers(List<Teacher> teachers) {
this.teachers = teachers;
}
}<bean id="teacher1" class="com.test.bean.Teacher"/>
<bean id="teacher2" class="com.test.bean.Teacher"/>
<bean id="student" class="com.test.bean.Student">
<property name="teachers">
<list>
<ref bean="teacher1"/>
<ref bean="teacher2"/>
</list>
</property>
</bean>在构造时依赖注入(提前)
public class Student {
private final Teacher teacher; //构造方法中完成,所以说是一个final变量
public Student(Teacher teacher){ //Teacher属性是在构造方法中完成的初始化
this.teacher = teacher;
}
...bean是由IoC容器进行创建的,现在我们提供了带参构造函数,Java就不再提供默认无参构造函数。因此再使用之前的xml,会报错找不到匹配的构造函数(xml的bean默认是找无参构造)
因此,需要在xml中指明构造方法:
<bean name="teacher" class="com.test.bean.ArtTeacher"/>
<bean name="student" class="com.test.bean.Student">
<constructor-arg name="teacher" ref="teacher"/>
</bean>public class Student {
private final String name;
public Student(String name) {
System.out.println("我是一号构造方法");
this.name = name;
}
public Student(int age) {
System.out.println("我是二号构造方法");
this.name = String.valueOf(age);
}
}此时我们希望使用的是二号构造方法,那么怎么才能指定呢?有2种方式,我们可以给标签添加类型:(value/type)
<bean name="student" class="com.test.bean.Student">
<constructor-arg value="1" type="int"/>
</bean>也可以指定为对应的参数名称:(value/name)
<bean name="student" class="com.test.bean.Student">
<constructor-arg value="1" name="age"/>
</bean>在构造时多参数注入
可以写多个constructor=arg,会自动匹配符合参数最多的构造方法。
默认按参数顺序:
<bean id="student" class="com.test.bean.Student">
<constructor-arg value="张三"/>
<constructor-arg value="18" type="int"/>
<constructor-arg ref="teacher"/>
</bean>或index指定顺序:
<bean id="student" class="com.test.bean.Student">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="18" type="int"/>
<constructor-arg index="2" ref="teacher"/>
</bean>或name指定参数名,必须在编译时加上 -parameters 参数
<bean id="student" class="com.test.bean.Student">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="18" type="int"/>
<constructor-arg name="teacher" ref="teacher"/>
</bean>为什么<property>注入不需要-parameters,而<constructor-arg name="...">需要?
因为:
<property name="teacher">→ Spring 用 JavaBean 规范 自动生成方法名setTeacher,通过 方法名 + 参数类型 定位 setter,完全绕开了参数名。<constructor-arg name="age">→ Spring 必须读取构造函数的 真实参数名,而 Java 默认不保存它,所以需要-parameters来保留。
Setter 注入靠“约定”(命名规则),构造器注入靠“元数据”(参数名)。
自动装配
不再手写property,而是让IoC容器自己寻找。
<bean name="student" class="com.test.bean.Student" autowire="byType"/>当有多个候选项时,无法自动装配:
解决方法一:选定一个bean,关闭其他候选bean的自动装配
autowire-candidate="false"
<bean name="teacher" class="com.test.bean.ArtTeacher"/>
<bean name="teacher2" class="com.test.bean.ProgramTeacher" autowire-candidate="false"/>
<bean name="student" class="com.test.bean.Student" autowire="byType"/>解决方法二:选定一个bean,设定primary属性,作为首选项
primary="true"
<bean name="teacher" class="com.test.bean.ArtTeacher" primary="true"/>
<bean name="teacher2" class="com.test.bean.ProgramTeacher"/>
<bean name="student" class="com.test.bean.Student" autowire="byType"/>三、bean的生命周期/bean的继承/工厂bean
生命周期
继承
工厂bean
四、使用注解开发
本文系作者 @xiin 原创发布在To Future$站点。未经许可,禁止转载。
暂无评论数据