Spring框架學習 -- 讀取和存儲Bean對象

目錄 🚀🚀

回顧

getBean()方法的使用?

根據name來獲取對象

再談getBean()

(1) 配置掃描路徑

(2) 添加注解

① spring注解簡介?

② 對類注解的使用

③ 注解Bean對象的命名問題

④ 方法加Bean注解

(3) @Bean 注解的重命名

(4) 獲取Bean對象 -- 對象裝配?

?屬性注入

屬性注入優缺點

setter注入?

?setter注入優缺點

構造方法注入 (官方推薦)

構造方法優缺點

(5) 另外一種注入關鍵字: @Resource

(6) 同一個變量多個@Bean注入報錯

總結?


😍創作不易多多支持🫡?


回顧

? ? ? ? 回憶一下我們之前是如何存儲和使用Bean對象的, 首先我們需要在spring配置文件寫入Bean對象, 或者說是spring容器中注入Bean對象:

<?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"><bean id="user1" class="org.example.User"></bean><bean id="user2" class="org.example.User"></bean>
</beans>

? ? ? ? 然后在啟動類中new出一個上下文對象, 要么使用BeanFactory, 要么使用ApplicationContext, 然后調用上下文對象中的getBean方法, 來獲取到對應的Bean對象.?

? ? ? ? 但是我每次使用都要去在spring配置文件中寫入bean標簽, 然后寫上對應id和類. 這些步驟過于繁瑣, 于是spring的作者就創造出了一種更簡單的方式, 那就是直接在對應的類下面寫上注解, 注解中填入id, 就省去了寫路徑的痛苦.

? ? ? ? 我們現在就可以直接通過一行注解, 來代替我們之前要寫一行配置的尷尬了,

? ? ? ? 不過在寫注解之前需要一點準備工作.

? ? ? ? 首先在這之前我們應該去了解一下獲取Bean對象的方法, 也就是getBean方法

getBean()方法的使用?

? ? ? ? ?spring容器提供了五種獲取Bean的方法, 可以根據Bean的name來獲取Bean, 也可以根據classType 來獲取Bean對象, 所有的根據Bean的name來獲取Bean的方法, 最后底層都會調用下面的doGetBean方法來獲取Bean對象

根據name來獲取對象

? ? ? ? ?spring 容器提供了三種根據Bean 的name來獲取Bean的方法;

        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));User user = (User) beanFactory.getBean("user");User user1 = (User) beanFactory.getBean("user", User.class);User user2 = (User) beanFactory.getBean("user", new OrderUser());
  • ?getBean("user") 是根據Bean的name 來獲取Bean對象
  • ?getBean("user", User.class) 是根據Bean的name來獲取Bean, 然后判斷Bean是否屬于User類
  • ? getBean("user", new OrderUser());則是使用自定義的方法來生成Bean對象

? ? ? ? 上面三種方法都會調用doGetBean方法來生成Bean對象, 下面是doGetBean方法的原碼:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {// name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx// name有可能傳入進來的是別名,那么beanName就是idString beanName = transformedBeanName(name);Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {// 如果sharedInstance是FactoryBean,那么就調用getObject()返回對象beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}

? ? ? ? 真正的BeanName來自這個transformedBeanName(name) 方法;?

? ? ? ? 下面是這個方法的具體實現:

protected String transformedBeanName(String name) {return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

? ? ? ? ?順藤摸瓜. 先來看看BeanFactoryUtils.transformedBeanName(name) 的實現:

public static String transformedBeanName(String name) {Assert.notNull(name, "'name' must not be null");if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {return name;}return transformedBeanNameCache.computeIfAbsent(name, beanName -> {do {beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());}while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));return beanName;});
}

? ? ? ? ?什么是BeanFactory.FACTORY_BEAN_PREFIX?

? ? ? ? 翻閱原碼可以知道這個是一個字符串對象, 值為"&".

? ? ? ? 我們再來看看String的subString方法:

? ? ? ? ?結合原碼, 也就是說beanName的生成為:

beanName = beanName.subString(1);

? ? ? ? 里層是一個do while循環, 也就是先去掉一個字符, 然后循環查看beanName的前面是否有"&"字符, 如果有就一直去掉這&字符.?

