当前位置: 首页 > news >正文

JavaEE进阶:Spring 更简单的读取和存储对象

文章目录

  • 前言
  • 一、存储 Bean 对象
    • 1、前置⼯作:配置扫描路径(重要)
    • 2、添加注解存储 Bean 对象
      • ① @Controller(控制器存储)
      • ② @Service(服务存储)
      • ③ @Repository(仓库存储)
      • ④ @Component(组件存储)
      • ⑤ @Configuration(配置存储)
      • ⑥ 小提示
    • 3、为什么要这么多类注解?
      • ① 类注解之间的关系
      • ② 注意 Bean 的命名
    • 4、方法注解 @Bean
      • ① 方法注解要配合类注解使用
      • ② @Bean 注解命名规则
        • Ⅰ 问题
        • Ⅱ 解决方案
      • ③ 重命名 Bean
        • Ⅰ 问题
        • Ⅱ 解决方案
        • Ⅲ 练习
      • ④ 注意事项
  • 二、获取 Bean 对象(对象装配)
    • 1、属性注入
      • ① 练习1
      • ② 练习2
      • ③ 优点
      • ④ 缺点
    • 2、Setter 注入
      • ① 优点
      • ② 缺点
    • 3、构造方法注入(推荐)
      • ① 优点
        • Ⅰ 注入不可变对象(final)
        • Ⅱ 注入的对象不会被修改
        • Ⅲ 完全初始化
        • Ⅳ 通用性更好
    • 4、三种注入优缺点分析
    • 5、@Resource:另⼀种注⼊关键字
      • ① @Autowired 和 @Resource 的区别
    • 6、同⼀类型多个 @Bean 报错
      • ① 问题
      • ② 方案1:@Resource注解
      • ③ 方案2:@Qualifier注解
  • 三、综合练习

前言

经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的⽅法。

在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相关注解,来存储和读取 Bean 对象。

一、存储 Bean 对象

之前我们存储 Bean 时,需要在 spring-config 中添加⼀⾏ bean 注册内容才⾏,如下图所示:
在这里插入图片描述
⽽现在我们只需要⼀个注解就可以替代之前要写⼀⾏配置的尴尬了,不过在开始存储对象之前,我们先要来点准备⼯作。

1、前置⼯作:配置扫描路径(重要)

注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。

在 spring-config.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"xmlns:content="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><content:component-scan base-package="com.wzr。service"></content:component-scan>
</beans>

其中标红的⼀⾏为注册扫描的包,如下图所示:
在这里插入图片描述

即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。

2、添加注解存储 Bean 对象

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

  1. 类注解:
    • @Controller:【控制器】验证前端传递的参数的 “安全检查”;
    • @Service:【服务层】服务调用的编译和汇总;
    • @Repository:【仓库(数据仓库…)】直接操作数据库
    • @Component:【组件】通用化的工具类
    • @Configuration:【配置】项目的所有配置
  2. ⽅法注解:@Bean。
    接下来我们分别来看。

① @Controller(控制器存储)

使⽤ @Controller 存储 bean 的代码如下所示:

@Controller
public class UserController {public void sayHello() {System.out.println("hello");}
}

此时我们先使⽤之前读取对象的⽅式来读取上⾯的 UserController 对象,如下代码所示:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");// 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】UserController userController = context.getBean("userController", UserController.class);// 3.操作 Bean 对象userController.sayHello();}
}

② @Service(服务存储)

使⽤ @Service 存储 bean 的代码如下所示:

@Service
public class UserService {public void doService() {System.out.println("Do user service.");}
}

读取 bean 的代码:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();UserService userService =context.getBean("userService", UserService.class);userService.doService();}
}

③ @Repository(仓库存储)

使⽤ @Repository 存储 bean 的代码如下所示:

@Repository
public class UserRepository {public void doRepository() {System.out.println("Do user repository.");}
}

读取 bean 的代码:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();UserRepository userRepository =context.getBean("userRepository", UserRepository.class);userRepository.doRepository();}
}

④ @Component(组件存储)

使⽤ @Component 存储 bean 的代码如下所示:

@Component
public class UserComponent {public void doComponent() {System.out.println("Do user component.");}
}

读取 bean 的代码:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();//        UserRepository userRepository =
//                context.getBean("userRepository", UserRepository.class);
//        userRepository.doRepository();UserComponent userComponent =context.getBean("userComponent", UserComponent.class);userComponent.doComponent();}
}

⑤ @Configuration(配置存储)

使⽤ @Configuration 存储 bean 的代码如下所示:

@Configuration
public class UserConfiguration {public void doConfiguration() {System.out.println("Do user configuration.");}
}

读取 bean 的代码:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();//        UserRepository userRepository =
//                context.getBean("userRepository", UserRepository.class);
//        userRepository.doRepository();//        UserComponent userComponent =
//                context.getBean("userComponent", UserComponent.class);
//        userComponent.doComponent();UserConfiguration userConfiguration =context.getBean("userConfiguration", UserConfiguration.class);userConfiguration.doConfiguration();}
}

⑥ 小提示

我们之前提到过:即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。

