spring 源碼分析 -- 1 -- core 核心簡介
sschrodinger
2019/03/04
Spring 框架簡介
spring 框架是 Java 開發(fā)中最著名的輕量級框架,使用各種方式簡化 Java 程序的開發(fā)楼熄。spring 采取了如下4個(gè)關(guān)鍵策略來簡化程序的開發(fā)休傍。
- 基于POJO的輕量級和最小侵入性編程
- 通過依賴注入和面向接口實(shí)現(xiàn)松耦合
- 基于切面和慣例進(jìn)行聲明式編程
- 通過切面和模板減少樣板式代碼
基于POJO的輕量級和最小侵入性編程
想象在沒有 spring 框架時(shí)瞭吃,我們構(gòu)建 web 程序,需要顯式的繼承 HttpServlet 并且改寫 doGet 等函數(shù)孝宗。樣例代碼如下:
class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.writer("Hello,world");
writer.flush();
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
這樣的程序和 servlet 協(xié)議緊耦合,必須要實(shí)現(xiàn) HttpServlet 接口才能夠編寫程序。基于POJO的輕量級和最小侵入性編程的基本思想就是使用簡單的 Java 語言( Plain Old Java Object)飒焦,實(shí)現(xiàn)功能的實(shí)現(xiàn)。比如說,我們使用 spring 框架搭建 servlet牺荠,就可以寫成如下形式:
@Controller
@RequestMapping("/")
public class MyController {
@RequestMapping(method = RequestMethod.GET)
public String hello() {
return "hello";
}
}
note
- POJO stands for Plain Old Java Object. It is an ordinary Java object, not bound by any special restriction other than those forced by the Java Language Specification and not requiring any class path.
- Extend prespecified classes, Ex: public class GFG extends javax.servlet.http.HttpServlet { … } is not a POJO class.
- Implement prespecified interfaces, Ex: public class Bar implements javax.ejb.EntityBean { … } is not a POJO class.
- Contain prespecified annotations, Ex: @javax.persistence.Entity public class Baz { … } is not a POJO class.
通過依賴注入和面向接口實(shí)現(xiàn)松耦合
IoC控制反轉(zhuǎn)的實(shí)現(xiàn)方式是依賴注入翁巍。正常的 Java 程序中,我們在代碼中使用新建對象的方式獲得對象實(shí)例休雌,但是在一些情況下灶壶,我們不知道使用具體的哪個(gè)類新建獲得實(shí)例對象,比如說對數(shù)據(jù)庫的訪問杈曲。這個(gè)時(shí)候我們就需要定義接口驰凛,并在運(yùn)行時(shí)自動(dòng)選擇具體的實(shí)現(xiàn)。IoC 將類的新建托付給第三方担扑,并讓第三方在運(yùn)行時(shí)自動(dòng)選擇需要?jiǎng)?chuàng)建的具體類實(shí)現(xiàn)(反射機(jī)制)恰响。
基于切面和慣例進(jìn)行聲明式編程
面向切面編程,即 AOP涌献,是通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)胚宦。
想象在一個(gè)大型教育系統(tǒng)中,我們提供講師服務(wù)燕垃,學(xué)生服務(wù)等服務(wù)枢劝,為了方便管理,我們需要為每一個(gè)服務(wù)添加日志接口利术,但是日志接口并不是各種服務(wù)的主要功能呈野,為了松耦合,我們需要將日志服務(wù)單獨(dú)出來印叁。
在緊耦合的代碼中被冒,可能代碼的結(jié)構(gòu)如下:
public class StudentService {
public Logger logger;
service() {
try {
logger.logBefore();
//do some service
logger.logAfter();
} catch(Exception e) {
logError();
}
}
}
public class Logger {
public void logError() {
//log error
}
public void logBefore() {
//log before service
}
public void logAfter() {
//log after
}
}
在如上所示的代碼中,StudentService 的 service 實(shí)現(xiàn)轮蜕,將 日志記錄代碼也寫了進(jìn)去昨悼。但是日志記錄并不應(yīng)該出現(xiàn)在 service 的實(shí)現(xiàn)中,而是應(yīng)該將 service 作為一個(gè)切點(diǎn)跃洛,將日志記錄的功能添加在這個(gè)切點(diǎn)中率触,通常的做法是使用 xml 等配置文件進(jìn)行配置,如下所示:
public class StudentService {
service() {
//do some service
}
}
public class Logger {
public void logError() {
//log error
}
public void logBefore() {
//log before service
}
public void logAfter() {
//log after
}
}
<配置>
<h>在 service 之前執(zhí)行Logger.logBefore()</h>
<h>在 service 之后執(zhí)行Logger.logAfter()</h>
<h>在 service 錯(cuò)誤之后執(zhí)行Logger.logError()</h>
</配置>
相當(dāng)于框架對 service 函數(shù)進(jìn)行了包裝汇竭。
通過切面和模板減少樣板式代碼
一些程序的代碼會(huì)多次使用葱蝗,比如說數(shù)據(jù)庫的登陸等代碼,spring 提供這些代碼的模板细燎,可以讓人更加專注于實(shí)現(xiàn)自己的邏輯代碼两曼。
Spring 核心技術(shù)
Spring IOC
在我們的日常開發(fā)中,創(chuàng)建對象的操作隨處可見以至于對其十分熟悉的同時(shí)又感覺十分繁瑣玻驻,每次需要對象都需要親手將其new出來悼凑,甚至某些情況下由于壞編程習(xí)慣還會(huì)造成對象無法被回收,這是相當(dāng)糟糕的。但更為嚴(yán)重的是户辫,我們一直倡導(dǎo)的松耦合渐夸,少入侵原則,這種情況下變得一無是處渔欢。于是前輩們開始謀求改變這種編程陋習(xí)墓塌,考慮如何使用編碼更加解耦合,由此而來的解決方案是面向接口的編程膘茎,于是便有了如下寫法:
public class BookServiceImpl {
//class
private BookDaoImpl bookDaoImpl;
public void oldCode(){
//原來的做法
bookDaoImpl=new bookDaoImpl();
bookDaoImpl.getAllCategories();
}
}
//=================new====================
public class BookServiceImpl {
//interface
private BookDao bookDao;
public void newCode(){
//變?yōu)槊嫦蚪涌诰幊? bookDao=new bookDaoImpl();
bookDao.getAllCategories();
}
}
BookServiceImpl 類中由原來直接與 BookDaoImpl 打交互變?yōu)?BookDao桃纯,即使 BookDao 最終實(shí)現(xiàn)依然是 BookDaoImp,這樣的做的好處是顯而易見的披坏,所有調(diào)用都通過接口bookDao 來完成态坦,而接口的真正的實(shí)現(xiàn)者和最終的執(zhí)行者就是 BookDaoImpl,當(dāng)替換 bookDaoImpl 類棒拂,也只需修 改bookDao 指向新的實(shí)現(xiàn)類伞梯。
雖然上述的代碼在很大程度上降低了代碼的耦合度,但是代碼依舊存在入侵性和一定程度的耦合性帚屉,比如在修改 bookDao 的實(shí)現(xiàn)類時(shí)谜诫,仍然需求修改 BookServiceImpl 的內(nèi)部代碼,當(dāng)依賴的類多起來時(shí)攻旦,查找和修改的過程也會(huì)顯得相當(dāng)糟糕喻旷,因此我們?nèi)孕枰獙ふ乙环N方式,它可以令開發(fā)者在無需觸及 BookServiceImpl 內(nèi)容代碼的情況下實(shí)現(xiàn)修改 bookDao 的實(shí)現(xiàn)類牢屋,以便達(dá)到最低的耦合度和最少入侵的目的且预。實(shí)際上存在一種稱為反射的編程技術(shù)可以協(xié)助解決上述問題,反射是一種根據(jù)給出的完整類名(字符串方式)來動(dòng)態(tài)地生成對象烙无,這種編程方式可以讓對象在生成時(shí)才決定到底是哪一種對象锋谐,因此可以這樣假設(shè),在某個(gè)配置文件截酷,該文件已寫好 bookDaoImpl 類的完全限定名稱涮拗,通過讀取該文件而獲取到 bookDao 的真正實(shí)現(xiàn)類完全限定名稱,然后通過反射技術(shù)在運(yùn)行時(shí)動(dòng)態(tài)生成該類迂苛,最終賦值給 bookDao 接口三热,也就解決了剛才的存在問題,這里為簡單演示三幻,使用 properties 文件作為配置文件就漾,className.properties 如下:
bookDao.name=com.zejian.spring.dao.BookDaoImpl
獲取該配置文件信息動(dòng)態(tài)為bookDao生成實(shí)現(xiàn)類:
public class BookServiceImpl implements BookService
{
//讀取配置文件的工具類
PropertiesUtil propertiesUtil = new PropertiesUtil("conf/className.properties");
private BookDao bookDao;
public void DaymicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//獲取完全限定名稱
String className=propertiesUtil.get("bookDao.name");
//通過反射
Class c=Class.forName(className);
//動(dòng)態(tài)生成實(shí)例對象
bookDao= (BookDao) c.newInstance();
}
}
的確如我們所愿生成了 bookDao 的實(shí)例,這樣做的好處是在替換 bookDao 實(shí)現(xiàn)類的情況只需修改配置文件的內(nèi)容而無需觸及 BookServiceImpl 的內(nèi)部代碼赌髓,從而把代碼修改的過程轉(zhuǎn)到配置文件中,相當(dāng)于 BookServiceImpl 及其內(nèi)部的 bookDao 通過配置文件與 bookDao 的實(shí)現(xiàn)類進(jìn)行關(guān)聯(lián),這樣 BookServiceImpl 與 bookDao 的實(shí)現(xiàn)類間也就實(shí)現(xiàn)了解耦合锁蠕,當(dāng)然 BookServiceImpl 類中存在著 BookDao 對象是無法避免的夷野,畢竟這是協(xié)同工作的基礎(chǔ),我們只能最大程度去解耦合荣倾。
了解了上述的問題再來理解IOC就顯得簡單多了悯搔。Spring IOC 也是一個(gè) Java 對象,在某些特定的時(shí)間被創(chuàng)建后舌仍,可以進(jìn)行對其他對象的控制妒貌,包括初始化、創(chuàng)建铸豁、銷毀等灌曙。簡單地理解,在上述過程中节芥,我們通過配置文件配置了 BookDaoImpl 實(shí)現(xiàn)類的完全限定名稱在刺,然后利用反射在運(yùn)行時(shí)為 BookDao 創(chuàng)建實(shí)際實(shí)現(xiàn)類,包括 BookServiceImpl 的創(chuàng)建头镊,Spring 的 IOC 容器都會(huì)幫我們完成蚣驼,而我們唯一要做的就是把需要?jiǎng)?chuàng)建的類和其他類依賴的類以配置文件的方式告訴IOC容器需要?jiǎng)?chuàng)建那些類和注入哪些類即可。Spring 通過這種控制反轉(zhuǎn)(IoC)的設(shè)計(jì)模式促進(jìn)了松耦合相艇,這種方式使一個(gè)對象依賴其它對象時(shí)會(huì)通過被動(dòng)的方式傳送進(jìn)來(如 BookServiceImpl 被創(chuàng)建時(shí)颖杏,其依賴的 BookDao 的實(shí)現(xiàn)類也會(huì)同時(shí)被注入 BookServiceImpl 中),而不是通過手動(dòng)創(chuàng)建這些類坛芽。我們可以把IoC模式看做是工廠模式的升華留储,可以把IoC看作是一個(gè)大工廠,只不過這個(gè)大工廠里要生成的對象都是在配置文件(XML)中給出定義的靡馁,然后利用 Java 的反射技術(shù)欲鹏,根據(jù) XML 中給出的類名生成相應(yīng)的對象。從某種程度上來說臭墨,IoC 相當(dāng)于把在工廠方法里通過硬編碼創(chuàng)建對象的代碼赔嚎,改變?yōu)橛?XML 文件來定義,也就是把工廠和對象生成這兩者獨(dú)立分隔開來胧弛,目的就是提高靈活性和可維護(hù)性尤误,更是達(dá)到最低的耦合度,因此我們要明白所謂為的 IOC 就將對象的創(chuàng)建權(quán),交由 Spring 完成结缚,從此解放手動(dòng)創(chuàng)建對象的過程损晤,同時(shí)讓類與類間的關(guān)系到達(dá)最低耦合度。
快速入門案例
理解了 Spring IOC 模式(容器)后红竭,我們來看一個(gè)簡單入門實(shí)例尤勋。使用 Spring 的 IOC 功能喘落,必須先引入 Spring 的核心依賴包(使用 Maven 作為構(gòu)建工具):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
然后創(chuàng)建 Dao 層(AccountDao):
public interface AccountDao {
void addAccount();
}
實(shí)現(xiàn)類(AccountDaoImpl):
public class AccountDaoImpl implements AccountDao{
@Override
public void addAccount() {
System.out.println("addAccount....");
}
}
再創(chuàng)建 Service,AccountService
public interface AccountService {
void doSomething();
}
實(shí)現(xiàn)類:
public class AccountServiceImpl implements AccountService {
/**
* 需要注入的對象
*/
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void doSomething() {
System.out.println("AccountServiceImpl#doSomething......");
accountDao.addAccount();
}
}
上面我們創(chuàng)建了 Dao 層和 Service 層的接口類及其實(shí)現(xiàn)類最冰,其中 Service 層的操作依賴于 Dao 層瘦棋,下面通過 Spring 的 IOC 容器幫助我們創(chuàng)建并注入這些類。IOC 使用的是 XML 配置文件暖哨,代碼如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
">
<!-- 聲明accountDao對象,交給spring創(chuàng)建 -->
<bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
<!-- 聲明accountService對象,交給spring創(chuàng)建 -->
<bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl">
<!-- 注入accountDao對象,需要set方法-->
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
從 xml 文件中赌朋,我們需要聲明一個(gè)beans的頂級標(biāo)簽,同時(shí)需要引入核心命名空間篇裁,Spring 的功能在使用時(shí)都需要聲明相對應(yīng)的命名空間沛慢,上述的命名空間是最基本的。然后通過 bean 子標(biāo)簽聲明那些需要IOC容器幫助我們創(chuàng)建的類达布,其中 name 是指明 IOC 創(chuàng)建后該對象的名稱(當(dāng)然也可以使用 id 替換 name团甲,這個(gè)后面會(huì)講到),class 則是告訴 IOC 這個(gè)類的完全限定名稱往枣,IOC 就會(huì)通過這組信息利用反射技術(shù)幫助我們創(chuàng)建對應(yīng)的類對象伐庭,如下:
<!-- 聲明accountDao對象,交給spring創(chuàng)建 -->
<bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
接著我們還看到如下聲明,accountService 聲明中多出了一個(gè) property 的標(biāo)簽分冈,這個(gè)標(biāo)簽指向了我們剛才創(chuàng)建的 accountDao 對象圾另,它的作用是把 accountDao 對象傳遞給 accountService 實(shí)現(xiàn)類中的 accountDao 屬性,該屬性必須擁有 set 方法才能注入成功雕沉,我們把這種往類 accountService 對象中注入其他對象(accountDao)的操作稱為依賴注入集乔,這個(gè)后面會(huì)分析到,其中的 name 必須與 AccountService 實(shí)現(xiàn)類中變量名稱相同坡椒,到此我們就完成對需要?jiǎng)?chuàng)建的對象聲明扰路。接著看看如何使用它們。
<!-- 聲明accountService對象,交給spring創(chuàng)建 -->
<bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl">
<!-- 注入accountDao對象,需要set方法-->
<property name="accountDao" ref="accountDao"/>
</bean>
使用這些類需要利用 Spring 提供的核心類倔叼,ApplicationContext
汗唱,通過該類去加載已聲明好的配置文件,然后便可以獲取到我們需要的類了丈攒。
public void testByXml() throws Exception {
//加載配置文件
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml");
// AccountService accountService=applicationContext.getBean("accountService",AccountService.class);
//多次獲取并不會(huì)創(chuàng)建多個(gè)accountService對象,因?yàn)閟pring默認(rèn)創(chuàng)建是單實(shí)例的作用域
AccountService accountService= (AccountService) applicationContext.getBean("accountService");
accountService.doSomething();
}
循環(huán)依賴
如下有兩個(gè) bean哩罪,A 和 B,這兩個(gè) bean 通過構(gòu)造函數(shù)互為依賴巡验,這種情況下 Spring 容器將無法實(shí)例化這兩個(gè) bean际插。
public class A{
private B b;
public A(B b){
this.b=b;
}
}
public class B{
private A a;
public B(A a){
this.a=a;
}
}
<bean id="a" class="com.zejian.springioc.pojo.A">
<constructor-arg ref="b" />
</bean>
<bean id="b" class="com.zejian.springioc.pojo.B">
<constructor-arg ref="a" />
</bean>
這是由于 A 被創(chuàng)建時(shí),希望 B 被注入到自身显设,然而框弛,此時(shí) B 還有沒有被創(chuàng)建,而且 B 也依賴于 A捕捂,這樣將導(dǎo)致 Spring 容器左右為難瑟枫,無法滿足兩方需求斗搞,最后腦袋奔潰,拋出異常慷妙。解決這種困境的方式是使用 Setter 依賴榜旦,但還是會(huì)造成一些不必要的困擾,因此景殷,強(qiáng)烈不建議在配置文件中使用循環(huán)依賴。
AOP 編程
面向切面編程澡屡,使得代碼能夠更加專注于該業(yè)務(wù)的邏輯猿挚。
比如說日志系統(tǒng),許多系統(tǒng)模塊都會(huì)需要用到日志系統(tǒng)驶鹉,但是每個(gè)模塊如果都增加對日志的寫入會(huì)顯得邏輯代碼不清晰绩蜻,因?yàn)槿罩敬a并不是該模塊的主要功能,如下偽代碼:
class Model {
public void addUser(User user) {
log.info("start...");
try {
add(user);
log.info("add user success");
} catch (IOException e) {
log.err("add user error");
}
}
Logger log = LoggerFactory.getInstance();
}
如上代碼展示了一個(gè)典型的日志系統(tǒng)的寫入功能室埋,可以發(fā)現(xiàn)日志系統(tǒng)占了邏輯的一大塊办绝,使得本身的邏輯不明顯。面向切面編程更加關(guān)注橫切面姚淆,即真正的業(yè)務(wù)邏輯孕蝉,將日志記錄通過 class 文件修改或者動(dòng)態(tài)代理等方式,實(shí)現(xiàn)日志記錄的功能腌逢。