Spring Boot2.x 開發技巧及實戰 第2章:Spring Boot2.x 的基礎配置(持續更新中...3月6日更新)

第2章:Spring Boot2.x 的基礎配置

本章概要

  • 什麼是註解?
  • Spring Boot2.x的核心註解有哪些?
  • 什麼是自動配置?
  • 我們怎麼用自動配置特性,擴展我們的應用?
  • 來個小例子,講解一下自動配置吧?

溫馨提示:
本文配套代碼:https://gitee.com/guduwuhen/springboot2-lesson 下的:lesson2-1

2.1 我們先來講註解

2.1.1 註解是什麼?

官方定義:註解(Annotation也被稱爲元數據)是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。通俗來講,也就是爲我們在代碼中添加信息提供了一種形式化的方法,使我們可以在稍後某個時刻非常方便地使用這些數據。

其實呢,註解本身沒有任何的作用。簡單說和註釋沒有什麼區別,而它有作用的原因是:註解解釋類,也就是對註解解釋的特定類。這樣講,還是比較抽象,下面我們利用一個實例來講解一下,讓大家親自動手去寫一個註解,也取大家就會深入瞭解了。

2.1.2 親手寫一個註解

爲了更好的理解註解,我們在編寫實例時,會對一些代碼進行解釋,同時也會調試,請大家一定要準備好Springboot開發環境(可以參照上一章最後一節的內容搭建),讓我們一起動手吧。

實例功能:通過實體類添加數據庫表註解、類中變量添加列註解方式,根據實體類對象,利用反射機制,生成查詢sql語句。這個功能類似於hibernate的思想。

2.1.2.1 編寫註解類

  1. 創建包:com.binge.springboot.lesson21.annotation
  2. 並在此包下創建MytableMyColumn兩個註解類

小技巧:創建註解小技巧。
在創建類時,在彈出的對話框中選擇Annatation類型即可,如下圖
在這裏插入圖片描述
創建接口類、枚舉類,也選擇響應的類型即可。

生成代碼如下:

public @interface Mytable {
}

從生成的代碼我們可以看出,註解的類定義其實和接口差不多,區別在與@interface前面多了一個@符號。也許你會問,這樣一個註解就定義完畢了嗎? 當然不會,下面我們繼續講一下,註解的屬性。

2.1.2.2 註解屬性

註解的屬性也稱爲成員變量,註解只有成員變量,沒有方法。註解的成員變量在註解的定義中以“無形參的方法”形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。

註解內的可使用的數據類型是有限制的,類型如下:

所有的基本類型(int,float,boolean 等)
String
Class
enum(@Retention 中屬性的類型爲枚舉)
Annotation
以上類型的數組(@Target 中屬性類型爲枚舉類型的數組)

編譯器對屬性的默認值也有約束。首先,屬性不能有不確定的的值。也就是說,屬性要麼具有默認值,要麼在使用註解時提供屬性的值。對於非基本類型的屬性,無論是在源代碼中聲明時,或是在註解接口中定義默認值時,都不能使用 null 爲其值。因此,爲了繞開這個約束,我們需要自己定義一些特殊的值,例如空字符串或負數,來表示某個屬性不存在。

2.1.2.3 註解組成

J2SE5.0版本在java.lang.annotation提供了四種元註解,專門註解其他的註解:

  • @Documented –註解是否將包含在JavaDoc中
  • @Retention –什麼時候使用該註解
  • @Target? –註解用於什麼地方
  • @Inherited – 是否允許子類繼承該註解

@Documented–一個簡單的Annotations標記註解,表示是否將註解信息添加在java文檔中。

@Retention– 定義該註解的生命週期。

  • RetentionPolicy.SOURCE :在編譯階段丟棄。這些註解在編譯結束之後就不再有任何意義,所以它們不會寫入字節碼。@Override,@SuppressWarnings都屬於這類註解。
  • RetentionPolicy.CLASS :在類加載的時候丟棄。在字節碼文件的處理中有用。註解默認使用這種方式。
  • RetentionPolicy.RUNTIME:始終不會丟棄,運行期也保留該註解,因此可以使用反射機制讀取該註解的信息。我們自定義的註解通常使用這種方式。

@Target – 表示該註解用於什麼地方。如果不明確指出,該註解可以放在任何地方。註解(annotation)可被用於 packages(包)、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在註解類型的聲明中使用了target可更加明晰其修飾的目標。取值(ElementType)有:

  • ElementType.ANNOTATION_TYPE 可以應用於註釋類型。
  • ElementType.CONSTRUCTOR 可以應用於構造函數。
  • ElementType.FIELD 可以應用於字段或屬性。
  • ElementType.LOCAL_VARIABLE 可以應用於局部變量。
  • ElementType.METHOD 可以應用於方法級註釋。
  • ElementType.PACKAGE 可以應用於包聲明。
  • ElementType.PARAMETER 可以應用於方法的參數。
  • ElementType.TYPE 可以應用於類的任何元素。

爲我們的註解類添加如下元註解:Mytable 註解是作用於上的,並且一直生效,MyColumn註解是作用於成員變量上的,並且一直生效

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mytable {
    String name() default "";
}

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumn {
    String name() default "";
}

2.1.2.4 註解使用類

緊接着我們創建註解的使用類:User.java,就是一個使用註解的Java bean,這裏使用我們的自定義註解@Mytable

package com.binge.springboot.lesson21.entity;

import com.binge.springboot.lesson21.annotation.MyColumn;
import com.binge.springboot.lesson21.annotation.Mytable;

/**
 * @Title: User.java
 * @Package com.binge.springboot.lesson21.entity
 * @Description: 用戶實體類
 * @author: binge
 * @date 2020/2/27
 * @version V1.0
 */

@Mytable("user")
public class User {
    @MyColumn("user_id")
    String userId;  	//用戶ID

    @MyColumn("user_name")
    String userName;	//用戶名

    @MyColumn("password")
    String password;	//用戶密碼
    public String getUserId() {
        return userId;
    }
    public void setUserId(String 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;
    }
}

2.1.2.5 註解解釋類

有了下面這個類,我們創建的兩個註解纔有實際的意義。這個類的思路就是:

  1. 我們會判斷一個類是否已經添加我們自定義的兩個註解
  2. 然後獲取到註解的參數值,加以使用

代碼中的註釋,非常清晰的註明了註解的使用,代碼如下:

package com.binge.springboot.lesson21.service;

import com.binge.springboot.lesson21.annotation.MyColumn;
import com.binge.springboot.lesson21.annotation.Mytable;
import com.binge.springboot.lesson21.entity.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @Title: UserService.java
 * @Package com.binge.springboot.lesson21.service
 * @Description: 用戶業務類
 * @author: binge
 * @date 2020/2/27
 * @version V1.0
 */

public class UserService {
    /**
     * 通過用戶對象生成查詢SQL語句的方法
     * @param user  用戶對象
     * @return 生成的sql語句
     */
    public static String createSql(User user){
        StringBuffer sb = new StringBuffer();
        //通過反射,獲取用戶的class對象
        Class cla = user.getClass();
        //判斷是否添加了某種註解,這裏判斷是否添加了Mytable註解
        boolean hasAnnotation = cla.isAnnotationPresent(Mytable.class);
        //如果沒有添加註解,則直接返回,因爲沒有添加註解,無法生成sql語句
        if(!hasAnnotation){
            return null;
        }
        //如果添加了註解,把此註解類型強制轉換爲Mytable
        Mytable mytable = (Mytable)cla.getAnnotation(Mytable.class);
        //這個就是給User類上添加註解時,傳入的參數
        String tableName = mytable.value();
        //接下來便是,利用反射機制,遍歷類的變量進行生成拼接sql語句
        sb.append("select * from ").append(tableName).append(" where 1=1 ");
        Field[] fields = cla.getDeclaredFields();
        for (Field field : fields){
            //判斷字段是否添加了MyColumn註解
            hasAnnotation = field.isAnnotationPresent(MyColumn.class);
            if(!hasAnnotation){
                return null;
            }

            //此處將生成getXXX方法,然後利用反射機制,獲取字段的值
            String fieldName = field.getName();
            String methodName = "get" + fieldName.substring(0,1).toUpperCase()
                    + fieldName.substring(1);

            //這個變量用戶存儲每個變量的數值
            //這部分代碼就是利用反射機制獲取每個變量的數值
            Object fieldValueObj = null;
            try{
                Method method = cla.getMethod(methodName);
                fieldValueObj = method.invoke(user);
            }catch (Exception e){
                e.printStackTrace();
            }

            //獲取列上配置的參數名,這個一般是數據庫的字段名
            MyColumn myColumn = (MyColumn)field.getAnnotation(MyColumn.class);
            String colName = myColumn.value();

            //拼接數據庫的字段及值
            sb.append(" and ").append(colName).append("=").append(fieldValueObj);
        }

        //返回拼接好的sql
        return sb.toString();
    }

