Spring IOC(控制反转)

Spring框架的两大理念之一,控制反转【Inversion of Control, IoC】


IOC容器

Spring的IOC容器用来创建、存储、销毁对象并维护对象之间的关系,IOC容器也被称为【Bean容器】。

IOC的概念

IOC全称【Inversion of Control】,意为控制反转,有的地方称为IOC容器,它是一项对象生成、获取和管理的技术,Java作为一门面向对象的语言,对象的管理至关重要。在传统的java编程中,对象一般通过new构建,二spring通过描述构建对象。

项目中会有各式各样的对象,这些对象也不是独立存在互不相关的,spring提供了依赖注入【Dependency Injection,DI】来管理各个对象之间的关系。

常见的关系有:依赖、关联、聚合、组合、继承、实现。

IOC容器介绍

spring通过工厂创建对象,涉及到的接口和类非常多,其中我们经常打交道的有:

  • BeanFactory:定义一系列获取bean的方法,根据不同参数,条件获取Bean,是整个IOC容器体系的根接口。
  • ApplicationContext:在BeanFactory基础上扩展了事件发布、资源加载、环境参数、国际化消息等功能。
  • ClassPathXmlApplicationContext:从类路径上读取spring声明文件来创建Bean。
  • FileSystemXmlApplicationContext:从文件中的spring声明文件来创建Bean。
  • AnnotationConfigApplicationContext:通过注解来创建Bean,需要指定要识别的包名,会识别通过诸如@Configuration、@Component、@Service、@Bean等描述的类。

ClassPathXmlApplicationContext实践

通过最原始的xml文件管理Bean,需要使用ClassPathXmlApplicationContext容器。

数据源对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package top.lime.entity;

public class User {
    private Long id;
    private String name;
    private Integer age;

    public User() {
    }

    public User(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}

xml配置

在resources目录下创建springContext.xml配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?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">
    <!-- spring描述Bean -->
    <!-- id属性是唯一的,不能重复 -->
    <bean id="user1" class="top.lime.entity.User">
        <!-- 指定具体的属性数据 -->
        <!-- property是通过set方法注入的,需要实体类实现set方法 -->
        <property name="id" value="1"/>
        <property name="name" value="zhangsan"/>
        <property name="age" value="18"/>
    </bean>
    <bean id="user2" class="top.lime.entity.User">
        <!-- 指定具体的属性数据 -->
        <!-- constructor-arg是通过构造方法注入的,需要实体类实现了构造方法 -->
        <constructor-arg name="id" value="2"/>
        <constructor-arg name="name" value="lisi"/>
        <constructor-arg name="age" value="24"/>
    </bean>
</beans>

获取Bean对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        //SpringApplication.run(App.class, args);
        //读取springContext.xml文件获取容器,并通过容器获取Bean
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("springContext.xml");
        //获取Bean,getBean()里面可以填Bean的id,也可填Bean的名字,对应xml里面Bean标签的id和name属性
        //默认返回的是Object类型,可以强转回原来的类型
        User user1 = (User) applicationContext.getBean("user1");
        //不想强转可以通过类型来获取Bean,如果有多个同类型的bean还要额外指定名字
        User user2 = applicationContext.getBean("user2"User.class);
        //打印结果
        System.out.println(user1);
        System.out.println(user2);
    }
}

FileSystemXmlApplicationContext实践

使用FileSystemXmlApplicationContext的话参数中填写文件路径,其余相同

获取Bean对象

1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        // 从文件系统获取,传入springContext.xml文件的路径,获取Bean
        FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\JavaWeb\\porject\\springContext.xml");
        User user2 = applicationContext.getBean("user2", User.class);
        System.out.println(user2);
    }
}

AnnotationConfigApplicationContext实践

此处介绍两种创建Bean方式,一种是在AnnotationConfigApplicationContext的构造方法中传入包名,另一种是在构造方法中传入配置类。

通过传入包名

