【Spring IOC&DI】知識點詳細介紹

Spring

簡介

  • Spring是一個IOC(DI)和AOP容器框架
  • IOC(DI):依賴注入
  • AOP:面向切面編程

Spring Framework 系統架構

  • Text:Spring的單元測試模塊
  • Core Container:核心容器(IOC);黑色表明這部分的功能由哪些jar包組成
  • AOP+Aspects(面向切面編程模塊)
  • ORM(Object Relation Mapping):對象關係映射
  • Transaction:事務

IOC & DI


簡介

IOC:(Inversion(反轉) Of Control):控制反轉java

DI:(Dependency Injection)依賴注入mysql


經過IOC容器建立對象,併爲屬性賦值

<!--一個Bean標籤能夠註冊一個組件(類、對象)-->
<!-- class:組件的全類名 id:惟一標識 -->
<bean id="person1" class="com.bean.Person">
    <!--使用property標籤爲Person對象的屬性賦值-->
    <!-- name="" : 指定屬性名 value="" :指定屬性的值-->
	<property name="personName" value="張三" ></property>
    <property name="personAge" value="18" ></property>
</bean>
複製代碼
public void test(){
    //ApplicationContext 表明ioc容器
    //ClassPathXmlApplicationContext:當前應用的xml配置文件在ClassPath下
    //根據配置文件獲得ioc容器對象
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    Person person = (Person)ioc.getBean("person1");
}
複製代碼

注意!web

  1. 容器中對象的建立在容器建立的時候就已經建立好了
  2. 同一個組件在ioc容器中是單實例的
  3. ioc容器在建立這個組件對象的時候,會利用setter方法爲javabean的屬性進行賦值
  4. javaBean的屬性名是由getter/setter方法決定的

根據bean的類型從IOC容器中獲取bean的實例

<bean id="person1" class="com.bean.Person">
	<property name="personName" value="張三" ></property>
    <property name="personAge" value="18" ></property>
</bean>
<bean id="person2" class="com.bean.Person">
	<property name="personName" value="小花" ></property>
    <property name="personAge" value="18" ></property>
</bean>
複製代碼
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //若是ioc容器中這個類型的bean有多個,查找就會報錯
    Person person = ioc.getBean(Person.class);
    //這樣使用 即使ioc容器中這個類型的bean有多個,查找也不會報錯
    Person person = ioc.getBean("person1",Person.class);
}
複製代碼

經過構造器爲bean的屬性賦值

調用有參構造器建立對象並賦值正則表達式

<bean id="person1" class="com.bean.Person">
	<constructor-arg name="personName" value="張三"></constructor-arg>
    <constructor-arg name="personAge" value="18"></constructor-arg>
    <!--此處能夠省略name屬性,但須要按照構造器參數的順序指定value值-->
    <constructor-arg value="張三"></constructor-arg>
    <constructor-arg value="18"></constructor-arg>
    <!--index="0" 爲參數指定索引 從0開始-->
    <constructor-arg value="張三" index="0"></constructor-arg>
    <constructor-arg value="18" index="1"></constructor-arg>
    <!--若是有多個有參構造器 使用type指定參數類型-->
    <constructor-arg value="張三" index="0"></constructor-arg>
    <constructor-arg value="18" index="1" type="java.lang.Integer"></constructor-arg>
</bean>
複製代碼
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    Person person = ioc.getBean("person1");
}
複製代碼

經過p名稱空間爲bean賦值

名稱空間:在xml中名稱空間是用來防止標籤重複的spring

<!--使用p名稱空間賦值時,需先導入p名稱空間-->
<bean id="person1" class="com.bean.Person" p:personName="張三" p:personAge="18">
    
</bean>
複製代碼

爲複雜類型的屬性賦值

//實體類
public class Person{
    private String name;
    private int age;
    
    private Car car;//Car是一個實體類
    private List<Book> books;//Book是一個實體類
    private Map<String,Object> maps;
    private Properties properties;
    
}
複製代碼
<bean id="car" class="com.bean.Car">
	<property name="carNmae" value="寶馬"></property>
</bean>
<bean id="book" class="com.bean.Book">
	<property name="bookNmae" value="西遊記"></property>
</bean>

