Spring 中简单存取 Bean 的相关注解

目录

前言

之前我们存储获取 Bean 的操作很繁琐,需要将 Bean 放入 xml 文件中,获取 Bean 还必须得获取上下文,就很麻烦。

其实这些都可以简化,在 Spring 中想要更简单的存储和读取对象,核⼼是使⽤注解。

想要将对象存储在 Spring 中,有两种注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration。

  2. ⽅法注解:@Bean
    获取对象的实现⽅法有以下 3 种:

  3. 属性注⼊

  4. 构造⽅法注⼊

  5. Setter 注⼊

存储 Bean 对象

五大类注解

五大类注解有:

@Controller(控制器)校验参数的合法性,相当于安检系统

@Service(服务)业务组装,相当于客服中心

@Repository(数据持久层)实际业务处理,就是实际办理的业务

@Component(组件)工具类层,基础工具

@Configuration(配置层)配置

首先,我们得配置一下 xml 文件:

注意:使用类注解存储 bean,和使用 xml 存储 bean 是可以混用的。

User 类:

java 复制代码
//使用类注解
@Controller
public class User {
    public void sayHi(String name) {
        System.out.println("hello " + name);
    }
}
java 复制代码
public class App {
    public static void main(String[] args) {
        //得到 spring 上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //得到 bean 对象
        User user = (User) context.getBean("user");
        user.sayHi("张三");
    }
}

关于 getBean 后面应该填什么?

对于不同的类名有不同的写法,如果首字母大写,第二个字母小写,那么 bean 的名称就是类名首字母小写;如果不满足首字母大写第二字母小写,则使用原类名。

当然了,我们也可以在类注解后面设置 id,通过 id 来得到 bean 对象。

java 复制代码
//id 可以随便取, 两种方式都可
//@Controller(value = "666")
@Controller("666")
public class User {
    public void sayHi(String name) {
        System.out.println("hello " + name);
    }
}
java 复制代码
public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user = (User) context.getBean("666");
        user.sayHi("张三");
    }
}

来看看五大类注解间的关系:



这些注解⾥⾯都有⼀个注解 @Component,说明它们本身就是属于 @Component 的"⼦类"。

所以它们在使用上都差不多,那为什么还要分出 5 大类呢?

直接一个 @Component 就可以 存储 bean 对象了,还要分这么多有什么用呢?

其实是让程序员看到类注解之后,就能直接了解当前类的⽤途,比如我们看到一辆车的车牌号就知道它的归属地是哪,这个也一样。

程序的⼯程分层,调⽤流程如下:

方法注解(@Bean)

类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的:

Student 类:

java 复制代码
//普通类
public class Student {
    private String name;
    private int id;
    private int score;

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

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }
}

class 类:

java 复制代码
//注意: 方法注解要配合五大类注解一起使用
//     该类也存储在 IoC 容器中
@Controller
public class Class {

    //使用方法注解, 将方法的返回值存储到 IoC 容器中
    @Bean
    public Student student() {
        Student student = new Student();
        student.setId(6);
        student.setName("张三");
        student.setScore(150);
        return student;
    }
}

获取 bean 对象:

java 复制代码
public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //注意:@Bean 默认命名是方法名
        // 也就是添加方法注解的那个方法名
        Student student = context.getBean("student", Student.class);
        System.out.println(student.toString());
    }
}

@Bean 还有几种重命名方式:

java 复制代码
    @Bean("aaa")
java 复制代码
    @Bean(value = "sss")
java 复制代码
    @Bean(name = "nnn")

点入 @Bean :

发现 name 和 value 效果是一样的。

除了取单个名称外,还可以取多个名称:

java 复制代码
    @Bean(value = {"sss","888"})
java 复制代码
    @Bean(name = {"sss","888"})
java 复制代码
    @Bean({"sss","888"})

注意:

  1. 如果重命名了,那么获取 bean 对象时,默认的方法获取 bean 对象的方式就不能用了。
  2. 添加了 @Bean 注解的方法无法传参(方法的调用是程序在控制),也就不能重载。
  3. 如果多个 Bean 使用相同的名称,那么程序执行不会报错,但是第一个 Bean 后的对象不会被放到容器中,也就是只有第一次创建 Bean 的时候会将对应的 Bean 名称关联起来,后续再有相同名称的 Bean 存储的时候,容器会自动忽略。

获取 Bean 对象 (@Autowired)

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。

属性注入

属性注⼊是使⽤ @Autowired 实现的:

普通类 User:

java 复制代码
public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

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

添加类注解的 UserService:

java 复制代码
@Service
public class UserService {
    public User getUser(int id, String name) {
        User user = new User();
        user.setId(id);
        user.setName(name);
        return user;
    }
}

添加类注解的 UserController:

java 复制代码
@Controller
public class UserController {

	//使用属性注入,将IoC容器中的UserService类型的对象
	//注入变量 userService中
    @Autowired
    private UserService userService;

    public User getUser(int id, String name) {
        return userService.getUser(id,name);
    }
}

注意:下面我们不使用 @Autowired 来进行依赖注入,因为 main 函数是静态方法,@Autowired 又不能给局部变量注入,那就只能放在类里面方法外面了,再加上 static 修饰 才能在 main 里访问,但可惜的是静态类属于方法的一部分,static 成员是不能使用 @Autowired 来注入。

java 复制代码
public class Test {
    public static void main(String[] args) {
    	//依赖查找
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        UserController userController = context.getBean("userController", UserController.class);
        //检验是否得到了 UserController 类型对象
        System.out.println(userController.getUser(3,"张三").toString());
    }
}