在需要创建的Bean上通过@Component或者@Configuration等注解声明bean,相当于bean标签,通过@Value注解注入值。

数据源对象

这里通过引入的lombok依赖,在类上加注解,实现在编译时生成get/set和构造方法的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Data   //生成get/set方法
@AllArgsConstructor   //生成全参构造函数
@NoArgsConstructor   //生成空参构造函数
@Component    //通过注解创建对象,spring启动时扫描到有该注解的类,会当作一个Bean加载到IOC容器中
public class Book {
@Value("13")	//通过@Value注入参数
    private Long id;
    @Value("西游记")
    private String bookName;
    @Value("zhangsan")
    private String author;
}
获取Bean对象
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class App {
    public static void main(String[] args) {
        //通过注解获取Bean
        //通过包名扫描 比如:top.lime 就会扫描该包下所有的类和子包下的所有类
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("top.lime");
        //获取Bean name默认的是首字母小写的类名
        Book book1 = applicationContext.getBean("book");
        Book book2 = applicationContext.getBean(Book.class);
        System.out.println(book1);
        System.out.println(book2);
    }
}

通过注解扫描包

在主类上添加@ComponentScan包扫描注解,注意:该注解会扫描述的类所在包和其子包的注解

1
2
3
4
5
6
7
8
9
@ComponentScan(value = "top.lime")     //扫描top.lime包下的所有与@Component相关的注解
public class App {
    public static void main(String[] args) {
        //通过包扫描注解获取Bean
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(App.class);
        Book book = applicationContext.getBean("book", Book.class);
        System.out.println(book);
    }
}

通过@Bean注解创建Bean

通过Bean注解创建对象的话,首先需要将User类中的@Value注解取消掉,否则Bean注入的值是Value注解注入的。

数据源对象

1
2
3
4
5
6
@Data
public class Car {
    private Long id;
    private String carName;
    private String brand;
}

创建配置类

在配置类中生成Bean并注入IOC容器

@Configuration:该注解使用在类上,标记该类为一个配置类,用@Component、@Service都可以,只是@Configuration注解更利于理解,更规范。目的就是项目启动时可以识别该类,并且加载该类到IOC容器中。因为该类中有Bean的创建,所以需要加载它!

@Bean:该注解可以使用在方法注解上,一般都使用在方法上,表示被描述的方法会返回一个Bean,方法内室创建这个Bean的过程,BeanName默认是方法名,可以通过Bean注解的value属性或name属性自定义BeanName

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//表示CarConfig是一个配置类,他是在@Component的基础上衍生出来的
@Configuration
public class CarConfig {
    //创建Bean,Bean的名字默认是方法名
    @Bean   //如果需要指定名字则 @Bean(name="car1")
    public Car myCar() {
        Car car = new Car();
        car.setId(10L);
        car.setCarName("bird");
        car.setBrand("bbb");
        //返回对象,交给IOC管理
        return car;
    }
}

获取Bean对象

1
2
3
4
5
6
7
8
public class App {
    public static void main(String[] args) {
        //通过@Bean注解创建Bean,并获取
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(CarConfig.class);
        Car myCar = applicationContext.getBean("myCar", Car.class);
        System.out.println(myCar);
    }
}

装配Bean最佳实践

装配Bean使用全注解 + 配置类的方式装配自定义Bean,是目前通用最优的方式了,因为SpringBoot用的就是这种方式。如果要修改SpringBoot的默认配置,也是通过配置文件或者JavaConfig的方式修改。比如通过数据源的案例

数据源对象

1
2
3
4
5
6
7
@Data
public class MySQLDataSource {
    private String driverName;
    private String url;
    private String username;
    private String password;
}

创建配置类

