眾所周知,Spring擁有兩大特性:IoC和AOP攻谁。IoC元媚,英文全稱(chēng)Inversion of Control轧叽,意為控制反轉(zhuǎn)。AOP刊棕,英文全稱(chēng)Aspect-Oriented Programming炭晒,意為面向切面編程。
Spring核心容器的主要組件是Bean工廠(BeanFactory)甥角,Bean工廠使用控制反轉(zhuǎn)(IoC)模式來(lái)降低程序代碼之間的耦合度网严,并提供了面向切面編程(AOP)的實(shí)現(xiàn)。
簡(jiǎn)單來(lái)說(shuō)嗤无,Spring是一個(gè)輕量級(jí)的控制反轉(zhuǎn)(IoC)和面向切面編程(AOP)的容器框架震束。
下面怜庸,我們簡(jiǎn)要說(shuō)明下這兩大特性。
- Spring常用注解
在具體介紹IoC和AOP之前驴一,我們先簡(jiǎn)要說(shuō)明下Spring常用注解
1休雌、@Controller:用于標(biāo)注控制器層組件
2、@Service:用于標(biāo)注業(yè)務(wù)層組件
3肝断、@Component : 用于標(biāo)注這是一個(gè)受 Spring 管理的組件杈曲,組件引用名稱(chēng)是類(lèi)名,第一個(gè)字母小寫(xiě)胸懈〉F耍可以使用@Component(“beanID”) 指定組件的名稱(chēng)
4、@Repository:用于標(biāo)注數(shù)據(jù)訪問(wèn)組件趣钱,即DAO組件
5涌献、@Bean:方法級(jí)別的注解,主要用在@Configuration和@Component注解的類(lèi)里首有,@Bean注解的方法會(huì)產(chǎn)生一個(gè)Bean對(duì)象燕垃,該對(duì)象由Spring管理并放到IoC容器中。引用名稱(chēng)是方法名井联,也可以用@Bean(name = "beanID")指定組件名
6卜壕、@Scope("prototype"):將組件的范圍設(shè)置為原型的(即多例)。保證每一個(gè)請(qǐng)求有一個(gè)單獨(dú)的action來(lái)處理烙常,避免action的線程問(wèn)題轴捎。
由于Spring默認(rèn)是單例的,只會(huì)創(chuàng)建一個(gè)action對(duì)象蚕脏,每次訪問(wèn)都是同一個(gè)對(duì)象侦副,容易產(chǎn)生并發(fā)問(wèn)題,數(shù)據(jù)不安全驼鞭。
7秦驯、@Autowired:默認(rèn)按類(lèi)型進(jìn)行自動(dòng)裝配。在容器查找匹配的Bean挣棕,當(dāng)有且僅有一個(gè)匹配的Bean時(shí)汇竭,Spring將其注入@Autowired標(biāo)注的變量中。
8穴张、@Resource:默認(rèn)按名稱(chēng)進(jìn)行自動(dòng)裝配细燎,當(dāng)找不到與名稱(chēng)匹配的Bean時(shí)會(huì)按類(lèi)型裝配。
簡(jiǎn)單點(diǎn)說(shuō)皂甘,就是玻驻,能夠明確該類(lèi)是一個(gè)控制器類(lèi)組件的,就用@Controller;能夠明確是一個(gè)服務(wù)類(lèi)組件的璧瞬,就用@Service户辫;能夠明確該類(lèi)是一個(gè)數(shù)據(jù)訪問(wèn)組件的,就用@Repository嗤锉;不知道他是啥或者不好區(qū)分他是啥渔欢,但是就是想讓他動(dòng)態(tài)裝配的就用@Component。
@Controller瘟忱、@Service奥额、@Component、@Repository都是類(lèi)級(jí)別的注解访诱,如果一個(gè)方法也想動(dòng)態(tài)裝配垫挨,就用@Bean。
當(dāng)我們想按類(lèi)型進(jìn)行自動(dòng)裝配時(shí)触菜,就用@Autowired九榔;當(dāng)我們想按名稱(chēng)(beanID)進(jìn)行自動(dòng)裝配時(shí),就用@Resource涡相;當(dāng)我們需要根據(jù)比如配置信息等來(lái)動(dòng)態(tài)裝配不同的組件時(shí)哲泊,可以用getBean("beanID")。
到這里催蝗,如果對(duì)這些注解切威,或是自動(dòng)裝配不太理解,可以繼續(xù)往下生逸,看完 控制反轉(zhuǎn)(IoC) 內(nèi)容后再回來(lái)理解這里的內(nèi)容牢屋。
- 控制反轉(zhuǎn)(IoC)
控制反轉(zhuǎn)且预,簡(jiǎn)單點(diǎn)說(shuō)槽袄,就是創(chuàng)建對(duì)象的控制權(quán),被反轉(zhuǎn)到了Spring框架上锋谐。
通常遍尺,我們實(shí)例化一個(gè)對(duì)象時(shí),都是使用類(lèi)的構(gòu)造方法來(lái)new一個(gè)對(duì)象涮拗,這個(gè)過(guò)程是由我們自己來(lái)控制的乾戏,而控制反轉(zhuǎn)就把new對(duì)象的工交給了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的主要實(shí)現(xiàn)方式有兩種:依賴查找三热、依賴注入鼓择。
依賴注入是一種更可取的方式。
那么依賴查找和依賴注入有什么區(qū)別呢就漾?
依賴查找呐能,主要是容器為組件提供一個(gè)回調(diào)接口和上下文環(huán)境。這樣一來(lái),組件就必須自己使用容器提供的API來(lái)查找資源和協(xié)作對(duì)象摆出,控制反轉(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)系的對(duì)象通過(guò)Java Bean屬性或構(gòu)造方法傳遞給需要的對(duì)象。
2.1 IoC容器
IoC容器:具有依賴注入功能的容器通危,可以創(chuàng)建對(duì)象的容器铸豁。IoC容器負(fù)責(zé)實(shí)例化、定位菊碟、配置應(yīng)用程序中的對(duì)象并建立這些對(duì)象之間的依賴节芥。
2.2 依賴注入
DI,英文全稱(chēng)逆害,Dependency Injection头镊,意為依賴注入。
依賴注入:由IoC容器動(dòng)態(tài)地將某個(gè)對(duì)象所需要的外部資源(包括對(duì)象魄幕、資源相艇、常量數(shù)據(jù))注入到組件(Controller, Service等)之中。簡(jiǎn)單點(diǎn)說(shuō)纯陨,就是IoC容器會(huì)把當(dāng)前對(duì)象所需要的外部資源動(dòng)態(tài)的注入給我們坛芽。
Spring依賴注入的方式主要有四個(gè),基于注解注入方式翼抠、set注入方式咙轩、構(gòu)造器注入方式、靜態(tài)工廠注入方式阴颖。推薦使用基于注解注入方式活喊,配置較少,比較方便量愧。
基于注解注入方式
服務(wù)層代碼
@Service
public class AdminService {
//code
}
控制層代碼
@Controller
@Scope("prototype")
public class AdminController {
@Autowired
private AdminService adminService;
//code
}
@Autowired與@Resource都可以用來(lái)裝配Bean钾菊,都可以寫(xiě)在字段、setter方法上偎肃。他們的區(qū)別是:
@Autowired默認(rèn)按類(lèi)型進(jìn)行自動(dòng)裝配(該注解屬于Spring)煞烫,默認(rèn)情況下要求依賴對(duì)象必須存在,如果要允許為null累颂,需設(shè)置required屬性為false滞详,例:@Autowired(required=false)。如果要使用名稱(chēng)進(jìn)行裝配,可以與@Qualifier注解一起使用茵宪。
@Autowired
@Qualifier("adminService")
private AdminService adminService;
@Resource默認(rèn)按照名稱(chēng)進(jìn)行裝配(該注解屬于J2EE)最冰,名稱(chēng)可以通過(guò)name屬性來(lái)指定。如果沒(méi)有指定name屬性稀火,當(dāng)注解寫(xiě)在字段上時(shí)暖哨,默認(rèn)取字段名進(jìn)行裝配;如果注解寫(xiě)在setter方法上凰狞,默認(rèn)取屬性名進(jìn)行裝配篇裁。當(dāng)找不到與名稱(chēng)相匹配的Bean時(shí),會(huì)按照類(lèi)型進(jìn)行裝配赡若。但是达布,name屬性一旦指定,就只會(huì)按照名稱(chēng)進(jìn)行裝配逾冬。
@Resource(name = "adminService")
private AdminService adminService;
除此之外黍聂,對(duì)于一些復(fù)雜的裝載Bean的時(shí)機(jī),比如我們需要根據(jù)配置裝載不同的Bean身腻,以完成不同的操作产还,可以使用getBean(“beanID”)的方式來(lái)加載Bean。
通過(guò)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);
}
}
}
我們?cè)谛枰b載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錯(cuò)誤");
}
}
}
- 面向切面編程(AOP)
面向切面編程(AOP)就是縱向的編程嘀趟。比如業(yè)務(wù)A和業(yè)務(wù)B現(xiàn)在需要一個(gè)相同的操作脐区,傳統(tǒng)方法我們可能需要在A、B中都加入相關(guān)操作代碼她按,而應(yīng)用AOP就可以只寫(xiě)一遍代碼牛隅,A、B共用這段代碼酌泰。并且媒佣,當(dāng)A、B需要增加新的操作時(shí)宫莱,可以在不改動(dòng)原代碼的情況下丈攒,靈活添加新的業(yè)務(wù)邏輯實(shí)現(xiàn)哩罪。
在實(shí)際開(kāi)發(fā)中授霸,比如商品查詢、促銷(xiāo)查詢等業(yè)務(wù)际插,都需要記錄日志碘耳、異常處理等操作,AOP把所有共用代碼都剝離出來(lái)框弛,單獨(dú)放置到某個(gè)類(lèi)中進(jìn)行集中管理辛辨,在具體運(yùn)行時(shí),由容器進(jìn)行動(dòng)態(tài)織入這些公共代碼。
AOP主要一般應(yīng)用于簽名驗(yàn)簽斗搞、參數(shù)校驗(yàn)指攒、日志記錄、事務(wù)控制僻焚、權(quán)限控制允悦、性能統(tǒng)計(jì)、異常處理等虑啤。
3.1 AOP涉及名詞
切面(Aspect):共有功能的實(shí)現(xiàn)隙弛。如日志切面、權(quán)限切面狞山、驗(yàn)簽切面等全闷。在實(shí)際開(kāi)發(fā)中通常是一個(gè)存放共有功能實(shí)現(xiàn)的標(biāo)準(zhǔn)Java類(lèi)。當(dāng)Java類(lèi)使用了@Aspect注解修飾時(shí)萍启,就能被AOP容器識(shí)別為切面总珠。
通知(Advice):切面的具體實(shí)現(xiàn)。就是要給目標(biāo)對(duì)象織入的事情勘纯。以目標(biāo)方法為參照點(diǎn)姚淆,根據(jù)放置的地方不同,可分為前置通知(Before)屡律、后置通知(AfterReturning)腌逢、異常通知(AfterThrowing)、最終通知(After)與環(huán)繞通知(Around)5種超埋。在實(shí)際開(kāi)發(fā)中通常是切面類(lèi)中的一個(gè)方法搏讶,具體屬于哪類(lèi)通知,通過(guò)方法上的注解區(qū)分霍殴。
連接點(diǎn)(JoinPoint):程序在運(yùn)行過(guò)程中能夠插入切面的地點(diǎn)媒惕。例如,方法調(diào)用来庭、異常拋出等妒蔚。Spring只支持方法級(jí)的連接點(diǎn)。一個(gè)類(lèi)的所有方法前月弛、后肴盏、拋出異常時(shí)等都是連接點(diǎn)。
切入點(diǎn)(Pointcut):用于定義通知應(yīng)該切入到哪些連接點(diǎn)上帽衙。不同的通知通常需要切入到不同的連接點(diǎn)上菜皂,這種精準(zhǔn)的匹配是由切入點(diǎn)的正則表達(dá)式來(lái)定義的。
比如厉萝,在上面所說(shuō)的連接點(diǎn)的基礎(chǔ)上恍飘,來(lái)定義切入點(diǎn)榨崩。我們有一個(gè)類(lèi),類(lèi)里有10個(gè)方法章母,那就產(chǎn)生了幾十個(gè)連接點(diǎn)母蛛。但是我們并不想在所有方法上都織入通知,我們只想讓其中的幾個(gè)方法乳怎,在調(diào)用之前檢驗(yàn)下入?yún)⑹欠窈戏ㄋ莼觯敲淳陀们悬c(diǎn)來(lái)定義這幾個(gè)方法,讓切點(diǎn)來(lái)篩選連接點(diǎn)舞肆,選中我們想要的方法焦辅。切入點(diǎn)就是來(lái)定義哪些類(lèi)里面的哪些方法會(huì)得到通知。
目標(biāo)對(duì)象(Target):那些即將切入切面的對(duì)象椿胯,也就是那些被通知的對(duì)象筷登。這些對(duì)象專(zhuān)注業(yè)務(wù)本身的邏輯,所有的共有功能等待AOP容器的切入哩盲。
代理對(duì)象(Proxy):將通知應(yīng)用到目標(biāo)對(duì)象之后被動(dòng)態(tài)創(chuàng)建的對(duì)象前方。可以簡(jiǎn)單地理解為廉油,代理對(duì)象的功能等于目標(biāo)對(duì)象本身業(yè)務(wù)邏輯加上共有功能惠险。代理對(duì)象對(duì)于使用者而言是透明的,是程序運(yùn)行過(guò)程中的產(chǎn)物抒线。目標(biāo)對(duì)象被織入共有功能后產(chǎn)生的對(duì)象班巩。
織入(Weaving):將切面應(yīng)用到目標(biāo)對(duì)象從而創(chuàng)建一個(gè)新的代理對(duì)象的過(guò)程。這個(gè)過(guò)程可以發(fā)生在編譯時(shí)嘶炭、類(lèi)加載時(shí)抱慌、運(yùn)行時(shí)。Spring是在運(yùn)行時(shí)完成織入眨猎,運(yùn)行時(shí)織入通過(guò)Java語(yǔ)言的反射機(jī)制與動(dòng)態(tài)代理機(jī)制來(lái)動(dòng)態(tài)實(shí)現(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 可以用 * 表示任意返回值
類(lèi)路徑匹配 declaring-type-pattern? 全路徑的類(lèi)名
方法名匹配 name-pattern 可以指定方法名或者用 * 表示所有方法;set* 表示所有以set開(kāi)頭的方法
參數(shù)匹配 (param-pattern) 可以指定具體的參數(shù)類(lèi)型睡陪,多個(gè)參數(shù)用“,”分隔寺渗;可以用 * 表示匹配任意類(lèi)型的參數(shù);可以用 (..) 表示零個(gè)或多個(gè)任意參數(shù)
異常類(lèi)型匹配throws-pattern? 例:throws Exception
其中后面跟著 ? 表示可選項(xiàng)
例:
@Pointcut("execution(public * cn.wbnull. springbootdemo.controller..(..))")
private void sign() {
}
3.3 一個(gè)例子
以 Spring Boot入門(mén):使用AOP實(shí)現(xiàn)攔截器 中的AOP為例
@Aspect
@Component
public class SignAop {
}
SignAop類(lèi)使用了@Aspect注解兰迫,則該類(lèi)可以被AOP容器識(shí)別為切面信殊。
@Aspect
@Component
public class SignAop {
@Pointcut("execution(public * cn.wbnull.springbootdemo.controller.*.*(..))")
private void signAop() {
}
}
@Pointcut聲明一個(gè)切入點(diǎn),范圍為controller包下所有的類(lèi)的所有方法
注:作為切入點(diǎn)簽名的方法必須返回void類(lèi)型
@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()")注解逮矛,表示前置通知(在某連接點(diǎn)之前執(zhí)行的通知)鸡号,但這個(gè)通知不能阻止連接點(diǎn)之前的執(zhí)行流程转砖,除非它拋出一個(gè)異常须鼎。
doAfterReturning()方法使用@AfterReturning(value = "signAop()", returning = "params")注解鲸伴,表示后置通知(在某連接點(diǎn)正常完成后執(zhí)行的通知),通常在一個(gè)匹配的方法返回的時(shí)候執(zhí)行晋控。
實(shí)際運(yùn)行時(shí)汞窗,在進(jìn)入controller包下所有方法前,都會(huì)進(jìn)入doBefore()方法赡译,在controller包下方法執(zhí)行完成后仲吏,都會(huì)進(jìn)入doAfterReturning()方法。
AOP具體使用可以參考 Spring Boot入門(mén):使用AOP實(shí)現(xiàn)攔截器
AOP全名Aspect-Oriented Programming蝌焚,中文直譯為面向切面(方面)編程裹唆,當(dāng)前已經(jīng)成為一種比較成熟的編程思想,可以用來(lái)很好的解決應(yīng)用系統(tǒng)中分布于各個(gè)模塊的交叉關(guān)注點(diǎn)問(wèn)題只洒。在輕量級(jí)的J2EE中應(yīng)用開(kāi)發(fā)中许帐,使用AOP來(lái)靈活處理一些具有橫切性質(zhì)的系統(tǒng)級(jí)服務(wù),如事務(wù)處理毕谴、安全檢查成畦、緩存、對(duì)象池管理等涝开,已經(jīng)成為一種非常適用的解決方案循帐。 AOP中比較重要的概念有:Aspect、JoinPoint舀武、PonitCut拄养、Advice、Introduction银舱、Weave衷旅、Target Object、Proxy Object等
引介(Introduction)是指給一個(gè)現(xiàn)有類(lèi)添加方法或字段屬性纵朋,引介還可以在不改變現(xiàn)有類(lèi)代碼的情況下柿顶,讓現(xiàn)有的Java類(lèi)實(shí)現(xiàn)新的接口,或者為其指定一個(gè)父類(lèi)從而實(shí)現(xiàn)多重繼承操软。相對(duì)于增強(qiáng)(Advice)可以動(dòng)態(tài)改變程序的功能或流程來(lái)說(shuō)嘁锯,引介(Introduction)則用來(lái)改變一個(gè)類(lèi)的靜態(tài)結(jié)構(gòu)。比如我們可以讓一個(gè)現(xiàn)有為實(shí)現(xiàn)java.lang.Cloneable接口聂薪,從而可以通過(guò)clone()方法復(fù)制這個(gè)類(lèi)的實(shí)例家乘。
攔截器是用來(lái)實(shí)現(xiàn)對(duì)連接點(diǎn)進(jìn)行攔截,從而在連接點(diǎn)前或后加入自定義的切面模塊功能藏澳。在大多數(shù)JAVA的AOP框架實(shí)現(xiàn)中仁锯,都是使用攔截器來(lái)實(shí)現(xiàn)字段訪問(wèn)及方法調(diào)用的攔截(interception)。所用作用于同一個(gè)連接點(diǎn)的多個(gè)攔截器組成一個(gè)連接器鏈(interceptor chain)翔悠,鏈接上的每個(gè)攔截器通常會(huì)調(diào)用下一個(gè)攔截器业崖。Spring AOP及JBoos AOP實(shí)現(xiàn)都是采用攔截器來(lái)實(shí)現(xiàn)的野芒。
面向?qū)ο缶幊蹋∣OP)解決問(wèn)題的重點(diǎn)在于對(duì)具體領(lǐng)域模型的抽象,而面向切面編程(AOP)解決問(wèn)題的關(guān)鍵則在于對(duì)關(guān)注點(diǎn)的抽象双炕。也就是說(shuō)狞悲,系統(tǒng)中對(duì)于一些需要分散在多個(gè)不相關(guān)的模塊中解決的共同問(wèn)題,則交由AOP來(lái)解決妇斤;AOP能夠使用一種更好的方式來(lái)解決OOP不能很好解決的橫切關(guān)注點(diǎn)問(wèn)題以及相關(guān)的設(shè)計(jì)難題來(lái)實(shí)現(xiàn)松散耦合摇锋。因此,面向方面編程 (AOP) 提供另外一種關(guān)于程序結(jié)構(gòu)的思維完善了OOP站超,是OOP的一種擴(kuò)展技術(shù)荸恕,彌補(bǔ)補(bǔ)了OOP的不足。