主頁 > 後端開發 > 一起來讀官方檔案-----SpringIOC(08)

一起來讀官方檔案-----SpringIOC(08)

2020-10-14 17:11:04 後端開發

1.9,基于注解的容器配置
                注解在配置Spring方面比XML更好嗎?

基于注解的配置的引入提出了一個問題,即這種方法是否比XML“更好”,
簡短的答案是“取決于情況”,

長話短說,每種方法都有其優缺點,通常,由開發人員決定哪種策略更適合他們,
由于定義方式的不同,注解在宣告中提供了很多背景關系,從而使配置更短,更簡潔,
但是,XML擅長連接組件而不接觸其源代碼或重新編譯它們,

一些開發人員更喜歡將布線放置在靠近源的位置,而另一些開發人員則認為帶注解的類不再是POJO,
而且,該配置變得分散且難以控制,

無論選擇如何,Spring都可以容納兩種樣式,甚至可以將它們混合在一起,
值得指出的是,通過其JavaConfig選項,Spring允許以非侵入方式使用注解,
而無需接觸目標組件的源代碼,

注解是XML配置的替代方法,該配置依賴位元組碼元資料來連接組件,而不是尖括號宣告,
通過使用相關的 類,方法或欄位 宣告上的注解,開發人員無需使用XML來描述bean的連接,而是將配置移入組件類本身,

如示例中所述:將RequiredAnnotationBeanPostProcessor,通過BeanPostProcessor的方式與注解結合使用是擴展Spring IoC容器的常用方法,

例如,Spring 2.0引入了使用@Required注解強制執行必需屬性的可能性,  

Spring 2.5引入@Autowired注解,提供的功能與自動裝配協作器中所述的功能相同,但具有更細粒度的控制和更廣泛的適用性,

Spring 2.5還添加了對JSR-250批注(例如 @PostConstruct和@PreDestroy)的支持, 
 
Spring 3.0增加了對javax.inject包中包含的JSR-330(Java依賴性注入)注解的支持,例如@Inject 和@Named,

注解注入在XML注入之前執行,因此,XML配置將覆寫通過注解注入的屬性

與往常一樣,您可以根據類名將它們注冊為單獨的bean定義,但也可以通過在基于XML的Spring配置中包含以下標記來隱式注冊它們:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
<context:annotation-config/> 隱式注冊后置處理器包括 :
    AutowiredAnnotationBeanPostProcessor
    CommonAnnotationBeanPostProcessor
    PersistenceAnnotationBeanPostProcessor
    RequiredAnnotationBeanPostProcessor
并且當使用<context:component-scan/>后,即可將<context:annotation-config/>省去

context:annotation-config/只在定義它的相同應用程式背景關系中查找關于bean的注解,

這意味著,如果你把context:annotation-config/定義在WebApplicationContext的DispatcherServlet中,它只是檢查controllers中的@Autowired注解,而不是你的services,

上邊的這段話意思不是很明確,需要解釋一下以前用web.xml配置時的Spring啟動流程
拿出幾段配置

<!--配置開始 -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
    
   <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:spring/spring-base.xml
        </param-value>
    </context-param>
<!--配置結束 -->

上邊的配置應該是多年前webi應用的基礎配置,理一下tomcat啟動后如何呼叫Spring的大概流程

1. tomcat讀取web.xml檔案(此處不管tomcat如何找到xml),決議內容并分組,
分成ServletContainerInitializer,servlet,listener,context-param等多個陣列

2.逐個進行決議,先決議ServletContainerInitializer
    //這個就相當典型了  這個東西就是之前的文章講過的ServletContainerInitializer
    //Tomcat啟動會查找ServletContainerInitializer實作類并執行其中的onStartup方法,
    //Spring-web模塊存在ServletContainerInitializer實作類,所以Tomcat啟動會呼叫Spring-web的代碼,
    //但是我們用Spring框架的話不需要實作這個介面,實作一個Spring的介面WebApplicationInitializer,
    //就可以由Tomcat呼叫Spring-web的ServletContainerInitializer實作類
    Iterator i$ = this.initializers.entrySet().iterator();
    while(i$.hasNext()) {
        Entry entry = (Entry)i$.next();
        try {
            ((ServletContainerInitializer)entry.getKey()).onStartup((Set)entry.getValue(), this.getServletContext());
        } catch (ServletException var22) {
            log.error(sm.getString("standardContext.sciFail"), var22);
            ok = false;
            break;
        }
    }
