Spring框架【java全端课29】

120 阅读11分钟

【java全端课24】# 一、框架概述

1.1 框架概念

目前市面主流框架有很多,本阶段主要学习:SSM

  • Spring:为所有bean(组件)提供管理解决方案
  • SpringMVC:为解决表述层(控制层)常见问题,而提供的一整套解决方案,如:处理请求数据,响应数据,RESTFul等问题
  • Mybatis:为解决数据访问层(Dao层),而提供一整套解决方案,如:简化传统JDBC代码,优化入参出参等问题。

1.2 组件与容器概念

组件:具有一定功能的对象,常见组件如下:

  • Controller层组件
  • Service层组件
  • Dao层组件

容器:管理组件的对象,包括组件的创建,获取及销毁等

  • 程序中容器
    • 简单容器:数组,集合等
    • 复杂容器:Spring框架

二、Spring Framework简介

2.1 Spring广义与狭义

spring官网:spring.io/

广义的 Spring:Spring 技术栈(全家桶)

广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。

  • 经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

  • 这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的 Spring:Spring Framework(基础框架)

  • 狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

  • Spring Framework(Spring框架)是一个开源的应用程序框架,由SpringSource公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛应用于Java企业开发领域。

  • Spring全家桶的其他框架都是以SpringFramework框架为基础!

2.2 Spring Framework概述

Spring是基于IOC和AOP的容器框架

  • IoC容器 | 核心容器

    Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

  • IoC(Inversion of Control)控制反转

    IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

  • DI (Dependency Injection) 依赖注入

    DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

    管理对象中属性

  • AOP:Aspect Oriented Programming面向切面编程思想,它可以在不修改源代码的情况下,给程序动态统一添加额外功能,AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。

  • 主要功能模块如下

    功能模块功能介绍
    Core Container核心容器,控制反转和依赖注入
    AOP&Aspects面向切面编程
    TX声明式事务管理
    Testing快速整合测试环境
    Data Access/Integration提供了对数据访问/集成的功能。
    Spring MVC提供了面向Web应用程序的集成功能。

2.3 注意

Spring 使创建 Java 企业应用程序变得容易。从Spring Framework 6.0开始,Spring 需要 Java 17+。

2.4 Spring Framework底层实现

Spring框架底层使用BeanFactory接口实现的,具体相关接口及实现类如下:

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!

ApplicationContextBeanFactory 的子接口。它补充说:

  • 更容易与 Spring 的 AOP 功能集成
  • 消息资源处理(用于国际化)
  • 特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext 简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContextBeanFactory 的完整超集!

三、SpringIOC/DI基本实现

实现SpringIOC核心思想

  • 将组件装配到SpringIOC容器对象中
  • 创建SpringIOC容器对象
  • 通过SpringIOC容器对象获取组件

实现Spring框架搭建方式

  • 方式一:纯XML方式
  • 方式二:XML+注解方式
  • 方式三:配置类+注解方式

实现Spring框架环境准备

3.1 基于XML方式装配组件

3.1.1 环境准备

<dependencies>
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.6</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
     <dependency>
         <groupId>org.junit.jupiter</groupId>
         <artifactId>junit-jupiter-api</artifactId>
         <version>5.10.2</version>
         <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
    </dependency>
</dependencies>

3.1.2 实现关键步骤

  • 准备POJO类:Student

POJO(Plain Old Java Object)“普通的Java对象”

```java
package com.mytest.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;

}
```
  • 创建配置文件: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">
        <!--    将Student装配到IOC容器-->
        <bean id="zs" class="com.mytest.bean.Student">
            <property name="stuId" value="1001"></property>
            <property name="stuName" value="zs"></property>
            <property name="stuAge" value="18"></property>
        </bean>
    </beans>
    
  • 测试类

@Test
public void testStudent(){
    //创建容器对象
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //从容器获取Student
    Student bean = ioc.getBean(Student.class);
    System.out.println("bean = " + bean);
}

3.1.3 xml方式完成三层搭建

之前:

之后: 不再需要 new

3.2 基于配置类装配组件

约定 > 注解 > 配置 > 代码

3.2.1 环境准备(同上)

  • 全类名 = 注解所在的类
  • id = 默认类名(首字母小写)
    • 或者 value="" 定义,参数只有一个value,可以省略,直接写""

进阶版,完全不用xml,使用配置类来代替xml。如下:3.2.2

3.2.2 实现关键步骤

  • 准备POJO类:Student

    • 代码同上
  • 创建配置类:SpringConfig

  1. @Value
  • property 的注解是成员变量上的 @Value("")
  • 注意:只能为字面量类型注入数值
    • 字面量:Java基本数据类型+String
  1. @Autowired 为对象中属性自动注入(非字面量,自动装配)
  • @Autowired 装配规则:先byType 再byId
  1. @Qualifier 为对象中属性自动注入(非字面量,自动装配)
  • 不能单独使用,一般匹配@Autowired共同使用
  • 目的为自动注入字段设置id
  1. @Resource 与 @Autowired 一样,不过装配规则相反