配置类一般使用**@Configuration**注解描述,配置类通常用来配置系统信息或者诸如:数据库、消息队列、缓存等的第三方组件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Configuration
public class MySQLDataSourceConfig {
    @Bean
    public MySQLDataSource dataSource() {
        System.out.println("初始化数据库连接......");
        MySQLDataSource dataSource = new MySQLDataSource();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/demo");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
}

获取Bean对象

主类上使用**@ComponentScan**注解来配置要扫描的包,所有的类都应该放到此包下

注意:@SpringBootApplication注解的启动类在启动时会扫描自己所在的包及其子包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@SpringBootApplication
//@ComponentScan(value = "top.lime")     //扫描top.lime包下的所有与@Component相关的注解
public class App {
    public static void main(String[] args) {
        //最优方案,使用包扫描,springboot启动时会扫描启动类所在包及其子包,如果不在启动类所在包则可用
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(App.class);
        MySQLDataSource dataSource = applicationContext.getBean(MySQLDataSource.class);

    }
}

小结

  • 装配Bean时一般在一个主类上通过@ComponentScan设置包扫描
  • 如果是第三方的配置通过配置类装载Bean,使用@Configuration注解描述类

配置文件

对于SpringBoot项目来说,很多配置项都是做了默认配置的,比如tomcat端口号,日志的输出规则,我们如果想要修改默认配置,要么自定义配置类覆盖原配置,要么在配置文件中修改参数,覆盖原配置。

SpringBoot中的配置文件名字按照约定来说叫application.properties或者application.yml两种,放到resources目录下,这样项目启动时会自动读取,无需做其他配置,贴出两种配置文件,只是写法不同,推荐使用yml格式文件,是缩进类型的不需要写重复的公用前缀。

properties类型文件

1
2
3
4
# 设置端口号
server.port=8081
#设置编码
server.servlet.encoding.charset=UTF-8

yml类型文件

1
2
3
4
5
6
# yml文件是层级结构,比较简介,清晰
server:
  port: 8082
  servlet:
    encoding:
      charset: UTF-8

读取配置文件

读取配置文件是开发中非常重要的操作,因为配置文件中通常会写重要的配置项,配置项在JavaConfig实现的配置类中往往会使用到,并不会在代码写死配置。

通过@Value注解获取文件值

配置类中使用@Value注解获取配置文件内容,语法格式为

@Value("${配置文件中的key}")

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.stt.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfig {

    // 读取信息
    @Value("${spring.application.name}")
    private String name;
    @Value("${spring.application.version}")
    private String version;
    @Value("${spring.application.author}")
    private String author;

    public void getInfo() {
        System.out.println("name: " + name + ",version: " + version + ",author: " +author);
    }
}

通过@ConfigurationProperties注解获取值

配置文件中新增person信息为例

1
2
3
4
5
6
7
person:
    name: lime
    age: 18
    id_card: 815811
    hobby:		# 列表、数组、集合
        - fly
        - game

配置类内容如下,必须有set方法才可赋值成功:

  • 通过@Configuration注解表名是配置类,被Spring加载
  • 通过@ConfigurationProperties(prefix = “person”),指明前缀
  • 属性名就写配置文件的后缀,如果有下划线,使用驼峰命名就可以了
  • 类中不能有其他方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Data	// lombok 生成get/set方法
@Configuration
@ConfigurationProperties(prefix = "person")
public class PersonConfig {

    private String name;
    private Integer age;
    private String idCard;
    private List<String> hobby;
}

读取自定义文件

我们上边读取的文件是springboot约定好的文件【application.yml/properties】,如果定义一个application-db.properties文件。这个文件不是SpringBoot约定好的,此时就需要指定文件读取

1
2
3
4
db.classname= mysql
db.url= localhost
db.username= root
db.password= 123456

读取properties文件

配置类中可以通过**@PropertySource**注解指定要读取的配置文件,可以指定多个

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Configuration
@PropertySource(value = {"application-db.properties"})
@ConfigurationProperties(prefix = "db")
@Data
public class DBConfig {
    private String classname;
    private String url;
    private String username;
    private String password;
}

读取yml文件

如果读取的是yml文件,则首先需要自定义一个资源工厂类,因为SpringBoot默认只实现了读取properties文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class YamlPropertySourceFactory extends DefaultPropertySourceFactory{
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        Resource resourceResource = resource.getResource();//先得到资源文件
        if(!resourceResource.exists()){
            return new PropertiesPropertySource(null, new Properties());
        } else if(
            //判断得到的资源文件是否是yml文件
                resourceResource.getFilename().endsWith(".yml") ||
                        resourceResource.getFilename().endsWith(".yaml")){
            System.out.println("resourceResource = " + resourceResource.getFilename());
            //开始加载yaml文件
            List<PropertySource<?>> sources = new YamlPropertySourceLoader()
                    .load(resourceResource.getFilename(),resourceResource);
            return sources.get(0);
        }
        return super.createPropertySource(name, resource);
    }
}

