深入淺出spring boot 學習筆記


Spring的兩個核心概念

  1. 控制反轉(IoC)
  2. 面向切面編程(AOP)

全註解下的Spring IOC

IOC簡介

Spring IOC是一個管理Bean的容器,所有Ioc都要實現BeanFactoty接口,它是一個頂級容器接口。

isSingleton() 方法判斷Bean對象是否是單例,如果是單例,getBean()方法返回的都是同一個對象
isPrototype()方法與isSingleton()方法相反,如果不是單例,調用getBean()時,Spring Ioc容器會創建一個的Bean返回給調用者。

實現中我們使用的大部分Spring Ioc容器是實現ApplicatuionContontext接口。
(ApplicatuionContontext是BeanFactoty的子接口之一,其擴展了BeanFactoty的方法,功能更強大)

裝配Bean

如何將Bean裝配到Ioc容器中
①Component

ackage com.example.demo.testbean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("user")
public class User {
    @Value("1")
    private Long id;
    @Value("user_name_1")
    private String username;
    @Value("note_1")
    private String note;

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}


@Configuration
@ComponentScan

package com.example.demo.testbean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {

}

@ComponentScan 默認只會掃描類AppConfig所在的當前和其子包,
@ComponentScan還可以我們自定義掃描的包。
例如:

③測試

package com.example.demo.testbean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IocTest {
    public static void main(String [] args){
        AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
        User user=ctx.getBean(User.class);
        System.out.println(user.getId());

    }


}

依賴注入(Dependency Injection ,DI)

將Bean裝配到Ioc容器後中後,如何獲取,即Bean之間的依賴
在Spring IoC的概念中,稱爲依賴注入
@Autowired



@Autowired會根據屬性的類型找到對應的Bean進行注入
上述例子中Spring Ioc容器會把Dog的實例注入到BussinessPerson中。
這樣通過Spring Ioc容器獲取BussinessPerson實例的時候就能夠使用Dog實例來提供服務了。

註解@Autowired

如果 Cat類和Dog類都實現Animal接口
那麼寫如下注入會出現問題

這是因爲
Spring Ioc容器並不知道你需要注入什麼動物(是狗?是貓?)給BussinessPerson類對象,從而引起錯誤。
使用如下方法解決這個問題
將animal修改爲dog:
在這裏插入圖片描述
這是爲什麼呢?
因爲@Autowired提供這樣的規則,首先它會根據類型找到對應的Bean,如果對應類型的Bean不是唯一的,那麼他會根據其屬性名稱(例如上面的dog)和Bean的名稱進行匹配。如果匹配得上,就會使用該Bean;如果還無法匹配,就會拋出異常。

消除歧義性——@Primary和@Quelifier

還是上述問題
另一種解決方法
使用@Primary

但是如果Ca和Dog類上都有@Primary註解仍然無法區分
另一種解決方法

@Quelifier的配置項value需要一個字符串去定義,它將與@Autowires組合在一起,通過類型和名稱一起找到Bean。我們知道Bean名稱在Spring Ioc容器中是爲的的標識,通過這個就可以消除歧義性了。
這是怎麼實現的呢???
因爲BeanFactory接口中有如下方法。

帶有參數的構造方法類的裝配

生命週期

四個部分:

  1. Bean的定義
  2. Bean的初始化
  3. Bean的生存期
  4. Bean的銷燬

Bean定義

Bean初始化

ComponentScan中海油一個配置項lazyInit,只可以配置Boolean值。且默認爲false,也就是默認不進行延遲初始化,因此默認情況下Spring 會對Bean進行實例化和依賴注入對應的屬性值。
測試;


在斷點出,我們並沒有獲取Bean的實例,而日誌就已經打出了,可見它在Spring Ioc 容器初始化時就執行了實例化和依賴注入。
爲了 讓當我們取出來的時候才做實例化和依賴注入等操作。
可以:

Spring在完成依賴注入之後,還會進行以下流程來完成它的生命週期。

整個生命中週期如下

