SpringIoC
控制反轉是一種通過描述(XML或者註解)讓第三方去產生或獲取特定對象的方式.
在Spring中實現控制反轉的是IoC容器,其實現方式是依賴注入(DI).
Bean對象的初始化
通過Resource進行資源定位(XML和註解),將定位到的信息存入BeanDefinition中,將BeanDefinition的信息放入Spring IoC容器中,至此,就完成了Bean在IoC容器中的定義.定義後根據lazy-init的值來決定是否按需加載初始化(多例不生效),如果爲false,則自動初始化Bean,如果爲true則在調用IoC容器的getBean方法時,纔會進行初始化,完成依賴注入.
BeanFactory是IoC容器的根接口,ApplicationContext是BeanFactory的子接口,ApplicationContext有兩個常用的實現類,分別是ClassPathXmlApplicationContext(通過XML文件獲取bean對象)和AnnotationConfigApplicationContext(通過註解或者代碼獲取bean對象)IoC.容器中有兩個Map對象,一個是Bean池(存放單例bean對象,多例不放入),一個是定義池(存放bean對象的定義信息).
Bean對象的創建
Bean對象如果沒有實現FactoryBean(一個工廠Bean),則直接使用構造方法創建.
Bean對象如果實現了FactoryBean,則調用getObject方法創建對象.
依賴注入的三種方式:
構造器注入(適合參數較少的,底層反射),對應標籤<constructor-arg>
Setter注入(主流,靈活,底層反射),對應標籤<property>
接口注入.(從外部獲取的)
Spring Bean的生命週期
要自定義初始化或銷燬方法只需要實現對應的生命週期接口,並覆蓋對應方法即可.
裝配Spring Bean
配置Bean的方式有三種:
- 在XML中顯式配置(包括命名空間定義).
- 在Java的接口和類中實現配置.
- 隱式Bean的發現機制和自動裝配.
根據約定大於配置的原則,優先選擇第三種隱式發現機制和自動裝配.
當沒有辦法使用自動裝配機制時,應當使用第二種方式,避免XML配置文件的泛濫.
開發中會使用以註解爲主(簡易,方便),以XML爲輔(方便管理,第三方)的方式.
Spring提供了兩種方式來讓IoC容器發現Bean:組件掃描,自動裝配(使用註解)
用@Primary(優先使用)和@Qualifier(按名稱查找)可以消除自動裝配的歧義性(@Autowired默認是按照類型匹配的).
JDK動態代理
JDK動態代理要求實現InvocationHandler接口並重寫invoke方法
public Object invoke(Object proxy, Method method, Object[] args) {}
proxy代表代理對象,method代表需要調度的方法,args代表被調度方法的參數
通過Proxy類的newProxyInstance靜態方法可以獲得生成的代理對象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
loader代表被代理對象(真實對象)的類加載器
interfaces代表真實對象實現的那些接口
h代表實現了InvocationHandler接口的實現類實例.
該方法首先會做一些安全檢查,然後使用getProxyClass0(loader, intfs)方法來生成一個修改過的新類,然後反射調用新類的構造方法來創建代理對象.
getProxyClass0(loader, intfs)方法會返回proxyClassCache.get(loader, interfaces),如果緩存中有代理對象,則直接返回,如果沒有則通過Proxy類中的內部類ProxyClassFactory來生成新類.
通過觀察,可以發現該類繼承了Proxy類並且實現了真實對象的父接口,該類的構造方法傳入的參數是InvocationHandler接口的實現類實例(也就是上面所說的h).該類的成員是Method類型,這幾個方法類型的成員對應的就是真實對象實現的所有接口中的方法.
在該類中的方法中會調用InvocationHandler接口實例中的invoke(Object proxy, Method method, Object[] args).
傳入的第一個參數是this,對應代理對象,第二個參數是對應的方法類型成員,第三個就是對應的參數).
而在InvocationHandler接口中的invoke方法中則可以通過Method類型的invoke方法反射調用真實對象的對應方法.
由此可知,JDK動態代理通過提供InvocationHandler接口的invoke方法,讓開發者在invoke方法中寫入自己的代理邏輯即可,而底層的動態代理交由JDK來實現,也知道了JDK動態代理爲什麼必須提供接口,而不能是類(因爲代理對象要繼承Proxy類中的方法).
AOP
常用術語:切面(類似於攔截器),通知(before,after,afterReturning,afterThrowing,around),引入(允許在現有的類裏添加自定義的類和方法),切點(告訴AOP在何處攔截並織入,一般用正則表達式表示,其實就是攔截方法的規則),連接點(具備需要被攔截的東西,也就是被攔截的方法),織入(生成代理對象並將切面內容放入流程中的過程,有接口使用JDK動態代理,沒接口使用CGLIB)
AOP的本質其實就是AOP橫切思想+攔截器+動態代理+責任鏈模式,攔截器通過切點規則攔截指定方法,通過動態代理技術生成代理對象,並將切面中定義的通知織入到執行流程中,如果有多個切面,則採用責任鏈模式層層攔截.
Spring的AOP只能支持到方法級別的攔截,也就是說Spring是方法級別的AOP框架.
實現AOP攔截的方法
- 使用ProxyFactoryBean和對應的接口實現AOP.
- 使用XML配置AOP(輔助)
- 使用@AspectJ註解驅動切面(主流)
- 使用AspectJ注入切面.
Spring中的AOP相關注解
@EnableAspectJAutoProxy啓用AspectJ框架的自動代理,Spring纔會生成動態代理對象,進而使用AOP功能
@Aspect定義切面
@Pointcut定義切點方法後,通知註解中只需要使用對應的切點簽名即可,如@Before("print()")
@Order(1)定義多切面環境下,切面的執行順序.數字越小,優先級越高. 還可以通過讓切面實現Ordered接口的getOrder方法或者XML配置來實現.
@Before前置通知
@Around環繞通知,將覆蓋原有方法,允許通過反射調用原有方法(ProceedingJoinPoint的proceed方法)
@After後置通知
@AfterReturning返回通知
@AfterThrowing異常通知
定義切點表達式
execution()用於匹配連接點的執行方法.粒度級別:方法
@annotation()用於匹配指定的註解.粒度級別:方法
within()限制連接點匹配指定的包.粒度級別:類
bean()用於匹配指定bean id的類.粒度級別:類
以上表達式可以使用邏輯運算符進行運算.
給通知傳遞參數:在切點表達式後加&&args(參數名稱1,參數名稱2)即可.如果是XML方式,則將$$換爲and.
AOP引入
@DeclareParents(value="com.ssm.service.impl.被增強類+" defaultImpl=實現類.class),原理是讓代理對象實現對應接口.
Spring事務管理
- @Transactional的配置項:
- value定義事務管理器.
- transactionManager同上.
- isolation隔離級別,默認Isolation.DEFAULT取數據庫默認隔離級別.isolation=Isolation.READ_UNCOMMITTED讀取未提交.iIsolation.READ_COMMITTED提交已讀取.iIsolation.REPEATABLE_READ可重複讀.iIsolation.SERIALIZABLE串行化/序列化
- propagation傳播行爲,默認爲REQUIRED
- timeout超時時間,單位爲秒.
- readOnly是否開啓只讀事務,默認爲false.
- rollbackFor只有該異常發生纔回滾.
- rollbackForClassName同上,只不過是用類名來定義.
- noRollbackFor該異常發生時不回滾事務.
- noRollbackForClassName同上,只不過是用類名來定義.
傳播行爲
- REQUIRED方法調用時,如果不存在當前事務,那麼就創建.如果已存在,就沿用之前的事務.
- SUPPORTS當方法調用時,如果不存在當前事務,那麼不啓用事務;如果存在當前事務,那麼就沿用當前事務.
- MANDATORY方法必須在事務內運行.
- REQUIRES_NEW無論是否存在當前事務,方法都會在新的事務中運行.
- NOT_SUPPORTED不支持事務,如果不存在當前事務,什麼也不做,如果存在當前事務,則掛起它,直至方法結束後才恢復.
- NEVER不支持事務,只有在沒有事務的環境中才能運行它.
- NESTED嵌套事務,調用方法如果拋出異常只回滾自己內部執行的SQL,而不回滾主方法的SQL.如果數據庫不支持保存點,則退回到REQUIRES_NEW級別.
@Transactional對於靜態方法和非public方法是失效的(因爲底層是動態代理).
@Transactional對於自調用(一個類中方法調用,不會產生代理對象)也會失效,解決方法:使用兩個服務類或者從容器中獲取bean.
常見錯誤使用:
控制層中使用service對象調用多個方法.這些方法會產生各自的事務,不在一個事務中,導致數據不一致.
長時間佔用事務會導致系統性能的低下,要避免在事務中做業務無關操作和耗時操作.
Spring常用註解
@Component
@Service
@Repository
@Controller
@Autowired
@Configuration
@Bean
@ComponentScan
@Scope
@Value
@EnableAsync
@Async
@Lazy