Spring注解?啥玩意?


隨着Spring的流行,我們經歷過基於XML-Based 的配置,隨着SpringBoot的流行,我們逐漸使用基於注解的配置替換掉了基於XML-Based的配置,那么你知道基於注解的配置的基礎組件都是什么嗎?都包括哪些要素?那么本節就來探討一下。注:本篇文章更多的是討論Spring基於注解的配置一覽,具體的技術可能沒有那么深,請各位大佬見諒。

探討主題:

  • 基礎概念:@Bean 和 @Configuration
  • 使用AnnotationConfigApplicationContext 實例化Spring容器
  • 使用@Bean 注解
  • 使用@Configuration 注解
  • 編寫基於Java的配置
  • Bean定義配置文件
  • PropertySource 抽象類
  • 使用@PropertySource
  • 占位符的聲明

基礎概念:@Bean 和 @Configuration

Spring中新的概念是支持@Bean注解 和 @Configuration 注解的類。@Bean 注解用來表明一個方法實例化,配置並且通過IOC容器初始化並管理一個新的對象。@Bean注解就等同於XML-Based中的<beans/>標簽,並且扮演了相同的作用。你可以使用基於注解的配置@Bean 和 @Component,然而他們都用在@Configuration配置類中。

使用@Configuration 注解的主要作用是作為bean定義的類,進一步來說,@Configuration注解的類允許通過調用同類中的其他@Bean標注的方法來定義bean之間依賴關系。 如下所示:

新建一個maven項目(我一般都直接創建SpringBoot項目,比較省事),創建AppConfig,MyService,MyServiceImpl類,代碼如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService(){
        return new MyServiceImpl();
    }
}

public interface MyService {}
public class MyServiceImpl implements MyService {}

上述的依賴關系等同於XML-Based:

<beans>
	<bean id="myService",class="com.spring.annotation.service.impl.MyServiceImpl"/>
</beans>

使用AnnotationConfigApplicationContext 實例化Spring容器

AnnotationConfigApplicationContext 基於注解的上下文是Spring3.0 新添加的注解,它是ApplicationContext的一個具體實現,它可以接收@Configuration注解的類作為輸入參數,還能接收使用JSR-330元注解的普通@Component類。

當提供了@Configuration 類作為輸入參數時,@Configuration類就會注冊作為bean的定義信息並且所有聲明@Bean的方法也都會作為bean的定義信息。

當提供@Component和JSR-330 聲明的類時,他們都會注冊作為bean的定義信息,並且假設在必要時在這些類中使用諸如@Autowired或@Inject之類的注解

簡單的構造

在某些基於XML-Based的配置,我們想獲取上下文容器使用ClassPathXmlApplicationContext,現在你能夠使用@Configuration 類來實例化AnnotationConfigApplicationContext。

MyService中添加一個printMessage()方法,實現類實現對應的方法。新建測試類進行測試

public class ApplicationTests {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService service = context.getBean(MyService.class);
      	// printMessage() 輸出something...
        service.printMessage();
    }
}

如前所述,AnnotationConfigApplicationContext不僅限於使用@Configuration類。 任何@Component或JSR-330帶注釋的類都可以作為輸入提供給構造函數,如下例所示

public class ApplicationTests {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyServiceImpl.class,Dependency1.class,Dependency2.class);
        MyService myService = context.getBean(MyService.class);
        myService.printMessage();
    }
}

使用register注冊IOC容器

你可以實例化AnnotationConfigApplicationContext通過使用無參數的構造器並且使用register方法進行注冊,它和AnnotationConfigApplicationContext帶參數的構造器起到的效果相同。

public class ApplicationTests {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(AppConfig.class, OtherConfig.class);
        ctx.register(AdditionalConfig.class);
        ctx.refresh();
        MyService myService = ctx.getBean(MyService.class);
        System.out.println(ctx.getBean(OtherConfig.class));
        System.out.println(ctx.getBean(AdditionalConfig.class));
        myService.printMessage();
    }
}

OtherConfig.class 和 AdditionalConfig.class 是使用@Component 標注的類。

允許scan()方法進行組件掃描

為了允許組件進行掃描,需要在@Configuration配置類添加@ComponentScan()注解,改造之前的AdditionalConfig類,如下:

@Configuration
@ComponentScan(basePackages = "com.spring.annotation.config")
public class AdditionalConfig {}

@ComponentScan指定了基礎掃描包位於com.spring.annotation.config下,所有位於該包范圍內的bean都會被注冊進來,交由Spring管理。它就等同於基於XML-Based的注解:

<beans>
    <context:component-scan base-package="com.spring.annotation.config/>
</beans>

AnnotationConfigApplicationContext中的scan()方法以允許相同的組件掃描功能,如以下示例所示:

public static void main(String[] args) {
  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  ctx.scan("com.spring.annotation");
  ctx.refresh();
  MyService myService = ctx.getBean(MyService.class);
}

為什么說@Configuration用法和@Component都能夠標注配置類?因為@Configuration的元注解就是@Component。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}

