Spring6 (for 理解&记忆)

1、IoC容器基础

2、Spring高级特性

3、SpringEL表达式

4、AOP面向切片

5、数据库框架整合

6、实现原理探究

(一)IoC容器基础

Spring为了简化开发而生,它是轻量级的IoC和AOP的容器框架。

2026-02-07T14:46:08.png

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时:

  1. 新增实现类
  2. 修改配置文件
  3. 不需要修改任何已有的业务类
<!-- 所有依赖关系在配置文件中一目了然 -->
<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

id是Bean的身份证号(唯一且严格),name是Bean的昵称(可以有多个,更灵活)

bean的生命周期

AppilcationContext context = new ClassPathXmlApplicationContext("test.xml");
// 创建容器,加载bean,执行对象初始化方法
context.close();
// 关闭容器,执行对象销毁方法,销毁bean

其中,初始化方法和bean方法需要在xml中为bean指定:

<bean name="student" class="com.test.bean.Student" init-method="init" destroy-method="destroy"/>
public void init() {
    // init
}
public void destroy() {
    // destroy
}

bean的继承

bean的继承不是类的继承,而是属性的继承

父bean可以设为abstract,防止被实例化

<!-- 父bean定义(通常设为abstract,防止被实例化) -->
<bean id="baseService" abstract="true">
    <property name="dataSource" ref="dataSource"/>
    <property name="timeout" value="5000"/>
</bean>

<!-- 子bean继承父bean配置 -->
<bean id="userService" class="com.example.UserService" parent="baseService">
    <!-- 可以覆盖父bean的属性 -->
    <property name="timeout" value="3000"/>
    <!-- 添加自己的属性 -->
    <property name="userDao" ref="userDao"/>
</bean>

工厂模式与工厂bean

静态工厂方法(工厂类未注册为 Bean)

public class Student {
    Student() {
        System.out.println("我被构造了");
    }
}

除了让spring容器直接调用构造方法外,还可以用工厂方法创建对象:

先写工厂类,

public class StudentFactory {
    public static Student getStudent(){
        return new Student();
    }
}

然后配置xml,填写的class是Student的工厂类StudentFactory、factory-method工厂方法,但实际注册的是工厂方法的返回类型——Student类

<bean class="com.test.bean.StudentFactory" factory-method="getStudent"/>

工厂类 不是 Spring 容器管理的 Bean,因此:

  • 无法注入其他依赖;
  • 无法参与 Spring 生命周期(如 @PostConstructInitializingBean 等);
  • 无法被 AOP 代理或拦截。

实例工厂方法(工厂类注册为 Bean)

我们将工厂类也注册为bean,称为工厂bean

<!--工厂bean-->
<bean name="student" class="com.test.bean.StudentFactory"/>

<!--bean-->
<bean factory-bean="studentFactory" factory-method="getStudent"/>

工厂类 是 Spring 容器管理的 Bean,因此:

  • 可以注入其他依赖(如数据库连接、配置属性等);
  • 可以参与完整的 Spring 生命周期;
  • 可以被 AOP 切面增强(如日志、事务等);

关于获取bean:

context.getBean()填参数factory的name,获取到的是实例bean,

而获取FactoryBean本身需要加&前缀:

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

// 获取FactoryBean创建的对象
Student bean = (Student) context.getBean("studentFactory");

// 获取FactoryBean本身(加&前缀)
StudentFactory bean = (StudentFactory) context.getBean("&studentFactory");

四、使用注解开发

@Configuration配置类

ApplicationContext context = new AnnotationConfigApplicationContext();

在之前,我们使用

<?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">
</beans>

现在,只需要一个配置类就可以了

@Configuration
public class MainConfiguration {
}

为AnnotationConfigApplicationContext指定一个默认的配置类

ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
//这个构造方法可以接收多个配置类

通过@Import还可以引入其他配置类:

@Import(LBWConfiguration.class)
@Configuration
public class MainConfiguration {

@Bean

@Bean(name = "", initMethod = "", destroyMethod = "", autowireCandidate = false)
@Lazy(true)     //对应lazy-init属性
@Scope("prototype")    //对应scope属性
@DependsOn("teacher")    //对应depends-on属性
public class Student(){
    Student() {
        
    }
}

@PostConstruct和@PreDestroy

效果和init-method和destroy-method是一样的

@PostConstruct
public void init(){
    System.out.println("我是初始化方法");
}

@PreDestroy
public void destroy(){
    System.out.println("我是销毁方法");
}

自动装配

基于构造器或Setter的依赖注入
@Configuration
public class MainConfiguration {
    @Bean
    public Teacher teacher(){
        return new Teacher();
    }

    @Bean
    public Student student(Teacher teacher){
        return new Student(teacher);
    }
}
@Autowired

用于字段:

public class Student {
    @Autowired   //使用此注解来进行自动装配,由IoC容器自动为其赋值
    private Teacher teacher;
}

也可以用于set方法:

public class Student {
    private Teacher teacher;

    @Autowired
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}
@Qualifier

当有相同类型的类时,不能直接根据类型匹配,需要配合@Qualifier注解,进行名称匹配:

@Bean("a")
public Teacher teacherA(){
    return new Teacher();
}

@Bean("b")
public Teacher teacherB(){
    return new Teacher();
}

public class Student {
    @Autowired
    @Qualifier("a")   //匹配名称为a的Teacher类型的Bean
    private Teacher teacher;
}
@Autowired与@Resource
  • @Resource默认ByName,如果找不到则ByType,可以添加到set方法、字段上。
  • @Autowired默认是byType,只会根据类型寻找,可以添加在构造方法、set方法、字段、方法参数上。
@Component
public class UserService {
    
    // 规则1:先按 name 查找:
    // 如果@Resource设置了参数("name = xxx"),就根据xxx查找对应name的Bean
    // 否则按字段名 "userRepository" 查找 Bean:@Bean("userRepository")
    @Resource
    private UserRepository userRepository;
    
    // 规则2:如果找不到,再按类型 UserRepository 查找
}

注意,Spring会为bean添加默认name:

比如:(1)UserRepository类默认name="userRepository"(小驼峰)

@Component
public class UserRepository {
}
UserRepository userRepository = (UserRepository) context.getBean("userRepository");   //这样同样可以获取到
System.out.println(userRepository);

(2)通过给工厂方法添加@Bean注解注册的bean,默认名称是对应的方法名称

@Bean
public Student artStudent(){
    return new Student();
}
Student student = (Student) context.getBean("artStudent");
System.out.println(student);

因此,在上例中,@Resource通过byName就能直接匹配成功(字段名=bean name)

工厂模式与工厂bean

对于工厂模式,Spring也提供了接口,我们可以直接实现接口,表示这是一个工厂bean:

@Component
public class StudentFactory implements FactoryBean<Student> {
    @Override
    public Student getObject() {  //生产的bean对象
        return new Student();
    }
    @Override
    public Class<?> getObjectType() {  //生产bean的类型
        return Student.class;
    }
    @Override
    public boolean isSingleton() {  //是否采用单例模式
        return false;
    }
}
分类: Java后端 标签: JavaSpring

评论

全部评论 1

  1. xiin
    xiin
    Google Chrome Windows 10

    本文纯手打,耗时两天两夜[加油]
    参考资料:柏码-SSM笔记(一)Spring基础

目录