1. 程式人生 > >Spring系列教程六: Spring基於註解的Ioc以及Ioc案例

Spring系列教程六: Spring基於註解的Ioc以及Ioc案例

 

學習基於註解的Ioc配置,我們腦海中需要有一個認知,就是註解配置和xml配置實現的功能都是一樣的,都是要降低程式間的耦合,只是配置的形式不一樣

在實際開發中到底是使用xml還是註解,每個公司有不同的使用習慣,所有這兩種配置方式我們都需要掌握

我們在講解註解配置時,採用上一章的案例,把Spring的xml配置內容改為使用註解逐步實現

1、Spirng中Ioc的常用註解按照作用分類

用於建立物件:作用是和xml配置檔案中編寫一個bean標籤建立物件的功能是一樣的

@Component(以下三個是Spring框架為我們明確提供的三層使用註解,使我們的三層物件更加清晰,以下是Component的衍生類)

Controller(這個物件一般用在表現層)

Service(這個物件一遍用於業務層)

Repository(這個業務一般用於持久層)
作用:把當前物件存入Spring容器中
屬性:value 用於指定bean的id,當我們不寫的時候,它的預設是當前類名,且首字母小寫,也可以指定一個名稱

①在介面中實現@Component的程式碼改寫

@Component("accountServiceImpl4")
public class AccountServiceImpl4 implements IAccountService {

   private String name;
   private Integer age;
   private Date brithday;

   AccountServiceImpl4(String name, Integer age, Date brithday){
       this.name=name;
       this.age=age;
       this.brithday=brithday;
   }

   public void saveAccount(){
       System.out.println("service中的saveAccount方法執行了...."+name+age+brithday);
   }
}

②配置檔案中改寫(需要新新增引入context的約束)

<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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx">
    <!--新新增一個約束:告訴Spring在建立容器時要掃描的包,配置所需要的標籤不是在bean的約束中,而是一個名稱為
    context的名稱和空間中,這個時候就會掃描base-pack類上或者介面上的註解-->
    <context:component-scan base-package="com.ithema.jdbc"></context:component-scan>
</beans>

③測試類

package com.ithema.jdbc.ui;
import com.ithema.jdbc.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 模擬一個表現層,用於呼叫業務層
 */
public class Client2 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext2.xml");
        IAccountService as= (IAccountService) ac.getBean("accountServiceImpl4");
        System.out.println(as);
        as.saveAccount();
    }
}

顯示結果如下

用於注入資料:作用是和xml配置檔案中編寫一個property標籤建立物件的功能是一樣的

Autowired

作用:自動按照型別注入,主要容器中有唯一一個bean物件和要注入的變數型別匹配,就可以注入成功,如果Ioc容器中沒有任何bean型別和要注入的變數型別匹配,則報錯

如果有Ioc容器中有多個匹配型別:首先按照型別圈定出來匹配的物件,使用變數名稱作為bean的id,在圈定出來的兩個裡面繼續查詢,如果bean id有一樣也可以注入成功

出現位置:可以是變數上,也可以是方法上

細節:在使用註解注入時候,set方法就不用了

但是這種情況不是我們想看到的,那麼有新的方法嗎??如下

Qualifier(不能獨立使用,需要和Autowired配合使用,有解決的方案嗎?如下一個屬性)

作用:在按照類中注入的基礎上再按照名稱注入,它在給類成員注入時,不能單獨使用,但在給方法引數注入時候可以

屬性:value 用於指定bean的id

Resource:直接按照bean的id注入,平時用的比較多,它可以獨立使用

@Resource(name="accountDao")

以上三個註解只能注入其他bean型別的資料,而基本資料型別和String型別無法使用上述註解實現另外,集合型別的注入只能使用xml來實現,怎麼解決如下

Value:

作用:湧入注入基本型別和String型別的資料

屬性 value:用於指定資料的值,它可以使用Spring中spel(也就是Spring中的el表示式),spel寫法$(表示式)

①介面上修改程式碼