? ? ? ? 如果傳進來的name從一開始就不是&字符開頭的字符串, 那么就直接返回當前的name.?

? ? ? ? 總結來說就是?BeanFactoryUtils.transformedBeanName(name) 是去掉name前面的所有的&字符

? ? ? ? 接下來回頭看看這個canonicalName方法:

protected String transformedBeanName(String name) {return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

? ? ? ? ?看看這個方法的具體實現:

public String canonicalName(String name) {String canonicalName = name;// Handle aliasing...String resolvedName;do {resolvedName = this.aliasMap.get(canonicalName);if (resolvedName != null) {canonicalName = resolvedName;}}while (resolvedName != null);return canonicalName;
}

?解析:

  • String name 為上面一層函數BeanFactoryUtils.transformedBeanName(name) 傳過來的去掉開頭的所有的&字符的字符串.
  • 此函數里面首先使用canonicalName字符串對象將這個去掉&的字符串給存起來了.
  • 創建了一個字符串變量: resolvedName
  • 隨后進入dowhile循環, 首先是調用了this.aliasMap.get(canonicalName);
  • aliasMap是一個hashMap, 這里調用get方法拿到key = canonicalName, 如果對應value值不為空, 那么就將這個值賦值給canonicalName, 然后返回這個值canonicalName.

????????Bean的別名存放在一個aliasMap中,其中KEY=別名,VALUE=beanName/別名,根據別名從aliasMap中拿到的可能是真正的beanName,也可能還是一個別名,所以用do-while循環,直到拿出來的名字從aliasMap再找不到對應的值,那么該名字就是真正的beanName了

????????對bean 進行定義的時候,除了可以使用id 命名,為了提供多個別名,使用alias來指定,這些所有的名稱都指向同一個bean:

<bean class="User1" name="user1"/>
<alias name="user1" alias="user2,user3"/>

再談getBean()

? ? ? ? 我們明明就可以根據一個String name來鎖定一個Bean對象, 為什么后面還需要傳入第二個參數 XXX.class??

? ? ? ? 首先我們如果傳入xxx.class, 我們在獲取Bean對象的時候, 是返回一個Object類型的對象, 這個時候還需要我們進行強制類型轉換成我們需要的類型, 但是這個時候可能會出錯. 也就是說, 如果將其轉換成了一個不是我們所需要的類, 那么在運行期間就會拋出異常.? ? ? ??

? ? ? ? 我們第二個參數傳入一個class,?此方法更安全,因為我們可以編譯階段就發現錯誤而不是在運行階段。?

(1) 配置掃描路徑

? ? ? ? ?想要將對象成功存儲到容器中, 我們需要先配置一下存儲對象的掃描路徑, 只有被掃描到的包下的類, 添加注解才能被成功的識別到并被保存到容器中.

? ? ? ? 首先我們拿下面這個這個目錄結構為例:

?其中User類的代碼如下:

package org.example;public class User {public void sayHi() {System.out.println("hello, how are you?");}
}

Student的代碼如下:

package org.select;public class Student {public void sayHi() {System.out.println("hello student!! ");}
}

????????下面我們首先添加掃描路徑, 我們掃描select包下的所有的類, 于是就在spring配置文件中寫入其掃描路徑:

<content:component-scan base-package="org.select"></content:component-scan>

? ? ? ? 將其訪問spring配置文件的beans標簽中:

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><content:component-scan base-package="org.select"></content:component-scan>
</beans>

? ? ? ? -- 其實添加這個掃描路徑 就是為了更加精確的掃描出需要的類的所在的包, 為了更佳的性能

(2) 添加注解

? ? ? ? 想要通過添加注解的方式來存儲Bean對象, 那么首先就需要去了解一下spring的注解:

① spring注解簡介?

Spring的注解是一種用于簡化配置和開發的方式,它可以幫助開發人員更輕松地使用Spring框架。下面是幾個常用的Spring注解的介紹:

  1. @Autowired:用于自動裝配依賴關系。當Spring容器需要為一個屬性注入一個bean時,它會查找與該屬性類型匹配的bean,并將其自動注入到屬性中。

  2. @Qualifier:用于指定要注入的bean的名稱。當有多個與屬性類型匹配的bean時,可以使用@Qualifier注解來指定要注入的bean的名稱。

  3. @Component:用于將一個類標記為Spring容器的組件。被標記為@Component的類將被Spring自動掃描并注冊為bean。

  4. @Controller:用于標記一個類作為Spring MVC的控制器。被標記為@Controller的類將處理HTTP請求并返回相應的視圖。

  5. @Service:用于標記一個類作為業務邏輯層的組件。被標記為@Service的類通常包含業務邏輯,并被其他組件調用。

  6. @Repository:用于標記一個類作為數據訪問層的組件。被標記為@Repository的類通常用于訪問數據庫或其他持久化存儲。

  7. @Configuration:用于標記一個類作為Spring的配置類。被標記為@Configuration的類通常包含了一些用于配置Spring容器的bean定義。

  8. @Bean:用于在配置類中定義一個bean。被標記為@Bean的方法將返回一個對象,并將其注冊為Spring容器的bean。

  9. @Value:用于注入屬性值。可以將@Value注解應用于屬性上,從而將屬性值從配置文件中注入到屬性中。

下面是一個使用@Autowired和@Qualifier注解的示例:

@Component
public class MyComponent {@Autowired@Qualifier("myBean")private MyBean myBean;// ...
}

? ? ? ? 接下來我們仔細看看該如何使用...!!!

② 對類注解的使用

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));Student student = beanFactory.getBean("student", Student.class);student.sayHi();

