1. 程式人生 > >Spring重要註解用法分析

Spring重要註解用法分析

Table of Contents

Spring註解

@Bean

宣告一個bean

Bean依賴 - Bean Dependencies

獲取bean的生命週期回撥:

指定Bean的scope

@Configuration

注入bean間依賴關係

有關基於Java的配置如何在內部工作的更多資訊

@Autowired

@Primary

@Qualifer

@Value

@Profile以及@ActiveProfile

使用@Profile

Default Profile

基於Java的配置

@Import

AOP註解

@Before

@AfterReturning

@AfterThrowing

 @After

@Around


Spring註解

@Bean

@Bean註解是方法級別的註解(method-level)。並且是XML的<bean/>元素的直接模擬。@Bean註解支援<bean/>提供的其中一些屬性,如:init-method, detroy-method,autowiring等。

我們可以在@Configuration-annotated或@Component-annotate的類中使用@Bean註解。

宣告一個bean

要宣告一個bean,可以使用@Bean註解對方法進行批註。 可以使用此方法在指定為方法返回值的型別的ApplicationContext中註冊bean定義。 預設情況下,bean名稱與方法名稱相同

。例如:

@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

上述配置與以下Spring XML完全等效:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

這兩個宣告都在ApplicationContext中建立了一個名為transferService的bean,繫結到TransferServiceImpl型別的物件例項,如下面的文字所示:

transferService -> com.acme.TransferServiceImpl

還可以使用介面(或基類)作為返回型別宣告@Bean方法,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

Bean依賴 - Bean Dependencies

@ Bean-annotated的方法可以有任意數量的引數來描述構建該bean所需的依賴關係。 例如,如果我們的TransferService需要AccountRepository,我們可以使用方法引數來實現該依賴項,如下:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

其解析機制與基於建構函式的依賴注入非常相似。

獲取bean的生命週期回撥:

使用@Bean批註定義的任何類都支援常規生命週期回撥,並且可以使用JSR-250規範中的@PostConstruct和@PreDestroy註釋。常規的Spring生命週期回撥也是完全支援。 如果bean實現InitializingBean,DisposableBean或Lifecycle,則它們各自的方法由容器呼叫。

@Bean註釋支援指定任意初始化和銷燬回撥方法,就像Spring XML上的init-method和destroy-method屬性一樣,如

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

指定Bean的scope

Spring包含@Scope註釋,以便您可以指定bean的scope。你可以指定使用@Bean註解定義的bean應具有的特定scope。

預設的bean的scope是單例,但你可以使用@Scope註釋覆蓋它,如以下示例所示

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

@Configuration

@ConfiConfiguration這個註解由四個註解組成,分別是:@Target(ElementType.TYPE) , @Retention(RetentionPolicy.RUNTIME), @Documented, @Component。

@Configuration是一個類級註釋,指示物件是bean定義的源。 @Configuration類通過用@Bean註解來註釋public方法宣告bean。 在@Configuration類上呼叫@Bean方法也可用於定義bean間依賴項。

其定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated
	 * with this Configuration class. If left unspecified (the common case),
	 * a bean name will be automatically generated.
	 * <p>The custom name applies only if the Configuration class is picked up via
	 * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
	 * If the Configuration class is registered as a traditional XML bean definition,
	 * the name/id of the bean element will take precedence.
	 * @return the suggested component name, if any (or empty String otherwise)
	 * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

其作用是,表示一個類聲明瞭一個或多個@Bean方法,並且可以由Spring容器處理,以便在執行時為這些bean生成bean定義和服務請求。例如

 @Configuration
 public class AppConfig {

     @Bean
     public MyBean myBean() {
         // instantiate, configure and return bean ...
     }
 }

注入bean間依賴關係

當bean彼此依賴時,表達該依賴關係就像讓一個bean方法呼叫另一個bean一樣簡單,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

在前面的示例中,beanOne通過建構函式注入接收對beanTwo的引用。

有關基於Java的配置如何在內部工作的更多資訊

