Spring框架學習筆記(1.Spring IOC)

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表達式比較複雜,這裏就先不展開描述了!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章