?輸出:

? ? ? ? 使用spring容器手動添加Bean對象和掃描注解Bean對象是可以混用的.

③ 注解Bean對象的命名問題

? ? ? ? 使用注解訪問Bean對象, 那么getBean傳入的第一個參數開頭字母直接小寫即可, 但是如果首字母和第二個字母都是大寫, 那么開頭一個字母小寫是錯誤的, 這個時候直接使用原來的類名即可.?

其源碼如下:

public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}// 如果第?個字?和第?個字?都為?寫的情況,是把 bean 的?字?也?寫存儲了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);
}

舉個例子, 有三個類, 他們添加了注解, 然后使用getBean方法取出去Bean對象, 如下:?

? ? ? ? 其中第二種就會找不到Bean對象.

④ 方法加Bean注解

? ? ? ? 下面我們創建一個Test1類, 此類中有兩個字段, 同時實現了其toString方法, 下面我們將通過添加Bean注解的方法來將這個類注入到容器:

package org.example2;/*** 普通的文章實體類*/
public class Test1 {private int age;private String name;@Overridepublic String toString() {return "Test1{" +"age=" + age +", name='" + name + '\'' +'}';}
}

? ? ? ? 但是@Bean注解必須配合五大類注解一起使用

package org.select;/*** 普通的文章實體類*/
public class Test1 {private int age;private String name;@Overridepublic String toString() {return "Test1{" +"age=" + age +", name='" + name + '\'' +'}';}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

? ? ? ? 添加Bean注解和五大類注解:

package org.example2;import org.select.Test1;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;@Controller
public class RetTest1 {@Beanpublic Test1 returnTest() {Test1 test1 = new Test1();test1.setAge(18);test1.setName("張三");return test1;}
}

? ? ? ? 文章目錄結構:

? ? ? ? 添加掃描路徑:

<content:component-scan base-package="org.example2"></content:component-scan>

? ? ? ? ?配置上下文, 使用getBean獲取Test1 的對象

ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
//        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));RetTest1 retTest1 = context.getBean("retTest1", RetTest1.class);retTest1.sayHi();Test1 test1 = context.getBean("test1", Test1.class);System.out.println(test1.toString());

? ? ? ? ?-- 輸出結果如下:

? ? ? ? 可以發現, 一個類的方法添加了@Bean注解, 這個@Bean注解可以讓被標記的方法返回的對象存儲進入容器, 同時@Bean需要配合五大類注解進行, 所以被五大類注解標記的類對象同樣也會被存入容器.

? ? ? ? 需要注意的是, 使用@Bean注解的時候, 這里如果去使用BeanFactory去獲取上下文的話, 就會找不到@Bean注解的方法返回的類.

? ? ? ? 還有一點就是, 最后@Bean注解的方法, 在獲取這個類的時候, getBean中的參數是@Bean注解的方法名, 而不是類名.

? ? ? ? 如果將ApplicationContext 獲取的上下文對象換成 BeanFactory, 就會顯示如下:

? ? ? ? 原因定位:
????????使用`new XmlBeanFactory(new ClassPathResource("bean.xml"))`實例化出來的對象是無法讀取Spring注解配置文件的,因為`XmlBeanFactory`只能解析XML格式的配置文件,而無法解析注解配置。而`ClassPathXmlApplicationContext`可以同時解析XML和注解配置文件,因此使用`new ClassPathXmlApplicationContext("bean.xml")`可以成功讀取Spring注解配置文件。

? ? ? ? 所以我們在IDEA中使用BeanFactory的時候可以看到一個刪除線:

? ? ? ? 此刪除線的意思是 IDEA 不推薦使用這個方法, 而不是說不能用, 不推薦使用的原因是此方法已經過時, 有更好, 更安全的方法替代. 例如 ApplicationContext.

(3) @Bean 注解的重命名

?????????以下是@Bean注解的源碼, 我們可以參考參考

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.core.annotation.AliasFor;@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {@AliasFor("name")String[] value() default {};@AliasFor("value")String[] name() default {};/** @deprecated */@DeprecatedAutowire autowire() default Autowire.NO;boolean autowireCandidate() default true;String initMethod() default "";String destroyMethod() default "(inferred)";
}

? ? ? ? 在使用@Bean注解的時候, 可以通過name 屬性來對Bean進行重新命名, 例如將下面一個名為myBean的Bean對象重新命名為newName:

@Bean(name = "newName")
public MyBean myBean() {return new MyBean();
}

? ? ? ? 或者是直接在里面傳入字符串, 即name = 此字符串

????????此外,還可以使用@Bean注解的value屬性來進行重命名,例如:

@Bean(name = "newName")
public MyBean myBean() {return new MyBean();
}

? ? ? ? ?我們總結一下這三種方式:

? ? ? ? @Bean支持指定多個別名?

? ? ? ? ?需要注意的是, 不管是value 還是 name 來命名這個Bean注解, 都不能再使用原來的方法名來獲取這個Bean對象了.

? ? ? ? 不管是value還是name. 它們都是一個字符串的數組, 可以傳入多個別名:

 @Bean( value = {"newName","newName2","newName3"})public Test1 test1() {Test1 test1 = new Test1();test1.setAge(18);test1.setName("張三");return test1;}

? ? ? ? 但是這幾個名字newName, newName2,newName3都是指的同一個類.?

? ? ? ? 需要注意的一個問題是, 如果我連續有三個方法或者是更多的方法都用的同一個Bean別名, 如下:

    @Bean("getTest1")public Test1 test1() {Test1 test1 = new Test1();test1.setAge(18);test1.setName("張三");return test1;}@Bean("getTest1")public Test1 test2() {Test1 test1 = new Test1();test1.setAge(17);test1.setName("李四");return test1;}@Bean("getTest1")public Test1 test3() {Test1 test1 = new Test1();test1.setAge(19);test1.setName("王五");return test1;}

? ? ? ? ?這個時候我再去訪問這個getTest1這個Bean對象, 將會輸出哪一個??

-- 輸出:

? ? ? ? 從結果上來看是輸出的第一個,? 其實這個是和它的加載的順序時有關系的, 其中我們可以使用@Order注解來設定我們@Bean注解的加載順序

? ? ? ? 如果多個Bean的名稱相同, 那么程序執行不會報錯, 但是第一個Bean之后的對象不會被存放到容器中, 也就是只有在第一次創建Bean的時候, 會將對象和Bean名稱關聯起來, 后續再有相同名稱的Bean存儲的時候,? 容器會自動忽略.

? ? ? ? 同時如果我們的方法里面傳入了參數, 那么在編譯期間也會拋出異常:

    @Bean("getTest1")public Test1 test1(int age) {Test1 test1 = new Test1();test1.setAge(age);test1.setName("張三");return test1;}

? ? ? ? ?或者版本更高一點的編譯器會在編譯前就出現錯誤提示:

(4) 獲取Bean對象 -- 對象裝配?

? ? ? ? 獲取Bean對象, 也叫作對象裝配, 是把對象取出來放到某個類中, 有時候頁腳對象注入

? ? ? ? 對象裝配的實現方法有以下三種:

  1. 屬性注入
  2. 構造方法注入
  3. Setter注入?

?屬性注入

? ? ? ? 屬性注入是使用@Autowired實現的, 將Service類注入到Controller中, Service類中的代碼實現如下:

首先創建User類:

package com.java.demo.User;public class User {private int age;private String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"age=" + age +", name='" + name + '\'' +'}';}
}

創建UserService類:

@Service
public class UserService {public User getUser(Integer id) {User user = new User();user.setAge(id);user.setName("my" + id);return user;}
}

創建UserController類:

@Controller
public class UserController {@Autowiredprivate UserService userService;public User getUser(Integer id) {return userService.getUser(id);}
}

?生成啟動類:

public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");UserController userController = context.getBean(UserController.class);System.out.println(userController.getUser(1).toString());}
}

????????解釋: 我們首先配置好spring上下文對象之后, 就去獲取這個userController的Bean對象, 然后調用其getUser方法?,但是你可能會問, userController的getUser方法里面是UserService的getUser方法,但是這個對象里面的UserService字段userService未賦值, 怎么能調用其getUser方法啊, 這其實就是Autowired注解的作用, 他自動將UserService的Bean對象賦值給這個UserService字段, 然后調用.

? ? ? ? 隨后在userService的對象中調用getUser(1)

? ? ? ? ?最后返回User, 調用這個User的toString方法. 生成的結果如下:

屬性注入優缺點

? ? ? ? 屬性注入使用非常簡單, 但是也存在著一些問題:

(a) 無法注入被final修飾的變量

? ? ? ? 圖中顯示, 變臉沒有被初始化, 這個時候如果我們在這個類中加上這個變量的構造方法:

    @Autowiredprivate final UserService userService;public UserController() {userService = new UserService();}

? ? ? ? 就不會出現編譯前異常.?

(b) 通用性問題: 只是用與ioc容器

(c) 更容易違背單一性設計元素, 但是用起來更簡單.

setter注入?

? ? ? ? 創建類UserService:

public class UserService {public UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}public void sayHi() {System.out.println("UserService says hi");userRepository.add();}
}

? ? ? ? ?創建類UserRepository類:

@Repository
public class UserRepository {public int add() {System.out.println("userRepository add");return 1;}
}

? ? ? ? 添加main方法:

public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");UserService userService = context.getBean("userService", UserService.class);userService.sayHi();}
}

?

? ? ? ? 此時UserService類中的UserRepository字段會根據@Autowired修飾的setter方法自動注入Bean對象.

? ? ? ? 結果輸出--

?setter注入優缺點

(a) 無法注入被final修飾的變量 .

(b) setter中每一次都只設置一個屬性, 遵守設置的單一性設計原則.?

(c) setter注入的對象可以被修改.

構造方法注入 (官方推薦)

? ? ? ? ?創建UserRepository類:

@Repository
public class UserRepository {public int add() {System.out.println("userRepository add");return 1;}
}

? ? ? ? 創建UserService類:

@Service
public class UserService {private UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public void sayHi() {System.out.println("sayHi in UserService: " + userRepository.add());}
}

? ? ? ? 添加啟動項:

public class Main {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");UserService userService = context.getBean("userService", UserService.class);userService.sayHi();}
}

? ? ? ? ?-- 輸出

? ? ? ? ?此時此刻,我將UserService類中的構造方法上面的@Autowired注解給注釋掉:

@Service
public class UserService {private UserRepository userRepository;// @Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public void sayHi() {System.out.println("sayHi in UserService: " + userRepository.add());}
}

? ? ? ? 啟動, 發現仍然可以輸出結果:

? ? ? ? 為什么? 其實這是官方推薦的注入方法,所以被spring官方特別照顧的注入方式,??標準的方法是要加的.?

? ? ? ? 如果當前類中只存在一個構造方法時, @Autowired是可以省略的.

