1. 程式人生 > >深入學習Spring框架(二)- 註解配置

深入學習Spring框架(二)- 註解配置

1.為什麼要學習Spring的註解配置?  

  基於註解配置的方式也已經逐漸代替xml。所以我們必須要掌握使用註解的方式配置Spring。
  關於實際的開發中到底使用xml還是註解,每家公司有著不同的使用習慣。所以這兩種配置方式都需要掌握。
  學習基於註解的IoC配置,首先得有一個認知,即註解配置和xml配置要實現的功能都是一樣的,都是要降低程式間的耦合。只是配置的形式不一樣。

2.入門示例

步驟:
  1.匯入jar包,相對於之前的,在基於註解的配置中,我們還要多拷貝一個aop的jar包。

  

  2.在classpath下建立一個配置檔案applicationContext.xml,並匯入約束,基於註解整合時,配置檔案匯入約束時需要多匯入一個context名稱空間下的約束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
">

</beans>

  3.建立一個用於測試的類,並且加入使用@Component註解,宣告該類允許注入到Spring容器

import org.springframework.stereotype.Component;
/*
 * @Component 元件註解,spring在啟動的時候掃描對應的包下面的所有型別
 * 如果哪一個類上只要有 @Component 註解,說明這個就需要被Spring管理
 * Spring在容器就建立這個類的物件
 * 
 * @Component 屬性介紹
 *     @Component(value="id值")
 *  value :指定 bean 的 id值
 *    可以不寫,預設bean的id就是當前類名的 首字母小寫
 *    如果寫,“value=”可以省略,直接"id值"
 * 
 */
@Component("service")
public class Service {
    
    public void say() {
        System.out.println("你好!Spring");
    }
}

  4.往配置檔案加入掃描元件配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
">
    <!-- 配置spring要進行掃描的元件註解的包(預設包含子包)的位置 -->
    <context:component-scan base-package="com.gjs.service"/>
</beans>

  5.測試程式碼

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.gjs.service.Service;

public class TestSpring {
    @Test
    public void testName() throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Service service = context.getBean("service",Service.class);
        service.say();
        
    }
}

3.常用註解說明

  3.1 IOC相關注解

  用於被掃描建立物件的註解,統稱為元件註解。元件包括:@Component,@Controller,@Service,@Repository。它們的作用是標識類為註解的元件類,啟動Spring框架的程式時,宣告將這些元件類注入到Spring容器裡面。功能類似原來配置檔案的<bean>標籤。
其他它們的功能是一樣的並沒有本質上的區別,哪為什麼會有4個呢?
  Spring第一版註解的實現(spring 2.5),就是使用一個@Component。從3.0以後,作者認為根據分層的需要,把它拆成了四個。為了可以讓開發人員,可見即可得,一看到註解,立即知道類的性質。所以分成了四個。

規範:

@Controller:用於宣告表示層的元件註解
@Service:用於宣告服務層的元件註解
@Repository:用於宣告持久層的元件註解
@Component:用於宣告三層以外的元件註解
除了@Controller在SpringMVC裡面有強制的要求,SpringMVC的表示層必須使用@Controller元件註解。其他情況不按規範使用也不會有問題,但既然是規範就要遵守。

 

  @Scope:指定作用範圍,等同於Xml配置<bean>標籤中的scope

@Component("service")
@Scope("prototype")
public class Service {
    
    public void say() {
        System.out.println("你好!Spring");
    }
}

  @PostConstruct:初始化方法註解,等同於Xml配置<bean>標籤中的init-method

@PostConstruct 
public void init() {
    System.out.println("初始化方法執行了");
}

 

  @PreDestroy:銷燬方法註解,等同於Xml配置<bean>標籤中的destroy-method

@PreDestroy
public void destroy() {
     System.out.println("銷燬方法執行了");
}

 

  3.2 依賴注入的註解

  Spring提供了兩套用註解依賴注入的解決方案
    1.@Autowired +@Qualifier():是Spring定義的標籤
    2.@Resouce:是J2EE的規範

  

@Autowired +@Qualifier()

@Autowired +@Qualifier()有三種注入的方式:
  1.在欄位上面注入
  2.在方法上面注入
  3.在構造方法上面注入

示例:

整體結構:

  CustomeService介面:

package com.gjs.service;

public interface CustomeService {
    public void say();
}

  CustomServiceImpl1:

package com.gjs.service.impl;

import org.springframework.stereotype.Service;

import com.gjs.service.CustomeService;
@Service("service1")
public class CustomServiceImpl1 implements CustomeService {

    @Override
    public void say() {
        System.out.println("CustomerServiceImpl1.say()");
    }
}

  CustomServiceImpl2:

package com.gjs.service.impl;

import org.springframework.stereotype.Service;

import com.gjs.service.CustomeService;

@Service("service2")
public class CustomServiceImpl2 implements CustomeService {

    @Override
    public void say() {
        System.out.println("CustomerServiceImpl2.say()");
    }
}

  CustomController:

package com.gjs.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

import com.gjs.service.CustomeService;


@Controller("client")
public class CustomController {
    /*
     * 方式一(推薦) : 在欄位(成員變數)上注入
     * @Autowired :
     *     預設會從Spring容器找對應型別的物件注入進來
     *  使用@Autowired 必須保證Spring容器中最少一個型別對應bean ,如果沒有就會拋異常
     *   org.springframework.beans.factory.NoSuchBeanDefinitionException 
     *     可以使用 註解的 required屬性(除特殊情況,一般不使用)
     *  required = true/false 是否是必須有對應的物件,true 是必須有(預設),false 不是必須有
     * 
     *  如果spring容器有多個相同型別的物件,預設無法注入也會拋異常
     *  org.springframework.beans.factory.NoUniqueBeanDefinitionException 不是唯一的bean異常
     *  這時就需要配合使用 @Qualifier() 註解了
     *  @Qualifier(value="對應bean的id值")可以在多個相同型別的物件中篩選指定唯一id的物件,“value=”可以省略
     */
    //@Autowired(required=false)
    //@Qualifier("service1")
    private CustomeService customeService;
    
    /*
     * 方式二 :使用setter方法(屬性)注入
     * 將@Autowired直接貼在set方法上面即可,程式執行,會執行set方法
     * 將Spring容器對應的型別的引數賦值給 set方法的引數,型別不存在或存在多個,處理方式與方式一一樣
     */
    //@Autowired()
    //@Qualifier("service1")
    public void setCustomeService(CustomeService customeService) {
        this.customeService = customeService;
    }
    
    /*
     * 方式三 : 構造器注入
     * 使用註解的IOC建立bean的情況下
     * 預設bean中有什麼樣的構造器,spring就呼叫那個構造器去建立對應的bean物件
     * 並且會自動注入 構造器中對應型別引數的物件,無須@Autowired()
     * 
     * 如果建構函式的引數型別對應的bean有多個就在 在引數前面 使用 @Qualifier()註解,指定 對應的bean的id
     */

    public CustomController(@Qualifier("service1")CustomeService customeService) {
        this.customeService = customeService;
    }

    public void say() {
        customeService.say();
    }
    
}

  applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
">
    <!-- 配置spring要進行掃描的元件註解的包(預設包含子包)的位置 -->
    <context:component-scan base-package="com.gjs"/>
    
</beans>

  測試類TestSpring:

package com.gjs.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.gjs.client.CustomController;

public class TestSpring {
    @Test
    public void testName() throws Exception {
        //1.讀取配置檔案,建立Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //獲取呼叫方 CustomClient物件
        CustomController client = context.getBean("client", CustomController.class);
        //呼叫CustomClient物件的say()方法
        client.say();
    }
}

  @Resouce

  @Resource 功能等同 @Autowired + @Qualifier
  @Resource只能注入欄位和setter方法,不能注入構造方法

  CustomController類,其他參考上面的

package com.gjs.client;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

import com.gjs.service.CustomeService;


@Controller("client")
public class CustomController {
    /*
     * 方式一: 欄位注入
     *  也是預設會從Spring容器找對應型別的物件注入進來
     *  有多個相同型別時,可以使用@Resource(name="對應bean的id")指定注入哪個物件
     *  @Resource 必須保證需要注入的型別在Spring容器中最少有一個物件,沒有直接拋異常
     */
    //@Resource(name="service1")
    private CustomeService customeService;
    