但是這里我們并沒有用這種方式而是用了listener的方式繼續往下看

3. 決議listener,這里this.listenerStart()會決議我們配置的ContextLoaderListener
    if (ok && !this.listenerStart()) {
        log.error(sm.getString("standardContext.listenerFail"));
        ok = false;
    }
    就在這里tomcat關聯上了Spring的ApplicationContext,會實體化XmlWebApplicationContext,
    實體化時取出context-param中的name為contextConfigLocation的組態檔,來進行決議注冊
    
4.決議servlet,this.loadOnStartup(this.findChildren())來決議servlet,
    if (ok && !this.loadOnStartup(this.findChildren())) {
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }
這里就會進入DispatcherServlet的init方法,
init方法中會根據當前的ServletContext查找在此之前有沒有初始化過Spring的ApplicationContext,
然后再判斷當前DispatcherServlet有沒有ApplicationContext,
如果沒有就初始化一個并把之前初始化ApplicationContext的設定為父節點

總結一下,也就是說用了上邊的配置的話,tomcat在啟動程序中,會初始化兩遍并生成兩個ApplicationContext物件,
第一遍決議context-param中param-name 為contextConfigLocation的組態檔,
    并以此組態檔生成一個ApplicationContext ROOT
第二遍是決議DispatcherServlet servlet的spring-mvc.xml組態檔,
    再以此組態檔生成一個ApplicationContext,并將ROOT設定為父節點
因此就產生了一個問題,當你在兩個ApplicationContext都可以掃描到同一個Bean的時候,
那么這個bean在連個ApplicationContext中都各存在一個實體,并且實體不一樣



舉一個之前遇到的問題:
    之前想給某個controller加一個AOP,攔截某些方法進行特殊處理,但是我把	<aop:aspectj-autoproxy/>這個注解
    放到了下面這個層次的組態檔中了
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath*:spring/spring-base.xml
            </param-value>
        </context-param>
    最后我的AOP并沒有生效,后來又把注解挪到了spring-mvc.xml中,才生效,
    
    之前百度搜到說:spring-mvc 的配置掃描優先于spring的組態檔
    通過除錯才理解這句話:
        我的controller在spring的ApplicationContext中有一份被AOP代理的物件
        在spring-mvc的ApplicationContext中還有一份沒被代理的普通物件
        因為spring-mvc配置加載的晚,所以用到的都是沒有被代理的物件
1.9.1,@Required

該@Required注解適用于bean屬性setter方法,如下面的例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

這個注解要求,必須在配置時通過bean定義中的顯式屬性值或自動裝配來填充bean屬性,
如果未填充bean屬性,容器將拋出例外,

這樣顯式的失敗,避免了實體在應用的時候出現NullPointerException的情況,

@Required注解在Spring Framework 5.1時正式被棄用,
Spring更贊同使用建構式注入來進行必需引數的設定
(或者使用InitializingBean.afterPropertiesSet()的自定義實作來進行bean屬性的設定),
1.9.2,@Autowired
在本節包含的示例中,JSR330的@Inject注釋可以用來替代Spring的@Autowired注釋,

您可以將@Autowired注解應用于建構式,如以下示例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
從Spring Framework 4.3開始,@Autowired如果目標bean僅定義一個建構式作為開始,則不再需要在此類建構式上進行注解,

但是,如果有多個建構式可用,并且沒有主/默認建構式,則必須至少注解一個建構式,@Autowired以指示容器使用哪個建構式,

您還可以將@Autowired注解應用于傳統的setter方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

您還可以將注解應用于具有任意名稱和多個引數的方法,如以下示例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

}

您也可以將其應用于@Autowired欄位,甚至可以將其與建構式混合使用,如以下示例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
確保目標組件(例如MovieCatalog或CustomerPreferenceDao)與帶@Autowired注解的注入點的型別一致地宣告,
否則,注入可能會由于運行時出現“no type match found”錯誤而失敗,

對于通過類路徑掃描找到的xml定義的bean或組件類,容器通常預先知道具體的型別,

但是,對于@Bean工廠方法,您需要確保宣告的回傳型別具有足夠的表達能力,
對于實作多個介面的組件,或者對于可能由其實作型別參考的組件,
考慮在您的工廠方法上宣告最特定的回傳型別(至少與參考您的bean的注入點所要求的那樣特定),

您還可以將@Autowired注解添加到需要該型別陣列的欄位或方法中,指示Spring提供特定型別的所有bean ,如以下示例所示:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

如以下示例所示,這同樣適用于型別化集合:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
如果希望陣列或串列中的專案以特定順序排序,
則目標bean可以實作該org.springframework.core.Ordered介面或使用@Order或標準@Priority注解,
否則,它們的順序將遵循容器中相應目標bean定義的注冊順序,

您可以使用@Order在目標類級別和@Bean方法上宣告注解,

@Order值可能會影響注入點的優先級,但請注意它們不會影響單例啟動順序,
這是由依賴關系和@DependsOn宣告確定的正交關系,(舉例:a,b,c三個bean設定的order分別為1,2,3,
但是a依賴c,所以a在初始化的時候會初始化c,導致c比b提前初始化)


請注意,標準javax.annotation.Priority注解在該@Bean級別不可用 ,因為無法在方法上宣告它,

可以通過將@Order值與@Primary每個型別的單個bean結合使用來對其語意進行建模,

即使是型別化的Map實體也可以自動注入,鍵包含相應的bean名稱是String型別,值是對應的bean實體,如下面的示例所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

}

默認情況下,當給定注入點沒有匹配的候選bean可用時,自動裝配將失敗,對于宣告的陣列,集合或映射,至少應有一個匹配元素,

默認是將帶注解的方法和欄位視為必須要注入的依賴項,
你可以通過標記為非必需注入來改變這個行為(例如,通過在@Autowired中設定required屬性為false):

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

@Autowired(required = false)用在方法上時
當存在任何一個引數不可注入,則根本不會呼叫該方法,
在這種情況下,完全不需要填充非必需欄位,而保留其默認值,

當方法有多個引數時,可以使用該注解標識其中的某個引數可以不被注入

public class ServiceController {
	private ServiceTwo serviceTwo;
	private CusService serviceOne;
	
	public ServiceController(CusService cusService,
	        @Autowired(required = false) ServiceTwo serviceTwo){

		this.serviceOne = cusService;
		this.serviceTwo = serviceTwo;
	}
}
在任何給定bean類中,只有一個建構式可以宣告@Autowired,并將required屬性設定為true,以指示當作為Spring bean使用時要自動裝配的建構式,
因此,如果required屬性的默認值為true,那么只有一個建構式可以使用@Autowired注解,

如果有多個建構式宣告注解,那么它們都必須宣告required=false,才能被認為是自動裝配的候選者(類似于XML中的autowire=constructor),
通過在Spring容器中匹配bean可以滿足的依賴關系最多的建構式將被選擇,
如果沒有一個候選函式可以滿足,那么將使用主/默認建構式(如果存在),

類似地,如果一個類宣告了多個建構式,但是沒有一個是用@Autowired注解的,那么一個主/默認建構式(如果有的話)將會被使用,
如果一個類只宣告了一個建構式,那么它將始終被使用,即使沒有@Autowired注解,
請注意,帶注解的建構式不必是public的,

建議在setter方法上的已棄用的@Required注釋上使用@Autowired屬性,

將required屬性設定為false表示該屬性對于自動裝配目的是不需要的,并且如果該屬性不能自動裝配,則忽略它,
另一方面,@Required更強制,因為它強制用容器支持的任何方法設定屬性,如果沒有定義值,則會引發相應的例外,

另外,您可以通過Java8來表達特定依賴項的非必需性質java.util.Optional,如以下示例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

從Spring Framework 5.0開始,您還可以使用@Nullable注解(任何包中的Nullable注解,例如,javax.annotation.Nullable來自JSR-305的注解),
使用此注解標識該引數不一定會被注入,有可能會是空值

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

您還可以對這些介面(BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource)使用@Autowired,

這些介面及其擴展介面(例如ConfigurableApplicationContext或ResourcePatternResolver)將自動決議,而無需進行特殊設定,
以下示例自動裝配ApplicationContext物件:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
在@Autowired,@Inject,@Value,和@Resource注解由Spring注冊的BeanPostProcessor實作,  

這意味著您不能在自己的型別BeanPostProcessor或BeanFactoryPostProcessor型別(如果有)中應用這些注解,
必須通過使用XML或Spring@Bean方法顯式地“連接”這些型別,

不僅相當上一章的內容:
    您應該看到一條參考性日志訊息:
    Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying),
    這條訊息的意思大概就是說這個bean沒有得到所有BeanPostProcessor的處理

如果您自定義的BeanPostProcessor或BeanFactoryPostProcessor在自動注入的BeanPostProcessor之前實體化那么就無法為您注入您想要的引數,
1.9.3,@Primary

由于按型別自動布線可能會導致多個候選物件,因此通常有必要對選擇程序進行更多控制,
一種實作此目的的方法是使用Spring的 @Primary注解,
@Primary指示當多個bean是要自動裝配到單值依賴項的候選物件時,應給予特定bean優先權,
如果候選中恰好存在一個主bean,則它將成為自動裝配的值,

考慮以下定義firstMovieCatalog為主要配置的配置MovieCatalog:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

使用前面的配置,以下內容MovieRecommender將自動注入到 firstMovieCatalog:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
1.9.4,@Qualifier

@Primary當可以確定一個主要候選物件時,它是在幾種情況下按型別使用自動裝配的有效方法,

當需要對選擇程序進行更多控制時,可以使用Spring的@Qualifier注解,

您可以將限定符值與特定的引數相關聯,從而縮小型別匹配的范圍,以便為每個引數選擇特定的bean,

在最簡單的情況下,這可以是簡單的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

您還可以@Qualifier在各個建構式引數或方法引數上指定注解,如以下示例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

下面的示例顯示了相應的bean定義,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean >
        <qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定qualifier屬性 -->
    </bean>

</beans>

bean名稱被認為是默認的qualifier值,
也可以不使用qualifier而是將該bean的id定義為main,達到相同的匹配效果,

然而,盡管您可以使用這種約定來按名稱參考特定的bean,但@Autowired基本上是關于型別驅動的注入,qualifier只是在型別之上的可選選項,這意味著,即使使用了bean名稱來進行qualifier的限定,qualifier 值也總是在型別匹配集中選擇相同名稱的bean,

qualifier 也適用于collections,
如前所述—例如 Set,在這種情況下,根據宣告的qualifier值,所有匹配的bean都作為一個集合注入,
這意味著qualifier不必是惟一的,相反,它們構成了過濾標準,例如,您可以定義具有相同qualifier值“action”的多個MovieCatalog bean,
所有這些bean都被注入到帶有@Qualifier(“action”)注釋的集合中,

public class ServiceController {

	@Autowired
	@Qualifier("main")
	private List<MovieCatalog> serviceList;
}

<bean >
        <qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定相同的qualifier屬性 -->
</bean>

<bean >
        <qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定相同的qualifier屬性 -->
</bean>

<bean >
        <qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/action"/> <!-- 指定相同的qualifier屬性 -->
</bean>
如果沒有其他注解(例如qualifier或primary ),
對于非唯一依賴情況,Spring將注入點名稱(即欄位名稱或引數名稱)與目標bean名稱或者bean id匹配,
并選擇同名的候選物件(如果有同名的的話,沒有同名的話則依然拋出例外),

如果您打算通過名稱進行依賴注入,請不要主要使用@Autowired,即使它能夠通過bean名稱在型別匹配的候選者中進行選擇,

使用JSR-250 @Resource注解:
1. 如果同時指定了name和type,按照bean  Name 和 bean 型別同時匹配
2. 如果指定了name,就按照bean Name 匹配 
3. 如果指定了type,就按照型別匹配
4. 如果既沒有指定name,又沒有指定type,就先按照beanName匹配;
    如果沒有匹配,再按照型別進行匹配;
測驗 @Resource的時候還發現一個有意思的東西,
當被注入的是個List的時候,不管是什么型別的List,
只要@Resource加了name條件,都能被注入進去,
比如 List<String> 會被注入到List<MovieCatalog> 大家可以試一下

@Autowired注解:
    在按型別選擇候選bean之后,再在候選者bean中選擇相同名稱的,