對於沒有實現ApplicationContext接口只實現BeanFactory接口的容器,在生命週期對應的ApplicationContextAware定義的方法不能被調用。

Spring IoC容器最低要求是實現BeanFactory接口
測試代碼
①定義一個bean類

@Component
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName() + "】調用BeanFactoryAware的setBeanFactory");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName() + "】DisposableBean方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName() + "】調用InitializingBean的afterPropertiesSet");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName() + "】調用ApplicationContextAware的setApplicationContext");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("【" + this.getClass().getSimpleName() + "】調用BeanNameAware的setBeanName");
    }

    @PostConstruct
    public void init(){
        System.out.println("【" + this.getClass().getSimpleName() + "】註解@PostConstruct定義的自定義初始化方法");
    }

    @PreDestroy
    public void destroy1(){
        System.out.println("【" + this.getClass().getSimpleName() + "】註解@PreDestroy定義的自定義銷燬方法");
    }
}

②定義一個處理類,涉及上圖中postProcessBeforeInitialization(預初始化)、postProcessAfterInitialization(後初始化)過程。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor調用postProcessBeforeInitialization方法,參數【" + bean.getClass().getSimpleName() + "】【" + beanName + "】");
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor調用postProcessAfterInitialization方法,參數【" + bean.getClass().getSimpleName() + "】【" + beanName + "】");
        return bean;
    }
}

③運行結果如下

BeanPostProcessor調用postProcessAfterInitialization方法,參數【ServletWebServerFactoryConfiguration$EmbeddedTomcat$$EnhancerBySpringCGLIB$$4506f63e】【org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat】
BeanPostProcessor調用postProcessBeforeInitialization方法,參數【ServletWebServerFactoryConfiguration$EmbeddedTomcat$$EnhancerBySpringCGLIB$$4506f63e】【org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat】
BeanPostProcessor調用postProcessAfterInitialization方法,參數【TomcatServletWebServerFactory】【tomcatServletWebServerFactory】
...

省略部分其它Bean的預初始化和後初始化日誌。

BeanPostProcessor調用postProcessAfterInitialization方法,參數【BaseConfig$$EnhancerBySpringCGLIB$$ec4dd4fe】【baseConfig】
BeanPostProcessor調用postProcessBeforeInitialization方法,參數【BaseConfig$$EnhancerBySpringCGLIB$$ec4dd4fe】【baseConfig】
【MyBean】調用BeanNameAware的setBeanName
【MyBean】調用BeanFactoryAware的setBeanFactory
【MyBean】調用ApplicationContextAware的setApplicationContext
BeanPostProcessor調用postProcessAfterInitialization方法,參數【MyBean】【myBean】
【MyBean】註解@PostConstruct定義的自定義初始化方法
【MyBean】調用InitializingBean的afterPropertiesSet
BeanPostProcessor調用postProcessBeforeInitialization方法,參數【MyBean】【myBean】
BeanPostProcessor調用postProcessAfterInitialization方法,參數【WebConfig$$EnhancerBySpringCGLIB$$b79cfd7d】【webConfig】
BeanPostProcessor調用postProcessBeforeInitialization方法,參數【WebConfig$$EnhancerBySpringCGLIB$$b79cfd7d】【webConfig】
【MyBean】註解@PreDestroy定義的自定義銷燬方法
【MyBean】DisposableBean方法

使用屬性文件

application.properties或自定義配置文件

application.properties

屬性文件依賴

有了這個依賴,就可以直接使用application.properties文件了
例如:

引用pplication.properties文件的方法如下

通過@Value註解,使用${…}這樣的佔位符讀取配置文件在屬性文件的內容。

@ConfigurationProperties

使用載入屬性文件


將配置從application.properties中遷移到jdbc.properties中,然後使用@ProperSourse去定義對應的屬性文件,把他加載到Spring上下文。
value可以配置多個文件。使用classpath前綴,意味着去類文件路徑下找到屬性文件。
ignoreResourseNotFound則是是否忽略配置文件找不到的問題。默認爲false,找不到會報錯。
true,找不到就忽略掉。