②啟動測試類顯示結果如下,成功注入資料

存在一bean物件時候

存在多個bean物件時候,要注入的變數名稱和某個bean的id保持一致也可以注入成功

 

用於改變作用範圍:他們的作用是和xml配置檔案中編寫一個scope標籤的功能是一樣的

作用:用於指定bean的作用範圍,屬性:value 指定範圍取值,常用取值singleton(預設值)/prototype

測試如下,作用範圍預設是單例的

那麼如果我們把作用範圍改成prototype的啦?結果當然是false

和生命週期相關:他們的作用是和xml配置檔案中編寫一個init-method和destory-method標籤的功能是一樣的

PreDestory 作用:用於指定銷燬方法

PostConstruct 作用:初始化方法

 

2、使用xml方式和註解方式實現單表的CURD操作,持久技術層選用dbutils(案例)

3、改造基於註解的Ioc案例,使用註解的方式實現Spring的一些新註解的使用

如何實現在不是我們自己寫類上的註解,比如我們引入QueryRunner類

基於註解的 IoC 配置已經完成,但是大家都發現了一個問題:我們依然離不開 spring 的 xml 配置檔案,那麼能不能不寫這個 bean.xml,所有配置都用註解來實現呢?當然,我們們也需要注意一下,我們選擇哪種配置的原則是簡化開發和配置方便,而非追求某種技術。

①先建立一個config檔案,裡面建立一個Springconfiguration的類