<bean id="person1" class="com.bean.Person">
    <!--賦值爲null-->
    <property name="name">
    	<null/>
    </property>
    
    <!--ref="car" 這是一個嚴格的引用 person中的car跟 直接從容器中獲取的car是同樣的-->
    <property name="car" ref="car"></property>
    
    <!--爲list類型賦值-->
    <property name="books">
    	<list>
            <!--內部bean 寫id和不寫id是同樣的 外部獲取不到-->
        	<bean id="book" class="com.bean.Book" p:bookName="西遊記"></bean>
            
            <ref bean="book"/>
        </list>
    </property>
    
    <!--爲map類型賦值-->
    <bean id="maps">
        <!--底層用的是LinkedHashMap-->
		<map>
            <!--一個entry表明一個鍵值對-->
        	<entry key="key1"value="value1"></entry>
            <entry key="key2"value="value2"></entry>
            <entry key="key3"value-ref="book"></entry>
            <entry key="key4">
                <!--內部bean沒法用id獲取 不管是否有id-->
            	<bean id="" class="com.bean.Car">
                	<property name="carName" value="寶馬"></property>
                </bean>
            </entry>
        </map>
	</bean>
    
    <!--爲Properties類型賦值-->
    <bean name="properties">
        <!--properties 裏面 全部的k=v都是String類型-->
    	<props>
        	<prop key="username">root</prop>
            <prop key="username">123456</prop>
        </props>
    </bean>
 
</bean>
複製代碼

級聯屬性賦值

<bean id="car01" class="cam.bean.Car">
    <property name="carName" value="寶馬"></property>
</bean>
<bean id="person" class="cam.bean.Person">
	<property name="car" ref="car01"></property>
    <property name="car.carName" value="奔馳"></property>
</bean>
複製代碼

注意sql

  1. 級聯屬性能夠修改屬性的屬性,可是原來的bean值將會被修改

經過繼承實現bean配置信息的重用

<bean id="person1" class="com.bean.Person">
	<property name="perName" value="張三"></property>
    <property name="perAge" value="15"></property>
    <property name="perGender" value="男"></property>
</bean>

<!--parent="" : 指定當前的bean的配置信息繼承於哪一個bean class=""能夠省略不寫 -->
<bean id="person2" class="com.bean.Person" parent="person1">
	<property name="perName" value="張三"></property>
</bean>

<!--abstract="true" 表示這個bean只能被繼承 不能夠獲取-->
<bean id="person3" class="com.bean.Person" parent="person1" abstract="true">
	<property name="perGender" value="男"></property>
</bean>
複製代碼

IOC容器中改變bean的建立順序

<!--原來是按照配置的順序建立bean-->
<bean id="person" class="com.bean.Person"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>

<!-- depends-on="car,book" 改變bean的建立順序-->
<bean id="person" class="com.bean.Person" depends-on="car,book"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>

複製代碼

bean的做用域

<bean id="book" class="com.bean.Book" scope=""></bean>
複製代碼

scope="" 設置做用域 默認全部的bean都是單實例的數據庫

  • prototype:多實例的
    • 多實例的bean,容器啓動默認不會去建立多實例的bean
    • 只有在獲取的時候纔會建立這個bean
    • 每次獲取都會建立一個新的對象
  • singleton:單實例的 默認的
    • 單實例的bean在容器啓動完成以前就已經建立好對象,並保存在容器中了
    • 單實例的bean從始至終 獲取到的都是以前建立好的那個對象
  • request:在web環境下,同一個請求建立一個Bean實例(沒用)
  • session:在web環境下,同一次會話建立一個Bean實例(沒用)

靜態工廠和實例工廠建立bean

靜態工廠:工廠自己不用建立對象,經過靜態方法調用,對象 = 工廠類.工廠方法名();express

public class AirPlaneStaticFactory{
    //這個方法是靜態方法
    public static AirPlane getAirPlane(String planeName){
        AirPlane airplane = new AirPlane();
    	airplane.setPlaneName(planeName);
        return airPlane;
    }
}
複製代碼
<!--靜態工廠(不須要建立工廠自己) 1.class指定靜態工廠全類名, 2.factory-method 指定工廠方法, 3.constructor-arg 能夠爲方法傳參 -->
<bean id="airPlane" class="com.factory.AirPlaneStaticFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飛機1號" ></constructor-arg>         
</bean>
複製代碼
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //獲取到的是飛機 並非工廠
    AirPlane airplane = ioc.getBean("airPlane");
}
複製代碼