@Autowired適用于 欄位,構造方法,和多引數方法,允許通過qualifier注解在引數級別上縮小范圍,
相比之下,@Resource只支持具有單個引數的欄位和bean屬性設定器方法,
因此,如果注入目標是建構式或多引數方法,則應該堅持使用qualifier,

您可以創建自己的自定義限定符注解,為此,請定義一個注解并在您的定義中提供該注解,如以下示例所示:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

然后,您可以在自動連接的欄位和引數上提供自定義限定符,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

接下來,您可以為候選bean定義提供資訊,您可以添加標記作為標記的子元素,然后指定型別和值來匹配您的自定義qualifier注解,該型別與注釋的全限定類名匹配,
另外,為了方便起見,如果不存在名稱沖突的風險,您可以使用簡短的類名,
下面的例子演示了這兩種方法:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean >
        <qualifier type="Genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean >
        <qualifier type="example.Genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" />

</beans>

在某些情況下,使用沒有值的注解就足夠了,當注解用于更通用的目的,并且可以跨幾種不同型別的依賴項應用時,這一點非常有用,例如,您可以提供一個脫機目錄,當沒有可用的Internet連接時可以搜索該目錄,首先,定義簡單注釋,如下例所示:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

然后將注解添加到要自動裝配的欄位或屬性,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

現在,bean定義僅需要一個限定符type,如以下示例所示:

<bean >
    <qualifier type="Offline"/> 
    <!-- inject any dependencies required by this bean -->
</bean>

您還可以定義自定義qualifier注解,自定義的注解可以定義除了簡單value屬性之外的屬性,
如果隨后在要自動裝配的欄位或引數上指定了多個屬性值,則bean定義必須與所有此類屬性值匹配才能被視為自動裝配候選,
例如,請考慮以下注解定義:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}

在這種情況下Format是一個列舉,定義如下:

public enum Format {
    VHS, DVD, BLURAY
}

要自動裝配的欄位將用自定義qualifier進行注解,并包括這兩個屬性的值:genre和format,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

最后,bean定義應該包含匹配的限定符值,這個例子還演示了您可以使用bean元屬性來代替元素,
如果可用,元素及其屬性優先,但是如果沒有這樣的限定符,自動裝配機制就會回到標簽中提供的值上,就像下面例子中的最后兩個bean定義一樣:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean >
        <qualifier type="MovieQualifier">
            <attribute key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/VHS"/>
            <attribute key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean >
        <qualifier type="MovieQualifier">
            <attribute key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/VHS"/>
            <attribute key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean >
        <meta key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/DVD"/>
        <meta key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean >
        <meta key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/BLURAY"/>
        <meta key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>
1.9.5,將泛型用作自動裝配限定符

除了@Qualifier注解之外,您還可以將Java泛型型別用作資格的隱式形式,例如,假設您具有以下配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}

假設前面的bean實作了一個通用介面(即Store和 Store

    class StringStore implements Store<String>{

	}
    
    class IntegerStore implements Store<Integer>{

	}

則可以@Autowire將該Store介面和通用用作限定符,如以下示例所示:

@Autowired
private Store<String> s1; // <String> qualifier, 注入 stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, 注入 the integerStore bean

在自動裝配串列,Map實體和陣列時,通用限定符也適用,下面的示例自動連接泛型List:


// 只注入 Store<Integer> 型別
// Store<String> 不會被注入
@Autowired
private List<Store<Integer>> s;

1.9.6,使用CustomAutowireConfigurer

CustomAutowireConfigurer 是一個BeanFactoryPostProcessor
您可以注冊自己的自定義限定符注解型別,即使它們未使用Spring的@Qualifier注解進行注解,

像之前我們定義的注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String value();
}

這種寫法主要就是托@Qualifier的福氣
但我們也可以不依賴它
以下示例顯示如何使用CustomAutowireConfigurer:

<bean id="customAutowireConfigurer"
        >
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

example.CustomQualifier Spring會根據這個類路徑加載這個類,
并將這個類作為和@Qualifier同作用來對待

自動注入是如何處理候選物件的?

  • bean definition 的 autowire-candidate 值,值為false表示該bean不參于候選
  • 元素default-autowire-candidates上可用的任何模式,值為false表示該組的bean不參與候選
  • @Qualifier注解 和 任何在customautowiresfigurer注冊的自定義注解的存在會被優先使用