接下来在@PropertySources注解上通过factory属性指定这个自定义的工厂

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Configuration
@PropertySources(value = {@PropertySource(value = {"application-db.yml"},factory = YamlPropertySourceFactory.class)})
@ConfigurationProperties(prefix = "db")
@Data
public class DBConfig {
    private String classname;
    private String url;
    private String username;
    private String password;
}

注意:@ImportResource注解用来加载xml文件,用SpringBoot就不需要xml文件了,这个注解基本不用

通过Environment获取文件数据

还可以通过Spring提供的Environment对象获取文件数据,这个对象就霸道了。每一个文件中的值都可以获取到,用法相对较少,因为读取配置文件数据一般用在第三方组件的配置类中,配置类都是相互独立的,不会说读取所有数据,一般也是仅仅获取改配置类所需要的配置数据就可以

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@RestController
public class UserController {

    @Autowired
    private Environment environment;

    public void controllerMethod() {
        System.out.println("SttController=======》controllerMethod()");
    }

    @GetMapping("value")
    public String getValue() {
        String username = environment.getProperty("db.username");
        String applicationName = environment.getProperty("spring.application.name");
        System.out.println(username);
        System.out.println(applicationName);
        return username + "  :  " + applicationName;
    }
}

profile

一般软件有开发环境、测试环境、生产环境,不同的环境有些配置可能不同,比如端口、回调地址、数据库地址、redis地址等,SpringBoot支持加载不同的配置文件,称之为profile。通常就是创建不同的配置文件,命名格式为application-${profile}.yml

默认只会加载application.yml文件不会加载其他文件。从日志也可以看出,下方红色框中的日志就表明没有激活任何profile,使用default

如果希望使用分环境加载,常见的方式有以下三种方式:

  • application.yml文件中通过spring.profiles.active配置
  • IDEA中配置参数,打包时不生效
  • 启动jar命令添加–spring.profiles.active配置

使用spring.profiles.active

指定dev环境

1
2
3
spring:
  profiles:
    active: dev

IDEA配置

首先点击编辑配置

选中项目,在有效配置文件选项中填入对应文件profile,点击应用和确定完成

如果同时存在则使用IDEA配置

通过命令注入参数

在启动jar时,通过java -jarxxx.jar –spring.profiles.active=prod,命令追加参数,会使用注入参数的配置文件,这个参数是注入到虚拟机里边的哦!会覆盖配置文件中的spring.profiles.active的配置

依赖注入

Bean之间相互依赖可以通过依赖注入【Dependency Injection, DI】功能将一个Bean注入到另一个Bean中使用。例如Controller层调用Service层,Service层调用Dao层。

简单的三层架构案例

下方列出核心代码,通过**@Autowired**注解注入对象。

Dao层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 接口
public interface IUserDao {
    int insertUser();
}

// 实现类
@Repository
public class UserDaoImpl implements IUserDao {
    @Override
    public int insertUser() {
        System.out.println("用户数据层添加用户");
        return 0;
    }
}

Service层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 接口
public interface IUserService {
    String createUser();
}

// 实现类
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IUserDao userDao;

    @Override
    public String createUser() {
        System.out.println("用户服务层创建用户");
        int result = userDao.insertUser();
        return result > 0 ? "添加成功" : "添加失败";
    }
}