請考慮以下示例,該示例顯示了被呼叫兩次的@Bean註釋方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()在clientService1()中呼叫一次,在clientService2()中呼叫一次。 由於此方法建立了ClientDaoImpl的新例項並將其返回,因此可能我們會認為應該會有兩個例項(每個服務一個)。 這肯定會有問題:在Spring中,例項化的bean預設具有單例的scope。 在spring當中,所有@Configuration類在啟動時都使用CGLIB進行子類化, 在子類中,子方法在呼叫父方法並建立新例項之前,首先檢查容器是否有任何快取(作用域)bean。這就是魔術發生的地方。

當然,根據bean的scope不同,行為可能會有所不同。 我們在這裡討論的是單例模式

@Autowired

您可以將@Autowired註解應用於建構函式,如以下示例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

您還可以將@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;
    }

    // ...
}

您還可以通過將註釋新增到需要該型別陣列的欄位或方法,從ApplicationContext提供特定型別的所有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;
    }

    // ...
}

只要預期的鍵型別是String,即使是型別化的Map例項也可以自動裝配。 Map值包含所有期望型別的bean,並且鍵包含相應的bean名稱,如以下示例所示

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

    // ...
}

預設情況下,只要當只有0個候選的bean可用時,自動裝配就會失敗。 spring的預設行為是將帶註釋的方法,建構函式和欄位視為指示所需的依賴項。 您可以在以下示例中更改此行為:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}

@Autowired,@ Inject,@ Resource和@Value註釋由Spring BeanPostProcessor實現處理。 這意味著您無法在自己的BeanPostProcessor或BeanFactoryPostProcessor型別(如果有)中應用這些註釋。 必須使用XML或Spring @Bean方法顯式地“連線”這些型別。

使用@Autowired的一個缺點是,不夠直觀,從程式碼當中我們並不能看出我們注入的Bean的宣告在何處。

@Primary

由於按型別自動裝配可能會導致多個候選人candiate,因此通常需要對選擇過程有更多控制權。@Primary是其中一個方法。@Primary表示當多個bean可以自動裝配到單值依賴項時,應該優先選擇特定的bean。如果候選者中只存在一個主bean,則它將成為自動裝配的值。

請考慮以下配置程式碼,將firstMovieCatalog定義為主要primary的MovieCatalog:

@Configuration
public class MovieConfiguration {

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

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

    // ...
}

使用上述配置,以下MovieRecommender將與firstMovieCatalog一起自動裝配:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}

@Qualifer

在使用具有多個例項的型別進行自動裝配,確定一個主要候選者bean時,使用@Primary是一種有效的方法。當我們需要更多的控制選擇候選者bean的過程時,可以使用Spring的@Qualifier註釋。

我們可以將限定符值與特定引數相關聯,縮小型別匹配集,以便為每個引數選擇特定的bean。 在最簡單的情況下,這可以是一個簡單的描述性值,如以下示例所示:

public class MovieRecommender {

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

    // ...
}

以下示例顯示了相應的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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

對於fallback的預設匹配,bean名稱被視為預設限定符值。

@Value

要指定預設值,可以在欄位,方法和方法或建構函式引數上放置@Value註釋。以下示例設定欄位變數的預設值:

public static class FieldValueTestBean

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

以下示例顯示了相同效果但在屬性的setter方法上的用法:

public static class PropertyValueTestBean

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

@Autowired方法和建構函式也可以使用@Value註釋,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

 

@Profile以及@ActiveProfile

@profile註解是spring提供的一個用來標明當前執行環境的註解。基於某些任意系統狀態,有條件地啟用或禁用完整的@Configuration類甚至單獨的@Bean方法通常很有用。 一個常見的例子是隻有在Spring環境中啟用了特定的配置檔案時才使用@Profile批註來啟用bean。

@Profile註釋實際上是通過使用更靈活的註釋@Conditional實現的。 @Conditional批註指示在註冊@Bean之前應該參考的特定org.springframework.context.annotation.Condition實現。

Condition介面的實現提供了一個返回true或false的matches(...)方法。 例如,以下清單顯示了用於@Profile的實際Condition實現:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