當多個bean符合自動裝配候選條件時,
確定“primary”的步驟如下:如果候選中恰好有一個bean定義將primary屬性設定為true,則將其選中,

1.9.7,@Resource

Spring還通過在欄位或bean屬性設定器方法上使用JSR-250@Resource批注(javax.annotation.Resource)支持注入,

1. 如果同時指定了name和type,按照bean  Name 和 bean 型別同時匹配
2. 如果指定了name,就按照bean Name 匹配 
3. 如果指定了type,就按照型別匹配
4. 如果既沒有指定name,又沒有指定type,就先按照beanName匹配;
    如果沒有匹配,再按照型別進行匹配;

@Resource具有name屬性,默認情況下,Spring將該值解釋為要注入的Bean名稱,
換句話說,它遵循名稱語意,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果未明確指定名稱,則默認名稱是從欄位名稱或setter方法派生的,
如果是欄位,則采用欄位名稱,
在使用setter方法的情況下,它采用bean屬性名稱,
以下示例將名為 movieFinder的bean注入到setter方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

因此,在下例中,customerPreferenceDao欄位首先查找名為“customerPreferenceDao”的bean,找不到的話然后回傳到與型別customerPreferenceDao匹配的bean:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

}
1.9.8,使用@Value

@Value 通常用于注入外部屬性:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

使用以下配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

和以下application.properties檔案:

catalog.name=MovieCatalog

在這種情況下,catalog引數和欄位將等于MovieCatalog值,

Spring提供了一個默認的值決議器,
它將嘗試決議屬性值,如果無法決議,
${catalog.name} 則將被當做值注入到屬性中,
例如:catalog="${catalog.name}"

如果要嚴格控制不存在的值,則應宣告一個PropertySourcesPlaceholderConfigurerbean,如以下示例所示:

@Configuration
public class AppConfig {

     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}
當配置PropertySourcesPlaceholderConfigurer使用JavaConfig,該@Bean方法必須是static,

如果${} 無法決議任何占位符,則使用上述配置可確保Spring初始化失敗,

默認情況下,Spring Boot配置一個PropertySourcesPlaceholderConfigurer
從application.properties和application.yml獲取bean的屬性,

Spring提供的內置轉換器支持允許自動處理簡單的型別轉換(例如轉換為Integer 或轉換為簡單的型別int),
多個逗號分隔的值可以自動轉換為String陣列,而無需付出額外的努力,

可以像下邊一樣提供默認值:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}

Spring BeanPostProcessor在后臺使用ConversionService來處理將@Value中的字串值轉換為目標型別的程序,如果你想為自己的自定義型別提供轉換支持,你可以提供自己的ConversionService bean實體,如下面的例子所示:

@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}

當@Value包含SpEL運算式時,該值將在運行時動態計算,如以下示例所示:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}

SpEL還支持使用更復雜的資料結構:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
1.9.9,使用@PostConstruct和@PreDestroy

CommonAnnotationBeanPostProcessor不僅處理@Resource注解
也處理javax.annotation.PostConstruct和 javax.annotation.PreDestroy,

在Spring 2.5中引入了對這些注解的支持,為初始化回呼和銷毀回呼中描述的生命周期回呼機制提供了一種替代方法,

如果在Spring ApplicationContext中注冊了CommonAnnotationBeanPostProcessor,帶有這兩個注解的方法將會被回呼執行,

在下面的例子中,快取在初始化時被預填充,在銷毀時被清除:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
像@Resource一樣,@PostConstruct和@PreDestroy注解是JDK6到8的標準Java庫的一部分, 
但是,整個javax.annotation 程式包都與JDK 9中的核心Java模塊分開,并最終在JDK 11中洗掉了,  
如果需要,需要對javax.annotation-api工件進行處理,
現在可以通過Maven Central獲取,只需像其他任何庫一樣將其添加到應用程式的類路徑中即可,

本文由博客一文多發平臺 OpenWrite 發布!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/172118.html

標籤:Java

上一篇:打工四年總結的資料庫知識點

下一篇:[PHP] 工廠方法設計模式-創建型設計模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more