依赖查找 VS 依赖注入

依赖查找:依赖 Bean 的名称来获取 Bean 对象。

依赖注入:通过注解 @Autowired 根据对象类型进行依赖注入,首先根据 getType 从容器中获取对象,如果 IoC 容器中只有一个该类型对象,则直接注入到当前属性上;如果容器中有多个该类型对象,则会使用 getName (根据名称)进行匹配。(具体体现如下)

多个同类型 Bean 注入怎么办?

问题:如若有多个同类型的 Bean 对象存储进 IoC 容器中,那么我们该如何准确获取该类型对象?

问题场景:

Users 类:

java 复制代码
@Service
public class Users {

	//通过方法注解,添加两个同类型 bean
    @Bean("user1")
    public User user1() {
        User user = new User();
        user.setName("李四");
        user.setId(22);
        return user;
    }

    @Bean("user2")
    public User user2() {
        User user = new User();
        user.setName("王五");
        user.setId(88);
        return user;
    }
}

UserService 类:

java 复制代码
@Service
public class UserService {

	//对 user 进行属性注入
    @Autowired
    User user;

    public void add() {
    	//拿到 user 后打印内容,判断是哪个 user
        System.out.println(user.toString());
    }
}

Test 类:

java 复制代码
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

看结果:

它是说期望有一个 bean 进行匹配,但出现了两个:user1、user2

解决问题:

  1. 将属性的名字和 Bean 的名字对应上。

就比如我要得到的是 王五 这个对象,那我只需要将下面两点的名称对应上即可:


  1. @Autowired 配合 @Qualifier 使用

这个就是在注入时,声明要注入的 bean 名称

属性注入分析:

优点: 使用简单

缺点:

  1. 无法注入 final 修饰的变量
  2. 只适用于 IoC 容器
  3. 容易违背单一设计原则

Setter 注入

我们写类属性时,可以对这些属性生成相应的 get 和 set 方法,Setter 注入就是针对 set 方法,进行的注入:

User 类:

java 复制代码
@Service
public class Users {

        @Bean("user")
        public User getUser() {
            User user = new User();
            user.setName("老王");
            user.setId(26);
            return user;
        }
}

UserService 类:

java 复制代码
@Service
public class UserService {

    private User user;

	//对类属性 user 进行 Setter 注入
    @Autowired
    public void setUser(User user) {
        this.user = user;
    }

    public void add() {
        System.out.println(user.toString());
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

注意:@Autowired 不能省略
Setter 注入分析:

优点:通常 Setter 只是注入一个属性,所以 Setter 更符合单一设计原则。

缺点:

  1. 无法注入一个 final 修饰的变量
  2. Setter 注入的对象可以被修改。(setter 是一个方法,既然是方法就可能被多次调用和修改)

构造方法注入(官方推荐)

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

只对 UserService 进行修改:

java 复制代码
@Service
public class UserService {

    private User user;
	
	//构造方法注入
    @Autowired
    public UserService(User user) {
        this.user = user;
    }

    public void add() {
        System.out.println(user.toString());
    }
}

注意:如果只有一个构造方法,不写 @Autowired 也可以,但若是有多个构造方法,就得加上 @Autowired,表明是哪个构造方法需要注入。
构造方法注入分析:

优点:

  1. 可以注入一个 final 修饰的变量
  2. 注入的变量不会被修改,因为构造方法只加载一次
  3. 构造方法注入可以保证注入对象完全初始化
  4. 构造方法注入通用性更好

缺点:

  1. 写法比属性注入更复杂
  2. 使用构造方法注入,无法解决循环依赖的问题

其实 @Resource 的功能和 @Autowired 差不多,那它俩有啥区别呢?

@Autowired 与 @Resource 的区别:

  1. 出生不同:@Resource 来自 JDK,@Autowired 来自 Spring 框架
  2. 支持参数不同:@Resource 支持很多参数设置,@Autowired 只支持一个参数设置
  3. 使用上的区别:@Resource 不支持构造方法注入,@Autowired 支持构造方法注入
  4. idea 兼容性支持不同:使用 @Autowired 在 idea 专业版下可能会误报,@Resource 不存在误报问题(@Resource 相当于是亲儿子了)

关于第四点:因为@Autowired 来自 Spring 框架,@Resource 来自 JDK,所以执行顺序有差异,Spring 框架的执行是在 java 程序执行之后的,当我们使用 @Autowired 时,它不能检测到 IoC 容器中是否有这个类型的 Bean,所以就报错(运行起来不影响结果);使用 @Resource 的话,执行顺序是靠前的,它知道这个 Bean 是否存在。

相关推荐
写代码写到手抽筋7 分钟前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
wang090716 分钟前
自己动手写一个spring之系列
spring
xieliyu.18 分钟前
Java算法精讲:双指针(二)
java·开发语言·算法
jeffer_liu43 分钟前
Spring AI 生产级实战:裁判员
java·人工智能·后端·spring·大模型
小bo波2 小时前
枚举实战
java·设计模式·枚举·后端开发·代码重构
夜微凉42 小时前
三、Spring
java·后端·spring
橘右今2 小时前
2026 Java后端高频面试宝典
java·开发语言·面试
xyzzklk3 小时前
解决Salesforce无法向外发送邮件
android·java·开发语言·网络·crm·salesforce·客户关系管理
biubiubiu07063 小时前
SpringBoot关于外部化配置
java·spring boot·spring
Full Stack Developme3 小时前
Spring Bean 依赖注入
python·spring·log4j