**實例工廠:**工廠自己須要建立對象,編程

​ 工廠類 工廠對象 = new 工廠類();session

​ 對象 =工廠對象.工廠方法名();

public class AirPlaneInstanceFactory{
    //這個方法不是靜態方法
    public AirPlane getAirPlane(String planeName){
        AirPlane airplane = new AirPlane();
    	airplane.setPlaneName(planeName);
        return airPlane;
    }
}
複製代碼
<!--實例工廠(須要建立工廠自己)-->
<bean id="airPlaneInstanceFactory" class="com.factory.AirPlaneInstanceFactory">      
</bean>
<!--factory-bean="" 指定當前對象由哪一個工廠建立 factory-method=""指定工廠方法-->
<bean id="airPlane" class="com.bean.AirPlane" factory-bean="airPlaneInstanceFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飛機2號" ></constructor-arg>
</bean>
複製代碼
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //獲取到的是飛機 並非工廠
    AirPlane airplane = ioc.getBean("airPlane");
}
複製代碼

實現FactoryBean接口的工廠

FactoryBean 是Spring規定的一個接口,只要是這個接口的實現類 ,spring都認爲是一個工廠,Spring會自動調用工廠方法建立實例

第一步:須要寫一個實現了FactoryBean接口的類

public class MyFactoryBeanImpl implements FactoryBean<Book>{
    //getObject:工廠方法 返回建立的對象
    @Override
    public Book getObject() throws Exception{
        Book book = new Book();
        book.setId(1);
        return book;
    }
    //返回 建立的對象的類型
    @Override
    public Class<?> getObjectType(){
        return Book.class;
    }
    //返回 是不是單例
    //false : 不是單例 true: 是單例
    @Override
    public boolean isSingleton(){
        return false;
    }
}
複製代碼

第二步:在配置文件中進行註冊

<!-- 注意!:不管 isSingleton()這個方法返回值是什麼 ioc容器啓動的時候不會建立這個實例-->
<bean id="myFactoryBeanImpl" class="com.factory.MyFactoryBeanImpl"></bean>
複製代碼

**注意 獲取到的是book對象 **

public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //獲取到的是book對象
    Book book = ioc.getBean("myFactoryBeanImpl");
}
複製代碼

建立帶有生命週期方法的bean

  • 生命週期:bean的建立到銷燬,咱們能夠自定義一些生命週期方法,spring在建立或銷燬的時候會調用指定的方法
  • 自定義初始化方法和銷燬方法,可是不能夠有參數 能夠拋異常
  • 單例Bean的生命週期
    • (容器啓動)構造器——>>>初始化方法——>>>(容器關閉)銷燬方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" >
</bean>
複製代碼
  • 多例Bean的生命週期
    • (獲取bean)構造器——>>>初始化方法——>>>(容器關閉)不會調用bean的銷燬方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" scope="protorype" >
</bean>
複製代碼

spring管理鏈接池

​ 數據庫連接池做爲單實例是最好的,一個項目就一個鏈接池,鏈接池裏面管理不少連接,連接是直接從連接池裏面拿

​ 可讓Spring幫咱們建立鏈接池對象 管理鏈接池

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="user" value="root"></property>
    <property name="password" value="123456"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
複製代碼

spring管理鏈接池引用外部配置文件

jdbc.properties

#username=root
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test
jdbc.driverClass=com.mysql.jdbc.Driver
複製代碼

在ApplicationContext.xml中配置時 須要引入context命名空間

注意!

  • username 是spring中的一個關鍵字 在這裏不可使用username**
  • value="${jdbc.username}" 引號先後不能有空格
<!--加載外部配置文件的 classpath: 表示引用類路徑下的配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--username 是spring中的一個關鍵字 在這裏不可使用username-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<!--<property name="user" value="${username}"></property>-->
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
複製代碼

基於xml的自動裝配

**注意!自動裝配僅限於自定義類型的屬性 **

<bean id="car" class="com.bean.Car">
	<property name="carName" value="寶馬"></property>
    <property name="color" value="白色"></property>