? ? ? ? 如果有多個構造方法, 仍然注釋掉@Autowired :

//    @Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public UserService(UserRepository userRepository, Integer i) {this.userRepository = userRepository;}

? ? ? ? ?此時啟動的話是會報錯的:

? ? ? ? ?釋放掉這個 @Autowired :

@Service
public class UserService {private UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}public UserService(UserRepository userRepository, Integer i) {this.userRepository = userRepository;}public void sayHi() {System.out.println("sayHi in UserService: " + userRepository.add());}
}

? ? ? ? 問題消失:

? ? ? ? ?使用構造方法去注入一個對象, 即使這個對象是被final修飾的, 仍然可以注入:

?

    private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}

構造方法優缺點

? ? ? ? 優點

  • ?可以注入一個被final修飾的變量
  • 注入的對象不會被修改, 因為構造方法只會加載一次
  • 構造方法注入可以保證注入對象完全初始化
  • 構造方法注入通用性更好

? ? ? ? 缺點

  • 寫法比屬性注入復雜
  • 使用構造方法注入, 無法解決循環依賴問題.

(5) 另外一種注入關鍵字: @Resource

? ? ? ? ?在進行類注入的時候,除了可以使用@Autowired 注解之外, 還可以使用@Resource進行注入, 如下:

@Controller
public class UserController {@Resourceprivate UserService userService;public User getUser(Integer id) {return UserService.getUser(id);}
}

?@Autowired 和 @Resource 的區別

  • 出身不同, @Autowired 來自spring, 而@Resource來自JDK注解
  • 使用時設置的參數不同, 相比于@Autowired來說, @Resource支持更多的參數設置, 例如name, 根據名詞獲取Bean
  • @Autowired 可以用于setter注入, 構造方法注入, 屬性注入, 但是@Resource只能用于setter注入, 和屬性注入, 不能使用構造方法注入.
  • IDEA兼容性不同, 使用@Autowired 可能在idea專業版可能會出現誤報的問題.

(6) 同一個變量多個@Bean注入報錯

? ? ? ? 對于同一個對象進行注入的時候, 但是找到了多個Bean對象, 此時spring就不知道注入哪一個,就會產生報錯信息, 代碼如下;

@Component
public class Users {@Beanpublic User user1() {User user = new User();user.setAge(18);user.setName("張三");return user;}@Beanpublic User user2() {User user = new User();user.setAge(20);user.setName("李四");return user;}
}

? ? ? ? 在另外一個類中獲取User對象, 如下:

public class UserController {@Resourceprivate User user;public User getUser() {return user;}
}

? ? ? ? main方法:

    public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");UserController userController = context.getBean("userController", UserController.class);}

? ? ? ? ?-- 輸出

? ? ? ? 找到了兩個Bean對象. 拋出此異常.?

? ? ? ? ?分析: User類中有兩個方法都是使用了@Bean注解, 在UserController類中使用@Resource進行屬性注入, 此時就會根據@Bean來獲取到這個User對象,但是這里有兩個Bean對象, 到底賦值哪一個?

? ? ? ? 原因: 非唯一的Bean對象

? ? ? ? 解決方案:

  • @Resource的name值去定義, 例如 @Resource(name = "user1")
  • 使用@Qualifier注解定義其使用的Bean對象的名稱

?????????解決方案的實例:

(a) 使用@Resource 的name注解:

@Controller
public class UserController {@Resource(name = "user1")private User user;public User getUser() {return user;}
}

(b) 使用@Qualifier注解:

@Controller
public class UserController {@Resource@Qualifier(value = "user1")private User user;public User getUser() {return user;}
}

總結?

  • 將對象存儲到spring中的方法:
    • 使用類注解: @Controller, @Service, @Repository, @Configuration, @Component
    • 使用方法注解: @Bean, 必須配合上面的類注解來使用
  • Bean命名的規則, 首字母和第二個字母都非大寫, 獲取Bean的用首字母小寫,如果首字母和第二個字母都是大寫, 那么直接使用歐原來的Bean名獲取Bean對象.
  • 從spring中獲取對象
    • 屬性注入
    • setter注入
    • 構造方法注入(官方推薦)
  • 注入的關鍵字還有
    • @Autowired
    • @Resource
    • @Resource和@Autowired的區別
  • 解決同一變量不同的Bean對象的問題
    • 使用@Resource(name = "xxx")?
    • 使用@Qualifier(value = "xxx")


