深入理解Spring兩大特性:IoC和AOP

眾所周知,Spring擁有兩大特性:IoC和AOP。IoC,英文全稱Inversion of Control,意為控制反轉(zhuǎn)。AOP,英文全稱Aspect-Oriented Programming,意為面向切面編程。

Spring核心容器的主要組件是Bean工廠(BeanFactory),Bean工廠使用控制反轉(zhuǎn)(IoC)模式來降低程序代碼之間的耦合度,并提供了面向切面編程(AOP)的實現(xiàn)。

簡單來說,Spring是一個輕量級的控制反轉(zhuǎn)(IoC)和面向切面編程(AOP)的容器框架。

下面,我們簡要說明下這兩大特性。

  1. Spring常用注解
    在具體介紹IoC和AOP之前,我們先簡要說明下Spring常用注解

1、@Controller:用于標(biāo)注控制器層組件

2、@Service:用于標(biāo)注業(yè)務(wù)層組件

3、@Component : 用于標(biāo)注這是一個受 Spring 管理的組件,組件引用名稱是類名,第一個字母小寫??梢允褂聾Component(“beanID”) 指定組件的名稱

4、@Repository:用于標(biāo)注數(shù)據(jù)訪問組件,即DAO組件

5、@Bean:方法級別的注解,主要用在@Configuration和@Component注解的類里,@Bean注解的方法會產(chǎn)生一個Bean對象,該對象由Spring管理并放到IoC容器中。引用名稱是方法名,也可以用@Bean(name = "beanID")指定組件名

6、@Scope("prototype"):將組件的范圍設(shè)置為原型的(即多例)。保證每一個請求有一個單獨的action來處理,避免action的線程問題。

由于Spring默認(rèn)是單例的,只會創(chuàng)建一個action對象,每次訪問都是同一個對象,容易產(chǎn)生并發(fā)問題,數(shù)據(jù)不安全。

7、@Autowired:默認(rèn)按類型進行自動裝配。在容器查找匹配的Bean,當(dāng)有且僅有一個匹配的Bean時,Spring將其注入@Autowired標(biāo)注的變量中。

8、@Resource:默認(rèn)按名稱進行自動裝配,當(dāng)找不到與名稱匹配的Bean時會按類型裝配。

簡單點說,就是,能夠明確該類是一個控制器類組件的,就用@Controller;能夠明確是一個服務(wù)類組件的,就用@Service;能夠明確該類是一個數(shù)據(jù)訪問組件的,就用@Repository;不知道他是啥或者不好區(qū)分他是啥,但是就是想讓他動態(tài)裝配的就用@Component。

@Controller、@Service、@Component、@Repository都是類級別的注解,如果一個方法也想動態(tài)裝配,就用@Bean。

當(dāng)我們想按類型進行自動裝配時,就用@Autowired;當(dāng)我們想按名稱(beanID)進行自動裝配時,就用@Resource;當(dāng)我們需要根據(jù)比如配置信息等來動態(tài)裝配不同的組件時,可以用getBean("beanID")。

到這里,如果對這些注解,或是自動裝配不太理解,可以繼續(xù)往下,看完 控制反轉(zhuǎn)(IoC) 內(nèi)容后再回來理解這里的內(nèi)容。

  1. 控制反轉(zhuǎn)(IoC)
    控制反轉(zhuǎn),簡單點說,就是創(chuàng)建對象的控制權(quán),被反轉(zhuǎn)到了Spring框架上。

通常,我們實例化一個對象時,都是使用類的構(gòu)造方法來new一個對象,這個過程是由我們自己來控制的,而控制反轉(zhuǎn)就把new對象的工交給了Spring容器。

《expert ONE-ON-ONE J2EE Development without EJB》第6章中指出

P128

IoC Implementation Strategies

IoC is a broad concept that can be implemented in different ways. There are two main types:

Dependency Lookup: The container provides callbacks to components, and a lookup context.This is the EJB and Apache Avalon approach. It leaves the onus on each component to use container APIs to look up resources and collaborators. The Inversion of Control is limited to the container invoking callback methods that application code can use to obtain resources.

Dependency Injection: Components do no look up; they provide plain Java methods enabling the container to resolve dependencies. The container is wholly responsible for wiring up components, passing resolved objects in to JavaBean properties or constructors. Use of JavaBean properties is called Setter Injection; use of constructor arguments is called Constructor Injection.

P130

The second IoC strategy-Dependency Injection-is usually preferable.

主要意思為:

IoC的主要實現(xiàn)方式有兩種:依賴查找、依賴注入。

依賴注入是一種更可取的方式。

那么依賴查找和依賴注入有什么區(qū)別呢?

依賴查找,主要是容器為組件提供一個回調(diào)接口和上下文環(huán)境。這樣一來,組件就必須自己使用容器提供的API來查找資源和協(xié)作對象,控制反轉(zhuǎn)僅體現(xiàn)在那些回調(diào)方法上,容器調(diào)用這些回調(diào)方法,從而應(yīng)用代碼獲取到資源。