條件裝配Bean

滿足一定條件才進行bean的裝配
@Conditional


這裏加入了@Conditional,並且配置了類DataConditional,那麼這個類就必須實現Condition接口,對於這個接口,要求實現matches方法

matches方法首先讀取其上下文環境,然後判定是否配置了對應的數據庫信息。這樣,這些都已經配置好後則返回true。這時候,Spring會裝配數據庫連接池的Bean,否則是不安裝配的。

Bean的作用域

使用@Profile

項目往往要面臨
開發環境、測試環境、準生產環境、生產環境
爲了方便Spring 提供了Profile機制,使我們可以很方便地實現各個環境之間的切換。

引入XML配置Bean

@ImportResourse

使用Spring EL

${…} 代表佔位符 ,它將讀取上細紋的屬性值裝配到屬性中。

#{…}代表啓用spring表達式,它將具有運算功能
T(…)代表引入的類


System是java.lang.*包的類,這是java默認加載的包,因此可以不必寫全限定名,如果其他包需要寫出全限定名。


開始約定編程——Spring AOP

AOP:面向切面編程
約定是Spring AOP的本質。
實際上Spring AOP 是一種約定流程的編程。

AOP概念

爲什麼會用AOP

AOP最典型的應用實際就是數據庫事務的管控。
例如,當我們需要保存一個用戶時,可能要連同它的角色信息一併保存到數據庫中。

這裏的用戶信息和用戶角色信息,我們可以使用面向對象編程OOP進行設計,但是它們在數據庫事務中的要求是,要麼一起成功,要麼一起失敗,這樣OOP就無能爲了了。
AOP可以解決這個問題。
AOP還可以減少大量的重複工作。在Spring流行之前,我們可以使用JDBC代碼來實現很多的數據庫操作。例如

Class.forName(xxx.xx.xx)的作用是要求JVM查找並加載指定的類,也就是說JVM會執行該類的靜態代碼段。


從上圖可以看到,關於數據庫的打開和關閉以及事務的提交和回滾都有流程默認給你實現。換句話說,你不需要完成它們,系需要完成的完成的任務是編寫SQL這一步而已,然後織入流程中。於是你可以看到大量在工作中的雷士局域Spring開發的代碼。

這裏看到僅僅使用一個註解@Transactional,表明該方法需要食物運行,沒有任何數據庫打開和關閉的代碼,也沒有事務的提交和回滾代碼,卻實現了數據庫資源的打開和關閉、事務的回滾和提交。那麼Spring是怎麼做到的呢?
大致流程是:Spring幫助我們把insertUser方法織入類似於上圖的流程中。

So:Spring AOP可以處理一些無法使用OOP實現的業務邏輯。其次,通過約定,可以將一些業務邏輯織入到流程中,而且可以將一些通用的邏輯抽取出來,然後給予默認實現。後面的數據庫事務和Redis的開發中,可以再次見識到它的威力。

AOP術語和流程

連接點(join point):對應的是具體被攔截的對象(Spring 支持方法)

切點(point cut):有時候,切面不單單應用於單個方法,也可能是多個類的不同方法,這時,可以通過正則式和指示器的規則去定義,從而適配連接點。切點就是提供這樣一個功能的概念。

通知(advice)
就是按照約定的流程下的方法,分爲前置通知(before advice)、後置通知(after advice)、環繞通知(around advice)、事後返回通知(afterReturning advice)、異常通知(afterThrowing advice),它會根據約定織入流程中。

目標對象(target):被代理對象。

引入(introdution):指引入新的類和其方法,增強現有Bean的功能。

織入(weaving)它是一個動態代理技術,委員由的服務對象生成代理對象,然後將與切入點定義匹配的連接點攔截,並按約定將各類通知織入流程的過程。

切面(aspect)是一個可以定義切點、各類通知和引入的內容。Spring AOP將通過它的信息來增強Bean的功能或者將對應的方法織入流程。

AOP開發詳解

@AspectJ

確定連接點

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