1. 基礎概念
IOC:即控制反轉,是一種解耦類與類之間關係的一種設計模式;例如當A類中需要B類的時候,並不是在A類中直接new一個B類,而是通過IOC容器統一創建A類和B類,並通過DI(依賴注入),將B類注入到A類中!
DI:即依賴注入,是IOC模式的一種實現方式;包括設值注入和構造注入
SpringBean:與JavaBean不同,所有被Spring IOC容器管理的實例都可以稱作SpringBean,下文簡稱Bean
2. SpringBean的裝配
SpringBean的裝配有三種配置方式:xml文件配置方式、JavaConfig配置類配置方式、註解實現自動裝配的配置方式
2.1 xml文件配置:
Bean類代碼:
package springtest;
public class TestBean {
private String str;
private DIBean di;
public TestBean() {
System.out.println("TestBean init");
}
/*通過構造方法注入直接量*/
public TestBean(String s) {
this.str = s;
System.out.println("TestBean init s is " + s);
}
/*通過構造方法注入Bean*/
public TestBean(DIBean di) {
this.di = di;
System.out.println("TestBean init di ");
}
/*通過設值方法注入直接量*/
public void setStr(String s) {
this.str = s;
System.out.println("Set str s is " + s);
}
/*通過設值方法注入Bean*/
public void setDi(DIBean di) {
this.di = di;
System.out.println("Set di");
}
public void fun() {
System.out.println("TestBean fun");
}
}
class DIBean {
/*Bean的構造方法爲private還是public屬性,並不影響裝配*/
private DIBean () {
System.out.println("DIBean init");
}
}
xml文件配置:
<?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">
<beans>
<!-->不設置id的Bean配置<-->
<bean class="springtest.TestBean" />
<!-->設置id的Bean配置<-->
<bean id="TBean1" class="springtest.TestBean" />
<!-->通過構造器注入常量<-->
<bean id="TBean2" class="springtest.TestBean">
<constructor-arg value = "Hello"/>
</bean>
<!-->通過設值方法注入常量<-->
<bean id="TBean3" class="springtest.TestBean">
<property name="str" value = "Hello"/>
</bean>
<bean id="DIBean" class="springtest.DIBean" />
<!-->通過構造器注入Bean<-->
<bean id="TBean4" class="springtest.TestBean">
<constructor-arg ref = "DIBean"/>
</bean>
<!-->通過設值方法注入Bean<-->
<bean id="TBean5" class="springtest.TestBean">
<property name="di" ref = "DIBean"/>
</bean>
</beans>
</beans>
Bean獲取代碼:
package springtest;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通過xml配置文件,生成Spring IOC容器*/
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
/*通過包名.類名#序號爲id的格式,獲取無顯示id的Bean*/
TestBean t = (TestBean)context.getBean("springtest.TestBean#0");
t.fun();
/*通過id獲取Bean*/
TestBean t1 = (TestBean)context.getBean("TBean1");
t1.fun();
}
}
Ps:
- xml中配置一個Bean的時候最好指定id,如果不指定id會被默認用包名.類名#序號的格式賦予一個默認的id
- 獲取Bean的時候,如果該類型的Bean只有一個實例,可以通過Bean的類類型對象來獲取;如果存在多個實例的時候,則必須使用id來獲取!
- 可以使用util命名空間的標籤來實現容器類型的注入(包括構造注入和設值注入)
2.2 JavaConfig配置類配置:
Bean類代碼(與上述代碼相似,此處只展示差異代碼):
/*Java Bean示例代碼與上面代碼,僅僅修改了DIBean類的構造方法爲public屬性*/
class DIBean {
/*JavaConfig類方式配置Bean,構造方法不能是private屬性*/
public DIBean () {
System.out.println("DIBean init");
}
JavaConfig配置:
package springtest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
//不設置id的Bean配置
@Bean
public TestBean TBean() {
return new TestBean();
}
//設置id的Bean配置
@Bean(name = "TBean1")
public TestBean TBean1() {
return new TestBean();
}
//通過構造器注入常量
@Bean(name = "TBean2")
public TestBean TBean2() {
return new TestBean("Hello");
}
//通過設值方法注入常量
@Bean(name = "TBean3")
public TestBean TBean3() {
TestBean t = new TestBean();
t.setStr("Hello");
return t;
}
@Bean(name = "DIBean")
public DIBean DIBeanInit() {
return new DIBean();
}
//通過構造器注入Bean
@Bean(name = "TBean4")
public TestBean TBean4() {
/*通過DIBeanInit()方法,獲取id爲"DIBean"的Bean實例,
該方法由於有@Bean註解的封裝,並不是真正的執行方法調用,只是獲取Bean實例而已*/
return new TestBean(DIBeanInit());
}
//通過設值方法注入Bean
@Bean(name = "TBean5")
public TestBean TBean5() {
TestBean t = new TestBean();
t.setDi(DIBeanInit());
return t;
}
}
Bean獲取代碼:
package springtest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通過Java Config類,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
/*通過Bean id獲取Bean實例*/
TestBean t = (TestBean)context.getBean("TBean1");
t.fun();
}
}
Ps:
- JavaConfig類實現Bean裝配的配置,可以自己實現具體Bean裝配時的初始化邏輯,對應Bean的初始化是通過調用被@Bean註解修飾的方法實現的!
- JavaConfig類中被@Bean註解修飾的方法,裝配的Bean始終是一個單例;該方法的調用並不會像普通調用方法一樣,每次都重複執行方法體,而僅僅是通過該方法獲取Bean實例而已!
2.3 通過註解實現自動裝配:
Bean類代碼:
package springtest;
import javax.inject.Named;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*通過@Component註解,配置當前類爲一個JavaBean*/
@Component("TBean1")
//@Named("TBean1")
public class TestBean {
//@Autowired
public DIBean di;
/*通過@Value註解設值注入直接量*/
@Value("Hello")
public String s;
/*默認構造方法*/
public TestBean (){
System.out.println("TestBean init");
}
/*通過@Value註解構造注入直接量*/
public TestBean(@Value("Hello") String s) {
System.out.println("Init TestBean S is" + s);
}
/*通過構造方法注入Bean*/
/*通過@Autowired註解,注入一個JavaBean*/
@Autowired
public TestBean(DIBean di) {
this.di = di;
System.out.println("TestBean init di ");
}
/*通過設值方法注入Bean*/
//@Autowired
public void setDi(DIBean di) {
this.di = di;
System.out.println("Set di");
}
public void fun() {
System.out.println("TestBean fun");
}
}
@Component
class DIBean {
public int a;
private DIBean () {
this.a = 100;
System.out.println("DIBean init");
}
}
JavaConfig類配置自動掃描:
package springtest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"springtest"})
public class Config {
}
xml文件配置自動掃描:
<?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"
xmlns:util="http://www.springframework.org/schema/util"
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:component-scan base-package="springtest"/>
</beans>
Bean獲取代碼:
package springtest;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTest {
public static void main(String[] args) {
/*通過Java Config類,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
/*通過Bean id獲取Bean實例*/
TestBean t = (TestBean)context.getBean("TBean1");
t.fun();
System.out.println(t.di.a);
}
}
Ps:
- 依賴注入分爲兩種:設值注入和構造注入,兩種方式分別可以注入直接量和Bean引用;切記通過設值注入的時候,被注入的屬性必須要有一個public屬性的setter方法!
- Bean的自動裝配,需要配置自動掃描包路徑,可以使用xml配置,也可以使用JavaConfig類配置;Java Config配置類使用@ComponentScan註解的時候,可以傳入一個包名數組給basePackages屬性,也可以傳入一個類名數組給basePackageClasses屬性;用來指示掃描自動裝配Bean的範圍!當不設置參數的時候,默認掃描當前Java Config類的同包範圍!
- 通過@Component註解,實現Bean的配置;可以通過參數指定Bean的id
- 通過@Autowired註解,實現Bean的依賴注入,但是@Autowired只能注入Bean,不能注入直接量;可以通過@Autowired註解修飾構造方法、setter方法、成員變量來實現Bean的注入
- 通過@Bean註解,實現直接量的注入;可以通過@Bean註解修飾成員變量、方法入參來實現直接量的注入
- 當通過@Autowired註解,自動注入Bean失敗的時候,Spring會拋出異常,可以通過使用@Autowired註解的required屬性設置爲false來屏蔽這個異常的拋出
- Spring支持JSR330的相關注解,可以使用@Named註解替換@Component註解;使用@Inject註解替換@Autowired註解
- Spring支持JSR250的相關注解,可以使用@Resource註解替換@Autowired註解,@Resource註解可以明確通過Bean id注入;支持@PostConstruct註解(創造資源之後的回調處理),支持@PreDestroy註解(銷燬資源之前的回調處理)
2.4 混合配置(即上述三種方式是可以混合使用的):
2.4.1 xml配置中引入JavaConfig配置類:
<!-- xml配置文件中引入其他xml配置文件 -->
<import resource="配置文件.xml" />
<!-- xml配置文件中引入JavaConfig配置類 -->
<bean class="包路徑.JavaConfig配置類名" />
2.4.2 JavaConfig配置類中引入xml配置:
@Configuration
/*JavaConfig配置類中引入其他配置類*/
@Import({Config.class})
/*JavaConfig配置類中引入xml配置文件*/
@ImportResource({"config.xml"})
public class Config {
}
比較:
- xml配置和JavaConfig類配置屬於顯示裝配Bean和顯示注入依賴,註解配置屬於根據掃描範圍和IOC容器實現自動裝配及自動注入依賴!
- xml配置和註解自動裝配的Bean的構造方法可以爲private屬性,JavaConfig類配置的Bean的構造方法必須要是public屬性
- xml文件相比JavaConfig配置類,依賴注入配置比較麻煩;而且JavaConfig配置類是基於Java代碼實現的比起xml配置可以更好的利用Java的語言特性,並且在Bean的初始配置的時候可以增加自己的邏輯!
- 註解自動裝配在很多場景下使用都比較簡單,但是在針對一個類進行多個Bean裝配的時候和多個同類型的Bean注入的時候,就顯的比較複雜了(即由於同一個類不能有兩個@Component註解,@Autowired註解不可以明確指定注入的Bean id)!!!
3. Profile配置
Spring可以通過Profile的配置,來控制不同Bean在不同的Profile屬性中差異化裝配
3.1 Profile屬性的配置:
3.1.1 JavaConfig配置類配置:
可以通過@Profile註解整體標記一個JavaConfig配置類,也可以通過@Profile標記一個Bean的配置方法
/*標記一個JavaConfig類*/
@Configuration
@Profile("profile1")
public class JavaConfig {
}
/*標記一個Bean*/
@Configuration
public class JavaConfig {
@Bean
@Profile("profile1")
public TestBean TBean() {
return new TestBean();
}
}
3.1.2 xml文件的配置:
可以通過<beans>標籤的profile屬性,標記一個或者多個Bean
<beans profile="profile1">
<bean class="A1"/>
<bean class="A2"/>
</beans>
<beans profile="profile2">
<bean class="B1"/>
</beans>
3.2 Profile屬性的激活:
Profile屬性默認情況下是不激活的,即所有被標記爲Profile的Bean都是默認不裝配的;只有當對應的Profile屬性值被激活後,對應的Profile標記的Bean纔會進行裝配!
3.2.1 屬性值:
spring.profiles.default:profile的默認值,優先active;active沒設置,再找defualt
spring.profiles.active: profile的屬性值
3.2.2 激活屬性值:
- DispatcherServlet的初始參數
- web應用上下文參數
- 環境變量
- JVM系統屬性
可以通過上面描述的參數設置方式,激活Profile屬性值!
4. 條件化創建Bean
Spring4引入了@Conditional註解,可以在@Component或@Bean註解後面使用@Conditional註解,並給出Condition函數式接口的實現類,通過返回值確定Bean是否可以創建;返回true,則創建;返回false,則不創建
Ps:Spring4後的版本,@Profile註解也是依賴@Conditional註解實現的
5. @Autowired和@Inject註解注入的歧義性
由於@Autowired和@Inject註解都是依靠類類型進行注入的,所以當存在多個同類型Bean的情況下,依賴注入就會發生歧義性!針對這樣的歧義性,Spring給出兩種解決方式!
5.1 Primary標籤:
可以在@Component註解和@Bean註解後面增加@Primary註解,用來指示當前Bean類型被注入的時候進行首選注入!也可以在xml配置文件<bean>標籤中增加Primary屬性!
/*JavaConfig配置類中,使用@Primary註解*/
@Configuration
public class JavaConfig {
@Bean
@Primary
public TestBean TBean() {
return new TestBean();
}
}
/*通過註解自動裝配Bean使用@Primary註解*/
@Component("TBean")
@Primary
public class TestBean {
}
<bean class="Bean" primary="true"/>
5.2 @Qualifier註解:
可以在@Component註解後面使用@Qualifier註解設置限定符,在@Autowired註解後面使用相同的@Qualifier限定符就可以把指定的Bean注入進來!但是Spring並沒有約束@Component後面@Qualifier註解限定符必須是唯一的,所以依然有可能存在多對一的歧義場景!
@Component("DIBean")
@Qualifier("DI")
public class DIBean {
}
@Component("TBean")
public class TestBean {
@Autowired
@Qualifier("DI")
DIBean di;
}
5.3 @Autowired、@Inject和@Resource區別:
- @Resource是JSR250規範中描述的註解、@Inject是JSR330規範中描述的註解、@Autowired是Spring框架的註解;
- 使用上@Autowired除了比@Inject多一個required參數,並無其他區別
- @Resource相比@Autowired和@Inject,可以指定Bean id或者Bean類型進行注入
6. SpringBean的作用域
SpringBean有四種作用域:單例作用域、原型作用域、會話作用域、請求作用域
單例作用域:
整個應用中,只有一個Bean實例,每次依賴注入都是相同的一個Bean實例
/*可以修飾JavaConfig類中的@Bean配置*/
/*可以修飾註解配置的@Component*/
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
<bean class="Bean1" scope="singleton"/>
原型作用域:
每次注入或者通過Spring應用上下文獲取Bean的時候,都會創建一個新的Bean實例
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
<bean class="Bean2" scope="prototype"/>
會話作用域:
在web應用中,每次會話重新創建一個新的Bean實例
@Scope(value=WebApplicationContext.SCOPE_SESSION)
<bean class="Bean3" scope="session"/>
請求作用域:
在web應用中,每次請求重新創建一個新的Bean實例
@Scope(value=WebApplicationContext.SCOPE_REQUEST)
<bean class="Bean3" scope="rquest"/>
會話作用域和請求作用域的代理:
當會話作用域或者請求作用域的Bean注入的時候,由於Spring初始上下文的時候並不存在會話和請求,需要注入一個代理類,通過代理類進行不同會話作用域或者請求作用域Bean的委託!
- 當注入類型爲接口的時候,使用ScopedProxyMode.INTERFACES代理模式
- 當注入類型爲實現類的時候,使用ScopedProxyMode.TARGET_CLASS代理模式
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
<bean class="Bean4" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
<bean/>
7 Spring獲取資源文件
7.1 加載資源文件:
7.1.1 註解方式:
在JavaConfig配置類上,@Configuration註解後面使用@PropertySource註解指定資源文件路徑,實現資源文件的加載!
@Configuration
@PropertySource("classpath:app.properties")
public class JavaConfig {
@Bean
public PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
7.1.2 xml配置文件方式:
xml文件配置同樣可以實現資源文件的加載,可以使用context:property-placeholder命名空間和裝配PropertySourcesPlaceholderConfigurer類兩種方式
context:property-placeholder命名空間:
<context:property-placeholder location="classpath:app.properties" file-encoding="UTF-8"/>
裝配PropertySourcesPlaceholderConfigurer類:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location">
<value>app.properties</value>
</property>
</bean>
7.2 資源信息獲取:
資源文件
#app.properties文件
t1=test1
t2=test2
7.2.1 Environment接口實現(通過getProperty方法獲取參數):
@Component("TBean")
public class TestBean {
@Autowired
public Environment env;
}
public class SpringTest {
public static void main(String[] args) {
/*通過Java Config類,生成Spring IOC容器*/
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
/*通過Bean id獲取Bean實例*/
TestBean t = (TestBean)context.getBean("TBean");
System.out.println(t.env.getProperty("t1"));
}
}
7.2.2 佔位符方式:
@Component("TBean")
public class TestBean {
@Value("${t1}")
public String s;
}
7.2.3 SpEL表達式方式:
@Component("TBean")
public class TestBean {
@Value("#{'${t1}'}")
public String s;
}
Ps:
- 當使用$佔位符的時候,一定要裝配PropertySourcesPlaceholderConfigurer類,因爲PropertySourcesPlaceholderConfigurer是佔位符實現類,否則報錯!JavaConfig配置類需要顯示配置、xml配置文件也可以顯示配置,同樣也可以使用context:property-placeholder命名空間,Spring會自動完成裝配!
- SpEL表達式比較複雜,這裏就先不展開描述了!