如何假裝成一個服務端開發(一)java
如何假裝成一個服務端開發(二)android
如何假裝成一個服務端開發(三) spring
首先,咱們拋開Spring 或者 服務端開發,來聊聊這兩個概念。app
IoC和麪向對象設計同樣,從本質上來講是一種概念。而這種概念的主要目的就在於對象之間的解耦。在原來的邏輯裏面,假如咱們有一個User類,User類中須要使用UserInfo類,而後咱們還有一個Client類。框架
如今我須要在Client中使用User,那麼我首先須要在Client類中new 一個User類, 而後再new 一個UserInfo類,最後吧UserInfo,注入到User類中。這個時候,咱們發現Client類耦合了User類,和UserInfo類。ide
因此聰明的咱們決定下降耦合,既然UserInfo是在User類內部的,那麼咱們能夠在User類內部本身建立UserInfo。這樣的話,Client類就和UserInfo解耦了。不過引入的問題就是User類又和UserInfo類耦合了。函數
因此怎麼辦,要不咱們建立一箇中央平臺吧,咱們在中央平臺中建立User,在中央平臺中建立UserInfo。當Client須要User的時候,直接去中央平臺獲取這個User便可。當User須要UserInfo時,也直接去中央平臺獲取這個UserInfo。這樣的話,那麼Client,User,UserInfo相互之間就那麼耦合了。工具
這種思想就是所謂的IOC,而這個中央平臺就是所謂的容器,這種技術就是所謂的DI。學習
這實現的過程當中,原來,咱們須要在Client中主動去建立對象,這是一個主動的過程,而使用中央平臺以後,中央平臺負責了對象的建立(固然高級的中央平臺還會幫你管理這些對象的生命週期)。Client中若是須要使用一個對象,直接告訴中央平臺,中央平臺吐給你結果,這過程當中咱們沒有主動去建立。ui
ok,以上是網上的權威說法,可是……這TM也算反轉?總以爲多少有些牽強啊喂。
再舉個簡單的例子,咱們是如何找女友的?常見的狀況是,咱們處處去看哪裏有長得漂亮身材又好的mm,而後打聽她們的興趣愛好、qq號、電話號、ip號、iq號………,想辦法認識她們,投其所好送,這個過程是複雜深奧的,咱們必須本身設計和麪對每一個環節。傳統的程序開發也是如此,在一個對象中,若是要使用另外的對象,就必須獲得它(本身new一個,或者從JNDI中查詢一個),使用完以後還要將對象銷燬(好比Connection等),對象始終會和其餘的接口或類藕合起來。
那麼IoC是如何作的呢?有點像經過婚介找女友,在我和女友之間引入了一個第三者:婚姻介紹所。婚介管理了不少男男女女的資料,我能夠向婚介提出一個列表,告訴它我想找個什麼樣的女友,好比長得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術像齊達內之類的,而後婚介就會按照咱們的要求,提供一個mm,咱們只須要去和她談戀愛、結婚就好了。簡單明瞭,若是婚介給咱們的人選不符合要求,咱們就會拋出異常。整個過程再也不由我本身控制,而是有婚介這樣一個相似容器的機構來控制。
在Spring中,IoC的思想也體如今對象建立和管理的流程上。全部的類都會在spring容器中登記,告訴spring你是個什麼東西,你須要什麼東西,而後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其餘須要你的東西。全部的類的建立、銷燬都由 spring來控制,也就是說控制對象生存週期的再也不是引用它的對象,而是spring。
在 Spring 中把每個須要管理的對象稱爲 Spring Bean(簡稱 Bean),而 Spring 管理這些 Bean 的容器,被咱們稱爲 Spring IoC 容器(或者簡稱 IoC 容器)。IoC 容器須要具有兩個基本的功能:
在Spring中,全部的IoC容器都會實現BeanFactory這個接口。
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; //getBean 表示從容器獲取對象的方法, // 能夠經過對象的名字獲取,也能夠經過對象的類型獲取。 Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> var2) throws BeansException; Object getBean(String name, Object... var2) throws BeansException; <T> T getBean(Class<T> name) throws BeansException; <T> T getBean(Class<T> name, Object... var2) throws BeansException; // <T> ObjectProvider<T> getBeanProvider(Class<T> var1); <T> ObjectProvider<T> getBeanProvider(ResolvableType var1); //檢測是否包含bean boolean containsBean(String name); //檢測類是不是單例 默認狀況下,全部的類型都是單例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; //返回true時,每次getBean都會建立一個新的對象。 boolean isPrototype(String name) throws NoSuchBeanDefinitionException; //檢測bean是否匹配某個類型 boolean isTypeMatch(String name, ResolvableType var2) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> var2) throws NoSuchBeanDefinitionException; //獲取一個bean的類型 @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; //獲取一個bean的別名 String[] getAliases(String name); }
Bean屬於最基礎的IoC容器,只提供了對於注入對象最基本的操做,Spring在BeanFactory基礎上還派生出了更高級的接口ApplicationContext,在實際使用中,大部分的Ioc容器都是ApplicationContext接口的實現類。
在BeanFactory基礎上,ApplicationContext接口擴展了消息國際化接口,環境可配置接口,應用事件發佈接口和資源模式解析接口。
在當前的Spring版本中,咱們多會使用註解的方式來裝配Spring IoC容器(還有經過xml的方式),因此這裏咱們就使用一個基於註解的IoC容器——AnnotationConfigApplicationContext。
咱們建立一個model包用於存放各類bean,而後建立一個bean 好比 User
public class User { private Long id; private String userName; private String note; /**setter and getter **/ .... }
而後再建立一個另外的包,好比config,而後在config中建立一個AppConfig文件
import com.guardz.first_spring_boot.model.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean(name = "user") public User initUser() { User user = new User(); user.setId(1L); user.setUserName("user_name_1"); user.setNote("note_1"); return user; } }
關鍵在於註解,@Configuration表示這是一個Java配置文件,Spring會根據它來初始化IoC容器,用於裝配Bean.
@Bean表示將initUser返回的對象放入IoC容器中進行管理,而這個對象的名字就是name屬性定義的名字,若是沒有定義name屬性,默認就會使用方法名,好比 "initUser"。
而後,咱們就可使用AnnotationConfigApplicationContext 來建立一個IoC容器。
咱們修改XXXApplication文件,在main方法中加入以下代碼
private static Logger log = Logger.getLogger(FirstSpringBootApplication.class.getName()); public static void main(String[] args) { SpringApplication.run(FirstSpringBootApplication.class, args); ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); User user = applicationContext.getBean(User.class); log.info("this is yxwang " + user.getId()+""); }
運行後,咱們會看到打印出了
INFO 97781 --- [ main] c.g.f.FirstSpringBootApplication : this is yxwang 1
這就表示user對象已經正確裝載到了applicationContext這個IoC容器中,而且咱們經過getBean,順利拿到了這個user對象。
PS:這裏有個疑惑,我在AppConfig中文件中移除了@Configuration的註解,發現也能正確注入,這讓我很疑惑@Configuration的做用。不過姑且仍是加上吧,隨着深刻學習,我想我會弄清楚的。
當咱們擁有很是多的bean以後,每個bean都寫入AppConfig中去注入也是一種略顯麻煩事情,所以,Spring還提供了一種掃描注入的方式,讓IoC容器本身去掃描須要裝配的bean。
首先須要改造一個bean
@Component("user") public class User { @Value("1") private Long id; @Value("user_name_1") private String userName; @Value("note_1") private String note; ... }
添加了@Component註解,這個註解表示,這個類須要被Spring IoC容器掃描裝載。
@Value表示被注入時屬性的值。
而後,咱們須要修改AppConfig的內容
@Configuration @ComponentScan public class AppConfig { }
刪除了initUser的方法,添加了@ComponentScan的註解,這個註解表示當使用AppConfig初始化IoC容器時,須要主動掃描(掃描哪些@Component)。
運行,可是咱們發現報錯了,沒法找到User對象了,User沒有被裝配到IoC容器中。緣由在於@ComponentScan只會掃描當前包和它的子包。咱們的AppConfig和User並不在同一個包級下,因此沒法被掃描到。
一種解決方案是修改包的結構,可是這未免會破壞原有的代碼結構。好在@ComponentScan提供了很多參數
public @interface ComponentScan { //定義掃描的包 @AliasFor("basePackages") String[] value() default {}; //定義掃描的包 和 value互爲別名 @AliasFor 表示和xx是別名 @AliasFor("value") String[] basePackages() default {}; //主動指出須要掃描的類名 Class<?>[] basePackageClasses() default {}; //Bean name的生成器 Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; //做用於解析器 Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; //做用於代理模式 ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; //資源匹配模式 String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; //是否啓用默認過濾器 boolean useDefaultFilters() default true; //過濾器,當知足條件時纔會掃描載入 Filter[] includeFilters() default {}; //過濾器, 當知足條件時不會掃描載入 Filter[] excludeFilters() default {}; //是否延遲初始化 (對象被get時纔會初始化bean) boolean lazyInit() default false; //過濾器 @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { //過濾器類型,有不少種,默認是根據註解過濾 FilterType type() default FilterType.ANNOTATION; //定義過濾的類 @AliasFor("classes") Class<?>[] value() default {}; //定義過濾的類 @AliasFor("value") Class<?>[] classes() default {}; //匹配方式 String[] pattern() default {}; } }
比較經常使用的就是制定包名和過濾器。
如今,咱們能夠修改AppConfig,修改成
//三種選一種 @ComponentScan("com.guardz.first_spring_boot.model") //@ComponentScan(basePackages = {"com.guardz.first_spring_boot.model"}) //@ComponentScan(basePackageClasses = {User.class})
假設我如今有一個@Service類,(@Service類內部會帶有@Component註解)
@Service public class XXXService { .... }
這個時候AppConfig就會自動注入該類(假設該類的路徑包含在了AppConfig設置的掃描路徑中),這個時候,若是我不想注入該類,就可使用Filter進行排除,好比這樣
@ComponentScan(basePackages = "xxxxx", excludeFilters = {@Filter(classes = {XXXService.class})})
PS: @AliasFor屬性的互爲別名必須在兩個屬性上都寫上@AliasFor。另外AliasFor還能夠用於覆蓋指定註解中的值。
好比
.... public @interface EnableAutoConfiguration { ..... Class<?>[] exclude() default {}; } ... @EnableAutoConfiguration ... public @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ //表示重寫了EnableAutoConfiguration中的exclude值 @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; ..... }
雖然第二種方式看上去很好用,可是對於三方類庫,咱們沒辦法在對方的類中添加@Component註解,這個時候,仍是會使用@Bean標籤。
引用咱們最上面的UserInfo和User類的例子,如今存在兩個接口
public interface User{ public static Logger log = Logger.getLogger(User.class.getName()); public void setUserInfo(UserInfo userInfo); public void printUserInfo(); } public interface UserInfo { public String getUserType(); }
分別存在兩個子類
@Component("clientInfo") public class ClientInfo implements UserInfo { @Override public String getUserType() { return "client"; } } @Component("gameUser") public class GameUser implements User { private UserInfo userInfo = null; @Override public void setUserInfo(UserInfo userInfo) { log.info("setUserInfo is called"); this.userInfo = userInfo; } @Override public void printUserInfo() { log.info("I am Game User ,type is "+userInfo.getUserType()); } }
這兩個類都會被自動注入到IoC容器中,而後我在XXXApplication中使用
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); User user = applicationContext.getBean(GameUser.class); user.setUserInfo(applicationContext.getBean(ClientInfo.class)); user.printUserInfo();
這操做並無問題,可是咱們發現咱們仍是參與了user的構造,調用了set方法,設置了一個值。而Spring的IoC容器正是致力於處理此類問題的工具。
咱們能夠修改GameUser類
@Component("gameUser") public class GameUser implements User { @Autowired private UserInfo userInfo = null; ........ }
在userInfo關鍵字上添加了@Autowired關鍵字,表示讓spring 根據類型自動注入對象。
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); User user = applicationContext.getBean(GameUser.class); user.printUserInfo();
好了咱們能夠直接如上這樣獲取對象,並調用方法了,UserInfo自動被附上了ClientInfo的值。
PS: 這裏的賦值應該是直接經過反射寫入的,由於setUserInfo方法並無被調用。(android中有些依賴注入框架會根據屬性尋找相關的set方法,而後調用set方法)
那麼問題來了,若是這是時候咱們新添加了一個類
@Component("adminInfo") public class AdminInfo implements UserInfo{ @Override public String getUserType() { return "admin"; } }
這個時候再去編譯就會報錯(IDEA都不須要編譯,直接下劃線告訴你有問題),爲何,由於Spring 懵逼了,我有兩個UserInfo的子類,你讓我注入哪一個?
因此這個時候咱們能夠將UserInfo類型改成ClientInfo,這樣就能注入,可是這和設計思想不符啊,怎麼就把我類型寫死了?因此咱們就須要手動指出咱們須要的對象名字
@Component("gameUser") public class GameUser implements User { @Autowired @Qualifier("adminInfo") private UserInfo userInfo = null; ........ }
好比這裏,咱們經過@Qualifier指定了注入的對象是"adminInfo"(這是AdminInfo在IoC容器中的名字,經過@Component指定了)。
除此以外還有幾個使用方法也常常出現。
首先對於@Autowired註解,表示必定要找到一個惟一類進行注入,可是咱們能夠經過 @Autowired(required = false) 表示要麼找到惟一的類,要麼找不到爲空,不進行注入。
對於上述出現注入分歧的狀況,咱們能夠在ClientInfo中添加註解 @Primary 表示優先注入ClientInfo類型,這個時候能夠把@Qualifier 註解刪掉,Spring會選擇ClientInfo進行注入。可是若是多個子類同時擁有@Primary 仍是會出現分歧報錯。另外若是同時出現@Primary和@Qualifier,那麼之後者爲準。
@Autowired能夠用在方法上,也可以實現自動調用
@Override @Autowired @Qualifier("adminInfo") public void setUserInfo(UserInfo userInfo) { log.info("setUserInfo is called"); this.userInfo = userInfo; }
最後,不少狀況下構造函數式帶有參數的,這個時候簡單的添加@Component是沒法進行注入的,這個時候@Autowired還能夠用在參數上,好比這樣
public GameUser(@Autowired @Qualifier("adminInfo") UserInfo userInfo){ this.userInfo = userInfo; }