如何假裝成一個服務端開發(三)

目錄

如何假裝成一個服務端開發(一)java

如何假裝成一個服務端開發(二)android

如何假裝成一個服務端開發(三) spring

IoC(控制反轉) 以及 DI(依賴注入)

    首先,咱們拋開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中,IoC的思想也體如今對象建立和管理的流程上。全部的類都會在spring容器中登記,告訴spring你是個什麼東西,你須要什麼東西,而後spring會在系統運行到適當的時候,把你要的東西主動給你,同時也把你交給其餘須要你的東西。全部的類的建立、銷燬都由 spring來控制,也就是說控制對象生存週期的再也不是引用它的對象,而是spring。

    在 Spring 中把每個須要管理的對象稱爲 Spring Bean(簡稱 Bean),而 Spring 管理這些 Bean 的容器,被咱們稱爲 Spring IoC 容器(或者簡稱 IoC 容器)。IoC 容器須要具有兩個基本的功能:

  • 經過描述管理 Bean,包括髮布和獲取 Bean;
  • 經過描述完成 Bean 之間的依賴關係。
     

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;
    }
相關文章
相關標籤/搜索