?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/163583.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/163583.shtml
英文地址,請注明出處:http://en.pswp.cn/news/163583.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于YOLO模型建筑工地個人防護設備目標檢測

使用安全裝備可以保護他們免受建筑工地的意外事故。據統計&#xff0c;每年有數以萬計的工人在建筑工地受到嚴重傷害&#xff0c;造成終生困難。然而&#xff0c;通過自我監控來確保工人穿戴個人防護裝備非常重要。在這方面&#xff0c;需要一個準確和快速的系統來檢測工人是否…

微信小程序其他環境都能顯示在正式環境顯示不出來

踩坑日記 用了uni.getImageInfo 用了uni.getImageInfo 本地開發環境&#xff0c;測試環境全都可以&#xff0c;就是更新到正式環境不顯示。后面看代碼百度了這個api發現圖片所涉及的地址需要在小程序配置download域名白名單 https://uniapp.dcloud.net.cn/api/media/image.ht…

termios.h 頭文件包含問題

報奇怪的錯誤&#xff0c;解決掉其他錯誤或告警信息后&#xff0c;調整頭文件的順序。

地埋式積水監測儀廠家批發,實時監測路面積水

地埋式積水監測儀是針對城市內澇推出的積水信息監測采集設備&#xff0c;采用超聲波傳感技術和超聲波抗干擾功能&#xff0c;對路面的積水進行實時精準的監測。該設備能夠在零下-5℃至高溫70℃的嚴寒酷暑環境下可靠運行。它對城市道路積水進行實時監測并上報到監測系統之中&…

opencv-python的圖像分割算法

OpenCV-Python中提供了一些圖像分割算法&#xff0c;常用的有以下幾種&#xff1a; 1.基于閾值的分割&#xff1a; cv2.threshold()&#xff1a;根據設定的閾值將圖像分為兩個類別。cv2.adaptiveThreshold()&#xff1a;根據圖像局部區域的像素值進行自適應閾值分割。 2.基于…

線上問題排查實例分析|關于 Redis 內存泄漏

Redis 作為高性能的 key-value 內存型數據庫&#xff0c;普遍使用在對性能要求較高的系統中&#xff0c;同時也是滴滴內部的內存使用大戶。本文從 KV 團隊對線上 Redis 內存泄漏定位的時間線維度&#xff0c;簡要介紹 Linux 上內存泄漏的問題定位思路和工具。 16:30 問題暴露 業…

電動機保護方式

3.3.1、電動機溫度保護 溫度保護是利用安裝在電動機內部的溫度繼電器或變換器來實現的。當電動機達到一定溫度時繼電器動作&#xff0c;通過控制電路斷開電動機的主電路。對于單相小容量電動機&#xff0c;可以用繼電器直接斷開動力電路。 根據溫度傳感器的不同可以分為&…

cv2.threshold()函數參數講解

cv2.threshold()函數用于對圖像進行閾值化處理。它的參數如下&#xff1a; src&#xff1a;要處理的輸入圖像&#xff0c;可以是灰度圖像或彩色圖像&#xff0c;類型為uint8。thresh&#xff1a;設定的閾值&#xff0c;如果像素值大于閾值&#xff0c;則將其設為maxval&#x…

【Proteus仿真】【51單片機】籃球比賽計分器

文章目錄 一、功能簡介二、軟件設計三、實驗現象聯系作者 一、功能簡介 本項目使用Proteus8仿真51單片機控制器&#xff0c;使用聲光報警模塊、動態數碼管模塊、按鍵模塊等。 主要功能&#xff1a; 系統運行后&#xff0c;數碼管顯示比賽時間和AB隊得分&#xff1b;系統還未開…

數據中心運維管理:從人工到智能需要走幾步?

一切的變化來自于數據中心規模、復雜度、設備多樣性的挑戰&#xff0c;將運維平臺的重要性推向歷史高點。 此外&#xff0c;基于業務連續性方面的考慮&#xff0c;分布式數據中心成為越來越多客戶的選擇。 一、數據中心面臨的挑戰 運維管理分散&#xff0c;缺乏統一的管理 I…