依賴注入,組件不做定位查詢,只提供標(biāo)準(zhǔn)的Java方法讓容器去決定依賴關(guān)系。容器全權(quán)負(fù)責(zé)組件的裝配,把符合依賴關(guān)系的對象通過Java Bean屬性或構(gòu)造方法傳遞給需要的對象。

2.1 IoC容器
IoC容器:具有依賴注入功能的容器,可以創(chuàng)建對象的容器。IoC容器負(fù)責(zé)實例化、定位、配置應(yīng)用程序中的對象并建立這些對象之間的依賴。

2.2 依賴注入
DI,英文全稱,Dependency Injection,意為依賴注入。

依賴注入:由IoC容器動態(tài)地將某個對象所需要的外部資源(包括對象、資源、常量數(shù)據(jù))注入到組件(Controller, Service等)之中。簡單點說,就是IoC容器會把當(dāng)前對象所需要的外部資源動態(tài)的注入給我們。

Spring依賴注入的方式主要有四個,基于注解注入方式、set注入方式、構(gòu)造器注入方式、靜態(tài)工廠注入方式。推薦使用基于注解注入方式,配置較少,比較方便。

基于注解注入方式

服務(wù)層代碼

@Service
public class AdminService {
//code
}
控制層代碼

@Controller
@Scope("prototype")
public class AdminController {

@Autowired
private AdminService adminService;

//code

}
@Autowired與@Resource都可以用來裝配Bean,都可以寫在字段、setter方法上。他們的區(qū)別是:

@Autowired默認(rèn)按類型進行自動裝配(該注解屬于Spring),默認(rèn)情況下要求依賴對象必須存在,如果要允許為null,需設(shè)置required屬性為false,例:@Autowired(required=false)。如果要使用名稱進行裝配,可以與@Qualifier注解一起使用。

@Autowired
@Qualifier("adminService")
private AdminService adminService;

@Resource默認(rèn)按照名稱進行裝配(該注解屬于J2EE),名稱可以通過name屬性來指定。如果沒有指定name屬性,當(dāng)注解寫在字段上時,默認(rèn)取字段名進行裝配;如果注解寫在setter方法上,默認(rèn)取屬性名進行裝配。當(dāng)找不到與名稱相匹配的Bean時,會按照類型進行裝配。但是,name屬性一旦指定,就只會按照名稱進行裝配。

@Resource(name = "adminService")
private AdminService adminService;

除此之外,對于一些復(fù)雜的裝載Bean的時機,比如我們需要根據(jù)配置裝載不同的Bean,以完成不同的操作,可以使用getBean(“beanID”)的方式來加載Bean。

通過BeanID加載Bean方法如下:

@Component
public class BeanUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    if (BeanUtils.applicationContext == null) {
        BeanUtils.applicationContext = applicationContext;
    }
}

public static ApplicationContext getApplicationContext() {
    return applicationContext;
}

public static Object getBean(String id) throws Exception {
    try {
        return applicationContext.containsBean(id) ? applicationContext.getBean(id) : null;
    } catch (BeansException e) {
        e.printStackTrace();
        throw new Exception("not found bean id: " + id);
    }
}

}
我們在需要裝載Bean的地方調(diào)用該方法即可

public class BaseController {

protected IService loadService(String id) throws Exception {
    IService iService = (IService) BeanUtils.getBean(id);
    if (iService != null) {
        return iService;
    } else {
        throw new Exception("加載Bean錯誤");
    }
}

}

  1. 面向切面編程(AOP)
    面向切面編程(AOP)就是縱向的編程。比如業(yè)務(wù)A和業(yè)務(wù)B現(xiàn)在需要一個相同的操作,傳統(tǒng)方法我們可能需要在A、B中都加入相關(guān)操作代碼,而應(yīng)用AOP就可以只寫一遍代碼,A、B共用這段代碼。并且,當(dāng)A、B需要增加新的操作時,可以在不改動原代碼的情況下,靈活添加新的業(yè)務(wù)邏輯實現(xiàn)。

在實際開發(fā)中,比如商品查詢、促銷查詢等業(yè)務(wù),都需要記錄日志、異常處理等操作,AOP把所有共用代碼都剝離出來,單獨放置到某個類中進行集中管理,在具體運行時,由容器進行動態(tài)織入這些公共代碼。

AOP主要一般應(yīng)用于簽名驗簽、參數(shù)校驗、日志記錄、事務(wù)控制、權(quán)限控制、性能統(tǒng)計、異常處理等。

3.1 AOP涉及名詞
切面(Aspect):共有功能的實現(xiàn)。如日志切面、權(quán)限切面、驗簽切面等。在實際開發(fā)中通常是一個存放共有功能實現(xiàn)的標(biāo)準(zhǔn)Java類。當(dāng)Java類使用了@Aspect注解修飾時,就能被AOP容器識別為切面。