那么如果我们像把不是在配置的扫描包下的类对象存储到 Spring 中,应该怎么做呢?
我们的第一想法应该是:采用之前我们所学的方法,直接注册 Bean 到 Spring 中。

那么这种方式究竟可不可行呢?
答案是:可行!

例如:
此时无法扫描到 UserConfiguration 这个 Bean,我们在使用 UserConfiguration 的时候就会报错。
在这里插入图片描述
但是当我们用之前学的方法,手动的 把 UserConfiguration 注册到 Spring 中:
在这里插入图片描述
此时就可以成功使用 UserConfiguration 了。

总结:扫描和手动注册两种方法可以混用!

3、为什么要这么多类注解?

类的注解其实就是将类进行分类,让程序更加精细化,让程序员看到类注解之后,就能直接了解当前类的⽤途,方便维护,⽐如:

  • @Controller:表示的是业务逻辑层;
  • @Servie:服务层;
  • @Repository:持久层;
  • @Configuration:配置层。

程序的⼯程分层,调⽤流程如下:
在这里插入图片描述

① 类注解之间的关系

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
在这里插入图片描述
结论:@Controller / @Service / @Repository / @Configuration 都是基于 @Component,它们的作用都是将 Bean 储存到 Spring 中。

② 注意 Bean 的命名

默认情况下,使用 5 大类注解的 Bean 名称是将类首字母小写的命名规则。

ex:UserConfiguration -> userConfiguration

特殊情况:当⾸字⺟和第⼆个字⺟都是⼤写时
在这里插入图片描述
我们可以在 Idea 中使⽤搜索关键字“beanName”可以看到以下内容:
在这里插入图片描述
顺藤摸⽠,我们最后找到了 bean 对象的命名规则的⽅法:
在这里插入图片描述
它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,ctrl+左键 得到 源码如下:

	public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&Character.isUpperCase(name.charAt(0))){return name;}char chars[] = name.toCharArray();chars[0] = Character.toLowerCase(chars[0]);return new String(chars);}

我们可以用一个类来测试一下:

public class App2 {public static void main(String[] args) {String name = "UserController"; // 首字母小写String name2 = "UController"; // 原类名System.out.println("name: " + Introspector.decapitalize(name));System.out.println("name2: " + Introspector.decapitalize(name2));}
}

得到的结果为:
在这里插入图片描述
所以对于上⾯报错的代码,我们只要改为以下代码就可以正常运⾏了:
在这里插入图片描述
总结:当⾸字⺟和第⼆个字⺟都是⼤写时,那么 Bean 的名称为原类名!

4、方法注解 @Bean

类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的,如以下代码的实现:

public class UserBeans {@Bean // 方法注解public User user(){// 构建数据方法User user = new User();user.setId(1);user.setName("张三");user.setAge(18);return user;}
}

然⽽,当我们写完以上代码,尝试获取 Bean 对象中的 user 时却发现,根本获取不到:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("user", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}

以上程序的执⾏结果如下:
在这里插入图片描述
这是为什么呢?

① 方法注解要配合类注解使用

在 Spring 框架的设计中,⽅法注解 @Bean 要配合五大类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:

@Component
public class UserBeans {@Bean // 方法注解public User user(){// 构建数据方法User user = new User();user.setId(1);user.setName("张三");user.setAge(18);return user;}
}

再次执⾏以上代码,运⾏结果如下:
在这里插入图片描述

② @Bean 注解命名规则

Ⅰ 问题

我们之前学习了 五大类注解 Bean 命名规则:

  1. 首字母和第⼆个字⺟都是⼤写时,那么 Bean 的名称为原类名
  2. 其他情况,类名首字母小写为 Bean 名称。

刚刚我们也学习到了,方法注解 要配合 类注解 使用,那么 @Bean 注解 的命名规则也和 五大类注解 相同吗?
我们用一个测试类来解决,如下所示:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("user", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}
@Component
public class UserBeans {@Bean // 方法注解public User user1(){// 构建数据方法User user = new User();user.setId(1);user.setName("张三");user.setAge(18);return user;}
}

在这里插入图片描述
如果程序正确运行,则说明 @Bean 注解 的命名规则也和 五大类注解 相同;反之,则 @Bean 注解 有其独立的命名规则。

其结果为:
在这里插入图片描述
程序报错,说明 @Bean 注解 的命名规则也和 五大类注解 不同。

Ⅱ 解决方案

然而,我们对程序做出如下改变:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("user1", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}

其结果为:
在这里插入图片描述
说明 @Bean 注解的名称是方法名

③ 重命名 Bean

Ⅰ 问题

我们在使用五大类注解的时候,因为行为规范的原因,类很少有同名的情况出现;但是使用 @Bean 注解的时候会遇到一个问题 -> 不同类下方法名可能相同,例如:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("user1", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}
@Component
public class StudentBeans {@Bean // 方法注解public User user1(){// 构建数据方法User user = new User();user.setId(1);user.setName("StudentBeans: 小李");user.setAge(18);return user;}
}
@Component
public class UserBeans {@Bean // 方法注解public User user1(){// 构建数据方法User user = new User();user.setId(1);user.setName("UserBeans: 张三");user.setAge(18);return user;}
}

在这里插入图片描述
其运行结果为:
在这里插入图片描述
这样就会出现 数据覆盖 的问题,这里的 StudentBeans 的 user1 就被覆盖了,那我们不想让 StudentBeans 的 user1 该怎么办呢?

Ⅱ 解决方案

为了解决上述问题,我们可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所示:

@Component
public class StudentBeans {@Bean(name = "student_user1") // 方法注解public User user1(){// 构建数据方法User user = new User();user.setId(1);user.setName("StudentBeans: 小李");user.setAge(18);return user;}
}

此时我们使⽤ student_user1 就可以获取到 StudentBeans 的 user1 了,如下代码所示:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("student_user1", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}

运行结果为:
在这里插入图片描述
其实除了上述方法,还有其他方法进行重命名:
实际上这个重命名的 name 其实是⼀个数组,⼀个 Bean 可以有多个名字,同时 name= 可以省略。

@Component
public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")@Bean(name = {"stu_user1", "student_user1"})public User user1() {// 构建数据方法User user = new User();user.setId(1);user.setName("StudentBeans: 小李");user.setAge(18);return user;}
}