controller层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@RestController
@RequestMapping("user")
public class UserController {
    
    @Autowired
    private IUserService userService;

    @RequestMapping("create")
    public String createUser() {
        System.out.println("创建用户接口");
        return userService.createUser();
    }
}

注入值的方式

Spring中注入值一般有三种方式:

  • @Autowired:Spring提供的注解,默认根据type(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类),当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个,这个时候,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。另外,如果type无法辨别注入对象时,也可以配合@Qualifier或@Primary注解来分辨注入类。
  • @Resource:JavaEE提供的注解,默认根据name注入,如果不成功则按照type注入,name属性解析为bean的名称,type属性解析为bean的类型,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
  • 构造方法:根据构造方法注入数据,原理与@Autowired相同,属性可以加final修饰,避免在业务中修改Bean的引用,增加安全性。

resource注解

1
2
@Resource
private IUserService userService;

构造方法

1
2
3
4
5
private IUserService userService;

public UserController(IUserService userService) {
    this.userService = userService;
}

setter方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@RestController
@RequestMapping("user")
public class UserController {
    
    private IUserService userService;

    // 使用set方法注入
    public void setUserService(IUserService userService) {
        this.userService = userService;
    }
}

歧义性对象现象

当使用@Autowired注入时,编译期间IDEA就会提示,同时启动项目时,JVM报错,也是说存在两个Bean,构造方法的方式也会出现同样的错误

注意:一般三层架构简引用不会存在一个接口实现多个实现类,但是当我们引入第三方组件,比如SpringSecurity、Druid等时一个组件可能会创建多个Bean,不同的功能会使用不同的Bean,就会出现这种问题。

解决方案

使用**@Autowired**注解有两种方案:

  • @Primary:在一个实现类上添加该注解,标明权重较高,当发现多个同类型Bean时优先使用被@Primary修饰的Bean。如果多个Bean都加了@Primary。那么IOC容器就又无法区分了
  • @Qualifier:使用在注入Bean的属性声明上,配置@Autowired实现根据类型和名字注入数据。

@Primary使用在实现类上

1
2
3
4
@Service
@Primary
public class UserServiceImpl implements IUserService {
}

@Qualifier使用在数据注入上,先根据Type注入,再根据Name注入

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    @Qualifier(value = "userServiceImpl")
    private IUserService userService;

}

对于构造方法和set方法也是这样解决

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@RestController
@RequestMapping("user")
public class UserController {

    private IUserService userService;

    public UserController(@Autowired @Qualifier(value = "userServiceImpl") IUserService userService) {
        this.userService = userService;
    }

    @RequestMapping("create")
    public String createUser() {
        System.out.println("创建用户接口");
        return userService.createUser();
    }
}

setter方法

1
2
3
4
5
@Autowired
@Qualifier("userServiceImpl")
public void setUserService(IUserService userService) {
    this.userService = userService;
}

当使用@Resource注解注入数据时,会先根据name注入,再根据type注入,name值默认和属性名一致。如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@RestController
@RequestMapping("user")
public class UserController {
    // name属性默认名为 userService。
    @Resource
    private IUserService userService;

    @RequestMapping("create")
    public String createUser() {
        System.out.println("创建用户接口");
        return userService.createUser();
    }
}

属性名为userService那么就会默认去找beanName为userService的Bean,其实容器中没有,会再根据type注入,会找到两个对象,无法确定注入哪一个,就会出现以下错误:注入资源失败,没有合格的top.lime.service.IUserService类型bean

解决方案就是根据name属性指明名字,或将属性名修改为正确的beanName

1
2
3
4
5
6
7
@Resource(name = "userServiceImpl")
private IUserService userService;



@Resource
private IUserService userServiceImpl;

IDEA不建议使用Autowired

当使用Autowired注解时,IDEA中给出警告,不建议使用字段注入.

但是使用@Resource注解时则没有警告提示。