Bean定義配置檔案在核心容器中提供了一種機制,允許在不同環境中註冊不同的bean。 “環境”這個詞對不同的使用者來說意味著不同的東西,這個功能可以幫助解決許多的問題,包括:

  • 在QA或生產環境中,針對開發中的記憶體資料來源而不是從JNDI查詢相同的資料來源。
  • 僅在將應用程式部署到效能環境時註冊監視基礎結構。
  • 為客戶A和客戶B部署註冊bean的自定義實施。

考慮需要DataSource的實際應用程式中的第一個用例。 在測試環境中,Datasource配置可能類似於以下內容:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

現在考慮如何將此應用程式部署到QA或生產環境中,假設應用程式的資料來源已註冊到生產應用程式伺服器的JNDI目錄。 我們的dataSource bean現在看起來如下:

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

現在我們面臨的問題是如何根據當前環境在使用這兩種資料來源之間切換。 隨著時間的推移,Spring使用者已經設計了許多方法來完成這項工作,通常依賴於系統環境變數和包含$ {placeholder}標記的XML <import />語句的組合,這些標記根據值解析為正確的配置檔案路徑 一個環境變數。 Bean定義配置檔案是核心容器功能,可為此問題提供解決方案。

使用@Profile

@Profile註釋允許我們指明當一個或多個指定的配置檔案處於活動狀態時,元件符合的註冊條件。 使用前面的示例,我們可以重寫dataSource配置,如下所示:

@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

配置檔案字串可以包含簡單的配置檔名稱(例如,生產)或配置檔案表示式。 概要表示式允許表達更復雜的概要邏輯(例如,production & us-east)。 配置檔案表示式支援以下運算子:

  • !: A logical “not” of the profile

  • &: A logical “and” of the profiles

  • `|`_ A logical “or” of the profiles

Default Profile

預設配置檔案表示預設啟用的配置檔案。 請考慮以下示例:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

如果沒有啟用配置檔案,則建立dataSource。 您可以將此視為一種為一個或多個bean提供預設定義的方法。 如果啟用了任何配置檔案,則預設配置檔案不適用。

您可以使用環境上的setDefaultProfiles()或宣告性地使用spring.profiles.default屬性更改預設配置檔案的名稱。

 

基於Java的配置

Spring允許我們使用基於Java的註釋功能,這可以降低配置的複雜性。

@Import

就像在Spring XML檔案中使用<import />元素來幫助模組化配置一樣,@ Immort註釋允許從另一個配置類載入@Bean定義,如下例所示:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

現在,在例項化上下文時,不需要同時指定ConfigA.class和ConfigB.class,只需要顯式提供ConfigB即可,如下例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

這種方法簡化了容器例項化,因為只需要處理一個類,而不需要在構造期間記住可能出現的大量的@Configuration類

AOP註解

@Before

你可以使用@Before註釋在aspect的宣告前置advice:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

如果我們使用就地切入點表示式,我們可以重寫前面的示例,如下例所示:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}

@AfterReturning

返回建議後,匹配的方法執行正常返回。 您可以使用@AfterReturning註釋宣告它:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

@AfterThrowing

丟擲建議執行時,匹配的方法執行通過丟擲異常退出。 您可以使用@AfterThrowing註釋宣告它,如以下示例所示

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

 @After

在匹配的方法執行退出之後(finally)建議執行之後。 它是使用@After註釋宣告的。 在建議之後必須準備好處理正常和異常返回條件。 它通常用於釋放資源和類似目的。 以下示例顯示了在finally建議之後如何使用:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

@Around

增強處理是功能比較強大的增強處理,它近似等於Before 和 AfterReturning的總和。@Around既可在執行目標方法之前織入增強動作,也可在執行目標方法之後織入增強動作。@Around甚至可以決定目標方法在什麼時候執行,如何執行,更甚者可以完全阻止目標方法的執行。

使用@Around註釋宣告around建議。 advice方法的第一個引數必須是ProceedingJoinPoint型別。 在通知的主體內,在ProceedingJoinPoint上呼叫proceed()會導致執行基礎方法。 proceed方法也可以傳入Object []。 陣列中的值在進行時用作方法執行的引數。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}