Spring實戰(一)-DI和AOP入門
前言
作為一個Java開發者,對Spring不可謂不熟悉。但從來沒有系統的對他進行過學習,都是建立在專案開發的基礎上對他進行一些使用。現在抽出一些時間從頭開始學習Spring,便於更好的梳理整個框架的結構,對今後學習SpringBoot和SpringCloud打下良好的基礎。
Spring簡介
Spring是一個開源框架,它的最根本的使命就是簡化Java開發。為了降低Java開發的發雜性,Spring採取了以下四種關鍵策略:
1. 基於POJO的輕量級和最小入侵性程式設計
2. 通過依賴注入和麵向介面實現鬆耦合
3. 基於切面和慣例進行宣告式程式設計
4. 通過切面和模板減少樣板式程式碼
1.如何基於POJO的最小入侵性程式設計(DI)
實際上,Java開發中很多框架的使用都是通過強迫應用繼承它們的類或者實現它們的介面從而導致應用與框架綁死,一個典型的例子就是EJB2時代的無狀態會話bean。而在基於Spring構建的應用中,通常沒有任何痕跡表明你使用了Spring,最不濟一個類或許會使用Spring註解,但它依舊是POJO類。
舉個簡單的例子:
public class HelloWorldBean{
public String sayHello(){
return "Hello World";
}
}
這是一個普通的Java類(POJO),沒有任何地方表明它是一個Spring元件。雖然是個簡單的POJO,但Spring可通過DI
那麼DI(依賴注入)又是如何實現的呢?
在我們的實際應用當中,都會由多個類組成,並且這些類相互之間進行寫作來完成特定的業務邏輯,現在舉個例子看看傳統的做法:
在傳統的三層架構中,我們經常會這麼使用
public class UserDAOImpl implements UserDAO{
@Override
public User findByName(String name){
return null;
}
}
public class UserServiceImpl implements UserService{
private UserDAO userDAO = new UserDAOImpl();
@Override
public User findUser(String name){
return userDAO.findByName(name);
}
}
當然,我們可能不會直接new UserDAOImpl()
,而是通過一個工廠方法來獲取這個物件,但是不管是前者還是後者,UserDAO
的物件都是在UserService
中進行建立的,即它的控制權掌握在另一個類中。在這樣的情況下,它們具有較高的耦合度,如果我們修改了UserDAO
的實現類,那麼也要在對應耦合的類中進行一定的修改。
在這樣的情況下,對於物件的管理和維護就會變得不友好,那麼我們能不能實現在我們需要的時候自動建立物件呢?這就要用到Spring
的DI
(依賴注入)了,在我們需要建立物件的時候,通知Spring容器,它就會把這個物件傳遞到需要的地方。
在spring中,BeanFactory
就是這樣一個容器,我們只需將我們的Bean
配置到BeanFactory
的配置檔案中,就能夠進行統一的管理,如下 spring.xml
:
<bean id="userDAO" class="com.example.DAO.impl.UserDAOImpl"/>
也可以在配置中告訴Spring你需要什麼樣的物件:
<bean id="userService" class="com.example.service.impl.UserService.Impl">
<!-- userDAO屬性需要提供setter -->
<property name="userDAO" ref="userDAO"/>
</bean>
這樣,整個物件的管理就都交給Spring進行統一的管理,當需要修改某個型別物件的實現的時候,只需要修改這個配置檔案,而不需要對程式碼進行修改了。
Spring
通過ApplicationContext
裝載bean
的定義並把它們組裝起來,因為上述使用的是xml
配置方式,因而使用ClassPathXmlApplicationContext
來載入配置。
使用方式如下:
@Test
public void test(){
// 基於spring.xml檔案建立了Spring應用上下文
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring.xml");
// 根據上下文物件獲得一個 id 為userService的bean
UserService userService = ctx.getBean("userService", UserService.class);
User user = userService.findUser("admin");
assertNull(user);//斷言user物件為空
}
2.如何基於切面進行宣告式程式設計(AOP)
我們知道,
DI-Dependency Injection
能夠讓相互協作的軟體元件保持鬆耦合
,而AOP
(面向切面程式設計-aspect-oriented programming
)則是把應用中各處的功能分離出來形成可以重用的元件。
系統往往由許多不同的元件組成,每一個元件都有各自負責的功能。除了實現自身核心的功能之外,這些元件常常還承擔著額外的責任。諸如 日誌
、事務管理
和安全
這樣的系統服務。這些系統服務通常被稱為橫切關注點
,因為它們會跨越系統的多個元件,如果將這些關注點
分散到多個元件中,會帶來以下問題:
- 如果要修改關注點的邏輯,那麼就必須修改它所橫跨的所有元件模組中的相關實現。即使這些關注點已經被抽象成一個獨立的模組,其他部分只是呼叫它的方法,但方法的呼叫還是會重複的出現在各個模組中。
- 元件會因為那些與自身核心業務無關的程式碼變得混亂,比如:一個對註冊使用者的儲存操作只應關注如何儲存新使用者的資訊,而不是關注它的日誌和事務等。
如下圖所示:
但是,AOP很好的解決了這個問題,它能夠使這些服務模組化,並以宣告的方式將它們應用到它們需要影響的元件中去。在使用AOP後,上圖中各個元件之間的關係會如下所示:
接下來,我們結合之前的例子,在應用中看看AOP是如何進行處理和配置的。
先新建如下程式碼:
public interfact Hello(){
void sayHello();
}
public class IHello implements Hello(){
@Override
public void sayHello(){
System.out.println("---HelloWorld---")
}
}
還記得之前寫的三層架構DAO層和Service層嗎,它們在spring.xml
的配置如下:
<bean id="userDAO" class="com.example.DAO.impl.UserDAOImpl"/>
<bean id="userService" class="com.example.service.impl.UserService.Impl">
<!-- userDAO屬性需要提供setter -->
<property name="userDAO" ref="userDAO"/>
</bean>
我們在spring.xml
中加入管理Hello的元件的配置,在將其AOP配置加上,如下所示:
<!-- 在這裡新增Hello的元件配置,可以把它當成一個簡單的日誌處理 -->
<bean id="iHello" class="com.example.hello.impl.IHello">
<aop:config>
<!-- 配置切入點 -->
<aop:pointcut expression="execution(public * com.example.service.impl..*.findUser(..))"
id="servicePointcut" />
<!-- 配置切面,即要執行切入點的地方 -->
<aop:aspect id="helloAspect" ref="iHello">
<!-- 配置為在執行方法之前執行切面元件 -->
<aop:before method="before" pointcut-ref="servicePointcut" />
</aop:aspect>
</aop:config>
以上配置表示,在切入點表示式所涵蓋的方法執行之前,都需先執行切面程式,即先打印出 “—HelloWorld—“,這裡就完成了一個簡單的切面元件形式的“日誌處理“。
總結
現在我們來回顧以下這次介紹的Spring內容:
1.Spring的核心—容器:容器是Spring框架的最核心的部分,它管理著Spring應用中bean的建立、配置和管理。在該模組中,包括了Spring bean工廠,它為Spring提供了DI的功能。基於bean工廠,還有多種Spring應用上下文的實現,每一種都提供了配置Spring的不同的方式。
2.Spring的AOP模組:AOP可以幫助應用物件解耦,藉助AOP,可以將遍佈系統的關注點(如事物和安全等)從它們所應用的物件中解耦出來。
以上是對DI和AOP進行簡單的認識,下一篇我們會對Spring中Bean的裝配進行詳細的介紹。