使用字段注入有一定风险存在:

  1. 可能导致空指针异常:如果对象创建不使用Spring容器,而是直接使用无参构造方法new一个对象,此时使用注入的对象可能导致空指针异常。
  2. 不能使用final修饰字段:缺乏final修饰会使得类的依赖可变,进而可能引发一些不可预料的异常。通常情况下,可以使用构造方法注入来声明强制依赖的Bean,使用Setter方法注入来声明可选依赖的Bean。
  3. 可能更容易违反单一职责原则:这是一个关键原因。使用字段注入可能会轻易地在类中引入各种依赖,导致类的职责过多,但开发者往往难以察觉。相比之下,使用构造方法注入,当构造方法的参数过多时,会提示开发者重构这个类。
  4. 不利于写单元测试:在单元测试中,使用Field注入,必须使用反射的方式来Mock依赖对象。

推荐方案:

  • 当类有强依赖于其他Bean时,优先使用构造方法注入。
  • 对于可选依赖,可以使用Setter方法注入,并在代码中处理可能出现的引用对象不存在的情况。

小结

依赖注入有两个注解、构造方法、setter方法三种方式,推荐使用构造方法方式注入bean,可以避免不必要的空指针异常,和Bean引用被修改的问题。

Bean作用域

在Spring容器中,Bean的作用域默认是单例的,可以通过@Scope注解设置,有6种作用域:

  • prototype:一个bean定义可以有多个bean实例。
  • singleton:(默认的)在每个Spring IoC容器中,一个bean定义对应只会有唯一的一个bean实例。
  • request:一个bean定义对应于单个HTTP 请求的生命周期。也就是说,每个HTTP 请求都有一个bean实例,且该实例仅在这个HTTP 请求的生命周期里有效。该作用域仅适用于WebApplicationContext环境。
  • session:一个bean 定义对应于单个HTTP Session 的生命周期,也就是说,每个HTTP Session 都有一个bean实例,且该实例仅在这个HTTP Session 的生命周期里有效。该作用域仅适用于WebApplicationContext环境。
  • application:一个bean 定义对应于单个ServletContext 的生命周期。该作用域仅适用于WebApplicationContext环境。
  • websocket【这个作用范围没有找到在哪声明】:一个bean 定义对应于单个websocket 的生命周期。该作用域仅适用于WebApplicationContext环境。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Repository
// 声明作用范围
@Scope(value = "singleton")
public class UserDaoImpl implements IUserDao {
    @Override
    public int insertUser() {
        System.out.println("用户数据层添加用户");
        return 0;
    }
}

条件装配

条件装配就是在满足相应条件的时候再装载对应的Bean,比如连接数据库时当数据源存在的时候再加载连接数据库的Bean,条件装配可以使用@Conditional和其派生注解实现。

下方以数据库连接为例:

数据源

1
2
3
4
5
6
7
8
9
@Data
public class DataSource {

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

}

数据库连接

数据库连接时需要用到数据数据源的参数数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Data
public class DataCollectionService {

    private DataSource dataSource;

    public void collect() {
        String url = dataSource.getUrl();
        String driverClassName = dataSource.getDriverClassName();
        System.out.println("连接数据库======》");
        System.out.println("url====》" + url);
        System.out.println("driverClassName====》" + driverClassName);
    }
}

配置类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class ApplicationConfig {

    // 数据源配置
    @Bean
    public DataSource dataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    // 连接配置
    @Bean
    // 当DataSource这个bena存在时加载
    @ConditionalOnBean(DataSource.class)
    public DataCollectionService dateCollectionService() {
        DataCollectionService dateCollectionService = new DataCollectionService();
        System.out.println("dataSource()====>" + dataSource());
        dateCollectionService.setDataSource(dataSource());
        return dateCollectionService;
    }
}

测试类

1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class ConditionalApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ConditionalApplication.class);

        DataCollectionService dateCollectionService = (DataCollectionService) context.getBean("dateCollectionService");
        dateCollectionService.collect();
    }
}

——来自B站【石添的编程哲学】