Spring核心机制:@Bean注解与依赖注入的终极实践指南

一、方法注解

1.1、@Bean

在之前的讲解中,我们了解到通过类注解可以将类交由Spring管理。然而,当我们使用第三方库中的类时,由于无法直接添加类注解或其他限制条件,这时候就需要借助@Bean方法注解来实现同样的功能。

java 复制代码
public class BeanConfig {
    @Bean
    public User user(){
        User user=new User();
        user.setName("路明非");
        user.setAge(18);
        return user;
    }
}

尝试获取bean 对象中的user

java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringDemo3Application.class, args);
        BeanConfig beanConfig=context.getBean(BeanConfig.class);
        User user=beanConfig.user();
        System.out.println(user);
    }
}

运行结果:获取不到该对象

为什么呢???

如下:

控制台正常输出我们预期的结果了

1.2、定义多个对象

1.2.1、构造方法注解

@AllArgsConstructor@NoArgsConstructor是用于自动生成类构造方法的注解,功能类似于 @Data注解。

回顾@Data注解功能:

在类上添加 @Data注解时,Lombok会在编译期自动生成以下方法:

(1)toString()

(2)equals(Object object)

(3)hashCode()

(4)所有字段的 getter 方法

(5)所有非 final 字段的 setter 方法

(6)一个包含使用 final 字段的构造器

我们可以看到 @Data 注解为我们自动添加 Getter 、Setter等方法、但没有我们想要的构造方法 ,所以我们为User类添加该注解

1.2.2、获取Bean对象

此时,User类就存在两个对象了,我们尝试获取Bean对象,运行下述代码

java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringDemo3Application.class, args);
        User student1=context.getBean(User.class);
        System.out.println(student1);
    }
}

运行结果显示:没有可用类型为...合适的Bean,期望匹配单个Bean,但发现两个"user、user1",从报错信息中可以看到@Bean注解的bean,bean的名称就是它的方法名

那么我们根据bean的名称来获取bean对象

java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringDemo3Application.class, args);
        User student1=(User) context.getBean("user1");
        System.out.println(student1);
        User student2=(User) context.getBean("user");
        System.out.println(student2);
    }
}

可以看出@Bean可以针对同一个类,定义多个对象

1.3、重命名Bean

1.3.1、替换原始类名
java 复制代码
@Configuration
public class BeanConfig {

    @Bean("getuser2")
    public User getuser(){
        return new User("路明非",18);
    }
    @Bean
    public User getuser1(){
       return new User("陈墨瞳",18);
    }
}
java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringDemo3Application.class, args);
        User student1=(User) context.getBean("getuser1");
        System.out.println(student1);
        User student2=(User) context.getBean("getuser");
        System.out.println(student2);
    }
}

我们给路明非的Bean 对象进行了重命名的操作,再次按照方法名尝试获取Bean对象:

从代码中可以看到,我们可以成功获取名为 getuser1 的 Bean 对象。需要注意的是,要获取路明非的信息,必须使用重命名操作。此外,观察上述代码可以发现小奥奇修改了方法名。这是因为在测试过程中发现,即使替换了原始的 user 名称,执行后续代码时仍会生成参数为空的 User 对象。这是由于 Spring 框架自动创建了 User 实例,但未提供任何初始化值,所以保留了 Java 的默认值(null 和 0)。

1.3.2、多名替换

我们可以对类进行多次重命名,当获取Bean对象时,使用任意一个名称都能成功获取该对象。这是因为:

二、DI详解

依赖注入是⼀个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象,在上面程序案例中,我们使用了 @Autowired 这个注解,完成了依赖注入的操作

简单来说,就是把对象取出来放到某个类的属性中,关于依赖注入,Spring 给我们提供了如下三种方式:

2.1、属性注入

UserController类:

java 复制代码
@org.springframework.stereotype.Controller
public class UserController {
    @Autowired
    private UserService service;
    public void print(){
        System.out.println("hello Controller....");
        service.print();
    }
}

UserService类:

java 复制代码
@org.springframework.stereotype.Service
public class UserService {
    public void print(){
        System.out.println("hello Service....");
    }
}

获取UserController中的print()方法:

java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context=SpringApplication.run(SpringDemo3Application.class, args);
        UserController userController=context.getBean(UserController.class);
        userController.print();
    }
}

运行结果:

2.2、构造方法注入

构造方法注入是在类的构造方法中实现注入,如下代码所示:

java 复制代码
@org.springframework.stereotype.Controller
public class UserController {
    private UserService service;
    @Autowired
    public UserController(UserService service) {
        this.service=service;
    }
    public void print(){
        System.out.println("hello Controller....");
        service.print();
    }
}

注意:如果类中只有一个构造方法,上述代码中的 @Autowired 注解是可以省略的,但是如果存在多个构造方法,我们就必须加上 @Autowired注解指定那个是默认的构造方法

我们移除无参构造方法,添加两个有参构造方法且不使用 @Autowired注解,然后观察运行结果:

java 复制代码
@org.springframework.stereotype.Controller
public class UserController {
    private UserService service;
    private UserConfig config;
    public UserController(UserService service) {
        this.service=service;
    }
//    @Autowired
    public UserController(UserService service, UserConfig config) {
        this.service=service;
        this.config=config;
    }
    public void print(){
        System.out.println("hello Controller....");
        service.print();
        config.print();
    }
}