image.png

  package com.mytest;
  
  import com.mytest.bean.Student;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.ComponentScan;
  import org.springframework.context.annotation.Configuration;
  
  @Configuration //标识当前类是一个[配置类:代替xml配置文件]
  public class SpringConfig {
  
      //将student对象装配到IOC容器中,id名称=方法名称
      @Bean
      public Student student(){
          return new Student(1001, "zhangsan", 18);
      }
  
  }
  • 测试类

    @Test
    public void testSpring(){
        //创建容器对象(基于配置类)
        ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
        Student bean = ioc.getBean(Student.class);
            System.out.println("bean = " + bean);
    }
    

3.3 @Bean注解详解

在Spring框架中,@Bean注解是用于方法级别的注解,它告诉Spring容器这是一个bean的定义,并且该方法会返回一个对象,这个对象应该被注册为Spring应用上下文中的一个bean。@Bean注解通常与@Configuration类一起使用,以声明性的方式配置Spring IoC容器。

3.3.1 @Bean源码

package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

@Bean注解中属性说明

  • name:指定bean的名称。如果不提供,默认会采用方法名作为bean的名称。
  • value和name属性是别名关系,即它们代表相同的配置项。
  • initMethod:指定初始化方法,在bean注入完成之后执行。
  • destroyMethod:指定销毁方法,在包含bean的应用上下文关闭时调用。
  • autowireCandidate: 定义了是否将此bean视为自动装配的候选者。默认值为true,意味着除非明确设置为false,否则该bean是可以作为其他bean依赖注入的候选对象。

3.3.2 使用@Bean装配DruidDataSource(第三方Bean)

  • 导入DruidDataSource依赖
<!--DuirdDataSource坐标 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
<!--DruidDataSource启动器坐标(与上面坐标二选一即可)-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
  • 编写配置文件:application.properties
#配置DruidDataSource
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/0923_demo
jdbc.username=root
jdbc.password=root
  • 编写配置类:

    • 未用属性类
    package com.mytest.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.mytest.properties.JdbcProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    
    @Configuration
    public class DruidConfig {
    
       	@Value("${jdbc.driverClassName}")
        private String driverClassName;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
        
        @Bean
        public DataSource dataSource(){
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName(driverClassName);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            return ds;
        }
    
    }
    
    • 使用属性类:
package com.mytest.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component("jdbcProperties")
@ConfigurationProperties(prefix = "jdbc")
@Data
public class JdbcProperties {

    private String driverClassName;
    private String url;
    private String username;
    private String password;

}
package com.mytest.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.mytest.properties.JdbcProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    @Autowired
    @Qualifier("jdbcProperties")
    private JdbcProperties jdbcProperties;

    @Bean
    public DataSource dataSource(/*JdbcProperties jdbcProperties*/){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(jdbcProperties.getDriverClassName());
        ds.setUrl(jdbcProperties.getUrl());
        ds.setUsername(jdbcProperties.getUsername());
        ds.setPassword(jdbcProperties.getPassword());
        return ds;
    }

}
package com.mytest.day13_springboot.property;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