package com.it.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
@Configuration
@ComponentScan("com.it")
public class SpringConfiguration {
    /**
    *建立一個QueryRunner物件
    */
    @Bean("runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    /**
     * 建立資料來源物件
     */
    @Bean("dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/fresh?characterEncoding=UTF-8");
            ds.setUser("root");
            ds.setPassword("root");
            return  ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Configuration
 *     作用:指定當前類是一個配置類
 *     細節:當配置類作為AnnotationConfigApplicationContext物件建立的引數時,該註解可以不寫

ComponentScan
 *      作用:用於通過註解指定spring在建立容器時要掃描的包
 *      屬性:
 *          value:它和basePackages的作用是一樣的,都是用於指定建立容器時要掃描的包。
 *                 我們使用此註解就等同於在xml中配置了:
 *                 <context:component-scan base-package="com.itheima"></context:component-scan>

 Bean
 *      作用:用於把當前方法的返回值作為bean物件存入spring的ioc容器中,該註解只能寫在方法上
 *      屬性:
 *          name:用於指定bean的id,當不寫時,預設值是當前方法的名稱
 *      細節:
 *          當我們使用註解配置方法時,如果方法有引數,spring框架會去容器中查詢有沒有可用的bean物件。
 *          查詢的方式和Autowired註解的作用是一樣的

②測試類檔案中的改寫

package com.it;

import com.it.config.SpringConfiguration;
import com.it.entity.Student;
import com.it.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class TestClient {
    @Test
    public  void findAll(){
        //ApplicationContext ac= new ClassPathXmlApplicationContext("ApplicationContext.xml");
        ApplicationContext ac= new AnnotationConfigApplicationContext(SpringConfiguration.class);
        StudentService as = ac.getBean("studentService",StudentService.class);
        List<Student> studentList =as.findAllStudent();
        for (Student student:studentList) {
            System.out.println(student);
        }
    }
}

測試結果如下:

問題來了,上面劃線的部分兩個的作用是一樣的嘛?效果是不一樣的,下面劃線部分,當把物件建立好了後,會把物件給你丟到Spring Ioc容器中,上面劃線的部分當然不會那麼做,它只是會給你返回一個QueryRunner的物件,這個時候就需要我們手動把物件丟到容器裡面去,就需要採用@Bean的方法

QueryRunner物件是不是一個單例物件

線面還有一個問題就是我們的QueryRunner物件是不是一個單例物件啦?這個問題關係到我們執行緒的問題

那怎麼改變bean的作用範圍啦?這個是時候寄需要用的scope屬性啦,結果如下

父配置類匯入子配置類用@Import

對於配置類我們想要把SpringConfiguration中的jbdc配置專門提取到一個配置類中去?這樣的話也方便後期的管理和修改

單元測試findAll的測試結果如下

匯入配置perproties配置檔案@PropertySource
 *      作用:用於指定properties檔案的位置
 *      屬性:
 *          value:指定檔案的名稱和路徑。
 *                  關鍵字:classpath,表示類路徑下
 *

在之前JdbcConfig配置類中dataSource的所有的屬性都是寫死的後來我們進行了如下改造

package com.it.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;

public class JbdcConfig {
    /**
     * 建立dataSource物件
     * @param dataSource
     * @return
     */
    @Value("${jdbc.driver}")
    private  String driver;
    @Value("${jdbc.url}")
    private  String url;
    @Value("${jdbc.username}")
    private  String username;
    @Value("${jdbc.password}")
    private  String password;

    @Bean("runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    /**
     * 建立資料來源物件
     */
    @Bean("dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return  ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

對於新建立的private String driver屬性,我們使用@Valeue("${jdbc.diver}")來注入perproties中的值,在SpringConfigruation配置父類中使用@PropertySource來注入配置檔案

測試結果如下,一樣能查詢到結果

那麼我們一般適合用那種配置方式?用註解還是,xml啦?

用到已經寫好的jar的,一般情況下我們用xml來配置,比較省事,要是這個類是我們自己寫的話,用註解來配置比較方便

4、Spring和Junit的整合

在我們的測試類中存在很多的重複的方法,那麼有沒方法解決啦!!我先採用init的方式先載入建立容器的方式

結果顯示沒有任何問題,那麼這個問題被徹底解決掉了嘛?

根據分析得到,junit執行原理

使用Junit單元測試:測試我們的配置,Spring整合junit的配置
1、匯入spring整合junit的jar:spring-test(座標)
2、使用Junit提供的一個註解把原有的main方法替換了,替換成spring提供的@Runwith
3、告知spring的執行器,spring和ioc建立是基於xml還是註解的,並且說明位置
@ContextConfiguration
屬性:locations:指定xml檔案的位置,加上classpath關鍵字,表示在類路徑下
          classes:指定註解類所在地位置
細節:當我們使用spring 5.x版本的時候,要求junit的jar必須是4.12及以上

package com.it;

import com.it.config.SpringConfiguration;
import com.it.entity.Student;
import com.it.service.StudentService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestClient {
    @Autowired
    private  StudentService as;
    @Test
    public  void findAll(){
        List<Student> studentList =as.findAllStudent();
        for (Student student:studentList) {
            System.out.println(student);
        }
    }

    @Test
    public  void findbyidtest() {
        //ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        Student student = as.findByid(4);
        System.out.println(student);
    }

    @Test
    public  void saveTest() {
        Student student=new Student();
        student.setId(7);
        student.setStuno("10007");
        student.setName("陳多糖");
        student.setClassid(2);
        as.saveStudent(student);
    }
    @Test
    public  void updatetest() {
        Student student = as.findByid(4);
        student.setName("陳柳柳");
        as.updateStudent(student);
    }

    @Test
    public  void deletetest() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        StudentService as = ac.getBean("studentService", StudentService.class);
        as.deleteStudent(7);
    }
}

測試結果如下:

為什麼 不把測試類配到 xml 

在解釋這個問題之前,先解除大家的疑慮,配到 XML 中能不能用呢?
答案是肯定的,沒問題,可以使用。
那麼為什麼不採用配置到 xml 中的方式呢?
這個原因是這樣的:
第一:當我們在 xml 中配置了一個 bean,spring 載入配置檔案建立容器時,就會建立物件。
第二:測試類只是我們在測試功能時使用,而在專案中它並不參與程式邏輯,也不會解決需求上的問
題,所以建立完了,並沒有使用。那麼存在容器中就會造成資源的浪費。
所以,基於以上兩點,我們不應該把測試配置到 xml 檔案