深入學習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