    public static void main(String[] args) {
        //創建一個user對象,只設置部分變量的值
        User user = new User();
        user.setUserId("12");
        user.setUserName("binge");
        String sql = UserService.createSql(user);
        System.out.println(sql);
    }
}

運行這個類,可以在控制檯中看到如下效果:
在這裏插入圖片描述

小技巧:註解配合反射使用
1)只有定義爲RetentionPolicy.RUNTIME時,我們才能通過註解反射獲取到註解。
2)通過反射獲取cla及field對象,然後通過cla.getAnnotation(Mytable.class)和field.getAnnotation(MyColumn.class) 方法可以獲取到註解值
3)然後根據註解的值,去實現一些業務邏輯

2.1.3 關於註解的總結

用處:

  1. 生成文檔。這是最常見的,也是java 最早提供的註解。常用的有@param @return 等(我們註釋裏面用到的)
  2. 跟蹤代碼依賴性,實現替代配置文件功能。(spring註解的大量使用,可以簡化配置文件)
  3. 在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出

優點 :

  1. 保存在 class 文件中,降低維護成本。
  2. 無需工具支持,無需解析。
  3. 編譯期即可驗證正確性,查錯變得容易。
  4. 提升開發效率。

缺點:

  1. 若要對配置項進行修改,不得不修改 Java 文件,重新編譯打包應用。
  2. 配置項編碼在 Java 文件中,可擴展性差。

通過這個章節,大家是否對註解有了一定的瞭解呢?概括一點來說就是不同的註解,作用不同。在SpringBoot框架下,增加了一些特性註解,用於方便開發、部署,後續我們會講解一些Springboot的核心註解。

2.2 Springboot 核心註解

2.2.1 @SpringBootApplication

SpringBoot的核心註解,主要目的是開啓自動配置。它也是一個組合註解,主要組合了@Configuration,@EnableAutoConfiguration(核心)和@ComponentScan。可以通過@SpringBootApplication(exclude={想要關閉的自動配置的類名.class})來關閉特定的自動配置,其中@ComponentScanspring Boot掃描到Configuration類並把它加入到程序上下文。

小技巧:是否開啓web功能
如果Maven中沒有添加web的依賴

2.2.2 @EnableAutoConfiguration:自動配置

此註釋自動載入應用程序所需的所有Bean——這依賴於Spring Boot在類路徑中的查找。該註解組合了@Import註解,@Import註解導入了EnableAutoCofigurationImportSelector類,它使用SpringFactoriesLoader.loaderFactoryNames方法來掃描具有META-INF/spring.factories文件的jar包。而spring.factories裏聲明瞭有哪些自動配置.

2.2.3 @Configuration:配置文件

@Configuration底層是含有@Component ,所以@Configuration 具有和 @Component 的作用。

@Configuration可理解爲用spring的時候xml裏面的標籤。

@Configuration註解可以達到在Spring中使用xml配置文件的作用。

@Bean可理解爲用spring的時候xml裏面的標籤

總結起來就是有三個重要用法:

  1. 通過配置文件注入類
  2. 使用@Configuration+@Bean方式註冊Bean
  3. 使用@Configuration啓動容器+@ComponentScan+@Component註冊Bean

後面講解自定義配置時會詳細講解。