我们使用 @Autowired注解指定后发现就能输出预期结果了:

2.3、Setter注入

Setter注入和属性注入类似,就是在生成 set 方法时加上 @Autowired 注解

java 复制代码
@org.springframework.stereotype.Controller
public class UserController {
    private UserService service;
    @Autowired
    public void setService(UserService service) {
        this.service = service;
    }
    public void print(){
        System.out.println("hello Controller....");
        service.print();
    }
}

当然若不加注解会报错:

2.4、三种方式的优缺点分析


属性注入:

优点 :简洁,使用方便

缺点

功能性问题:无法注入一个不可变的对象(final 修饰的对象)

通用性问题:只能适应于 IoC 容器

设计原则问题:更容易违背单一设计原则


构造方法注入:

优点

可注入不可变对象

注入的对象不会被修改

注入对象会被完全初始化(因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化)

通用性更好

缺点

注入多个对象时,代码会比较繁琐


Setter 注入:

优点

符合单一职责的设计原则,可以对该对象重新进行配置或者注入

缺点

不能注入不可变对象(final 修饰的对象)

注入的对象可被修改

2.5、@Autowired引发的问题

定义一个学生类:

java 复制代码
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {
    String name;
    int age;
}

AllStudent 类中创建路明非的学生实例,使用@Bean 注解将该对象交由Spring 容器管理,并对bean进行重命名

java 复制代码
@Component
public class AllStudent {
    @Bean({"s2","s3"})
    public Student S1(){
        return new Student("路明非",18);
    }
}

ControllerDemo1 类中定义 Student 类并完成注入,需注意此处学生对象命名为 "s1"。

java 复制代码
@Controller
public class ControllerDemo1 {
    @Autowired
    private Student s1;
    public void print(){
        System.out.println("hello controller...");
        System.out.println(s1);
    }
}

最后尝试获取Bean对象

java 复制代码
@SpringBootApplication
public class SpringDemo3Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringDemo3Application.class, args);
        ControllerDemo1 demo1 = (ControllerDemo1) context.getBean(ControllerDemo1.class);
        demo1.print();
    }
}

测试运行结果显示成功获取了路明非对象,这表明代码中的变量名"s1"并不影响最终结果。当系统中仅存在一个学生对象时(学生类型的Bean,或者说没有候选Bean),Spring框架会自动根据类型进行依赖注入,变量命名可以自由选择。

那么,当我们再次添加一个学生实例后,此时注入的学生是哪一个呢???

java 复制代码
@Component
public class AllStudent {
    @Bean({"s2","s3"})
    public Student S1(){
        return new Student("路明非",18);
    }
    @Bean
    public Student S4(){
        return new Student("陈墨瞳",18);
    }
}

我们点击运行后,观察结果:

报错信息下方提示:"Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed"。这表明可以通过以下方式解决:

  1. 使用@Primary注解标记默认Bean
  2. 使用@Qualifier指定具体Bean

我们将按照这些建议进行测试验证

2.5.1、 @Primary
2.5.2、@Qualifier

在需要注入的对象上方添加 @Qualifier 注解,可以明确指定要传入的具体对象。但需注意,@Qualifier不能单独使用,必须与@Autowired配合使用

java 复制代码
@Controller
public class ControllerDemo1 {
    @Qualifier("S4")
    @Autowired
    private Student s1;
    public void print(){
        System.out.println("hello controller...");
        System.out.println(s1);
    }
}

该注解比较适用于参数注入 :

2.5.3、@Resource

使用 @Resource 注解时,会按照bean名称进行注入。可以通过name属性来指定需要注入的bean名称

java 复制代码
@Controller
public class ControllerDemo1 {
    @Resource(name="S4")
    private Student s1;
    public void print(){
        System.out.println("hello controller...");
        System.out.println(s1);
    }
}

2.6、Autowired装配顺序

相关推荐
ahauedu1 分钟前
用Java 代码实现一个简单的负载均衡逻辑
java·python·负载均衡
不过普通话一乙不改名6 分钟前
第一章:Go语言基础入门之函数
开发语言·后端·golang
Java初学者小白17 分钟前
秋招Day18 - MyBatis - 基础
java·数据库·mybatis
大白玉米18 分钟前
TVBOXOS6.0双端APP二开源码完整版全开源源码重构版
java·重构·php
Dcs35 分钟前
Cisco爆出重大漏洞!无需登录即可获取Root权限,攻击者已在野利用!
java
青云交44 分钟前
Java 大视界 -- 基于 Java 的大数据分布式存储在工业互联网数据管理与边缘计算协同中的创新实践(364)
java·大数据·边缘计算·工业互联网·分布式存储·paxos·数据协同
屁股割了还要学1 小时前
【C语言进阶】柔性数组
c语言·开发语言·数据结构·c++·学习·算法·柔性数组
oioihoii1 小时前
C++实战案例:从static成员到线程安全的单例模式
java·c++·单例模式
☞下凡☜1 小时前
C语言(20250722)
linux·c语言·开发语言
whhhhhhhhhw2 小时前
Go语言-fmt包中Print、Println与Printf的区别
开发语言·后端·golang