    /*
     * 方式二: set方法(屬性)注入
     */
    @Resource(name="service1")
    public void setCustomeService(CustomeService customeService) {
        this.customeService = customeService;
    }
    

    public void say() {
        customeService.say();
    }
    
}

 

  @Value註解

  @Value註解:注入基本資料型別以及它們的包裝類和String型別資料的,支援${}注入Properties檔案的鍵值對,等同 <proprty name=”...” value=”${Key}”>。

@Repository
public class UserDaoImpl implements UserDao {
    
    /**
     * @Value(value="")
     * 可以從Spring容器讀取 .properties 配置檔案內容
     * value :配置檔案的對應的key -->使用 ${key} 獲取
     * 程式執行中自動將 properties 對應key的獲取出來設定給欄位
     * 
     */
    
    //等價 <property name="driverClassName" value="${jdbc.driverClassName}">
    @Value("${jdbc.driverClassName}") 
    private String driverClassName;
    
    @Value("${jdbc.url}")
    private String url;
    
    @Value("${jdbc.username}")
    private String username;
    
    @Value("${jdbc.password}")
    private String password;
    
    //@Value("${jdbc.maxActive}")
    @Value("10") //開發者也手動賦值
    private String maxActive;
    

    @Override
    public void insert(User user) {
        System.out.println(driverClassName);
        System.out.println(url);
        System.out.println(username);
        System.out.println(password);
        System.out.println(maxActive);

    }

}

  

4.純註解配置

  雖然使用註解的方式,但我們還是離不開xml檔案,因為我們還有配置元件掃描位置,如果這也能用註解配置,那麼我們就可以脫離xml檔案了。
  替換XML配置檔案的註解:

  

package com.gjs.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.alibaba.druid.pool.DruidDataSource;

/*
 * @Configuration 
 * 說明把當前類當做成Spring框架的配置檔案
 * @ComponentScan 
 *  配置註解包掃描的位置
 * @PropertySource("classpath:db.properties")
 *  讀取.peroperties 字尾的配置檔案
 */

@Configuration
@ComponentScan("com.gjs")
@PropertySource("classpath:db.properties")
public class SpringConfig {
    
    
    /**
     * @Value(value="")
     * 可以從Spring容器讀取 .properties 配置檔案內容
     * value :配置檔案的對應的key -->使用 ${key} 獲取
     * 程式執行中自動將 properties 對應key的獲取出來設定給欄位
     * 
     */
    
    //等價 <property name="driverClassName" value="${jdbc.driverClassName}">
    @Value("${jdbc.driverClassName}") 
    private String driverClassName;
    
    @Value("${jdbc.url}")
    private String url;
    
    @Value("${jdbc.username}")
    private String username;
    
    @Value("${jdbc.password}")
    private String password;
    
    @Value("${jdbc.maxActive}")
    private Integer maxActive;
    
    
    //<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 
      //init-method="init" destroy-method="close">
    @Bean(name="dataSource",initMethod="init",destroyMethod="close")
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaxActive(maxActive);
        return dataSource;
    }

}

5. Spring的測試

  5.1.傳統的單元測試

  存在的問題:
    1,每個測試都要重新啟動Spring容器,啟動容器的開銷大,測試效率低下。
    2,不應該是測試程式碼管理Spring容器,應該是Spring容器在管理測試程式碼。

  

  5.2 正確的Spring的測試

  

  5.3 如何使用Spring測試

  Spring測試必須保證Eclipse的單元測試的最低版本是 4.12版本,如果使用的Eclipse版本很低,那麼單元測試版本可能低於4.12,那麼需要開發者手動匯入單元測試的jar包

  要使用Spring測試就要先匯入test的jar包

  

 

 

package com.gjs.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.gjs.client.CustomController;

//表示先啟動Spring容器,把junit執行在Spring容器中
@RunWith(SpringJUnit4ClassRunner.class)
//表示從哪裡載入資原始檔,預設從src(源目錄)下面載入
@ContextConfiguration("classpath:applicationContext.xml")
public class TestSpring {
    @Test
    public void testName() throws Exception {
        //1.讀取配置檔案,建立Spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //獲取呼叫方 CustomClient物件
        CustomController client = context.getBean("client", CustomController.class);
        //呼叫CustomClient物件的say()方法
        client.say();
    }
}

&n