</bean>
<!-- autowire="" default/no:不自動裝配,不自動爲car屬性賦值 byName:以屬性名做爲id去容器中找到一個組件,給他賦值,若是找不到就賦值null byType:以屬性的類型做爲查找依據去容器中找到這個組件,若是容器中有多個這樣的類型會報錯 constructor:按照有參構造器爲car賦值 1.先按照有參構造器參數類型進行裝配,沒有就直接爲組件裝配null 2.若是按照類型找到了多個bean:以參數的名做爲id繼續裝配,找不到就null 假設有一個List<Book> books屬性,容器能夠把容器中全部的book封裝進list -->
<bean id="person" class="com.bean.Person" autowire="default"></bean>
複製代碼

SpEL(Spring Expression Language)spring表達式語言

表達式語言

  • 字面量:#{12*5}
  • 引用其餘bean的某個屬性值:#{car.carName}
  • 引用其餘bean:#{car}
  • 調用非靜態方法:#{對象.方法名(arg1,arg2)} eg:#{car.getCarName()}
  • 調用靜態方法:#{T(全類名).靜態方法名(arg1,arg2)} eg: #{T(java.util.UUID).randomUUID().toString()}
<bean id="car" class="com.bean.Person">
	<property name="carName" value="寶馬"></property>
</bean>
<bean id="person" class="com.bean.Person">
	<property name="age" value="#{12*5}"></property>
    <property name="perName" value="#{car.carName}"></property>
    <property name="car" value="#{car}"></property>
    <property name="email" value="#{T(java.util.UUID).randomUUID().toString()}"></property>
    <property name="testName" value="#{car.getCarName()}"></property>
</bean>
複製代碼

經過註解分別建立Controller、Dao、Service

spring有四個註解某個類上註解任何一個均可以講這個組件加入到ioc容器中

  • @Controller
    • 推薦給控制器層(servlet)的組件加這個註解
  • @Service
    • 推薦給業務邏輯層的組件加這個註解 Service層
  • @Repository
    • 推薦給數據庫層(持久化層,dao層)的組件添加這個註解
  • @Component
    • 給不屬於以上幾層的組件添加這個註解

使用註解將組價快速加入到容器中須要幾步

  1. 給要添加的組件上標上以上四個註解之一

  2. 告訴spring,自動掃描加了註解的組件,依賴context命名空間

    <!--自動組件掃描-->
    <!--base-package="" 指定掃描的基礎包-->
    <context:component-scan base-package=""></context:component-scan>
    複製代碼
    • 使用context:exclude-filter指定掃描包時不包含的類

      • 掃描的時候能夠排除一些不要的組件

      • type="annotation":按照註解進行排除-->標註了指定註解的組建不要

        expression="":註解的全類名

      • type="assignable":按照類進行排除

        expression="":類的全類名

      • type="aspectj":aspectj表達式(不多用)

      • type="custom":自定義一個TypeFilter;本身寫代碼決定哪些使用(不多用)

      • type="regex":正則表達式(不多用)

    <context:component-scan base-package="">
        <!--表示標註了@controller的註解不進行掃描-->
    	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--表示com.controller.BookController這個類不進行掃描-->
    	<context:exclude-filter type="assignable" expression="com.controller.BookController"/>
    </context:component-scan>
    複製代碼
    • 使用context:include-filter指定掃描包時包含的類

      • 指定只掃描哪些組件,

      • 注意!須要使用 use-default-filters="false" 取消默認行爲

      • type="annotation":按照註解進行排除-->標註了指定註解的組建不要

        expression="":註解的全類名

      • type="assignable":按照類進行排除

        expression="":類的全類名

      • type="aspectj":aspectj表達式(不多用)

      • type="custom":自定義一個TypeFilter;本身寫代碼決定哪些使用(不多用)

      • type="regex":正則表達式(不多用)

    <context:component-scan base-package="" use-default-filters="true">
        <!--表示只有標註了@controller的註解進行掃描-->
    	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--表示只有com.controller.BookController這個類進行掃描-->
    	<context:include-filter type="assignable" expression="com.controller.BookController"/>
    </context:component-scan>
    複製代碼
  3. 必定要導入AOP包 支持加註解模式

    這裏bean的id默認就是類名首字母小寫

    使用註解加入到容器中的組件,和使用配置加入到容器中的組件行爲都是默認同樣的:

    • 組件的id,默認就是類名首字母小寫
    • 組件的做用域,默認就是單例的
    public void test(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
        Object bean = ioc.getBean("");
    }
    複製代碼

注意,若是想要修改默認行爲

  • 修改bean的Id