Ⅲ 练习

使用 @Bean 注解并重命名,尝试使⽤原来的类名⾸字⺟⼩写是否能正确获取到对象?

没有重命名前

public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")
//    @Bean(name = {"stu_user1", "student_user1"})@Beanpublic User studentUser1() {// 构建数据方法User user = new User();user.setId(1);user.setName("StudentBeans: 小李");user.setAge(18);return user;}
}
public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("studentUser1", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}

运行结果为:
在这里插入图片描述
重命名后

@Component
public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")
//    @Bean(name = {"stu_user1", "student_user1"})@Bean(name = "stu_user1")public User studentUser1() {// 构建数据方法User user = new User();user.setId(1);user.setName("StudentBeans: 小李");user.setAge(18);return user;}
}
public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");User user =context.getBean("studentUser1", User.class);System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}

运行结果为:
在这里插入图片描述
结论使⽤ @Bean 注解并重命名,使用方法名就不能获得 Bean 对象了。

④ 注意事项

  • 必须配合 五大类注解 一起使用(不然注入不进去);
  • @Bean 方法注解 只能使用在无参的方法上。

二、获取 Bean 对象(对象装配)

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

对象装配(对象注⼊)的实现⽅法以下 3 种:

  1. 属性注⼊
  2. 构造⽅法注⼊
  3. Setter 注⼊

接下来,我们分别来看。下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中

1、属性注入

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。

UserService 类的实现代码如下:

@Service
public class UserService {public void doService() {System.out.println("Do user service.");}
}

UserController 类的实现代码如下:

@Controller
public class UserController {// 读取 UserService[从 Spring 读取]// 1.属性注入(Field Injection)@Autowired // 自动装配private UserService userService;public void sayHello() {System.out.println("Do User Controller.");userService.doService();}
}

获取 UserController 中的 sayHello ⽅法:

public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");UserController controller =context.getBean("userController", UserController.class);controller.sayHello();}
}

运行结果为:
在这里插入图片描述

① 练习1

我们可以使⽤ @Autowired 将 Service 类注⼊到 Controller 类中,那么我们做出如下修改,能否成功运行呢?
在这里插入图片描述

答案是:不行!

在这里插入图片描述

② 练习2

我们修改一下 Controller 类注入 Service 的对象名称,那么程序还能正确运行吗?

@Controller
public class UserController {// 读取 UserService[从 Spring 读取]// 1.属性注入(Field Injection)@Autowired // 自动装配private UserService us;public void sayHello() {System.out.println("Do User Controller.");us.doService();}
}

答案是:可以!

我们之前提到,五大类注解是根据类名进行命名,而 @Bean 注解是根据方法名进行注解;但是到了这里,由于 @Autowired 过于强大,它只需要我们二者满足其一即可!

这是因为,Spring 存储 Bean:实际上是以一个 HashMap<String,Object> 的形式存储。
@Bean key=方法名, value=方法返回的对象.

③ 优点

  • 写法简单

④ 缺点

  1. 功能缺陷(主要):不能注入一个 final 修饰的属性
    final 修饰的变量需要满足
    1. 使用时直接赋值
    2. 构造方法赋值
  2. 通用性问题(主要):只适用于 IoC 框架(容器)
  3. 设计原则问题:更容易违背单一设计原则(因为使用简单,所以滥用的风险更大)

2、Setter 注入

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解,如下代码所示:

@Controller
public class UserController {// 读取 UserService[从 Spring 读取]//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;// 2.Setter 注入(Setter Injection)private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void sayHello() {System.out.println("Do User Controller.");userService.doService();}
}

① 优点

  • 符合单一设计原则(一个 Setter 只针对一个对象)。

② 缺点

  1. 不能注入一个不可变的对象(例如:final)。
  2. 注入对象可能改变(setter 方法可能会被多次调用,所以就有被修改的风险)。

3、构造方法注入(推荐)

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