Win11和NewBing瀏覽器100%開啟Copilot的方法

嚴格按以下步驟來&#xff0c;100%開啟免費的AI&#xff1a; 1.系統升級到Win11最新版&#xff08;不要用家庭版&#xff0c;推薦專業版&#xff09; 升級完成之后的系統信息&#xff08;時間截至2023.11.22&#xff09; 版本號&#xff1a;23H2 操作系統版本&#xff1a;226…

MySQL數據庫_01

Web后端開發_02 數據庫介紹 什么是數據庫&#xff1f; 數據庫&#xff1a;DataBase&#xff08;DB&#xff09;&#xff0c;是存儲和管理數據的倉庫 數據庫管理系統&#xff1a;DataBase Management System (DBMS)&#xff0c;操縱和管理數據庫的大型軟件。SQL&#xff1a;St…

自定義注解+AOP

自定義注解與AOP&#xff08;面向切面編程&#xff09;的結合常常用于在應用程序中劃定切面&#xff0c;以便在特定的方法或類上應用橫切關注點。以下是一個簡單的示例&#xff0c;演示了如何創建自定義注解&#xff0c;并使用Spring AOP來在被注解的方法上應用通知。 如何創建…

java學習part08權限

1.權限表格 外部類都是公有和缺省&#xff0c;因為其他兩種對于外部類沒有意義 一些內部成分都各種權限都可以 2.如何體現java封裝性 答&#xff0c;通過權限控制&#xff0c;保證哪些可以給人看到&#xff0c;哪些不能

手持式無線通信頻譜分析儀 MS2713E

MS2713E 手持式無線通信頻譜分析儀 安立手持式無線通信頻譜分析儀 MS2713E 旨在處理最惡劣的現場條件&#xff0c;使您能夠監控、定位、識別和分析各種蜂窩、2G/3G/4G、陸地移動無線電、Wi-Fi 和廣播信號。多功能 Spectrum Master 在定位和識別寬頻率范圍內的信號時&#xff0…

rust內存優化

背景 在 Rust 中,repr 是一個屬性(attribute),用于指定數據類型在內存中的布局和表現形式 repr 屬性可以用于枚舉、結構體和聯合體的定義,以控制它們的內部表示方式 repr 屬性有多個選項,每個選項對應于一種不同的布局方式 常見的選項包括: C 將類型按照 C 語言的規則…

3D人臉掃描設備助力企業家數字人復刻,打破商業邊界

京都薇薇推出數字人VN&#xff0c;以京都薇薇董事長為原型制作&#xff0c;賦能品牌直播、短片宣傳、線上面診等活動&#xff0c;進一步增強消費者對品牌的交互體驗&#xff0c;把元宇宙與品牌相融合&#xff0c;推動品牌線上服務與線下服務實現數字一體化&#xff0c;打造一個…

「X」Embedding in NLP|一文讀懂 2023 年最流行的 20 個 NLP 模型

在上一篇文章中&#xff0c;我們已經科普了什么是自然語言處理&#xff08;NLP&#xff09;、常見用例及其與向量數據庫的結合。今天&#xff0c;依然是「X」Embedding in NLP 系列專題&#xff0c;本文為初階第二篇&#xff0c;我們將深入介紹在 2023 年爆火的大語言模型 NLP …

小白也看的懂的爬取視頻操作

1.獲取一段視頻 可以直接從抖音下&#xff0c;也可以從b站上爬取&#xff08;注意法律謝謝&#xff09; 保護原創 b站的視頻 直接復制網址鏈接到嗶哩嗶哩(bilibili)視頻解析下載 - 保存B站視頻到手機、電腦 去就好了&#xff0c;

Docker的入門

Docker的入門 防火墻Docker的命令鏡像相關的命令運行容器容器相關的命令 Docker作為一個軟件集裝箱化平臺&#xff0c;可以讓開發者構建應用程序時&#xff0c;將它與其依賴環境一起打包到一個容器中&#xff0c;然后很容易地發布和應用到任意平臺中。 docker有3大核心&#xf…