通知(Advice):切面的具體實現(xiàn)。就是要給目標(biāo)對象織入的事情。以目標(biāo)方法為參照點,根據(jù)放置的地方不同,可分為前置通知(Before)、后置通知(AfterReturning)、異常通知(AfterThrowing)、最終通知(After)與環(huán)繞通知(Around)5種。在實際開發(fā)中通常是切面類中的一個方法,具體屬于哪類通知,通過方法上的注解區(qū)分。

連接點(JoinPoint):程序在運行過程中能夠插入切面的地點。例如,方法調(diào)用、異常拋出等。Spring只支持方法級的連接點。一個類的所有方法前、后、拋出異常時等都是連接點。

切入點(Pointcut):用于定義通知應(yīng)該切入到哪些連接點上。不同的通知通常需要切入到不同的連接點上,這種精準(zhǔn)的匹配是由切入點的正則表達式來定義的。

比如,在上面所說的連接點的基礎(chǔ)上,來定義切入點。我們有一個類,類里有10個方法,那就產(chǎn)生了幾十個連接點。但是我們并不想在所有方法上都織入通知,我們只想讓其中的幾個方法,在調(diào)用之前檢驗下入?yún)⑹欠窈戏ǎ敲淳陀们悬c來定義這幾個方法,讓切點來篩選連接點,選中我們想要的方法。切入點就是來定義哪些類里面的哪些方法會得到通知。

目標(biāo)對象(Target):那些即將切入切面的對象,也就是那些被通知的對象。這些對象專注業(yè)務(wù)本身的邏輯,所有的共有功能等待AOP容器的切入。

代理對象(Proxy):將通知應(yīng)用到目標(biāo)對象之后被動態(tài)創(chuàng)建的對象??梢院唵蔚乩斫鉃?,代理對象的功能等于目標(biāo)對象本身業(yè)務(wù)邏輯加上共有功能。代理對象對于使用者而言是透明的,是程序運行過程中的產(chǎn)物。目標(biāo)對象被織入共有功能后產(chǎn)生的對象。

織入(Weaving):將切面應(yīng)用到目標(biāo)對象從而創(chuàng)建一個新的代理對象的過程。這個過程可以發(fā)生在編譯時、類加載時、運行時。Spring是在運行時完成織入,運行時織入通過Java語言的反射機制與動態(tài)代理機制來動態(tài)實現(xiàn)。

3.2 Pointcut用法
Pointcut格式為:

execution(modifier-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
修飾符匹配 modifier-pattern? 例:public private

返回值匹配 ret-type-pattern 可以用 * 表示任意返回值

類路徑匹配 declaring-type-pattern? 全路徑的類名

方法名匹配 name-pattern 可以指定方法名或者用 * 表示所有方法;set* 表示所有以set開頭的方法

參數(shù)匹配 (param-pattern) 可以指定具體的參數(shù)類型,多個參數(shù)用“,”分隔;可以用 * 表示匹配任意類型的參數(shù);可以用 (..) 表示零個或多個任意參數(shù)

異常類型匹配throws-pattern? 例:throws Exception

其中后面跟著 ? 表示可選項

例:

@Pointcut("execution(public * cn.wbnull. springbootdemo.controller..(..))")
private void sign() {

}

3.3 一個例子
以 Spring Boot入門:使用AOP實現(xiàn)攔截器 中的AOP為例

@Aspect
@Component
public class SignAop {

}
SignAop類使用了@Aspect注解,則該類可以被AOP容器識別為切面。

@Aspect
@Component
public class SignAop {

@Pointcut("execution(public * cn.wbnull.springbootdemo.controller.*.*(..))")
private void signAop() {

}

}
@Pointcut聲明一個切入點,范圍為controller包下所有的類的所有方法

注:作為切入點簽名的方法必須返回void類型

@Aspect
@Component
public class SignAop {

@Pointcut("execution(public * cn.wbnull.springbootdemo.controller.*.*(..))")
private void signAop() {

}

@Before("signAop()")
public void doBefore(JoinPoint joinPoint) throws Exception {
    //code
   }

@AfterReturning(value = "signAop()", returning = "params")
public JSONObject doAfterReturning(JoinPoint joinPoint, JSONObject params) {
    //code
    }

}
doBefore()方法使用@Before("signAop()")注解,表示前置通知(在某連接點之前執(zhí)行的通知),但這個通知不能阻止連接點之前的執(zhí)行流程,除非它拋出一個異常。

doAfterReturning()方法使用@AfterReturning(value = "signAop()", returning = "params")注解,表示后置通知(在某連接點正常完成后執(zhí)行的通知),通常在一個匹配的方法返回的時候執(zhí)行。

實際運行時,在進入controller包下所有方法前,都會進入doBefore()方法,在controller包下方法執(zhí)行完成后,都會進入doAfterReturning()方法。
————————————————
版權(quán)聲明:本文為CSDN博主「dkbnull」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/dkbnull/article/details/87219562

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容