2.2.4 @ComponentScan:自動掃描

表示將該類自動發現掃描組件。個人理解相當於,如果掃描到有@Component、@Controller、@Service等這些註解的類,並註冊爲Bean,可以自動收集所有的Spring組件,包括@Configuration類。

2.2.5 @Bean:

在Springboot 中,@Bean註解的用法常常結合@Configuration來使用,下面用一個實例來講解一下:

package com.dsx.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class TestConfiguration {
    public TestConfiguration() {
        System.out.println("TestConfiguration容器啓動初始化。。。");
    }

    // @Bean註解註冊bean,同時可以指定初始化和銷燬方法
    @Bean
    @Scope("prototype")
    public TestBean testBean() {
        return new TestBean();
    }
}

上述操作相當於實例化TestBean ,並交給spring管理。


(1)、@Bean註解在返回實例的方法上,如果未通過@Bean指定bean的名稱,則默認與標註的方法名相同;
(2)、@Bean註解默認作用域爲單例singleton作用域,可通過@Scope(“prototype”)設置爲原型作用域;
(3)、既然@Bean的作用是註冊bean對象,那麼完全可以使用@Component、@Controller、@Service、@Repository等註解註冊bean(在需要註冊的類上加註解),當然需要配置@ComponentScan註解進行自動掃描。

在Springboot的開發過程中,我們基本上都是採取第三種方法進行Bean的註冊的。

2.3 Springboot 自動配置

2.3.1 自動配置原理

自動配置意思就是框架幫我們把需要配置的參數進行了默認的配置,比如tomcat的端口號,默認是8080,但是這些默認配置在哪裏配置的呢?自動配置的原理是什麼呢?下面講解一下:

@SpringBootApplication包含了@EnableAutoConfiguration,這個註解又包含@Import(AutoConfigurationImportSelector.class)springboot啓動時,會調用AutoConfigurationImportSelector的回調函數,讓其解析所有包下的"META-INF/spring.factories"並把key爲"...EnableAutoConfiguration"對應的value裏的類都加載爲Bean,而那些類基本都是註解了@Configuration的配置類。這便是springboot能自動配置的原因。

在這裏插入圖片描述
如上圖所示,自動配置中,有以下4個配置文件參與(spring-boot-autoconfigure-2.2.4.RELEASE.jar包下):

  1. spring.factories
  2. spring-configuration-metadata.json和additional-spring-configuration-metadata.json
  3. spring-autoconfigure-metadata.properties

1) spring.factories:

spring.factories文件,裏面寫了自動配置(AutoConfiguration)相關的類名,因此產生了一個疑問:“明明自動配置的類已經打上了@Configuration的註解,爲什麼還要寫spring.factories文件?”

通過查看源碼可以得出結論(查看源碼過程省略):
spring.factories文件起到了幫助本項目包以外的bean(即在pom文件中添加依賴中的bean)註冊到spring-boot項目的spring容器的作用:由於@ComponentScan註解只能掃描spring-boot項目包內的bean並註冊到spring容器中,因此需要@EnableAutoConfiguration註解來註冊項目包外的bean。而spring.factories文件,則是用來記錄項目包外需要註冊的bean類名。

2) additional-spring-configuration-metadata.json 爲手動添加, spring-configuration-metadata.json自動生成。additional-spring-configuration-metadata.json將會合併到spring-configuration-metadata.json中,並覆蓋掉相同的說明。這兩個文件中,定義了默認配置項的相關信息。
比如:tomcat的默認端口就是在spring-configuration-metadata.json文件中:
在這裏插入圖片描述

在自定義啓動器時,要具備springboot的自動配置功能,就要填寫上面的文件(視需要而定,填寫其中的幾個)

附上:
官方網站上:https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/html/appendix-application-properties.html#common-application-properties 有全部的默認配置項信息。

比如我們項查看redis相關的配置項,打開此網站,ctrl+F進行搜索redis,會顯示相關的配置項及默認值,如下圖:

在這裏插入圖片描述

疑問: spring-configuration-metadata.json是怎麼自動生成的?

2.4 Springboot 自定義配置

2.4.1 自動配置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章