@Controller
public class UserController {// 读取 UserService[从 Spring 读取]//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;//    // 2.Setter 注入(Setter Injection)
//    private UserService userService;
//
//    @Autowired
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }// 3.构造方法注入(Constructor Injection)private UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}public void sayHello() {System.out.println("Do User Controller.");userService.doService();}
}

当当前类有且仅有一个构造方法时,@Autowired 可以省略!
即:当存在多个构造方法时,@Autowired 不能省略!!!!!!

① 优点

Ⅰ 注入不可变对象(final)

案例:

@Controller
public class UserController {// 读取 UserService[从 Spring 读取]//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;//    // 2.Setter 注入(Setter Injection)
//    private UserService userService;
//
//    @Autowired
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }// 3.构造方法注入(Constructor Injection)private final UserService userService;@Autowiredpublic UserController(UserService userService) {System.out.println("----------------- 执行第1个构造方法 ------------------");this.userService = userService;}public void sayHello() {System.out.println("Do User Controller.");System.out.println();userService.doService();}
}

运行结果为:
在这里插入图片描述
**原理:**遵循 Java 的规范。
使用 final 关键字的用法只有两种:

  • 使用时直接赋值
  • 构造方法赋值

Ⅱ 注入的对象不会被修改

构造方法只会在对象创建的时候执行一次,它不会像 Setter 注入一样,执行多次,所以,不存在注入对象被修改的情况。

Ⅲ 完全初始化

依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类创建之初就会执行的方法。

Ⅳ 通用性更好

通用性更好,因为构造方法是 Java(JDK)支持【最底层的框架】,所以更换任何的框架,它都是适用的。

4、三种注入优缺点分析

  • 属性注入主要的优点就是简洁;缺点就是只适用于 IoC 容器,其他容器不通用,同时属性注入不能注入 final 修饰的属性,其实属性注入还有一个不能算缺点的缺点:它容易违背单一设计原则。
  • Setter 注入是在属性注入上做出了优化,它不会违背单一设计原则;但它同样不能注入 final 修饰的属性,同时也因为 setter 方法可能被多次调用,所以可能会有 注入对象被修改的风险
  • 构造方法注入 是目前 Spring 推荐的注⼊⽅式,它实际上满足了属性注入和 Setter注入的缺点,构造方法注入可以注入 final 修饰的属性注入的对象不会被修改通用性好,保证注入前,完全初始化要注入的类;但它的不足之处在于:当同时注入多个类的时候,就需要考虑是否符合程序的单⼀原则的设计模式

5、@Resource:另⼀种注⼊关键字

其实除了 @Autowired,还有一种注入关键字,也可以实现对象注入,即 @Resource。

@Controller
public class UserController2 {
//     // 1.属性注入
//    @Autowired
//    @Resource
//    private UserService userService;//    // 2.Setter注入
//    private UserService userService;
//
//    @Resource
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }// 3.构造方法注入【不支持】private UserService userService;@Resourcepublic UserController2(UserService userService) {this.userService = userService;}public void doController() {System.out.println("Do user controller 2.");System.out.println();userService.doService();}
}

这里会有一种问题:
使用 @Resource 进行属性注入 以及 Setter 注入的时候,都和 @Autowired 一样,可以成功注入;但在进行 构造方法注入 的时候,就会报错,如图:
在这里插入图片描述
@Resource 注解,不能使用在构造方法上

① @Autowired 和 @Resource 的区别

  1. @Autowired 支持构造方法注入,@Resource不支持;
  2. 两者的参数不同:
    在这里插入图片描述

6、同⼀类型多个 @Bean 报错

① 问题

我们刚刚提到 @Autowired 和 @Resource 两者的参数不同。我们先来看一个例子:

@Component
public class UserBeans {@Bean(name = "user_user1") // 方法注解public User user1(){// 构建数据方法User user = new User();user.setId(1);user.setName("UserBeans: 张三");user.setAge(18);return user;}@Bean // 方法注解public User user2(){// 构建数据方法User user = new User();user.setId(1);user.setName("李四");user.setAge(18);return user;}
}
@Controller
public class UserController3 {@Autowired // type or nameprivate User user;public void doController() {System.out.println("Do user controller 3.");System.out.println();System.out.println("user id: " + user.getId() +" | name:" + user.getName());}
}
public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");UserController3 userController3 =context.getBean("userController3", UserController3.class);userController3.doController();}
}

运行结果为:
在这里插入图片描述
我们之前在学习 @Autowired 的时候,了解过,@Autowired 实际上是以 方法名和 返回类的类型 来进行注入的,先查询返回类的类型,然后再继续查询方法名,二者满足其一即可注入。回到本案例,这里的方法 user1 和 user2 同样都是 返回 User 对象,所以需要继续查询方法名,但是并没有一个叫 user 的方法,无法分辨到底注入那个方法,就会报错。

② 方案1:@Resource注解

这个时候我们想要使用 @Autowired 来解决问题就比较困难了,就需要用到 参数更多的 @Resource,通过更多的参数来获取 Bean,代码如下:

@Controller
public class UserController3 {//    @Autowired // type or name@Resource(name = "user_user1")private User user;public void doController() {System.out.println("Do user controller 3.");System.out.println();System.out.println("user id: " + user.getId() +" | name:" + user.getName());}
}