//@Data
@Component("jdbcProperties")
//定义属性类,可以指定属性前缀(prefix)
@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {

//    @Value("${jdbc.driverClassName}")
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    @Override
    public String toString() {
        return "JdbcProperties{" +
                "driverClassName='" + driverClassName + '\'' +
                ", url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package com.mytest.day13_springboot.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.mytest.day13_springboot.property.JdbcProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DruidConfig {

//    @Value("${jdbc.driverClassName}")
//    private String driverClassName;
//    @Value("${jdbc.url}")
//    private String url;
//    @Value("${jdbc.username}")
//    private String username;
//    @Value("${jdbc.password}")
//    private String password;

    @Autowired
    @Qualifier("jdbcProperties")
    private JdbcProperties jdbcProperties;


    @Bean("druidDataSource")
    public DruidDataSource getDruidDataSource() {

        DruidDataSource druidDataSource = new DruidDataSource();

        druidDataSource.setDriverClassName(jdbcProperties.getDriverClassName());
        druidDataSource.setUrl(jdbcProperties.getUrl());
        druidDataSource.setUsername(jdbcProperties.getUsername());
        druidDataSource.setPassword(jdbcProperties.getPassword());

        return druidDataSource;
    }

}
package com.mytest.day13_springboot;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.sql.SQLException;

@SpringBootApplication
public class Day13SpringbootApplication {

    public static void main(String[] args) throws SQLException {

        //springboot启动器,默认返回容器对象
        ConfigurableApplicationContext ioc = SpringApplication.run(Day13SpringbootApplication.class, args);

        //获取容器对象
//        ApplicationContext context = new AnnotationConfigApplicationContext();

        DruidDataSource dataSource = ioc.getBean(DruidDataSource.class);
        DruidPooledConnection connection = dataSource.getConnection();
        System.out.println("connection = " + connection);

    }

}

3.3.3 Bean生命周期

  • 生命周期的六个阶段
    1. 阶段一:加载Bean定义

      • Spring容器读取XML文件或其他配置文件,解析配置信息。
      • 将解析后的配置信息转换为Spring内部数据结构(BeanDefinition对象)。
      • 存储BeanDefinition对象,待进行组件实例化。
    2. 阶段二:实例化Bean组件

      • 根据BeanDefinition中的信息,实例化Bean对象。
      • 如果有依赖其他Bean的情况,先实例化被依赖的Bean。
      • 此步骤单纯实例化Bean和依赖的Bean组件,不会进行属性赋值。
    3. 阶段三:设置Bean属性: - Spring容器将根据BeanDefinition中的配置,通过setter方法或字段直接注入属性值。 - Spring容器属性和实例化过程是分离的,所有在配置的时候,组件声明和引用不分先后顺序。

    4. 阶段四:调用Bean的初始化方法

      • 如果Bean实现了InitializingBean接口,Spring将调用其afterPropertiesSet()方法。
      • 如果在XML配置中定义了init-method,则执行该方法。
      • 如果Bean使用了@PostConstruct注解,则执行被注解的方法。
      • 此阶段调用自定义初始化方法,可以进行相关的初始化工作,类似: Servletinit方法。
    5. 阶段五:Bean可以使用

      • 此时Bean已经初始化完成,可以被其他Bean引用或者容器直接使用。
    6. 阶段六:调用Bean的销毁方法阶段(仅适用于单例Bean):

      • 如果Bean实现了DisposableBean接口,Spring将调用其destroy()方法。
      • 如果在XML配置中定义了destroy-method,则执行该方法。
      • 如果Bean使用了@PreDestroy注解,则在销毁之前执行被注解的方法。
      • 此阶段调用自定义销毁方法,可以进行相关的初始化工作,类似: Servletdestroy方法。

  • 案例代码

    • POJO类:Student

      package com.mytest.bean;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.stereotype.Component;
      
      public class Student {
      
          private Integer stuId;
          private String stuName;
          private Integer stuAge;
      
          public Student() {
              System.out.println("Student==>构造器!!!");
          }
      
          public Student(Integer stuId, String stuName, Integer stuAge) {
              this.stuId = stuId;
              this.stuName = stuName;
              this.stuAge = stuAge;
          }
      
          public Integer getStuId() {
              return stuId;
          }
      
          public void setStuId(Integer stuId) {
              System.out.println("==>Student->setId()!!!");
              this.stuId = stuId;
          }
      
          public String getStuName() {
              return stuName;
          }
      
          public void setStuName(String stuName) {
              this.stuName = stuName;
          }
      
          public Integer getStuAge() {
              return stuAge;
          }
      
          public void setStuAge(Integer stuAge) {
              this.stuAge = stuAge;
          }
      
          public void initStudent() {
              System.out.println("==>Student->init-method()!!!");
          }
      
          public void destroyStudent() {
              System.out.println("==>Student->destroy-method()!!!");
          }
      
      }
      
    • 配置类:SpringConfig

      package com.mytest;
      
      import com.mytest.bean.Student;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration              //标识当前类是一个[配置类:代替xml配置文件]
      public class SpringConfig {
      
          @Bean(value = "student", initMethod = "initStudent", destroyMethod = "destroyStudent")
          public Student student(){
              Student student = new Student();
              student.setStuId(101);
              return student;
          }
      
      
      }
      
    • 测试类

      public class TestSpring {
      
          @Test
          public void testSpring(){
              //创建容器对象(基于配置类)
              ConfigurableApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
      		//从容器中获取Student对象
              Student bean = ioc.getBean(Student.class);
              System.out.println("bean = " + bean);
      		//关闭IOC容器对象
              ioc.close();
              
          }
      

3.3.4 Bean作用域

  • Bean作用域概念

    在Spring框架中,bean的作用域(@Scope)决定了该bean的生命周期和可见性。Spring提供了多种作用域来满足不同的需求。

  • 常用作用域

    取值含义创建对象的时机默认值
    singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
    prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时
    • 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
    取值含义创建对象的时机默认值
    request请求范围内有效的实例每次请求
    session会话范围内有效的实例每次会话
  • 案例代码

    • POJO类:Student

      package com.mytest;
      
      import com.mytest.bean.Student;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Scope;
      
      @Configuration              //标识当前类是一个[配置类:代替xml配置文件]
      public class SpringConfig {
      
          @Bean(value = "student", initMethod = "initStudent", destroyMethod = "destroyStudent")
      //    @Scope(value = "prototype")
          @Scope(value = "singleton")
          public Student student(){
              Student student = new Student();
              student.setStuId(101);
              return student;
          }
      }
      
    • 测试类

      @Test
      public void testSpring(){
      
          //创建容器对象(基于配置类)
          ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
      
          Student bean = ioc.getBean(Student.class);
          Student bean2 = ioc.getBean(Student.class);
          System.out.println("是否为同一对象:" + (bean==bean2));
          
      }