@Repository("你想要的id名")  四個註解都是同樣的
複製代碼
  • 修改做用域
@Scope(value="prototype")  
複製代碼

使用@Autowired註解實現自動裝配

@Autowired爲bookservice自動賦值

@Controller
public class BookServlet{ 
    @Autowired
    private BookService bookService;
}
複製代碼

@Autowired原理

  1. 按照類型去容器中找到對應的組件;bookService = ioc.getBean("BookService.class");

    1. 若是找到了就直接賦值
    2. 沒找到就拋出異常
    3. 若是找到多個,就按照變量名做爲id繼續匹配
      1. 若是匹配上就賦值
      2. 若是沒有匹配上,就拋出異常。
      3. 注意!這裏可使用@Qualifier註解 做用是能夠指定一個名稱做爲id,取消spring使用變量名做爲id的行爲。eg:@Qualifier(「newbookservice「)
        1. 若是找到就裝配
        2. 若是找不到,就拋出異常

    注意!

    可使用required=false 來解決 若是@Autowired找不到指定bean 就賦值爲null

    @Autowired(required=false)


在方法上使用@Autowired註解並在形參位置使用@Qualifier

  • 方法上有@Autuwired
    • 這個方法會在容器建立的時候自動運行
    • 這個方法的每個參數都會自動注入值
@Autowired
public void methods(@Qualifier("newbookservice")BookService bookservice){
    System.out.println("")
}
複製代碼

@Autowired 和 @Resource的區別

  • @Autowired 和 @Resource 和 @Inject都是自動裝配的意思
  • @Autowired最強大:Spring本身的註解
  • @Resource:j2ee java的標準 ,擴展性更強 由於若是咱們切換另一個容器框架@Resource仍是可使用,可是@Autowired離開了spring 就不能使用了

Spring的單元測試

  1. 導包 導入spring-test-4.0.0

  2. @ContextConfiguration使用這個註解來指定spring的配置文件的位置

  3. @RunWith()指定用哪一種驅動進行單元測試。默認就是junit

    ​ @RunWith(SpringJUnit4ClassRunner.class)

    ​ 使用spring的單元測試模塊來執行標註了@Test註解的測試方法

    ​ 之前的@Test只是由JUnit執行

@ContextConfiguration(locations="classpath:spring.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest{
    ApplicationContext ac = null;
    
    @Autowired
    BookServlet bookServlet;
}
複製代碼

泛型依賴注入

泛型依賴注入,注入一個組件的時候,他的泛型也是參考標準

  • 首先BookDao和UserDao都繼承BaseDao這個抽象類並實現抽象方法
public abstract class BaseDao<T>{
    public abstract void save();
    ...
}
複製代碼
@Repository
public class BookDao extends BaseDao<Book>{
    @Override
    public void save(){
        ...
    }
    ...
}
複製代碼
@Repository
public class UserDao extends BaseDao<User>{
    @Override
    public void save(){
        ...
    }
    ...
}
複製代碼
  • 而後BookService和UserService都繼承了BaseService,而且在繼承的時候,肯定了泛型的類型。
@Service
public class BookService extends BaseService<Book>{
    ...
}
複製代碼
@Service
public class UserService extends BaseService<User>{
    ...
}
複製代碼
public class BaseService<T>{
    @Autowired
    private BaseDao<T> baseDao;
    
    public void test(){
        baseDao.save();
    }
    ...
}
複製代碼

流程分析

  • 當在BookService的對象調用test()方法的時候,雖然BaseService沒有在容器中,可是BookService在容器中,因此繼承了BaseService的BookService會自動爲baseDao屬性賦值,此時至關於BookService中 有一個 BaseDao baseDao; 它便會去容器中找一個BaseDao類型的bean爲baseDao賦值。因此將容器中的BookDao賦值給baseDao,當BookService調用test()方法時其實調用的就是bookDao中的save()方法。
@Test
public void test() {
    	
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    	BookService bookService = ioc.getBean(BookService.class);
        bookService.test();
    }
複製代碼

IOC總結

  • IOC是一個容器,幫咱們管理全部的組件
    • 依賴注入:@Autowired:自動注入
    • 某個組件要使用spring提供的更多功能就必需要加入容器中
  • 容器啓動,建立全部單實例bean
  • @Autowired自動裝配的時候,是從容器中找到這些符合要求的bean
相關文章
相關標籤/搜索