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

发布于:2025-07-28 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、方法注解

1.1、@Bean

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

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

尝试获取bean对象中的user

@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对象,运行下述代码

@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对象 

@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、替换原始类名 
@Configuration
public class BeanConfig {

    @Bean("getuser2")
    public User getuser(){
        return new User("路明非",18);
    }
    @Bean
    public User getuser1(){
       return new User("陈墨瞳",18);
    }
}
@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类:

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

        UserService类:

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

         获取UserController中的print()方法:

@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、构造方法注入

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

@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 注解,然后观察运行结果:

@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 注解

@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引发的问题

        定义一个学生类:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {
    String name;
    int age;
}

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

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

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

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

        最后尝试获取Bean对象 

@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框架会自动根据类型进行依赖注入,变量命名可以自由选择。

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

@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配合使用

@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名称

@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装配顺序 


网站公告

今日签到

点亮在社区的每一天
去签到