先來看看入門使用Spring的代碼:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
對于這三行代碼應該,大家都是比較熟悉,這是學習Spring的hello world。可是,這三行代碼底層都做了什麼,比如:
第一行代碼,會構造一個ClassPathXmlApplicationContext對象,ClassPathXmlApplicationContext該如何了解,調用該構造方法除開會執行個體化得到一個對象,還會做哪些事情?
第二行代碼,會調用ClassPathXmlApplicationContext的getBean方法,會得到一個UserService對象,getBean()是如何實作的?傳回的UserService對象和我們自己直接new的UserService對象有差別嗎?
第三行代碼,就是簡單的調用UserService的test()方法,不難了解。
隻看這三行代碼,其實并不能展現出來Spring的強大之處,也不能了解為什麼需要ClassPathXmlApplicationContext和getBean()方法,随着課程的深入将會改變你此時的觀念,而對于上面的這些疑問,也會随着課程深入逐漸得到解決。對于這三行代碼,你現在可以認為:如果你要用Spring,你就得這麼寫。就像你要用Mybatis,你就得寫各種Mapper接口。
但是用ClassPathXmlApplicationContext其實已經過時了,在新版的Spring MVC和Spring Boot的底層主要用的都是AnnotationConfigApplicationContext,比如:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常類似的,隻不過需要傳入的是一個class,而不是一個xml檔案。
而AppConfig.class和spring.xml一樣,表示Spring的配置,比如可以指定掃描路徑,可以直接定義Bean,比如:
spring.xml中的内容為:
<context:component-scan base-package="com.zhouyu"/>
<bean id="userService" class="com.zhouyu.service.UserService"/>
AppConfig中的内容為:
@ComponentScan("com.zhouyu")
public class AppConfig {
@Bean
public UserService userService(){
return new UserService();
}
}
是以spring.xml和AppConfig.class本質上是一樣的。
目前,我們基本很少直接使用上面這種方式來用Spring,而是使用Spring MVC,或者SpringBoot,但是它們都是基于上面這種方式的,都需要在内部去建立一個ApplicationContext的,隻不過:
Spring MVC建立的是XmlWebApplicationContext,和ClassPathXmlApplicationContext類似,都是基于XML配置;
Spring Boot建立的是AnnotationConfigApplicationContext;
因為AnnotationConfigApplicationContext是比較重要的,并且AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底層都是共同的。
Spring中是如何建立一個對象?
其實不管是AnnotationConfigApplicationContext還是ClassPathXmlApplicationContext,目前,我們都可以簡單的将它們了解為就是用來建立Java對象的,比如調用getBean()就會去建立對象(此處不嚴謹,getBean可能也不會去建立對象,後續文章會講解)。
在Java語言中,肯定是根據某個類來建立一個對象的。我們在看一下執行個體代碼:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
當我們調用context.getBean("userService")時,就會去建立一個對象,但是getBean方法内部怎麼知道"userService"對應的是UserService類呢?
是以,我們就可以分析出來,在調用AnnotationConfigApplicationContext的構造方法時,也就是第一行代碼,會去做一些事情:
解析AppConfig.class,得到掃描路徑;
周遊掃描路徑下的所有Java類,如果發現某個類上存在@Component、@Service等注解,那麼Spring就把這個類記錄下來,存在一個Map中,比如Map<String, Class>。(實際上,Spring源碼中确實存在類似的這麼一個Map,叫做BeanDefinitionMap,後續文章會講解);
Spring會根據某個規則生成目前類對應的beanName,作為key存入Map,目前類作為value;
這樣,但調用context.getBean("userService")時,就可以根據"userService"找到UserService類,進而就可以去建立對象了。
Bean的建立過程
那麼Spring到底是如何來建立一個Bean的呢,這個就是Bean建立的生命周期,大緻過程如下:
利用該類的構造方法來執行個體化得到一個對象(但是如何一個類中有多個構造方法,Spring則會進行選擇,這個叫做推斷構造方法)
得到一個對象後,Spring會判斷該對象中是否存在被@Autowired注解了的屬性,把這些屬性找出來并由Spring進行指派(依賴注入)
依賴注入後,Spring會判斷該對象是否實作了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果實作了,就表示目前對象必須實作該接口中所定義的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就會調用這些方法并傳入相應的參數(Aware回調)
Aware回調後,Spring會判斷該對象中是否存在某個方法被@PostConstruct注解了,如果存在,Spring會調用目前對象的此方法(初始化前)
緊接着,Spring會判斷該對象是否實作了InitializingBean接口,如果實作了,就表示目前對象必須實作該接口中的afterPropertiesSet()方法,那Spring就會調用目前對象中的afterPropertiesSet()方法(初始化)
最後,Spring會判斷目前對象需不需要進行AOP,如果不需要那麼Bean就建立完了,如果需要進行AOP,則會進行動态代理并生成一個代理對象做為Bean(初始化後)
通過最後一步,我們可以發現,當Spring根據UserService類來建立一個Bean時:
如果不用進行AOP,那麼Bean就是UserService類的構造方法所得到的對象。
如果需要進行AOP,那麼Bean就是UserService的代理類所執行個體化得到的對象,而不是UserService本身所得到的對象。
Bean對象建立出來後:
如果目前Bean是單例Bean,那麼會把該Bean對象存入一個Map<String, Object>,Map的key為beanName,value為Bean對象。這樣下次getBean時就可以直接從Map中拿到對應的Bean對象了。(實際上,在Spring源碼中,這個Map就是單例池)
如果目前Bean是原型Bean,那麼後續沒有其他動作,不會存入一個Map,下次getBean時會再次執行上述建立過程,得到一個新的Bean對象。
推斷構造方法
Spring在基于某個類生成Bean的過程中,需要利用該類的構造方法來執行個體化得到一個對象,但是如果一個類存在多個構造方法,Spring會使用哪個呢?
Spring的判斷邏輯如下:
如果一個類隻存在一個構造方法,不管該構造方法是無參構造方法,還是有參構造方法,Spring都會用這個構造方法
如果一個類存在多個構造方法
這些構造方法中,存在一個無參的構造方法,那麼Spring就會用這個無參的構造方法;
這些構造方法中,不存在一個無參的構造方法,那麼Spring就會報錯;
Spring的設計思想是這樣的:
如果一個類隻有一個構造方法,那麼沒得選擇,隻能用這個構造方法
如果一個類存在多個構造方法,Spring不知道如何選擇,就會看是否有無參的構造方法,因為無參構造方法本身表示了一種預設的意義
不過如果某個構造方法上加了@Autowired注解,那就表示程式員告訴Spring就用這個加了注解的方法,那Spring就會用這個加了@Autowired注解構造方法了
需要重視的是,如果Spring選擇了一個有參的構造方法,Spring在調用這個有參構造方法時,需要傳入參數,那這個參數是怎麼來的呢?
Spring會根據入參的類型和入參的名字去Spring中找Bean對象(以單例Bean為例,Spring會從單例池那個Map中去找):
先根據入參類型找,如果隻找到一個,那就直接用來作為入參;
如果根據類型找到多個,則再根據入參名字來确定唯一一個;
最終如果沒有找到,則會報錯,無法建立目前Bean對象;
确定用哪個構造方法,确定入參的Bean對象,這個過程就叫做推斷構造方法。
AOP大緻流程
AOP就是進行動态代理,在建立一個Bean的過程中,Spring在最後一步會去判斷目前正在建立的這個Bean是不是需要進行AOP,如果需要則會進行動态代理。
如何判斷目前Bean對象需不需要進行AOP:
找出所有的切面Bean
周遊切面中的每個方法,看是否寫了@Before、@After等注解
如果寫了,則判斷所對應的Pointcut是否和目前Bean對象的類是否比對
如果比對則表示目前Bean對象有比對的的Pointcut,表示需要進行AOP
利用cglib進行AOP的大緻流程:
生成代理類UserServiceProxy,代理類繼承UserService
代理類中重寫了父類的方法,比如UserService中的test()方法
代理類中還會有一個target屬性,該屬性的值為被代理對象(也就是通過UserService類推斷構造方法執行個體化出來的對象,進行了依賴注入、初始化等步驟的對象)
代理類中的test()方法被執行時的邏輯如下:
執行切面邏輯(@Before)
調用target.test()
當我們從Spring容器得到UserService的Bean對象時,拿到的就是UserServiceProxy所生成的對象,也就是代理對象。
UserService代理對象.test()--->執行切面邏輯--->target.test(),注意target對象不是代理對象,而是被代理對象。
Spring事務
當我們在某個方法上加了@Transactional注解後,就表示該方法在調用時會開啟Spring事務,而這個方法所在的類所對應的Bean對象會是該類的代理對象。
Spring事務的代理對象執行某個方法時的步驟:
判斷目前執行的方法是否存在@Transactional注解;
如果存在,則利用事務管理器(TransactionMananger)建立一個資料庫連接配接;
修改資料庫連接配接的autocommit為false;
執行target.test(),執行程式員所寫的業務邏輯代碼,也就是執行sql;
執行完了之後如果沒有出現異常,則送出,否則復原;
Spring事務是否會失效的判斷标準:
某個加了@Transactional注解的方法被調用時,要判斷到底是不是直接被代理對象調用的,如果是則事務會生效,如果不是則失效。