使用AnnotationConfigWebApplicationContext支持web容器

AnnotationConfigApplicationContext的一個WebApplicationContext的變化是使用AnnotationConfigWebApplicationContext。配置Spring ContextLoaderListener的servlet監聽器,Spring MVC的DispatcherServlet等時,可以使用此實現。以下web.xml代碼段配置典型的Spring MVC Web應用程序(請注意context-param和init-param的使用)

<web-app>
  	<!-- 配置web上下文監聽器使用 AnnotationConfigWebApplicationContext 而不是默認的
		XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

  	<!-- 配置位置必須包含一個或多個以逗號或空格分隔的完全限定的@Configuration類。 也可以為組件掃描指定完全			限定的包-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.spring.annotation.config.AdditionalConfig</param-value>
    </context-param>

  	<!--使用ContextLoaderListener像往常一樣引導根應用程序上下文-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

  	<!-- 定義一個SpringMVC 核心控制器 DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置web上下文監聽器使用 AnnotationConfigWebApplicationContext 而不是默認的
				XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 配置位置必須包含一個或多個以逗號或空格分隔的完全限定的@Configuration類。 也可以為組件掃描指定					完全限定的包-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.spring.annotation.config.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- 將/app/* 的所有請求映射到調度程序servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

使用@Bean注解

@Bean 注解是一個方法級別的注解,能夠替換XML-Based中的 標簽,@Bean注解同樣支持 標簽支持的屬性,像是 init-method, destroy-method, autowiring

定義一個Bean

與基礎概念中Bean的定義相同,讀者可以參考基礎概念部分進行了解,我們不在此再進行探討。

Bean的依賴

@Bean 注解可以有任意數量的參數來構建其依賴項,例如

public class MyService {
    private final MyRepository myRepository;
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
    public String generateSomeString() {
        return myRepository.findString() + "-from-MyService";
    }
}

@Configuration
class MyConfiguration {
    @Bean
    public MyService myService() {
        return new MyService(myRepository());
    }
    @Bean
    public MyRepository myRepository() {
        return new MyRepository();
    }
}

public class MyRepository {
    public String findString() {
        return "some-string";
    }
}

接受生命周期回調

任何使用@Bean的注解都支持生命周期的回調,使用JSR-220提供的@PostConstruct@PreDestory注解來實現。如果bean實現了InitializingBean,DisposableBean或者Lifecycle接口,他們的方法會由IOC容器回調。一些以Aware的實現接口(像是BeanFactoryAware,BeanNameAware, MessageSourceAware, ApplicationContextAware等)也支持回調。

@Bean注解支持特定的初始化和銷毀方法,就像XML-Based中的init-methoddestory-method中的bean屬性,下面這個例子證實了這一點

AppConfig.java

@Configuration
public class AppConfig {

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

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

class BeanOne {
    public void init(){}
}

class BeanTwo {
    public void cleanup(){}
}

對於上面的例子,也可以手動調用init()方法,與上面的initMethod 方法等效

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

當你直接使用Java開發時,你可以使用對象執行任何操作,並且不必總是依賴於容器生命周期。

Bean的作用范圍

Spring包括@Scope注解能夠讓你指定Bean的作用范圍,Bean的Scope默認是單例的,也就是說@Bean標注的對象在IOC的容器中只有一個。你可以重寫@Scope的作用范圍,下面的例子說明了這一點,修改OtherConfig如下

OtherConfig.java

@Configuration
public class OtherConfig {

    @Bean
    @Scope("prototype")
    public Dependency1 dependency1(){
        return new Dependency1();
    }
}

每次嘗試獲取dependency1這個對象的時候都會重新生成一個新的對象實例。下面是Scope的作用范圍和解釋:

Scope Descriptionn
singleton 默認單例的bean定義信息,對於每個IOC容器來說都是單例對象
prototype bean對象的定義為任意數量的對象實例
request bean對象的定義為一次HTTP請求的生命周期,也就是說,每個HTTP請求都有自己的bean實例,它是在單個bean定義的后面創建的。僅僅在web-aware的上下文中有效
session bean對象的定義為一次HTTP會話的生命周期。僅僅在web-aware的上下文中有效
application bean對象的定義范圍在ServletContext生命周期內。僅僅在web-aware的上下文中有效
websocket bean對象的定義為WebSocket的生命周期內。僅僅在web-aware的上下文中有效

@Scope和Scoped-proxy

Spring提供了一種通過scoped proxies與scoped依賴一起作用的方式。最簡單的在XML環境中創建代理的方式是通過<aop:scoped-proxy/>標簽。使用@Scope注解為在Java中配置bean提供了與proxyMode屬性相同的功能。默認是不需要代理的(ScopedProxyMode.NO),但是你需要指定ScopedProxyMode.TARGET_CLASS或者ScopedProxyMode.INTERFACES

自定義Bean名稱

默認的情況下,配置類通過@Bean配置的默認名稱(方法名第一個字母小寫)進行注冊和使用,但是你可以更換@Bean的name為你想指定的名稱。修改AdditionalConfig 類

AdditionalConfig.java

@Configuration
//@ComponentScan(basePackages = "com.spring.annotation.config")
public class AdditionalConfig {

    @Bean(name = "default")
    public Dependency2 dependency2(){
        return new Dependency2();
    }
}

Bean的別名

有時候需要為單例的bean提供多個名稱,也叫做Bean的別名。Bean注解的name屬性接收一個Array數組。下面這個例子證實了這一點:

OtherConfig.java

@Configuration
public class OtherConfig {

//    @Bean
//    @Scope("prototype")
//    public Dependency1 dependency1(){
//        return new Dependency1();
//    }

		@Bean({"dataSource", "dataSourceA", "dataSourceB"})
    public DataSource dataSource(){
			return null;
    }
}

Bean的描述

有時,提供更詳細的bean描述信息會很有幫助(但是開發很少使用到)。為了增加一個對@Bean的描述,你需要使用到@Description注解

OtherConfig.java

@Configuration
public class OtherConfig {

//    @Bean
//    @Scope("prototype")
//    public Dependency1 dependency1(){
//        return new Dependency1();
//    }

//    @Bean({"dataSource", "dataSourceA", "dataSourceB"})
//    public DataSource dataSource(){
//        return null;
//    }

    @Bean
    @Description("此方法的bean名稱為dependency1")
    public Dependency1 dependency1(){
        return new Dependency1();
    }
}

使用@Configuration注解

更多關於@Configuration 的詳細說明,請你參考https://mp.weixin.qq.com/s/FLJTsT2bAru-w7cF4CG8kQ

已經把@Configuration的注解說明的比較詳細了。

組成Java-Based環境配置的條件

Spring基於注解的配置能夠允許你自定義注解,同時能夠降低配置的復雜性。

使用@Import注解

就像在Spring XML文件中使用 元素來幫助模塊化配置一樣,@Import 注解允許從另一個配置類加載@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);
  
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

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

有選擇性的包含@Configuration 類和@Bean 方法

選擇性的允許或者禁止@Configuration注解的類和@Bean注解的方法是很有用的,基於一些任意系統狀態。一個常見的例子是只有在Spring環境中啟用了特定的配置文件時才使用@Profile注釋激活bean。

@Profile注解也實現了更靈活的注解@Conditional,@Conditional 注解表明在注冊@Bean 之前應參考特定的Condition實現。

實現Condition接口就會提供一個matched方法返回true或者false

更多關於@Conditional 的示例,請參考

https://www.cnblogs.com/cxuanBlog/p/10960575.html

結合Java與XML配置

Spring @Configuration類能夠100%替換XML配置,但一些工具(如XML命名空間)仍舊是配置容器的首選方法,在這種背景下,使用XML使很方便的而且使剛需了。你有兩個選擇:使用以XML配置實例化容器為中心,例如:ClassPathXmlApplicationContext導入XML或者實例化以Java配置為中心的AnnotationConfigApplicationContext並提供ImportResource注解導入需要的XML配置。

將@Configuration聲明為普通的bean元素

請記住,@Configuration類存放的是容器中的bean定義信息,下面的例子中,我們將會創建一個@Configuration類並且加載了外部xml配置。下面展示了一個普通的Java配置類

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

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

下面是system-test-config.xml配置類的一部分

<beans>
   	
  	<!--允許開啟 @Autowired 或者 @Configuration-->
    <context:annotation-config/>
  	
 		<!-- 讀取外部屬性文件 -->
  	<!-- 更多關於屬性讀取的資料,參考 https://www.cnblogs.com/cxuanBlog/p/10927819.html -->
    <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>

    <bean class="com.spring.annotation.config.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      	<property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

引入jdbc.properties建立數據庫連接

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sys
jdbc.username=root
jdbc.password=123456
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/spring/annotation/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

system-test-config.xml中,AppConfig 對應的 標簽沒有聲明id屬性,雖然這樣做是可以接受的,但是沒有必要,因為沒有其他bean引用它,並且不太可能通過名稱從容器中獲取它。同樣的,DataSource bean只是按類型自動裝配,因此不嚴格要求顯式的bean id。

使用<context:component-scan/> 挑選指定的@Configuration類

因為@Configuration的原注解是@Component,所以@Configuration注解的類也能用於組件掃描,使用與前一個示例中描述的相同的方案,我們可以重新定義system-test-config.xml以利用組件掃描。 請注意,在這種情況下,我們不需要顯式聲明<context:annotation-config />,因為<context:component-scan />啟用相同的功能。

<beans>
   
    <context:component-scan base-package="com.spring.annotation"/>
    <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      	<property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

@Configuration 類使用@ImportResource

在基於Java注解的配置類中,仍然可以使用少量的@ImportResource導入外部配置,最好的方式就是兩者結合,下面展示了一下Java注解結合XML配置的示例

@Configuration
@ImportResource("classpath:/com/spring/annotation/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.driverClassName}")
  	private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}

Properties-config.xml

<beans>
    <context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>
</beans>

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sys
jdbc.username=root
jdbc.password=123456
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM