在 Spring 中要想更簡單的存儲和讀取對象 , 核心是 使用注解 , 所以我們需要通過 Spring 中相關(guān)注解 , 來存儲和讀取 Bean 對象.
1.存儲 Bean 對象
之前我們存儲 Bean 時 , 需要在 spring-config.xml 中添加一行注釋才行:
而現(xiàn)在我們只需一個注解就可以替代之前要寫一行配置 , 不過在存儲對象之前 , 我們先要來點準(zhǔn)備工作.
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/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="com.spring.demo"></content:component-scan>
</beans>
其中標(biāo)紅的一行為注冊掃描的包:
也就是說即使添加了注解 , 如果不是在配置的掃描包下的類對象 , 是不能被存儲到 Spring 中的 , 體現(xiàn)了 Spring 框架在追求方法的同時 , 更追求性能.
2.添加注解存儲 Bean 對象
想要將對象存儲在 Spring 中 , 有兩種注解類型可以實現(xiàn):
- 類注解: @Controller , @Service , @Repository , @Component , @Configuration
- 方法注解: @Bean
1. @Controller (控制器存儲)
驗證用戶請求的數(shù)據(jù)正確性(安保系統(tǒng))
代碼示例:
創(chuàng)建 StudentController 類 , 并添加 @Controller 注解
@Controller //將當(dāng)前類存儲到 Spring 中
public class StudentController {
public void sayHi(){
System.out.println("do student controller sayhi()");
}
}
2. @Service (服務(wù)層)
編排和調(diào)度具體執(zhí)行方法(客服中心)
代碼示例:
創(chuàng)建 StudentController2類 , 并添加 @Service 注解
@Service
public class StudentController2 {
public void sayHi(){
System.out.println("do StudentController2");
}
}
3. @Repostory(數(shù)據(jù)持久層)
和數(shù)據(jù)交互 , 操作數(shù)據(jù)庫 (調(diào)用 用戶表和日志表) (執(zhí)行者)
代碼示例:
創(chuàng)建 StudentController3類 , 并添加 @Reporstory 注解
@Repository
public class StudentController5 {
public void sayHi(){
System.out.println("do StudentController5");
}
}
4. @Configuration(配置層)
配置項 , 項目中的一些配置.
代碼示例:
創(chuàng)建 StudentController4 類 , 并添加 @Configuration注解
@Configuration
public class StudentController4 {
public void sayHi(){
System.out.println("do StudentController4");
}
}
5.@Component(組件)
組件. 工具類
代碼示例:
創(chuàng)建 StudentController2 類 , 并添加 @Component 注解
@Component
public class StudentController3 {
public void sayHi(){
System.out.println("do StudentController3");
}
}
6 . 啟動類測試:
從容器中取 Bean 對象 , 如果我們在配置文件中有注冊標(biāo)簽 , 那么 getBean() 中就可以添加 id 和 class 兩個參數(shù) , 確保在容器中找到 Bean. 可是此時我們把配置文件中的標(biāo)簽改為了 component-scan 包路徑下的掃描 , 這樣就沒法通過 id 來訪問包了 , 但 Spring 中約定可以 “當(dāng)類名為大駝峰命名時 , id 為小駝峰. 當(dāng)類名前兩個字符都是大寫時 , id 為原類名” , 這個規(guī)定后續(xù)會在剖析源碼中講解.
public class App {
public static void main(String[] args) {
//1.獲取 Spring 對象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//小駝峰
StudentController studentController =
context.getBean("studentController", StudentController.class);
//前兩個字符小寫
SController sController =
context.getBean("SController", SController.class);
結(jié)果表名五種注解修飾類的 , 調(diào)用其方法都可以正確執(zhí)行 , 且執(zhí)行結(jié)果一致. 那么這五種類究竟有什么區(qū)別呢?
public class App {
public static void main(String[] args) {
//1.獲取 Spring 對象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 對象
// StudentController studentController =
// context.getBean("studentController", StudentController.class);
// SController sController =
// context.getBean("SController", SController.class);
// StudentController3 studentController3 =
// context.getBean("studentController3", StudentController3.class);
// StudentController4 studentController4 =
// context.getBean("studentController4", StudentController4.class);
StudentController5 studentController5 =
context.getBean("studentController5", StudentController5.class);
//3. 使用 Bean 對象
studentController5.sayHi();
}
}
3. 常見問題
1. 和 component-scan 可以同時存在嗎?
創(chuàng)建 UserService 類
public class UserService {
public void sayHi(){
System.out.println("do UserService");
}
}
在 spring 配置文件中同時添加這兩種: (bean 的路徑與 component 的包路徑不一樣)
結(jié)果顯示可以執(zhí)行 , 說明 可以作為額外補充添加一些 , 不適合放在 component-scan 包路徑下的類.
public class App {
public static void main(String[] args) {
//1.獲取 Spring 對象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 對象
UserService userService =
context.getBean("userService", UserService.class);
//3. 使用 Bean 對象
userService.sayHi();
}
}
2. 五大類注解可以不在 component-scan 包下嗎?
public class App {
public static void main(String[] args) {
//1.獲取 Spring 對象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 對象
StudentService service =
context.getBean("studentService", StudentService.class);
//3. 使用 Bean 對象
service.sayHi();
}
}
結(jié)果顯然不可以 , 未找到該類. 因此五大類注解必須在 component-scan 包下
3. 在 component-scan 的路徑包下,不加五大類注解可以存儲到 Spring 中嗎?
//@Controller //將當(dāng)前類存儲到 Spring 中
public class StudentController {
public void sayHi(){
System.out.println("do student controller sayhi()");
}
}
結(jié)果依舊是找不到bean 對象.
4. 在 component-scan 下的子包類中 , 添加注解可以存儲到 Spring 中嗎?
在 component-scan 包路徑下創(chuàng)建子包 “java” , 該包中創(chuàng)建類 UserController
@Controller
public class UserController {
public void sayHi(){
System.out.println("do UserController");
}
}
結(jié)果顯示可以正常執(zhí)行 , 說明在 component-scan 下的所有子包下的類只要添加了五大類注解 , 同樣能存儲到 Spring 中.
5.不同包下的同名類 , 可以通過注解讀取嗎?
不同包下創(chuàng)建兩個相同的 UserController ,
@Controller
public class UserController {
public void sayHi(){
System.out.println("do UserController -> com.spring.demo.java");
}
}
@Controller
public class UserController {
public void sayHi(){
System.out.println("do UserController -> com.spring.demo");
}
}
報錯結(jié)果為"Bean 對象定義沖突" , 那么如何解決該問題呢?
通過查看 Controller 的源碼 ,我們可以給重名的類傳一個別名.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Controller() 中傳一個別名參數(shù)(字符串類型)
@Controller("UserController2")
public class UserController {
public void sayHi(){
System.out.println("do UserController -> com.spring.demo.java");
}
}
4. 為什么需要五大類注解
通過上述代碼 , 我們發(fā)現(xiàn)五大類注解都可以做到將當(dāng)前類存儲到 Spring 中 , 那么為什么需要五大類注解?
1.剖析源碼
查看五大類注解的源碼之后 , 可以發(fā)現(xiàn)一個共同點那就是 , 其他的四類注解都是繼承自 @Component , 可以認(rèn)為 @Controller @Service @Repository @Configuration 都是 @Component 的子類 , 都是針對 @Component 的擴展.
例如: 不同的省市甚至是縣區(qū)為什么要有自己單獨的車牌號? 如果只區(qū)分省不是更方面嗎? 其實這樣做的目的就是可以更直觀的標(biāo)識一輛車的歸屬地.
為什么需要這么多的注解 , 原因就是讓程序員看到注解能夠望文生義 , 清楚的知道當(dāng)前的類的作用.
- @Controller: 控制層 , 驗證參數(shù)正確性 , 與前端交互.
- @Service: 服務(wù)層 , 編排和調(diào)度程序執(zhí)行.
- @Repository: 數(shù)據(jù)持久層 , 直接操作數(shù)據(jù)庫.
- @Configuration: 存放配置信息.
2. JavaEE 標(biāo)準(zhǔn)分層(至少三層)
- 控制層
- 服務(wù)層
- 數(shù)據(jù)持久層
3. 阿里巴巴 java 開發(fā)手冊中標(biāo)準(zhǔn)分層:
5. Bean 命名規(guī)則
通過上面示例 , 我們可以看出 , 通常我們 Bean 使用的都是標(biāo)準(zhǔn)的大駝峰命名 , 而讀取時首字母小寫就可以讀取 , 特殊情況是 , 當(dāng)前兩個字符都是大寫字母 ,那么就用原字符串讀取。
那么為什么會有這樣的規(guī)則呢? 我們可以查看源碼 , 在全局搜索中找到注解名字生成。
最終我們找到了生成名稱的源代碼 , 發(fā)現(xiàn)與我們之前的結(jié)論一致:
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);
}
我們可以測試一下 Introspector 方法:
public class BeanNameTest {
public static void main(String[] args) {
String className = "UserClass";
String className2 = "UClass";
System.out.println("UserClass ->" + Introspector.decapitalize(className));
System.out.println("UClass ->" + Introspector.decapitalize(className2));
}
}
發(fā)現(xiàn)結(jié)果與推斷一致:
6. @Bean 方法注解
@Bean 注解就是將當(dāng)前方法的返回對象 , 存儲到 Spring 中.
類注解是添加到某個類上的 , 而方法注解是放到某個方法上的.
1. 實體類是什么?
通俗來講 , 實體類就是一個有 Get 和 Set 方法的類 , 通常和數(shù)據(jù)持久層聯(lián)系在一起. 因此實體類就是一個載體 , 通常和一張數(shù)據(jù)庫表聯(lián)系起來 , 實體類中的字段和數(shù)據(jù)庫表中的屬性一一對應(yīng).
2. 實體類的命名規(guī)則
-
基本對象(數(shù)據(jù)庫中的一張表); 表名: Userinfo
-
擴展對象: UserinfoVO(view object)
3. 使用方法注解將 Bean 存儲到 Spring
創(chuàng)建一個實體類:
public class User {
private Integer userId;//屬性=字段
private String username;
private String password;
private Integer age;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
創(chuàng)建一個 UserBeans 類 , 用于將實體類對象 , 存入 Spring 中.
public class UserBeans {
@Bean
public User user1(){
User user = new User();
user.setUserId(1);
user.setUsername("張三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
啟動類中 , 按照之前 Bean 命名規(guī)則 , 獲取 Bean 對象 , 并使用其方法.
public class App {
public static void main(String[] args) {
//1.獲取 Spring 容器
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 對象
User user = context.getBean( "user", User.class);
System.out.println(user.getUserId());
}
}
執(zhí)行結(jié)果:
報錯原因:
- @Bean 命名規(guī)則和五大類的命名規(guī)則不同. @Bean 命名規(guī)則默認(rèn)情況下 , 存儲對象的名稱 = 方法名.
- @Bean 注解必須配合五大類注解一起使用(基于 Spring 對性能的追求 , 快速定位到類).
只有將以上兩個原因解決才能防止報錯.
4. @Bean 重命名:
源碼中我們可以看出 , 可以給方法起多個名字 , 而且參數(shù)無論是 name 還是 value , 都是可以的 , 但 @Bean 方法中更建議 name.
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Bean 重命名之后 , 默認(rèn)的使用方法名獲取對象的方式就不能使用了.
@Controller
public class UserBeans {
@Bean({"User", "U1"})
public User user1(){
User user = new User();
user.setUserId(1);
user.setUsername("張三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
public class App {
public static void main(String[] args) {
//1.獲取 Spring 容器
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 對象
User user = context.getBean( "user1", User.class);
System.out.println(user.getUserId());
}
}
Spring 容器中允許將同一個類型的對象 , 在容器中存放多份.
@Controller
public class UserBeans {
@Bean({"User", "U1"})
public User user1(){
User user = new User();
user.setUserId(1);
user.setUsername("張三");
user.setPassword("123456");
user.setAge(18);
return user;
}
public User user2(){
User user = new User();
user.setUserId(1);
user.setUsername("李四");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
同名同類型方法在不同類中可以獲取到嗎?
類似于 HashMap 如果存儲相同的值 , 后來值的會將之前的值覆蓋.
同名方法也會被后面的方法覆蓋 , 但 @Order() 可以控制注入的順序 , 值越小優(yōu)先級越高.
明顯李四會先執(zhí)行.
@Controller
@Order(20)
public class UserBeans {
@Bean({"User", "U1"})
public User user1(){
User user = new User();
user.setUserId(1);
user.setUsername("張三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
@Controller
@Order(2)
public class UserBeans2 {
public class UserBeans {
@Bean({"User", "U1"})
public User user1() {
User user = new User();
user.setUserId(1);
user.setUsername("李四");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
}
2. 獲取 Bean 對象(對象注入)
對象注入: 更簡單的讀取 Bean (從 Spring 容器中讀取某個對象 , 放到當(dāng)前類中)
Spring 中常見的注入方式:
- 屬性注入(Field Injection)
- Setter 注入(Setter Injection)
- 構(gòu)造方法注入(Constructor Injection)
1. 屬性注入
屬性注入因其簡單的特性 , 是日常開發(fā)中使用最多的一種方式.
@Autowired 注解 , 相當(dāng)于是從 Spring 容器中讀取到對象 , 交給當(dāng)前的變量. 不必像啟動類那樣 , 先得到 Spring 容器 , 再從容器中獲取 Bean 對象.
@Controller
public class UserController {
@Autowired //注入對象 (更簡單的從 spring 容器中讀取到對象)
private UserService userService;
public void sayHi(){
System.out.println("do UserController -> com.spring.demo");
userService.sayHi();
}
}
Tips: 不可以在啟動類中使用 @Autowired 獲取對象 , 因為 main 方法屬于靜態(tài)方法 , 靜態(tài)類加載順序早于 Spring.
優(yōu)點: 很明顯就是簡單
缺點:
- 沒法注入 final 修飾的對象.(JavaSE 語法限制)
- 兼容性不強 , 只適用于 IoC 容器 , 非 IoC 項目直接拋 NULLPOINTEXCEPTION.
- 有風(fēng)險 , 因為寫法簡單 , 所以可能同時注入多個對象 , 會出現(xiàn)違反單一設(shè)計原則的可能性.
2. Setter 注入
@Controller
public class UserController {
private UserService userService;
@Autowired //注入對象 (更簡單的從 spring 容器中讀取到對象)
public void setUserService(UserService userService) {//Spring 賦值
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController -> com.spring.demo");
userService.sayHi();
}
}
優(yōu)點: 每次只傳一個對象 , 符合單一設(shè)計原則.
缺點:
- 無法注入一個 final 對象.
- 使用 Setter 注入的對象 , 可能會被修改.
@Controller
public class UserController {
@Autowired //注入對象 (更簡單的從 spring 容器中讀取到對象)
private UserService userService;
public void setUserService(UserService userService) {//Spring 賦值
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController -> com.spring.demo");
UserController controller = new UserController();
//故意修改成 null
controller.setUserService(null);
userService.sayHi();
}
}
3. 構(gòu)造方法注入(Spring 官方推薦寫法)
@Controller
public class UserController {
private UserService userService;
@Autowired //注入對象 (更簡單的從 spring 容器中讀取到對象)
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("do UserController -> com.spring.demo");
userService.sayHi();
}
}
構(gòu)造方法注入 如果只有一條構(gòu)造方法 不寫 @Autowired 照樣可以執(zhí)行. 但如果一個類中有多個構(gòu)造方法 @Autowried 不可省略.
優(yōu)點:
- 可注入 final 對象 (Java 中規(guī)定 , 在 Java 中 , 被 final 修飾的對象 , 必須滿足二者之一 , 要么直接賦值 , 要么在構(gòu)造方法中賦值)
- 注入對象不會被修改.(構(gòu)造方法只能在類加載時執(zhí)行一次)
- 構(gòu)造方法注入可以保證注入對象完全被初始化.(構(gòu)造方法在對象創(chuàng)建之前就已執(zhí)行完畢 , 因此被注入對象在使用前會完全初始化)
- 通用性和兼容性更強. (即使不在容器中也能注入)
綜上:
依賴注入常見方式有三種 , 屬性注入 , Setter 注入 , 構(gòu)造方法注入. 其中屬性注入最簡單高效 , 但可移植性不強. Spring 官方推薦 構(gòu)造方法注入 , 它可以注入不可變對象 , 且可移植性更強. 如果想注入可變對象 , 應(yīng)使用 Setter 注入.
4. @Resource: 另一種注入方式
通過源碼觀察@Resource 和 @Autowired 二者區(qū)別:
1.@Autowired 來自 Spring 框架 , @Resource 來自 jdk
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
2.@Resource 支持多種參數(shù).
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
/**
* The JNDI name of the resource. For field annotations,
* the default is the field name. For method annotations,
* the default is the JavaBeans property name corresponding
* to the method. For class annotations, there is no default
* and this must be specified.
*/
String name() default "";
/**
* The name of the resource that the reference points to. It can
* link to any compatible resource using the global JNDI names.
*
* @since Common Annotations 1.1
*/
String lookup() default "";
/**
* The Java type of the resource. For field annotations,
* the default is the type of the field. For method annotations,
* the default is the type of the JavaBeans property.
* For class annotations, there is no default and this must be
* specified.
*/
Class<?> type() default java.lang.Object.class;
/**
* The two possible authentication types for a resource.
*/
enum AuthenticationType {
CONTAINER,
APPLICATION
}
@Autowired 支持參數(shù)很少.
public @interface Autowired {
boolean required() default true;
}
3.@Resource 是 jdk 提供的一種注解 , 通過代碼測試發(fā)現(xiàn)其不可用于構(gòu)造方法注入.
因此 , @Autowired 支持更多的注入類型 , @Resource 支持更多的參數(shù)類型 , 二者能力五五開.
綜上二者區(qū)別如下:
- 來源不同.
- 支持參數(shù)種類不同.
- 注入的支持類型不同.
Tips: 在 Spring 容器中找 Bean 有兩種方式:
- 根據(jù)類型查找
- 根據(jù)名稱查詢
@Autowired 先根據(jù)類型去找 , 再根據(jù)名稱查找.文章來源:http://www.zghlxwxcb.cn/news/detail-433918.html
@Resource 先根據(jù)名稱去查 , 后根據(jù)類名去查.文章來源地址http://www.zghlxwxcb.cn/news/detail-433918.html
到了這里,關(guān)于Spring 更簡單的讀取和存儲對象的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!