运行结果为:
在这里插入图片描述

③ 方案2:@Qualifier注解

那有人就有疑问了,使用 @Autowired 比较困难,那如果一定要使用 @Autowired 注解呢?实际上还有一种方法可以解决这个问题,这个时候就要请出一个外援 @Qualifier 注解,代码如下:

@Controller
public class UserController3 {//    @Autowired // type or name
//    @Resource(name = "user_user1")@Autowired@Qualifier(value = "user_user1")private User user;public void doController() {System.out.println("Do user controller 3.");System.out.println();System.out.println("user id: " + user.getId() +" | name:" + user.getName());}
}

运行结果为:
在这里插入图片描述

三、综合练习

在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可。

  1. 创建一个 User 类:
public class User {private int id;private String name;private int age;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;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
  1. 在 Repository 类的一个方法中创建 User 对象,返回一个 User 对象:
@Repository
public class MyRepository {public User doRepository() {System.out.println("Do my repository.");User user = new User();user.setId(1);user.setName("MyRepository: 王五");user.setAge(18);return user;}
}
  1. 在 Service 类中通过构造方法注入的方式获取到 Repository 类,获得 User 对象,并返回:
@Service
public class MyService {private MyRepository myRepository;@Autowiredpublic MyService(MyRepository myRepository) {this.myRepository = myRepository;}public User doService() {System.out.println("Do my service.");System.out.println();return myRepository.doRepository();}
}
  1. 在 Controller 类中通过构造方法注入的方式获取到 Service 类,获得 User 对象,并返回:
@Controller
public class MyController {private MyService myService;@Autowiredpublic MyController(MyService myService) {this.myService = myService;}public User doController() {System.out.println("Do my controller.");System.out.println();return myService.doService();}
}
  1. 在 main ⽅法中获取到 Controller 类,并接收 User 类,打印信息:
public class App {public static void main(String[] args) {// 1.得到 Spring 上下文对象ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");MyController myController =context.getBean("myController", MyController.class);User user = myController.doController();System.out.println("id: " + user.getId() + " | name: " + user.getName());}
}
  1. 运行结果为:
    在这里插入图片描述

相关文章:

蓝桥杯刷题(二)

蓝桥杯刷题一.空间二.排序三.成绩分析四.蛇形填数五.跑步锻炼&#xff08;较难&#xff09;一.空间 这道题很简单&#xff0c;要弄清单位间的转换和如何输出就可以啦 #include <stdio.h>int main() {printf("%.0f",256/(32/4/2/1024.0000/1024));return 0; }记…...

flex blaze+java通信的例子

步骤&#xff1a; 1&#xff1a;建立java web程序 2&#xff1a; 下载blazeDS包&#xff0c;解压后将WEB-INF下的 flex&#xff0c;lib&#xff0c;web.xml复制到java程序的WEB-INF下 3&#xff1a;打开web.xml文件将以下代码的注释去掉&#xff0c;并修改 <param-value>…...

生产工艺审批管理系统java项目开发jsp编程软件myeclipse开发Mysql数据库计算机网页

一、源码特点 JSP 生产工艺审批管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql&#xff0…...

actionScript 数组去重

public function unique(array:Array):Array { for (var i:int0; i < array.length; i) { for (var j:inti 1; j < array.length; j) { //注意 if (array[i] array[j]) { array.splice(j, 1); j--; } } } return array…...

【C++音视频开发】初级篇 | RGB与YUV

前言 本专栏将不间断更新有关C音视频开发的内容&#xff0c;其中有初级篇、中级篇与高级篇的内容&#xff0c;包括但不限于音视频基础、FFmpeg实战、QT、流媒体客户端、流媒体服务器、WebRTC实战、Android NDK等等。是博主花了将近5000元购买的课程中的知识点&#xff0c;其中…...

Html-文本属性

常用的文本属性 属性描述说明font-size字体大小单位是px&#xff0c;浏览器默认是16px。font-family字体多个字体中间用逗号链接&#xff0c;先解析第1个字体,如果没有解析第2个字体,以此类推color颜色 red;#ff0;rgb(255,0,0); 0-255font-weight加粗 bolder(更粗的&#xff09…...

Dockerfile

Dockerfile指令集 对于Dockerfiel而言&#xff0c;是在学习docker工具里面&#xff0c;最重点的内容&#xff0c;它可以帮助我们生成自己想要的基础镜像。部署一个容器最重要的就是镜像&#xff0c;指令都已经内置好了。 FROM 这个镜像的妈妈是谁&#xff1f;&a…...

Java反射04:获取运行时类的属性结构及其内部结构

文章目录获取运行时类的属性结构及其内部结构新建测试类1.获取每一个Field&#xff08;属性&#xff09;2.获取运行时类的方法结构3.获取运行时类的构造器4.获取当前运行时所继承的父类和接口5.获取当前运行时类的注解、包、泛型获取运行时类的属性结构及其内部结构 通过反射获…...

qsettings 读写注册表

qsettings简单的实现一个注册表读写操作&#xff0c;记录程序中需要保存的信息。使用qsettings声明对象之前&#xff0c;需要指明qsettings的组织名和应用名&#xff0c;分别利用QCoreApplication::setOrganizationName()和QCoreApplication::setApplicationName()来指定组织名…...

【JavaScript高级进阶】构造函数和原型,学会prototype

目录 前言 1.构造函数和原型 1.1使用prototype解决内存浪费的问题 1.2constructor构造函数构造器构造函数 2.原型链 2.1js中成员查找规则 2.2原型对象this指向 2.3扩展内置对象 3.call作用 4.继承 4.1利用原型对象继承 写在最后 前言 哈喽哈喽大家好&#xff0c;因为…...

VMware16虚拟机添加硬盘(磁盘)和挂载硬盘(磁盘)

记录&#xff1a;317 场景&#xff1a;在VMware16虚拟机&#xff0c;安装了CentOS 7.9操作系统场景下&#xff0c;添加硬盘(磁盘)和挂载硬盘(磁盘)。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 1.机器配置 机器名称&#xff1a;B200&#xff1b;主机名称&#xff…...

【学生个人网页设计作品】使用HMTL制作一个超好看的保护海豚动物网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…...

短视频社交|电影点播平台Springboot+vue+ElementUI前后端分离

感谢您的关注&#xff0c;请收藏以免忘记&#xff0c;点赞以示鼓励&#xff0c;评论给以建议&#xff0c;爱你哟 项目编号&#xff1a;BS-PT-071 一&#xff0c;项目简介 本项目基于Springbootvue开发实现了一个电影点播和短视频分享平台&#xff0c;名为爱奇艺影视平台系统。…...

基于Springboot+mybatis+mysql+html图书管理系统

基于Springbootmybatismysqlhtml图书管理系统一、系统介绍二、功能展示1.用户登陆2.图书管理3.读者管理4.借还管理5.密码修改6.图书查询&#xff08;读者&#xff09;7.个人信息&#xff08;读者&#xff09;8.我的借还&#xff08;读者&#xff09;一、系统介绍 系统主要功能…...

公众号网课查题系统

公众号网课查题系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点击…...

02 LaTex之小tips

1.运行 2.头&#xff0b;尾 \documentclass[11pt]{article}\usepackage{algorithm, algpseudocode} \usepackage{amsmath,amssymb,amsthm} \usepackage{mathrsfs}% huaxie zimu \textwidth 16cm\textheight 22cm\oddsidemargin0cm\evensidemargin\oddsidemargin\usepackage{un…...

为了摸鱼,我开发了一个工具网站

&#x1f3e1; 博客首页&#xff1a;派 大 星 ⛳️ 欢迎关注 &#x1f433; 点赞 &#x1f392; 收藏 ✏️ 留言 &#x1f3a2; 本文由派大星原创编撰 &#x1f6a7; 系列专栏&#xff1a;《开源专栏》 &#x1f388; 本系列主要输出作者自创的开源项目 &#x1f517; 作品&…...

CodeForces - 545E Paths and Trees 最短路建树

题目链接&#xff1a;点击查看 Little girl Susie accidentally found her elder brothers notebook. She has many things to do, more important than solving problems, but she found this problem too interesting, so she wanted to know its solution and decided to a…...

koa + pug模板引擎

模板引擎 模板引擎&#xff1a;模板引擎是web应用中动态生成html的工具&#xff0c;负责将数据和模板结合。常见模板引擎有&#xff1a;ejs、jade&#xff08;现更名为pug&#xff09;、Handlebars、Nunjucks、Swig等&#xff1b;使用模板引擎可以是项目结构更加清晰&#xff…...

数字集成电路设计(二、Verilog HDL基础知识)

文章目录1. 语言要素1.1 空白符1.2 注释符1.3 标识符1.3.1 转义标识符1.4 关键字1.5 数值1.5.1 整数及其表示方式1.5.2 实数及其表示方式1.5.3 字符串及其表示方式2. 数据类型2.1 物理数据类型2.1.1 连线型2.1.2 寄存器型2.2 连线型和寄存器型数据类型的声明2.2.1 连线型数据类…...

【工具使用】Visual Studio Code远程调试

VS Code的其中一个关键的特征就是它极好的调试支持。VS Code的内置调试器帮助加速你的编辑、编译和调试循环。 调试扩展 VS Code有Node.js运行的内置的调试支持&#xff0c;并且能够调试Java脚本或者任何其他可以转译为JavaScript的语言。为了调试其他语言&#xff08;包括P…...

【Flutter】【widget】Table 表格widget

文章目录前言一、Table 是什么&#xff1f;二、使用步骤1.Table 基础使用2.宽度3.设置边框4.TableCell设置单元格式widget等其他设置总结前言 Table 表格widget&#xff0c;其实很少使用到的&#xff0c;等有需要的时候在查看该widget 一、Table 是什么&#xff1f; 表格widg…...

ADB学习笔记

简介&#xff1a; ADB的全称为Android Debug Bridge&#xff08;调试桥&#xff09;&#xff0c; 它是一个客户端-服务器端程序&#xff0c;其中客户端是你用来操作的电脑, 服务器端是android设备。作用显而易见&#xff0c;能方便我们在PC上对手机进行调试的一些工作。 原理…...

http load介绍

前几天工作中要对项目的接口做简单压测&#xff0c;就使用了http load做了简单测试&#xff0c;下面介绍一下这款工具的使用说明。简介&#xff1a;http_load是基于linux平台的性能测试工具&#xff0c;它体积非常小&#xff0c;仅100KB。它以并行复用的方式运行&#xff0c;可…...

Linux shell脚本之回顾及实用笔记

一、前言 我们从事运维的小伙伴,除了自动化运维外,在没有自动化条件下,借助shell脚本/Python脚本来提升运维效率,无疑是一个必选项,当前也可以自建自动化运维平台,我们这里还是以Linux shell脚本为主,来汇总一些常用的运维脚本,对于有基础的同学,也随本文一起回顾下相…...

TestNG使用总结

TestNG简介&#xff1a; TestNG是一个测试框架&#xff0c;其灵感来自JUnit和NUnit&#xff0c;但同时引入了一些新的功能&#xff0c;使其功能更强大&#xff0c;使用更方便。 TestNG相较于Junit的优点&#xff1a; 可指定执行顺序&#xff0c; dependsOnMethods 属性来应对…...

面向对象编程的弊端

英文原文&#xff1a;What’s Wrong with OOP and FP 我不理解为什么人们会对面向对象编程和函数式编程做无休无止的争论。就好象这类问题已经超越了人类智力极限&#xff0c;所以你可以几个世纪的这样讨论下去。经过这些年对编程语言的研究&#xff0c;我已经清楚的看到了问题…...

5.Servlet

一、Servlet快速入门 1.创建web项目&#xff0c;导入Servlet依赖坐标&#xff08;scope范围为provided因为上传后tomcat也有这个&#xff0c;可能会冲突&#xff09;pom.xml <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-a…...

(续)SSM整合之springmvc笔记(@RequestMapping注解)(P124-130)还没完

RequestMapping注解 一.准备工作 1 新建spring_mvc_demo com.atguigu 2. 导入依赖 <packaging>war</packaging><dependencies><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>sprin…...

c++入门必学算法 质数筛

文章目录一、什么是质数筛二、暴力枚举1、暴力枚举基本思想&#xff1a;2、模板代码3、运行结果三、埃氏筛1、埃氏筛基本思想&#xff1a;2、模板代码3、运行结果四、欧拉筛1、对比埃氏筛2、欧拉筛的基本思想3、模板代码3、运行结果五、总结一、什么是质数筛 质数筛也叫素数筛…...

Verilog结构语句和函数、任务语句

目录 结构说明语句 initial说明语句 always说明语句 task和function说明语句 task说明语句 function说明语句 关于使用任务和函数的小结 结构说明语句 Verilog语言中的任何过程模块都从属于以下4种结构的说明语句&#xff1a; initial说明语句 一个模块种可以有多个i…...

String 创建字符串对象和字符串常量池的联系推理

文章目录String 创建字符串对象和字符串常量池的联系推理ref前提intern方法String s "abc";字符串相加String 创建字符串对象和字符串常量池的联系推理 可能有错误理解 ref String s1 new String(“abc”);这句话创建了几个字符串对象&#xff1f; 我提的issue …...

flex 计算指定日期是本年度第几周

/** * 计算指定日期是本年度第几周 *传日年月日&#xff0c;返回number */ private function weekOfYear(yyyy:Number,mm:Number,dd:Number):Number{ var myDate:Date new Date(yyyy, mm - 1, dd); var startDate:Date new Date(yyyy,0,1); v…...

SpringCloud Zuul(四)之工作原理

一、筛选器概述 Zuul的中心是一系列过滤器&#xff0c;这些过滤器能够在HTTP请求和响应的路由期间执行一系列操作。 以下是Zuul过滤器的主要特征&#xff1a; 类型&#xff1a;通常定义路由流程中应用过滤器的阶段&#xff08;尽管它可以是任何自定义字符串&#xff09;执行…...

【毕业设计】大数据分析的航空公司客户价值分析 - python

文章目录0 前言1 数据分析背景2 分析策略2.1 航空公司客户价值分析的LRFMC模型2.2 数据2.3 分析模型3 开始分析3.1 数据预处理3.1.1 数据预览3.1.2 数据清洗3.2 变量构建3.3 建模分析4 数据分析结论4.1 整体结论4.2 重要保持客户4.3 重要挽留客户4.4 一般客户与低价值客户5 最后…...

软件工程毕业设计课题(80)微信小程序毕业设计PHP电影视频播放小程序系统设计与实现

项目背景和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于微信电影播放小程序系统&#xff0c;前台用户使用小程序&#xff0c;小程序使用微信开发者工具开发&#xff1b;后台管理使用基PPMySql的B/S架构&#xff0c;开发工具使用phpstorm&#xff1b;通过后台添…...

PyTorch搭建基于图神经网络(GCN)的天气推荐系统(附源码和数据集)

需要源码和数据集请点赞关注收藏后评论区留言~~~ 一、背景 极端天气情况一直困扰着人们的工作和生活。部分企业或者工种对极端天气的要求不同&#xff0c;但是目前主流的天气推荐系统是直接将天气信息推送给全部用户。这意味着重要的天气信息在用户手上得不到筛选&#xff0c;…...

Python 对象保存优化机制

Python 为了减少开销与内存的使用而设置一些规则: * 1. 但凡是不可变对象, 在同一个代码块中的对象, 只要是值相同的对象, 就不会重复创建, 而是直接引用已经存在的对象.交互环境下: 不写在一行, 字符类型数据指向一个内存地址, 整型超出小整数则执指向不同的地址. 代码块缩进相…...

隐式转换这个概念你听说过没?

世界上最遥远的距离不是生与死&#xff0c;而是你亲手制造的BUG就在你眼前&#xff0c;你却怎么都找不到她。 目录 1、隐式转换是什么意思 1.1整型截断 1.2整形提升 2、char的取值范围 2.1有符号char取值范围 2.2无符号char取值范围 前言&#xff1a; 大家好&#xff0c;…...

Web 性能优化:TLS

个人博客 Web 性能优化&#xff1a;TCP&#x1f3af; Web 性能优化&#xff1a;TLSWeb 性能优化&#xff1a;HTTP “do it, do it work, do it better … and secure ☠️” 随着追逐利益而来的恶意参与者越来越多&#xff0c;当前的 Web 应用&#xff0c;已经从野蛮生长转而…...

力扣113题引发的关于DFS和回溯的一点思考

最近刚学回溯和DFS&#xff0c;刷力扣遇到一道题&#xff08;113题&#xff09;&#xff0c;如下&#xff1a; 我们不细究回溯和DFS的区别联系。关于这道题的2种写法&#xff0c;我把第一种称为回溯。 class Solution {List<List<Integer>> res new LinkedList&l…...

Go 语言报错 StackGuardMultiplier redeclared in this block

前言 最近在 GitHub 刷到了 GitHub - golang-china/gopl-zh: Go语言圣经中文版&#xff0c; 然后又是周末&#xff0c;就起了玩心。搞一个 Go 玩玩&#xff0c;去 The Go Programming Language下载了 Go 语言安装包&#xff0c;一路默认安装。然后打开 VS Code 安装 Extensio…...

C\C++刷题ADY3

题目来源&#xff1a;力扣 1.第一题 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 思路分析:&#xff08;不带哨兵位的头节点&#xff09; 每次去分析一个节点&#xff0c; 如果节点不是存的是6&#xff0c;就拿节点来尾插 如果节点存的不是6&#xff0c;就把节…...

解决elementui 的省市区级联选择器数据不回显问题

上周写了一个省市区三级联动的地址选择组件&#xff0c;今天测试发现了一个大问题&#xff0c;那就是我可以正常提交地址是没错&#xff0c;可是当我后端返回了数据&#xff0c;我要点击编辑的时候&#xff0c;它并不会自动就给我绑定上去。 vue实现省市区三级联动地址选择组件…...

[CSS]圆角边框与阴影

前言 系列文章目录&#xff1a; [目录]HTML CSS JS 根据视频和PPT整理视频及对应资料&#xff1a;HTML CSS 老师笔记&#xff1a; https://gitee.com/xiaoqiang001/html_css_material.git视频&#xff1a;黑马程序员pink老师前端入门教程&#xff0c;零基础必看的h5(html5)css3…...

微信小程序原理

前言 微信小程序采用JavaScript. WXML. WXSS三种技术进行开发&#xff0c;从技术讲和现有的前端开发差不多&#xff0c;但深入挖掘的话却又有所不同。 一、原理 JavaScript&#xff1a;首先JavaScript的代码是运行在微信App中的&#xff0c;并不是运行在浏览器中&#xff0c;…...

Neo4j 开发者大会 NODES 2022 活动日程已发布 - 11.16 ~ 11.17

各位 Graphistas&#xff1a; Neo4j 开发者大会 NODES 2022 将在 2022 年 11 月 16&#xff5e;17 日召开&#xff0c;不要错过这连续 24 小时跨越 3 个主要时区的大型在线活动&#xff0c;欢迎加入我们一起庆祝来自全球图技术社区的隆重分享。 现在访问官方网站注册活动: ht…...

Google Swift 与 DC 传输

网络拥塞&#xff0c;默认指转发节点出现了严重的排队现象&#xff0c;甚至队列溢出而丢包。、 但接收端也是一个统计复用系统(通用 OS 均为统计复用系统&#xff0c;比如 Linux)&#xff0c;但凡统计复用系统就是潜在拥塞点&#xff0c;即可套用排队论模型。 人们很少将最后…...

webservice学习记录笔记(一)

一、先理解什么是服务 现在的应用程序变得越来越复杂&#xff0c;甚至只靠单一的应用程序无法完成全部的工作。更别说只使用一种语言了。 写应用程序查询数据库时&#xff0c;并没有考虑过为什么可以将查询结果返回给上层的应用程序&#xff0c;甚至认为&#xff0c;这就是数…...

[附源码]SSM计算机毕业设计-东湖社区志愿者管理平台JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…...